1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 2009 Free Software Foundation, Inc.
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
19 #include <libpspp/assertion.h>
20 #include <libpspp/start-date.h>
21 #include <libpspp/version.h>
22 #include <output/afm.h>
23 #include <output/chart.h>
24 #include <output/manager.h>
25 #include <output/output.h>
27 #include <cairo/cairo-pdf.h>
28 #include <cairo/cairo-ps.h>
29 #include <cairo/cairo-svg.h>
30 #include <cairo/cairo.h>
31 #include <pango/pango-font.h>
32 #include <pango/pango-layout.h>
33 #include <pango/pango.h>
34 #include <pango/pangocairo.h>
43 #define _(msgid) gettext (msgid)
45 /* Cairo driver options: (defaults listed first)
47 output-file="pspp.pdf"
48 output-type=pdf|ps|png|svg
49 paper-size=letter (see "papersize" file)
50 orientation=portrait|landscape
59 emph-font=Times-Italic
68 /* Measurements as we present to the rest of PSPP. */
69 #define XR_POINT PANGO_SCALE
70 #define XR_INCH (XR_POINT * 72)
72 /* Conversions to and from points. */
76 return x / (double) XR_POINT;
82 return x * XR_POINT + 0.5;
93 /* A font for use with Cairo. */
97 PangoFontDescription *desc;
99 PangoFontMetrics *metrics;
102 /* Cairo output driver extension record. */
105 char *file_name; /* Output file name. */
106 enum xr_output_type file_type; /* Type of output file. */
109 bool draw_headers; /* Draw headers at top of page? */
110 int page_number; /* Current page number. */
112 bool portrait; /* Portrait mode? */
113 int paper_width; /* Width of paper before dropping margins. */
114 int paper_length; /* Length of paper before dropping margins. */
115 int left_margin; /* Left margin in XR units. */
116 int right_margin; /* Right margin in XR units. */
117 int top_margin; /* Top margin in XR units. */
118 int bottom_margin; /* Bottom margin in XR units. */
120 int line_gutter; /* Space around lines. */
121 int line_space; /* Space between lines. */
122 int line_width; /* Width of lines. */
124 struct xr_font fonts[OUTP_FONT_CNT];
127 static bool handle_option (struct outp_driver *this, const char *key,
128 const struct string *val);
129 static void draw_headers (struct outp_driver *this);
131 static bool load_font (struct outp_driver *this, struct xr_font *);
132 static void free_font (struct xr_font *);
133 static int text_width (struct outp_driver *, const char *, enum outp_font);
135 /* Driver initialization. */
138 xr_open_driver (struct outp_driver *this, struct substring options)
140 cairo_surface_t *surface;
141 cairo_status_t status;
142 struct xr_driver_ext *x;
143 double width_pt, length_pt;
146 this->width = this->length = 0;
147 this->font_height = XR_POINT * 10;
149 this->ext = x = xmalloc (sizeof *x);
150 x->file_name = xstrdup ("pspp.pdf");
151 x->file_type = XR_PDF;
152 x->draw_headers = true;
155 outp_get_paper_size ("", &x->paper_width, &x->paper_length);
156 x->left_margin = XR_INCH / 2;
157 x->right_margin = XR_INCH / 2;
158 x->top_margin = XR_INCH / 2;
159 x->bottom_margin = XR_INCH / 2;
160 x->line_gutter = XR_POINT;
161 x->line_space = XR_POINT;
162 x->line_width = XR_POINT / 2;
163 x->fonts[OUTP_FIXED].string = xstrdup ("monospace");
164 x->fonts[OUTP_PROPORTIONAL].string = xstrdup ("serif");
165 x->fonts[OUTP_EMPHASIS].string = xstrdup ("serif italic");
166 for (i = 0; i < OUTP_FONT_CNT; i++)
168 struct xr_font *font = &x->fonts[i];
170 font->metrics = NULL;
174 outp_parse_options (options, handle_option, this);
176 width_pt = x->paper_width / 1000.0;
177 length_pt = x->paper_length / 1000.0;
180 this->width = pt_to_xr (width_pt);
181 this->length = pt_to_xr (length_pt);
185 this->width = pt_to_xr (width_pt);
186 this->length = pt_to_xr (length_pt);
189 x->top_margin += 3 * this->font_height;
190 this->width -= x->left_margin + x->right_margin;
191 this->length -= x->top_margin + x->bottom_margin;
193 if (x->file_type == XR_PDF)
194 surface = cairo_pdf_surface_create (x->file_name, width_pt, length_pt);
195 else if (x->file_type == XR_PS)
196 surface = cairo_ps_surface_create (x->file_name, width_pt, length_pt);
197 else if (x->file_type == XR_SVG)
198 surface = cairo_svg_surface_create (x->file_name, width_pt, length_pt);
202 status = cairo_surface_status (surface);
203 if (status != CAIRO_STATUS_SUCCESS)
205 error (0, 0, _("opening output file \"%s\": %s"),
206 x->file_name, cairo_status_to_string (status));
207 cairo_surface_destroy (surface);
211 x->cairo = cairo_create (surface);
212 cairo_surface_destroy (surface);
214 cairo_translate (x->cairo,
215 xr_to_pt (x->left_margin),
216 xr_to_pt (x->top_margin));
217 cairo_set_line_width (x->cairo, xr_to_pt (x->line_width));
219 for (i = 0; i < OUTP_FONT_CNT; i++)
220 if (!load_font (this, &x->fonts[i]))
223 if (this->length / this->font_height < 15)
225 error (0, 0, _("The defined page is not long "
226 "enough to hold margins and headers, plus least 15 "
227 "lines of the default fonts. In fact, there's only "
228 "room for %d lines."),
229 this->length / this->font_height);
233 this->fixed_width = text_width (this, "0", OUTP_FIXED);
234 this->prop_em_width = text_width (this, "0", OUTP_PROPORTIONAL);
236 this->horiz_line_width[OUTP_L_NONE] = 0;
237 this->horiz_line_width[OUTP_L_SINGLE] = 2 * x->line_gutter + x->line_width;
238 this->horiz_line_width[OUTP_L_DOUBLE] = (2 * x->line_gutter + x->line_space
239 + 2 * x->line_width);
240 memcpy (this->vert_line_width, this->horiz_line_width,
241 sizeof this->vert_line_width);
246 this->class->close_driver (this);
251 xr_close_driver (struct outp_driver *this)
253 struct xr_driver_ext *x = this->ext;
257 if (x->cairo != NULL)
259 cairo_status_t status;
261 cairo_surface_finish (cairo_get_target (x->cairo));
262 status = cairo_status (x->cairo);
263 if (status != CAIRO_STATUS_SUCCESS)
264 error (0, 0, _("writing output file \"%s\": %s"),
265 x->file_name, cairo_status_to_string (status));
266 cairo_destroy (x->cairo);
270 for (i = 0; i < OUTP_FONT_CNT; i++)
271 free_font (&x->fonts[i]);
277 /* Generic option types. */
291 /* All the options that the Cairo driver supports. */
292 static const struct outp_option option_tab[] =
294 {"output-file", output_file_arg,0},
295 {"output-type", output_type_arg,0},
296 {"paper-size", paper_size_arg, 0},
297 {"orientation", orientation_arg,0},
299 {"headers", boolean_arg, 1},
301 {"prop-font", string_arg, OUTP_PROPORTIONAL},
302 {"emph-font", string_arg, OUTP_EMPHASIS},
303 {"fixed-font", string_arg, OUTP_FIXED},
305 {"left-margin", dimension_arg, 0},
306 {"right-margin", dimension_arg, 1},
307 {"top-margin", dimension_arg, 2},
308 {"bottom-margin", dimension_arg, 3},
309 {"font-size", dimension_arg, 4},
310 {"line-width", dimension_arg, 5},
311 {"line-gutter", dimension_arg, 6},
312 {"line-width", dimension_arg, 7},
317 handle_option (struct outp_driver *this, const char *key,
318 const struct string *val)
320 struct xr_driver_ext *x = this->ext;
322 char *value = ds_cstr (val);
324 switch (outp_match_keyword (key, option_tab, &subcat))
328 _("unknown configuration parameter `%s' for Cairo device "
331 case output_file_arg:
333 x->file_name = xstrdup (value);
335 case output_type_arg:
336 if (!strcmp (value, "pdf"))
337 x->file_type = XR_PDF;
338 else if (!strcmp (value, "ps"))
339 x->file_type = XR_PS;
340 else if (!strcmp (value, "svg"))
341 x->file_type = XR_SVG;
344 error (0, 0, _("unknown Cairo output type \"%s\""), value);
349 outp_get_paper_size (value, &x->paper_width, &x->paper_length);
351 case orientation_arg:
352 if (!strcmp (value, "portrait"))
354 else if (!strcmp (value, "landscape"))
357 error (0, 0, _("unknown orientation `%s' (valid orientations are "
358 "`portrait' and `landscape')"), value);
361 if (!strcmp (value, "on") || !strcmp (value, "true")
362 || !strcmp (value, "yes") || atoi (value))
363 x->draw_headers = true;
364 else if (!strcmp (value, "off") || !strcmp (value, "false")
365 || !strcmp (value, "no") || !strcmp (value, "0"))
366 x->draw_headers = false;
369 error (0, 0, _("boolean value expected for %s"), key);
375 int dimension = outp_evaluate_dimension (value);
382 x->left_margin = dimension;
385 x->right_margin = dimension;
388 x->top_margin = dimension;
391 x->bottom_margin = dimension;
394 this->font_height = dimension;
397 x->line_width = dimension;
400 x->line_gutter = dimension;
403 x->line_width = dimension;
411 free (x->fonts[subcat].string);
412 x->fonts[subcat].string = ds_xstrdup (val);
421 /* Basic file operations. */
424 xr_open_page (struct outp_driver *this)
426 struct xr_driver_ext *x = this->ext;
435 xr_close_page (struct outp_driver *this)
437 struct xr_driver_ext *x = this->ext;
438 cairo_show_page (x->cairo);
442 xr_submit (struct outp_driver *this UNUSED, struct som_entity *s)
453 /* Draws a line from (x0,y0) to (x1,y1). */
455 dump_line (struct outp_driver *this, int x0, int y0, int x1, int y1)
457 struct xr_driver_ext *x = this->ext;
458 cairo_new_path (x->cairo);
459 cairo_move_to (x->cairo, xr_to_pt (x0), xr_to_pt (y0));
460 cairo_line_to (x->cairo, xr_to_pt (x1), xr_to_pt (y1));
461 cairo_stroke (x->cairo);
464 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
465 shortening it to X0...X1 if SHORTEN is true.
466 Draws a horizontal line X1...X3 at Y if RIGHT says so,
467 shortening it to X2...X3 if SHORTEN is true. */
469 horz_line (struct outp_driver *this,
470 int x0, int x1, int x2, int x3, int y,
471 enum outp_line_style left, enum outp_line_style right,
474 if (left != OUTP_L_NONE && right != OUTP_L_NONE && !shorten)
475 dump_line (this, x0, y, x3, y);
478 if (left != OUTP_L_NONE)
479 dump_line (this, x0, y, shorten ? x1 : x2, y);
480 if (right != OUTP_L_NONE)
481 dump_line (this, shorten ? x2 : x1, y, x3, y);
485 /* Draws a vertical line Y0...Y2 at X if TOP says so,
486 shortening it to Y0...Y1 if SHORTEN is true.
487 Draws a vertical line Y1...Y3 at X if BOTTOM says so,
488 shortening it to Y2...Y3 if SHORTEN is true. */
490 vert_line (struct outp_driver *this,
491 int y0, int y1, int y2, int y3, int x,
492 enum outp_line_style top, enum outp_line_style bottom,
495 if (top != OUTP_L_NONE && bottom != OUTP_L_NONE && !shorten)
496 dump_line (this, x, y0, x, y3);
499 if (top != OUTP_L_NONE)
500 dump_line (this, x, y0, x, shorten ? y1 : y2);
501 if (bottom != OUTP_L_NONE)
502 dump_line (this, x, shorten ? y2 : y1, x, y3);
506 /* Draws a generalized intersection of lines in the rectangle
507 (X0,Y0)-(X3,Y3). The line coming from the top to the center
508 is of style TOP, from left to center of style LEFT, from
509 bottom to center of style BOTTOM, and from right to center of
512 xr_line (struct outp_driver *this,
513 int x0, int y0, int x3, int y3,
514 enum outp_line_style top, enum outp_line_style left,
515 enum outp_line_style bottom, enum outp_line_style right)
517 /* The algorithm here is somewhat subtle, to allow it to handle
518 all the kinds of intersections that we need.
520 Three additional ordinates are assigned along the x axis. The
521 first is xc, midway between x0 and x3. The others are x1 and
522 x2; for a single vertical line these are equal to xc, and for
523 a double vertical line they are the ordinates of the left and
524 right half of the double line.
526 yc, y1, and y2 are assigned similarly along the y axis.
528 The following diagram shows the coordinate system and output
529 for double top and bottom lines, single left line, and no
533 y0 ________________________
539 y1 = y2 = yc |######### # |
544 y3 |________#_____#_______|
546 struct xr_driver_ext *ext = this->ext;
548 /* Offset from center of each line in a pair of double lines. */
549 int double_line_ofs = (ext->line_space + ext->line_width) / 2;
551 /* Are the lines along each axis single or double?
552 (It doesn't make sense to have different kinds of line on the
553 same axis, so we don't try to gracefully handle that case.) */
554 bool double_vert = top == OUTP_L_DOUBLE || bottom == OUTP_L_DOUBLE;
555 bool double_horz = left == OUTP_L_DOUBLE || right == OUTP_L_DOUBLE;
557 /* When horizontal lines are doubled,
558 the left-side line along y1 normally runs from x0 to x2,
559 and the right-side line along y1 from x3 to x1.
560 If the top-side line is also doubled, we shorten the y1 lines,
561 so that the left-side line runs only to x1,
562 and the right-side line only to x2.
563 Otherwise, the horizontal line at y = y1 below would cut off
564 the intersection, which looks ugly:
566 y0 ________________________
571 y1 |######### ########|
574 y2 |######################|
577 y3 |______________________|
578 It is more of a judgment call when the horizontal line is
579 single. We actually choose to cut off the line anyhow, as
580 shown in the first diagram above.
582 bool shorten_y1_lines = top == OUTP_L_DOUBLE;
583 bool shorten_y2_lines = bottom == OUTP_L_DOUBLE;
584 bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
585 int horz_line_ofs = double_vert ? double_line_ofs : 0;
586 int xc = (x0 + x3) / 2;
587 int x1 = xc - horz_line_ofs;
588 int x2 = xc + horz_line_ofs;
590 bool shorten_x1_lines = left == OUTP_L_DOUBLE;
591 bool shorten_x2_lines = right == OUTP_L_DOUBLE;
592 bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
593 int vert_line_ofs = double_horz ? double_line_ofs : 0;
594 int yc = (y0 + y3) / 2;
595 int y1 = yc - vert_line_ofs;
596 int y2 = yc + vert_line_ofs;
599 horz_line (this, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
602 horz_line (this, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
603 horz_line (this, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
607 vert_line (this, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
610 vert_line (this, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
611 vert_line (this, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
615 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
616 and with the given JUSTIFICATION for THIS driver. */
618 draw_text (struct outp_driver *this,
619 const char *string, int x, int y, int max_width,
620 enum outp_justification justification)
622 struct outp_text text;
625 text.font = OUTP_PROPORTIONAL;
626 text.justification = justification;
627 text.string = ss_cstr (string);
629 text.v = this->font_height;
632 this->class->text_metrics (this, &text, &width, NULL);
633 this->class->text_draw (this, &text);
637 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
638 and with the given JUSTIFICATION for THIS driver. */
640 text_width (struct outp_driver *this, const char *string, enum outp_font font)
642 struct outp_text text;
646 text.justification = OUTP_LEFT;
647 text.string = ss_cstr (string);
649 text.v = this->font_height;
652 this->class->text_metrics (this, &text, &width, NULL);
656 /* Writes LEFT left-justified and RIGHT right-justified within
657 (X0...X1) at Y. LEFT or RIGHT or both may be null. */
659 draw_header_line (struct outp_driver *this,
660 const char *left, const char *right,
661 int x0, int x1, int y)
665 right_width = (draw_text (this, right, x0, y, x1 - x0, OUTP_RIGHT)
666 + this->prop_em_width);
668 draw_text (this, left, x0, y, x1 - x0 - right_width, OUTP_LEFT);
671 /* Draw top of page headers for THIS driver. */
673 draw_headers (struct outp_driver *this)
675 struct xr_driver_ext *ext = this->ext;
680 y = -3 * this->font_height;
681 x0 = this->prop_em_width;
682 x1 = this->width - this->prop_em_width;
685 cairo_rectangle (ext->cairo, 0, xr_to_pt (y), xr_to_pt (this->width),
686 xr_to_pt (2 * (this->font_height
687 + ext->line_width + ext->line_gutter)));
688 cairo_save (ext->cairo);
689 cairo_set_source_rgb (ext->cairo, 0.9, 0.9, 0.9);
690 cairo_fill_preserve (ext->cairo);
691 cairo_restore (ext->cairo);
692 cairo_stroke (ext->cairo);
694 y += ext->line_width + ext->line_gutter;
696 r1 = xasprintf (_("%s - Page %d"), get_start_date (), ext->page_number);
697 r2 = xasprintf ("%s - %s", version, host_system);
699 draw_header_line (this, outp_title, r1, x0, x1, y);
700 y += this->font_height;
702 draw_header_line (this, outp_subtitle, r2, x0, x1, y);
708 /* Format TEXT on THIS driver.
709 If DRAW is nonzero, draw the text.
710 The width of the widest line is stored into *WIDTH, if WIDTH
712 The total height of the text written is stored into *HEIGHT,
713 if HEIGHT is nonnull. */
715 text (struct outp_driver *this, const struct outp_text *text, bool draw,
716 int *width, int *height)
718 struct xr_driver_ext *ext = this->ext;
719 struct xr_font *font = &ext->fonts[text->font];
721 pango_layout_set_text (font->layout,
722 text->string.string, text->string.length);
723 pango_layout_set_alignment (
725 (text->justification == OUTP_RIGHT ? PANGO_ALIGN_RIGHT
726 : text->justification == OUTP_LEFT ? PANGO_ALIGN_LEFT
727 : PANGO_ALIGN_CENTER));
728 pango_layout_set_width (font->layout, text->h == INT_MAX ? -1 : text->h);
729 pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD_CHAR);
730 /* XXX need to limit number of lines to those that fit in text->v. */
735 if (text->justification != OUTP_LEFT && text->h != INT_MAX)
738 pango_layout_get_size (font->layout, &w, &h);
739 excess = text->h - w;
742 if (text->justification == OUTP_CENTER)
748 cairo_save (ext->cairo);
749 cairo_translate (ext->cairo, xr_to_pt (text->x), xr_to_pt (text->y));
750 pango_cairo_show_layout (ext->cairo, font->layout);
751 cairo_restore (ext->cairo);
754 if (width != NULL || height != NULL)
757 pango_layout_get_size (font->layout, &w, &h);
766 xr_text_metrics (struct outp_driver *this, const struct outp_text *t,
767 int *width, int *height)
769 text (this, t, false, width, height);
773 xr_text_draw (struct outp_driver *this, const struct outp_text *t)
775 assert (this->page_open);
776 text (this, t, true, NULL, NULL);
780 xr_chart_initialise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
785 /* XXX libplot doesn't support Cairo yet. */
790 xr_chart_finalise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
793 /* XXX libplot doesn't support Cairo yet. */
797 /* Attempts to load FONT, initializing its other members based on
798 its 'string' member and the information in THIS. Returns true
799 if successful, otherwise false. */
801 load_font (struct outp_driver *this, struct xr_font *font)
803 struct xr_driver_ext *x = this->ext;
804 PangoContext *context;
805 PangoLanguage *language;
807 font->desc = pango_font_description_from_string (font->string);
808 if (font->desc == NULL)
810 error (0, 0, _("\"%s\": bad font specification"), font->string);
813 pango_font_description_set_absolute_size (font->desc, this->font_height);
815 font->layout = pango_cairo_create_layout (x->cairo);
816 pango_layout_set_font_description (font->layout, font->desc);
818 language = pango_language_get_default ();
819 context = pango_layout_get_context (font->layout);
820 font->metrics = pango_context_get_metrics (context, font->desc, language);
827 free_font (struct xr_font *font)
830 if (font->desc != NULL)
831 pango_font_description_free (font->desc);
832 pango_font_metrics_unref (font->metrics);
833 g_object_unref (font->layout);
836 /* Cairo driver class. */
837 const struct outp_class cairo_class =