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 <output/cairo.h>
21 #include <libpspp/assertion.h>
22 #include <libpspp/start-date.h>
23 #include <libpspp/version.h>
24 #include <output/afm.h>
25 #include <output/chart.h>
26 #include <output/manager.h>
27 #include <output/output.h>
29 #include <cairo/cairo-pdf.h>
30 #include <cairo/cairo-ps.h>
31 #include <cairo/cairo-svg.h>
32 #include <cairo/cairo.h>
33 #include <pango/pango-font.h>
34 #include <pango/pango-layout.h>
35 #include <pango/pango.h>
36 #include <pango/pangocairo.h>
45 #define _(msgid) gettext (msgid)
47 /* Cairo driver options: (defaults listed first)
49 output-file="pspp.pdf"
50 output-type=pdf|ps|png|svg
51 paper-size=letter (see "papersize" file)
52 orientation=portrait|landscape
61 emph-font=Times-Italic
70 /* Measurements as we present to the rest of PSPP. */
71 #define XR_POINT PANGO_SCALE
72 #define XR_INCH (XR_POINT * 72)
74 /* Conversions to and from points. */
78 return x / (double) XR_POINT;
84 return x * XR_POINT + 0.5;
95 /* A font for use with Cairo. */
99 PangoFontDescription *desc;
101 PangoFontMetrics *metrics;
104 /* Cairo output driver extension record. */
108 struct xr_font fonts[OUTP_FONT_CNT];
110 bool draw_headers; /* Draw headers at top of page? */
111 int page_number; /* Current page number. */
113 int line_gutter; /* Space around lines. */
114 int line_space; /* Space between lines. */
115 int line_width; /* Width of lines. */
118 struct xr_driver_options
120 struct outp_driver *driver;
122 char *file_name; /* Output file name. */
123 enum xr_output_type file_type; /* Type of output file. */
126 bool portrait; /* Portrait mode? */
128 int paper_width; /* Width of paper before dropping margins. */
129 int paper_length; /* Length of paper before dropping margins. */
130 int left_margin; /* Left margin in XR units. */
131 int right_margin; /* Right margin in XR units. */
132 int top_margin; /* Top margin in XR units. */
133 int bottom_margin; /* Bottom margin in XR units. */
136 static bool handle_option (void *options, const char *key,
137 const struct string *val);
138 static void draw_headers (struct outp_driver *this);
140 static bool load_font (struct outp_driver *this, struct xr_font *);
141 static void free_font (struct xr_font *);
142 static int text_width (struct outp_driver *, const char *, enum outp_font);
144 /* Driver initialization. */
146 static struct outp_driver *
147 xr_allocate (const char *name, int types)
149 struct outp_driver *this;
150 struct xr_driver_ext *x;
153 this = outp_allocate_driver (&cairo_class, name, types);
154 this->width = this->length = 0;
155 this->font_height = XR_POINT * 10;
156 this->ext = x = xzalloc (sizeof *x);
158 x->fonts[OUTP_FIXED].string = xstrdup ("monospace");
159 x->fonts[OUTP_PROPORTIONAL].string = xstrdup ("serif");
160 x->fonts[OUTP_EMPHASIS].string = xstrdup ("serif italic");
161 for (i = 0; i < OUTP_FONT_CNT; i++)
163 struct xr_font *font = &x->fonts[i];
165 font->metrics = NULL;
168 x->draw_headers = true;
170 x->line_gutter = XR_POINT;
171 x->line_space = XR_POINT;
172 x->line_width = XR_POINT / 2;
178 xr_set_cairo (struct outp_driver *this, cairo_t *cairo)
180 struct xr_driver_ext *x = this->ext;
185 cairo_set_line_width (x->cairo, xr_to_pt (x->line_width));
187 for (i = 0; i < OUTP_FONT_CNT; i++)
188 if (!load_font (this, &x->fonts[i]))
191 this->fixed_width = text_width (this, "0", OUTP_FIXED);
192 this->prop_em_width = text_width (this, "0", OUTP_PROPORTIONAL);
194 this->horiz_line_width[OUTP_L_NONE] = 0;
195 this->horiz_line_width[OUTP_L_SINGLE] = 2 * x->line_gutter + x->line_width;
196 this->horiz_line_width[OUTP_L_DOUBLE] = (2 * x->line_gutter + x->line_space
197 + 2 * x->line_width);
198 memcpy (this->vert_line_width, this->horiz_line_width,
199 sizeof this->vert_line_width);
205 xr_create_driver (cairo_t *cairo)
207 struct outp_driver *this;
209 this = xr_allocate ("cairo", 0);
210 this->width = INT_MAX / 8;
211 this->length = INT_MAX / 8;
212 if (!xr_set_cairo (this, cairo))
214 this->class->close_driver (this);
215 outp_free_driver (this);
222 xr_open_driver (const char *name, int types, struct substring option_string)
224 struct outp_driver *this;
225 struct xr_driver_ext *x;
226 struct xr_driver_options options;
227 cairo_surface_t *surface;
228 cairo_status_t status;
229 double width_pt, length_pt;
231 this = xr_allocate (name, types);
234 options.driver = this;
235 options.file_name = xstrdup ("pspp.pdf");
236 options.file_type = XR_PDF;
237 options.portrait = true;
238 outp_get_paper_size ("", &options.paper_width, &options.paper_length);
239 options.left_margin = XR_INCH / 2;
240 options.right_margin = XR_INCH / 2;
241 options.top_margin = XR_INCH / 2;
242 options.bottom_margin = XR_INCH / 2;
244 outp_parse_options (this->name, option_string, handle_option, &options);
246 width_pt = options.paper_width / 1000.0;
247 length_pt = options.paper_length / 1000.0;
248 if (options.portrait)
250 this->width = pt_to_xr (width_pt);
251 this->length = pt_to_xr (length_pt);
255 this->width = pt_to_xr (width_pt);
256 this->length = pt_to_xr (length_pt);
259 options.top_margin += 3 * this->font_height;
260 this->width -= options.left_margin + options.right_margin;
261 this->length -= options.top_margin + options.bottom_margin;
263 if (options.file_type == XR_PDF)
264 surface = cairo_pdf_surface_create (options.file_name,
265 width_pt, length_pt);
266 else if (options.file_type == XR_PS)
267 surface = cairo_ps_surface_create (options.file_name, width_pt, length_pt);
268 else if (options.file_type == XR_SVG)
269 surface = cairo_svg_surface_create (options.file_name,
270 width_pt, length_pt);
274 status = cairo_surface_status (surface);
275 if (status != CAIRO_STATUS_SUCCESS)
277 error (0, 0, _("opening output file \"%s\": %s"),
278 options.file_name, cairo_status_to_string (status));
279 cairo_surface_destroy (surface);
283 x->cairo = cairo_create (surface);
284 cairo_surface_destroy (surface);
286 cairo_translate (x->cairo,
287 xr_to_pt (options.left_margin),
288 xr_to_pt (options.top_margin));
290 if (this->length / this->font_height < 15)
292 error (0, 0, _("The defined page is not long "
293 "enough to hold margins and headers, plus least 15 "
294 "lines of the default fonts. In fact, there's only "
295 "room for %d lines."),
296 this->length / this->font_height);
300 if (!xr_set_cairo (this, x->cairo))
303 outp_register_driver (this);
304 free (options.file_name);
308 this->class->close_driver (this);
309 outp_free_driver (this);
310 free (options.file_name);
315 xr_close_driver (struct outp_driver *this)
317 struct xr_driver_ext *x = this->ext;
321 if (x->cairo != NULL)
323 cairo_status_t status;
325 cairo_surface_finish (cairo_get_target (x->cairo));
326 status = cairo_status (x->cairo);
327 if (status != CAIRO_STATUS_SUCCESS)
328 error (0, 0, _("error writing output file for %s driver: %s"),
329 this->name, cairo_status_to_string (status));
330 cairo_destroy (x->cairo);
333 for (i = 0; i < OUTP_FONT_CNT; i++)
334 free_font (&x->fonts[i]);
340 /* Generic option types. */
354 /* All the options that the Cairo driver supports. */
355 static const struct outp_option option_tab[] =
357 {"output-file", output_file_arg,0},
358 {"output-type", output_type_arg,0},
359 {"paper-size", paper_size_arg, 0},
360 {"orientation", orientation_arg,0},
362 {"headers", boolean_arg, 1},
364 {"prop-font", string_arg, OUTP_PROPORTIONAL},
365 {"emph-font", string_arg, OUTP_EMPHASIS},
366 {"fixed-font", string_arg, OUTP_FIXED},
368 {"left-margin", dimension_arg, 0},
369 {"right-margin", dimension_arg, 1},
370 {"top-margin", dimension_arg, 2},
371 {"bottom-margin", dimension_arg, 3},
372 {"font-size", dimension_arg, 4},
373 {"line-width", dimension_arg, 5},
374 {"line-gutter", dimension_arg, 6},
375 {"line-width", dimension_arg, 7},
380 handle_option (void *options_, const char *key, const struct string *val)
382 struct xr_driver_options *options = options_;
383 struct outp_driver *this = options->driver;
384 struct xr_driver_ext *x = this->ext;
386 char *value = ds_cstr (val);
388 switch (outp_match_keyword (key, option_tab, &subcat))
392 _("unknown configuration parameter `%s' for Cairo device "
395 case output_file_arg:
396 free (options->file_name);
397 options->file_name = xstrdup (value);
399 case output_type_arg:
400 if (!strcmp (value, "pdf"))
401 options->file_type = XR_PDF;
402 else if (!strcmp (value, "ps"))
403 options->file_type = XR_PS;
404 else if (!strcmp (value, "svg"))
405 options->file_type = XR_SVG;
408 error (0, 0, _("unknown Cairo output type \"%s\""), value);
413 outp_get_paper_size (value,
414 &options->paper_width, &options->paper_length);
416 case orientation_arg:
417 if (!strcmp (value, "portrait"))
418 options->portrait = true;
419 else if (!strcmp (value, "landscape"))
420 options->portrait = false;
422 error (0, 0, _("unknown orientation `%s' (valid orientations are "
423 "`portrait' and `landscape')"), value);
426 if (!strcmp (value, "on") || !strcmp (value, "true")
427 || !strcmp (value, "yes") || atoi (value))
428 x->draw_headers = true;
429 else if (!strcmp (value, "off") || !strcmp (value, "false")
430 || !strcmp (value, "no") || !strcmp (value, "0"))
431 x->draw_headers = false;
434 error (0, 0, _("boolean value expected for %s"), key);
440 int dimension = outp_evaluate_dimension (value);
447 options->left_margin = dimension;
450 options->right_margin = dimension;
453 options->top_margin = dimension;
456 options->bottom_margin = dimension;
459 this->font_height = dimension;
462 x->line_width = dimension;
465 x->line_gutter = dimension;
468 x->line_width = dimension;
476 free (x->fonts[subcat].string);
477 x->fonts[subcat].string = ds_xstrdup (val);
486 /* Basic file operations. */
489 xr_open_page (struct outp_driver *this)
491 struct xr_driver_ext *x = this->ext;
500 xr_close_page (struct outp_driver *this)
502 struct xr_driver_ext *x = this->ext;
503 cairo_show_page (x->cairo);
506 /* Draws a line from (x0,y0) to (x1,y1). */
508 dump_line (struct outp_driver *this, int x0, int y0, int x1, int y1)
510 struct xr_driver_ext *x = this->ext;
511 cairo_new_path (x->cairo);
512 cairo_move_to (x->cairo, xr_to_pt (x0), xr_to_pt (y0));
513 cairo_line_to (x->cairo, xr_to_pt (x1), xr_to_pt (y1));
514 cairo_stroke (x->cairo);
517 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
518 shortening it to X0...X1 if SHORTEN is true.
519 Draws a horizontal line X1...X3 at Y if RIGHT says so,
520 shortening it to X2...X3 if SHORTEN is true. */
522 horz_line (struct outp_driver *this,
523 int x0, int x1, int x2, int x3, int y,
524 enum outp_line_style left, enum outp_line_style right,
527 if (left != OUTP_L_NONE && right != OUTP_L_NONE && !shorten)
528 dump_line (this, x0, y, x3, y);
531 if (left != OUTP_L_NONE)
532 dump_line (this, x0, y, shorten ? x1 : x2, y);
533 if (right != OUTP_L_NONE)
534 dump_line (this, shorten ? x2 : x1, y, x3, y);
538 /* Draws a vertical line Y0...Y2 at X if TOP says so,
539 shortening it to Y0...Y1 if SHORTEN is true.
540 Draws a vertical line Y1...Y3 at X if BOTTOM says so,
541 shortening it to Y2...Y3 if SHORTEN is true. */
543 vert_line (struct outp_driver *this,
544 int y0, int y1, int y2, int y3, int x,
545 enum outp_line_style top, enum outp_line_style bottom,
548 if (top != OUTP_L_NONE && bottom != OUTP_L_NONE && !shorten)
549 dump_line (this, x, y0, x, y3);
552 if (top != OUTP_L_NONE)
553 dump_line (this, x, y0, x, shorten ? y1 : y2);
554 if (bottom != OUTP_L_NONE)
555 dump_line (this, x, shorten ? y2 : y1, x, y3);
559 /* Draws a generalized intersection of lines in the rectangle
560 (X0,Y0)-(X3,Y3). The line coming from the top to the center
561 is of style TOP, from left to center of style LEFT, from
562 bottom to center of style BOTTOM, and from right to center of
565 xr_line (struct outp_driver *this,
566 int x0, int y0, int x3, int y3,
567 enum outp_line_style top, enum outp_line_style left,
568 enum outp_line_style bottom, enum outp_line_style right)
570 /* The algorithm here is somewhat subtle, to allow it to handle
571 all the kinds of intersections that we need.
573 Three additional ordinates are assigned along the x axis. The
574 first is xc, midway between x0 and x3. The others are x1 and
575 x2; for a single vertical line these are equal to xc, and for
576 a double vertical line they are the ordinates of the left and
577 right half of the double line.
579 yc, y1, and y2 are assigned similarly along the y axis.
581 The following diagram shows the coordinate system and output
582 for double top and bottom lines, single left line, and no
586 y0 ________________________
592 y1 = y2 = yc |######### # |
597 y3 |________#_____#_______|
599 struct xr_driver_ext *ext = this->ext;
601 /* Offset from center of each line in a pair of double lines. */
602 int double_line_ofs = (ext->line_space + ext->line_width) / 2;
604 /* Are the lines along each axis single or double?
605 (It doesn't make sense to have different kinds of line on the
606 same axis, so we don't try to gracefully handle that case.) */
607 bool double_vert = top == OUTP_L_DOUBLE || bottom == OUTP_L_DOUBLE;
608 bool double_horz = left == OUTP_L_DOUBLE || right == OUTP_L_DOUBLE;
610 /* When horizontal lines are doubled,
611 the left-side line along y1 normally runs from x0 to x2,
612 and the right-side line along y1 from x3 to x1.
613 If the top-side line is also doubled, we shorten the y1 lines,
614 so that the left-side line runs only to x1,
615 and the right-side line only to x2.
616 Otherwise, the horizontal line at y = y1 below would cut off
617 the intersection, which looks ugly:
619 y0 ________________________
624 y1 |######### ########|
627 y2 |######################|
630 y3 |______________________|
631 It is more of a judgment call when the horizontal line is
632 single. We actually choose to cut off the line anyhow, as
633 shown in the first diagram above.
635 bool shorten_y1_lines = top == OUTP_L_DOUBLE;
636 bool shorten_y2_lines = bottom == OUTP_L_DOUBLE;
637 bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
638 int horz_line_ofs = double_vert ? double_line_ofs : 0;
639 int xc = (x0 + x3) / 2;
640 int x1 = xc - horz_line_ofs;
641 int x2 = xc + horz_line_ofs;
643 bool shorten_x1_lines = left == OUTP_L_DOUBLE;
644 bool shorten_x2_lines = right == OUTP_L_DOUBLE;
645 bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
646 int vert_line_ofs = double_horz ? double_line_ofs : 0;
647 int yc = (y0 + y3) / 2;
648 int y1 = yc - vert_line_ofs;
649 int y2 = yc + vert_line_ofs;
652 horz_line (this, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
655 horz_line (this, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
656 horz_line (this, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
660 vert_line (this, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
663 vert_line (this, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
664 vert_line (this, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
668 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
669 and with the given JUSTIFICATION for THIS driver. */
671 draw_text (struct outp_driver *this,
672 const char *string, int x, int y, int max_width,
673 enum outp_justification justification)
675 struct outp_text text;
678 text.font = OUTP_PROPORTIONAL;
679 text.justification = justification;
680 text.string = ss_cstr (string);
682 text.v = this->font_height;
685 this->class->text_metrics (this, &text, &width, NULL);
686 this->class->text_draw (this, &text);
690 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
691 and with the given JUSTIFICATION for THIS driver. */
693 text_width (struct outp_driver *this, const char *string, enum outp_font font)
695 struct outp_text text;
699 text.justification = OUTP_LEFT;
700 text.string = ss_cstr (string);
702 text.v = this->font_height;
705 this->class->text_metrics (this, &text, &width, NULL);
709 /* Writes LEFT left-justified and RIGHT right-justified within
710 (X0...X1) at Y. LEFT or RIGHT or both may be null. */
712 draw_header_line (struct outp_driver *this,
713 const char *left, const char *right,
714 int x0, int x1, int y)
718 right_width = (draw_text (this, right, x0, y, x1 - x0, OUTP_RIGHT)
719 + this->prop_em_width);
721 draw_text (this, left, x0, y, x1 - x0 - right_width, OUTP_LEFT);
724 /* Draw top of page headers for THIS driver. */
726 draw_headers (struct outp_driver *this)
728 struct xr_driver_ext *ext = this->ext;
733 y = -3 * this->font_height;
734 x0 = this->prop_em_width;
735 x1 = this->width - this->prop_em_width;
738 cairo_rectangle (ext->cairo, 0, xr_to_pt (y), xr_to_pt (this->width),
739 xr_to_pt (2 * (this->font_height
740 + ext->line_width + ext->line_gutter)));
741 cairo_save (ext->cairo);
742 cairo_set_source_rgb (ext->cairo, 0.9, 0.9, 0.9);
743 cairo_fill_preserve (ext->cairo);
744 cairo_restore (ext->cairo);
745 cairo_stroke (ext->cairo);
747 y += ext->line_width + ext->line_gutter;
749 r1 = xasprintf (_("%s - Page %d"), get_start_date (), ext->page_number);
750 r2 = xasprintf ("%s - %s", version, host_system);
752 draw_header_line (this, outp_title, r1, x0, x1, y);
753 y += this->font_height;
755 draw_header_line (this, outp_subtitle, r2, x0, x1, y);
761 /* Format TEXT on THIS driver.
762 If DRAW is nonzero, draw the text.
763 The width of the widest line is stored into *WIDTH, if WIDTH
765 The total height of the text written is stored into *HEIGHT,
766 if HEIGHT is nonnull. */
768 text (struct outp_driver *this, const struct outp_text *text, bool draw,
769 int *width, int *height)
771 struct xr_driver_ext *ext = this->ext;
772 struct xr_font *font = &ext->fonts[text->font];
774 pango_layout_set_text (font->layout,
775 text->string.string, text->string.length);
776 pango_layout_set_alignment (
778 (text->justification == OUTP_RIGHT ? PANGO_ALIGN_RIGHT
779 : text->justification == OUTP_LEFT ? PANGO_ALIGN_LEFT
780 : PANGO_ALIGN_CENTER));
781 pango_layout_set_width (font->layout, text->h == INT_MAX ? -1 : text->h);
782 pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD_CHAR);
783 /* XXX need to limit number of lines to those that fit in text->v. */
788 if (text->justification != OUTP_LEFT && text->h != INT_MAX)
791 pango_layout_get_size (font->layout, &w, &h);
792 excess = text->h - w;
795 if (text->justification == OUTP_CENTER)
801 cairo_save (ext->cairo);
802 cairo_translate (ext->cairo, xr_to_pt (text->x), xr_to_pt (text->y));
803 pango_cairo_show_layout (ext->cairo, font->layout);
804 cairo_restore (ext->cairo);
807 if (width != NULL || height != NULL)
810 pango_layout_get_size (font->layout, &w, &h);
819 xr_text_metrics (struct outp_driver *this, const struct outp_text *t,
820 int *width, int *height)
822 text (this, t, false, width, height);
826 xr_text_draw (struct outp_driver *this, const struct outp_text *t)
828 text (this, t, true, NULL, NULL);
831 /* Attempts to load FONT, initializing its other members based on
832 its 'string' member and the information in THIS. Returns true
833 if successful, otherwise false. */
835 load_font (struct outp_driver *this, struct xr_font *font)
837 struct xr_driver_ext *x = this->ext;
838 PangoContext *context;
839 PangoLanguage *language;
841 font->desc = pango_font_description_from_string (font->string);
842 if (font->desc == NULL)
844 error (0, 0, _("\"%s\": bad font specification"), font->string);
847 pango_font_description_set_absolute_size (font->desc, this->font_height);
849 font->layout = pango_cairo_create_layout (x->cairo);
850 pango_layout_set_font_description (font->layout, font->desc);
852 language = pango_language_get_default ();
853 context = pango_layout_get_context (font->layout);
854 font->metrics = pango_context_get_metrics (context, font->desc, language);
861 free_font (struct xr_font *font)
864 if (font->desc != NULL)
865 pango_font_description_free (font->desc);
866 pango_font_metrics_unref (font->metrics);
867 g_object_unref (font->layout);
870 /* Cairo driver class. */
871 const struct outp_class cairo_class =