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-provider.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=serif 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 %s device "
393 "driver"), key, this->class->name);
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);
507 xr_output_chart (struct outp_driver *this, const struct chart *chart)
509 struct xr_driver_ext *x = this->ext;
510 struct chart_geometry geom;
512 outp_eject_page (this);
513 outp_open_page (this);
515 cairo_save (x->cairo);
516 cairo_translate (x->cairo, 0.0, xr_to_pt (this->length));
517 cairo_scale (x->cairo, 1.0, -1.0);
518 chart_geometry_init (x->cairo, &geom,
519 xr_to_pt (this->width), xr_to_pt (this->length));
520 chart_draw (chart, x->cairo, &geom);
521 chart_geometry_free (x->cairo);
522 cairo_restore (x->cairo);
524 outp_close_page (this);
527 /* Draws a line from (x0,y0) to (x1,y1). */
529 dump_line (struct outp_driver *this, int x0, int y0, int x1, int y1)
531 struct xr_driver_ext *x = this->ext;
532 cairo_new_path (x->cairo);
533 cairo_move_to (x->cairo, xr_to_pt (x0), xr_to_pt (y0));
534 cairo_line_to (x->cairo, xr_to_pt (x1), xr_to_pt (y1));
535 cairo_stroke (x->cairo);
538 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
539 shortening it to X0...X1 if SHORTEN is true.
540 Draws a horizontal line X1...X3 at Y if RIGHT says so,
541 shortening it to X2...X3 if SHORTEN is true. */
543 horz_line (struct outp_driver *this,
544 int x0, int x1, int x2, int x3, int y,
545 enum outp_line_style left, enum outp_line_style right,
548 if (left != OUTP_L_NONE && right != OUTP_L_NONE && !shorten)
549 dump_line (this, x0, y, x3, y);
552 if (left != OUTP_L_NONE)
553 dump_line (this, x0, y, shorten ? x1 : x2, y);
554 if (right != OUTP_L_NONE)
555 dump_line (this, shorten ? x2 : x1, y, x3, y);
559 /* Draws a vertical line Y0...Y2 at X if TOP says so,
560 shortening it to Y0...Y1 if SHORTEN is true.
561 Draws a vertical line Y1...Y3 at X if BOTTOM says so,
562 shortening it to Y2...Y3 if SHORTEN is true. */
564 vert_line (struct outp_driver *this,
565 int y0, int y1, int y2, int y3, int x,
566 enum outp_line_style top, enum outp_line_style bottom,
569 if (top != OUTP_L_NONE && bottom != OUTP_L_NONE && !shorten)
570 dump_line (this, x, y0, x, y3);
573 if (top != OUTP_L_NONE)
574 dump_line (this, x, y0, x, shorten ? y1 : y2);
575 if (bottom != OUTP_L_NONE)
576 dump_line (this, x, shorten ? y2 : y1, x, y3);
580 /* Draws a generalized intersection of lines in the rectangle
581 (X0,Y0)-(X3,Y3). The line coming from the top to the center
582 is of style TOP, from left to center of style LEFT, from
583 bottom to center of style BOTTOM, and from right to center of
586 xr_line (struct outp_driver *this,
587 int x0, int y0, int x3, int y3,
588 enum outp_line_style top, enum outp_line_style left,
589 enum outp_line_style bottom, enum outp_line_style right)
591 /* The algorithm here is somewhat subtle, to allow it to handle
592 all the kinds of intersections that we need.
594 Three additional ordinates are assigned along the x axis. The
595 first is xc, midway between x0 and x3. The others are x1 and
596 x2; for a single vertical line these are equal to xc, and for
597 a double vertical line they are the ordinates of the left and
598 right half of the double line.
600 yc, y1, and y2 are assigned similarly along the y axis.
602 The following diagram shows the coordinate system and output
603 for double top and bottom lines, single left line, and no
607 y0 ________________________
613 y1 = y2 = yc |######### # |
618 y3 |________#_____#_______|
620 struct xr_driver_ext *ext = this->ext;
622 /* Offset from center of each line in a pair of double lines. */
623 int double_line_ofs = (ext->line_space + ext->line_width) / 2;
625 /* Are the lines along each axis single or double?
626 (It doesn't make sense to have different kinds of line on the
627 same axis, so we don't try to gracefully handle that case.) */
628 bool double_vert = top == OUTP_L_DOUBLE || bottom == OUTP_L_DOUBLE;
629 bool double_horz = left == OUTP_L_DOUBLE || right == OUTP_L_DOUBLE;
631 /* When horizontal lines are doubled,
632 the left-side line along y1 normally runs from x0 to x2,
633 and the right-side line along y1 from x3 to x1.
634 If the top-side line is also doubled, we shorten the y1 lines,
635 so that the left-side line runs only to x1,
636 and the right-side line only to x2.
637 Otherwise, the horizontal line at y = y1 below would cut off
638 the intersection, which looks ugly:
640 y0 ________________________
645 y1 |######### ########|
648 y2 |######################|
651 y3 |______________________|
652 It is more of a judgment call when the horizontal line is
653 single. We actually choose to cut off the line anyhow, as
654 shown in the first diagram above.
656 bool shorten_y1_lines = top == OUTP_L_DOUBLE;
657 bool shorten_y2_lines = bottom == OUTP_L_DOUBLE;
658 bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
659 int horz_line_ofs = double_vert ? double_line_ofs : 0;
660 int xc = (x0 + x3) / 2;
661 int x1 = xc - horz_line_ofs;
662 int x2 = xc + horz_line_ofs;
664 bool shorten_x1_lines = left == OUTP_L_DOUBLE;
665 bool shorten_x2_lines = right == OUTP_L_DOUBLE;
666 bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
667 int vert_line_ofs = double_horz ? double_line_ofs : 0;
668 int yc = (y0 + y3) / 2;
669 int y1 = yc - vert_line_ofs;
670 int y2 = yc + vert_line_ofs;
673 horz_line (this, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
676 horz_line (this, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
677 horz_line (this, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
681 vert_line (this, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
684 vert_line (this, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
685 vert_line (this, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
689 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
690 and with the given JUSTIFICATION for THIS driver. */
692 draw_text (struct outp_driver *this,
693 const char *string, int x, int y, int max_width,
694 enum outp_justification justification)
696 struct outp_text text;
699 text.font = OUTP_PROPORTIONAL;
700 text.justification = justification;
701 text.string = ss_cstr (string);
703 text.v = this->font_height;
706 this->class->text_metrics (this, &text, &width, NULL);
707 this->class->text_draw (this, &text);
711 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
712 and with the given JUSTIFICATION for THIS driver. */
714 text_width (struct outp_driver *this, const char *string, enum outp_font font)
716 struct outp_text text;
720 text.justification = OUTP_LEFT;
721 text.string = ss_cstr (string);
723 text.v = this->font_height;
726 this->class->text_metrics (this, &text, &width, NULL);
730 /* Writes LEFT left-justified and RIGHT right-justified within
731 (X0...X1) at Y. LEFT or RIGHT or both may be null. */
733 draw_header_line (struct outp_driver *this,
734 const char *left, const char *right,
735 int x0, int x1, int y)
739 right_width = (draw_text (this, right, x0, y, x1 - x0, OUTP_RIGHT)
740 + this->prop_em_width);
742 draw_text (this, left, x0, y, x1 - x0 - right_width, OUTP_LEFT);
745 /* Draw top of page headers for THIS driver. */
747 draw_headers (struct outp_driver *this)
749 struct xr_driver_ext *ext = this->ext;
754 y = -3 * this->font_height;
755 x0 = this->prop_em_width;
756 x1 = this->width - this->prop_em_width;
759 cairo_rectangle (ext->cairo, 0, xr_to_pt (y), xr_to_pt (this->width),
760 xr_to_pt (2 * (this->font_height
761 + ext->line_width + ext->line_gutter)));
762 cairo_save (ext->cairo);
763 cairo_set_source_rgb (ext->cairo, 0.9, 0.9, 0.9);
764 cairo_fill_preserve (ext->cairo);
765 cairo_restore (ext->cairo);
766 cairo_stroke (ext->cairo);
768 y += ext->line_width + ext->line_gutter;
770 r1 = xasprintf (_("%s - Page %d"), get_start_date (), ext->page_number);
771 r2 = xasprintf ("%s - %s", version, host_system);
773 draw_header_line (this, outp_title, r1, x0, x1, y);
774 y += this->font_height;
776 draw_header_line (this, outp_subtitle, r2, x0, x1, y);
782 /* Format TEXT on THIS driver.
783 If DRAW is nonzero, draw the text.
784 The width of the widest line is stored into *WIDTH, if WIDTH
786 The total height of the text written is stored into *HEIGHT,
787 if HEIGHT is nonnull. */
789 text (struct outp_driver *this, const struct outp_text *text, bool draw,
790 int *width, int *height)
792 struct xr_driver_ext *ext = this->ext;
793 struct xr_font *font = &ext->fonts[text->font];
795 pango_layout_set_text (font->layout,
796 text->string.string, text->string.length);
797 pango_layout_set_alignment (
799 (text->justification == OUTP_RIGHT ? PANGO_ALIGN_RIGHT
800 : text->justification == OUTP_LEFT ? PANGO_ALIGN_LEFT
801 : PANGO_ALIGN_CENTER));
802 pango_layout_set_width (font->layout, text->h == INT_MAX ? -1 : text->h);
803 pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD_CHAR);
804 /* XXX need to limit number of lines to those that fit in text->v. */
809 if (text->justification != OUTP_LEFT && text->h != INT_MAX)
812 pango_layout_get_size (font->layout, &w, &h);
813 excess = text->h - w;
816 if (text->justification == OUTP_CENTER)
822 cairo_save (ext->cairo);
823 cairo_translate (ext->cairo, xr_to_pt (text->x), xr_to_pt (text->y));
824 pango_cairo_show_layout (ext->cairo, font->layout);
825 cairo_restore (ext->cairo);
828 if (width != NULL || height != NULL)
831 pango_layout_get_size (font->layout, &w, &h);
840 xr_text_metrics (struct outp_driver *this, const struct outp_text *t,
841 int *width, int *height)
843 text (this, t, false, width, height);
847 xr_text_draw (struct outp_driver *this, const struct outp_text *t)
849 text (this, t, true, NULL, NULL);
852 /* Attempts to load FONT, initializing its other members based on
853 its 'string' member and the information in THIS. Returns true
854 if successful, otherwise false. */
856 load_font (struct outp_driver *this, struct xr_font *font)
858 struct xr_driver_ext *x = this->ext;
859 PangoContext *context;
860 PangoLanguage *language;
862 font->desc = pango_font_description_from_string (font->string);
863 if (font->desc == NULL)
865 error (0, 0, _("\"%s\": bad font specification"), font->string);
868 pango_font_description_set_absolute_size (font->desc, this->font_height);
870 font->layout = pango_cairo_create_layout (x->cairo);
871 pango_layout_set_font_description (font->layout, font->desc);
873 language = pango_language_get_default ();
874 context = pango_layout_get_context (font->layout);
875 font->metrics = pango_context_get_metrics (context, font->desc, language);
882 free_font (struct xr_font *font)
885 if (font->desc != NULL)
886 pango_font_description_free (font->desc);
887 pango_font_metrics_unref (font->metrics);
888 g_object_unref (font->layout);
891 /* Cairo driver class. */
892 const struct outp_class cairo_class =