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/chart-provider.h>
25 #include <output/manager.h>
26 #include <output/output.h>
28 #include <cairo/cairo-pdf.h>
29 #include <cairo/cairo-ps.h>
30 #include <cairo/cairo-svg.h>
31 #include <cairo/cairo.h>
32 #include <pango/pango-font.h>
33 #include <pango/pango-layout.h>
34 #include <pango/pango.h>
35 #include <pango/pangocairo.h>
44 #define _(msgid) gettext (msgid)
46 /* Cairo driver options: (defaults listed first)
48 output-file="pspp.pdf"
49 output-type=pdf|ps|png|svg
50 paper-size=letter (see "papersize" file)
51 orientation=portrait|landscape
60 emph-font=serif italic
69 /* Measurements as we present to the rest of PSPP. */
70 #define XR_POINT PANGO_SCALE
71 #define XR_INCH (XR_POINT * 72)
73 /* Conversions to and from points. */
77 return x / (double) XR_POINT;
83 return x * XR_POINT + 0.5;
94 /* A font for use with Cairo. */
98 PangoFontDescription *desc;
100 PangoFontMetrics *metrics;
103 /* Cairo output driver extension record. */
107 struct xr_font fonts[OUTP_FONT_CNT];
109 bool draw_headers; /* Draw headers at top of page? */
110 int page_number; /* Current page number. */
112 int line_gutter; /* Space around lines. */
113 int line_space; /* Space between lines. */
114 int line_width; /* Width of lines. */
117 struct xr_driver_options
119 struct outp_driver *driver;
121 char *file_name; /* Output file name. */
122 enum xr_output_type file_type; /* Type of output file. */
125 bool portrait; /* Portrait mode? */
127 int paper_width; /* Width of paper before dropping margins. */
128 int paper_length; /* Length of paper before dropping margins. */
129 int left_margin; /* Left margin in XR units. */
130 int right_margin; /* Right margin in XR units. */
131 int top_margin; /* Top margin in XR units. */
132 int bottom_margin; /* Bottom margin in XR units. */
135 static bool handle_option (void *options, const char *key,
136 const struct string *val);
137 static void draw_headers (struct outp_driver *this);
139 static bool load_font (struct outp_driver *this, struct xr_font *);
140 static void free_font (struct xr_font *);
141 static int text_width (struct outp_driver *, const char *, enum outp_font);
143 /* Driver initialization. */
145 static struct outp_driver *
146 xr_allocate (const char *name, int types)
148 struct outp_driver *this;
149 struct xr_driver_ext *x;
152 this = outp_allocate_driver (&cairo_class, name, types);
153 this->width = this->length = 0;
154 this->font_height = XR_POINT * 10;
155 this->ext = x = xzalloc (sizeof *x);
157 x->fonts[OUTP_FIXED].string = xstrdup ("monospace");
158 x->fonts[OUTP_PROPORTIONAL].string = xstrdup ("serif");
159 x->fonts[OUTP_EMPHASIS].string = xstrdup ("serif italic");
160 for (i = 0; i < OUTP_FONT_CNT; i++)
162 struct xr_font *font = &x->fonts[i];
164 font->metrics = NULL;
167 x->draw_headers = true;
169 x->line_gutter = XR_POINT;
170 x->line_space = XR_POINT;
171 x->line_width = XR_POINT / 2;
177 xr_set_cairo (struct outp_driver *this, cairo_t *cairo)
179 struct xr_driver_ext *x = this->ext;
184 cairo_set_line_width (x->cairo, xr_to_pt (x->line_width));
186 for (i = 0; i < OUTP_FONT_CNT; i++)
187 if (!load_font (this, &x->fonts[i]))
190 this->fixed_width = text_width (this, "0", OUTP_FIXED);
191 this->prop_em_width = text_width (this, "0", OUTP_PROPORTIONAL);
193 this->horiz_line_width[OUTP_L_NONE] = 0;
194 this->horiz_line_width[OUTP_L_SINGLE] = 2 * x->line_gutter + x->line_width;
195 this->horiz_line_width[OUTP_L_DOUBLE] = (2 * x->line_gutter + x->line_space
196 + 2 * x->line_width);
197 memcpy (this->vert_line_width, this->horiz_line_width,
198 sizeof this->vert_line_width);
204 xr_create_driver (cairo_t *cairo)
206 struct outp_driver *this;
208 this = xr_allocate ("cairo", 0);
209 this->width = INT_MAX / 8;
210 this->length = INT_MAX / 8;
211 if (!xr_set_cairo (this, cairo))
213 this->class->close_driver (this);
214 outp_free_driver (this);
221 xr_open_driver (const char *name, int types, struct substring option_string)
223 struct outp_driver *this;
224 struct xr_driver_ext *x;
225 struct xr_driver_options options;
226 cairo_surface_t *surface;
227 cairo_status_t status;
228 double width_pt, length_pt;
230 this = xr_allocate (name, types);
233 options.driver = this;
234 options.file_name = xstrdup ("pspp.pdf");
235 options.file_type = XR_PDF;
236 options.portrait = true;
237 outp_get_paper_size ("", &options.paper_width, &options.paper_length);
238 options.left_margin = XR_INCH / 2;
239 options.right_margin = XR_INCH / 2;
240 options.top_margin = XR_INCH / 2;
241 options.bottom_margin = XR_INCH / 2;
243 outp_parse_options (this->name, option_string, handle_option, &options);
245 width_pt = options.paper_width / 1000.0;
246 length_pt = options.paper_length / 1000.0;
247 if (options.portrait)
249 this->width = pt_to_xr (width_pt);
250 this->length = pt_to_xr (length_pt);
254 this->width = pt_to_xr (width_pt);
255 this->length = pt_to_xr (length_pt);
258 options.top_margin += 3 * this->font_height;
259 this->width -= options.left_margin + options.right_margin;
260 this->length -= options.top_margin + options.bottom_margin;
262 if (options.file_type == XR_PDF)
263 surface = cairo_pdf_surface_create (options.file_name,
264 width_pt, length_pt);
265 else if (options.file_type == XR_PS)
266 surface = cairo_ps_surface_create (options.file_name, width_pt, length_pt);
267 else if (options.file_type == XR_SVG)
268 surface = cairo_svg_surface_create (options.file_name,
269 width_pt, length_pt);
273 status = cairo_surface_status (surface);
274 if (status != CAIRO_STATUS_SUCCESS)
276 error (0, 0, _("opening output file \"%s\": %s"),
277 options.file_name, cairo_status_to_string (status));
278 cairo_surface_destroy (surface);
282 x->cairo = cairo_create (surface);
283 cairo_surface_destroy (surface);
285 cairo_translate (x->cairo,
286 xr_to_pt (options.left_margin),
287 xr_to_pt (options.top_margin));
289 if (this->length / this->font_height < 15)
291 error (0, 0, _("The defined page is not long "
292 "enough to hold margins and headers, plus least 15 "
293 "lines of the default fonts. In fact, there's only "
294 "room for %d lines."),
295 this->length / this->font_height);
299 if (!xr_set_cairo (this, x->cairo))
302 outp_register_driver (this);
303 free (options.file_name);
307 this->class->close_driver (this);
308 outp_free_driver (this);
309 free (options.file_name);
314 xr_close_driver (struct outp_driver *this)
316 struct xr_driver_ext *x = this->ext;
320 if (x->cairo != NULL)
322 cairo_status_t status;
324 cairo_surface_finish (cairo_get_target (x->cairo));
325 status = cairo_status (x->cairo);
326 if (status != CAIRO_STATUS_SUCCESS)
327 error (0, 0, _("error writing output file for %s driver: %s"),
328 this->name, cairo_status_to_string (status));
329 cairo_destroy (x->cairo);
332 for (i = 0; i < OUTP_FONT_CNT; i++)
333 free_font (&x->fonts[i]);
339 /* Generic option types. */
353 /* All the options that the Cairo driver supports. */
354 static const struct outp_option option_tab[] =
356 {"output-file", output_file_arg,0},
357 {"output-type", output_type_arg,0},
358 {"paper-size", paper_size_arg, 0},
359 {"orientation", orientation_arg,0},
361 {"headers", boolean_arg, 1},
363 {"prop-font", string_arg, OUTP_PROPORTIONAL},
364 {"emph-font", string_arg, OUTP_EMPHASIS},
365 {"fixed-font", string_arg, OUTP_FIXED},
367 {"left-margin", dimension_arg, 0},
368 {"right-margin", dimension_arg, 1},
369 {"top-margin", dimension_arg, 2},
370 {"bottom-margin", dimension_arg, 3},
371 {"font-size", dimension_arg, 4},
372 {"line-width", dimension_arg, 5},
373 {"line-gutter", dimension_arg, 6},
374 {"line-width", dimension_arg, 7},
379 handle_option (void *options_, const char *key, const struct string *val)
381 struct xr_driver_options *options = options_;
382 struct outp_driver *this = options->driver;
383 struct xr_driver_ext *x = this->ext;
385 char *value = ds_cstr (val);
387 switch (outp_match_keyword (key, option_tab, &subcat))
391 _("unknown configuration parameter `%s' for %s device "
392 "driver"), key, this->class->name);
394 case output_file_arg:
395 free (options->file_name);
396 options->file_name = xstrdup (value);
398 case output_type_arg:
399 if (!strcmp (value, "pdf"))
400 options->file_type = XR_PDF;
401 else if (!strcmp (value, "ps"))
402 options->file_type = XR_PS;
403 else if (!strcmp (value, "svg"))
404 options->file_type = XR_SVG;
407 error (0, 0, _("unknown Cairo output type \"%s\""), value);
412 outp_get_paper_size (value,
413 &options->paper_width, &options->paper_length);
415 case orientation_arg:
416 if (!strcmp (value, "portrait"))
417 options->portrait = true;
418 else if (!strcmp (value, "landscape"))
419 options->portrait = false;
421 error (0, 0, _("unknown orientation `%s' (valid orientations are "
422 "`portrait' and `landscape')"), value);
425 if (!strcmp (value, "on") || !strcmp (value, "true")
426 || !strcmp (value, "yes") || atoi (value))
427 x->draw_headers = true;
428 else if (!strcmp (value, "off") || !strcmp (value, "false")
429 || !strcmp (value, "no") || !strcmp (value, "0"))
430 x->draw_headers = false;
433 error (0, 0, _("boolean value expected for %s"), key);
439 int dimension = outp_evaluate_dimension (value);
446 options->left_margin = dimension;
449 options->right_margin = dimension;
452 options->top_margin = dimension;
455 options->bottom_margin = dimension;
458 this->font_height = dimension;
461 x->line_width = dimension;
464 x->line_gutter = dimension;
467 x->line_width = dimension;
475 free (x->fonts[subcat].string);
476 x->fonts[subcat].string = ds_xstrdup (val);
485 /* Basic file operations. */
488 xr_open_page (struct outp_driver *this)
490 struct xr_driver_ext *x = this->ext;
499 xr_close_page (struct outp_driver *this)
501 struct xr_driver_ext *x = this->ext;
502 cairo_show_page (x->cairo);
506 xr_output_chart (struct outp_driver *this, const struct chart *chart)
508 struct xr_driver_ext *x = this->ext;
509 struct chart_geometry geom;
511 outp_eject_page (this);
512 outp_open_page (this);
514 cairo_save (x->cairo);
515 cairo_translate (x->cairo, 0.0, xr_to_pt (this->length));
516 cairo_scale (x->cairo, 1.0, -1.0);
517 chart_geometry_init (x->cairo, &geom,
518 xr_to_pt (this->width), xr_to_pt (this->length));
519 chart_draw (chart, x->cairo, &geom);
520 chart_geometry_free (x->cairo, &geom);
521 cairo_restore (x->cairo);
523 outp_close_page (this);
526 /* Draws a line from (x0,y0) to (x1,y1). */
528 dump_line (struct outp_driver *this, int x0, int y0, int x1, int y1)
530 struct xr_driver_ext *x = this->ext;
531 cairo_new_path (x->cairo);
532 cairo_move_to (x->cairo, xr_to_pt (x0), xr_to_pt (y0));
533 cairo_line_to (x->cairo, xr_to_pt (x1), xr_to_pt (y1));
534 cairo_stroke (x->cairo);
537 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
538 shortening it to X0...X1 if SHORTEN is true.
539 Draws a horizontal line X1...X3 at Y if RIGHT says so,
540 shortening it to X2...X3 if SHORTEN is true. */
542 horz_line (struct outp_driver *this,
543 int x0, int x1, int x2, int x3, int y,
544 enum outp_line_style left, enum outp_line_style right,
547 if (left != OUTP_L_NONE && right != OUTP_L_NONE && !shorten)
548 dump_line (this, x0, y, x3, y);
551 if (left != OUTP_L_NONE)
552 dump_line (this, x0, y, shorten ? x1 : x2, y);
553 if (right != OUTP_L_NONE)
554 dump_line (this, shorten ? x2 : x1, y, x3, y);
558 /* Draws a vertical line Y0...Y2 at X if TOP says so,
559 shortening it to Y0...Y1 if SHORTEN is true.
560 Draws a vertical line Y1...Y3 at X if BOTTOM says so,
561 shortening it to Y2...Y3 if SHORTEN is true. */
563 vert_line (struct outp_driver *this,
564 int y0, int y1, int y2, int y3, int x,
565 enum outp_line_style top, enum outp_line_style bottom,
568 if (top != OUTP_L_NONE && bottom != OUTP_L_NONE && !shorten)
569 dump_line (this, x, y0, x, y3);
572 if (top != OUTP_L_NONE)
573 dump_line (this, x, y0, x, shorten ? y1 : y2);
574 if (bottom != OUTP_L_NONE)
575 dump_line (this, x, shorten ? y2 : y1, x, y3);
579 /* Draws a generalized intersection of lines in the rectangle
580 (X0,Y0)-(X3,Y3). The line coming from the top to the center
581 is of style TOP, from left to center of style LEFT, from
582 bottom to center of style BOTTOM, and from right to center of
585 xr_line (struct outp_driver *this,
586 int x0, int y0, int x3, int y3,
587 enum outp_line_style top, enum outp_line_style left,
588 enum outp_line_style bottom, enum outp_line_style right)
590 /* The algorithm here is somewhat subtle, to allow it to handle
591 all the kinds of intersections that we need.
593 Three additional ordinates are assigned along the x axis. The
594 first is xc, midway between x0 and x3. The others are x1 and
595 x2; for a single vertical line these are equal to xc, and for
596 a double vertical line they are the ordinates of the left and
597 right half of the double line.
599 yc, y1, and y2 are assigned similarly along the y axis.
601 The following diagram shows the coordinate system and output
602 for double top and bottom lines, single left line, and no
606 y0 ________________________
612 y1 = y2 = yc |######### # |
617 y3 |________#_____#_______|
619 struct xr_driver_ext *ext = this->ext;
621 /* Offset from center of each line in a pair of double lines. */
622 int double_line_ofs = (ext->line_space + ext->line_width) / 2;
624 /* Are the lines along each axis single or double?
625 (It doesn't make sense to have different kinds of line on the
626 same axis, so we don't try to gracefully handle that case.) */
627 bool double_vert = top == OUTP_L_DOUBLE || bottom == OUTP_L_DOUBLE;
628 bool double_horz = left == OUTP_L_DOUBLE || right == OUTP_L_DOUBLE;
630 /* When horizontal lines are doubled,
631 the left-side line along y1 normally runs from x0 to x2,
632 and the right-side line along y1 from x3 to x1.
633 If the top-side line is also doubled, we shorten the y1 lines,
634 so that the left-side line runs only to x1,
635 and the right-side line only to x2.
636 Otherwise, the horizontal line at y = y1 below would cut off
637 the intersection, which looks ugly:
639 y0 ________________________
644 y1 |######### ########|
647 y2 |######################|
650 y3 |______________________|
651 It is more of a judgment call when the horizontal line is
652 single. We actually choose to cut off the line anyhow, as
653 shown in the first diagram above.
655 bool shorten_y1_lines = top == OUTP_L_DOUBLE;
656 bool shorten_y2_lines = bottom == OUTP_L_DOUBLE;
657 bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
658 int horz_line_ofs = double_vert ? double_line_ofs : 0;
659 int xc = (x0 + x3) / 2;
660 int x1 = xc - horz_line_ofs;
661 int x2 = xc + horz_line_ofs;
663 bool shorten_x1_lines = left == OUTP_L_DOUBLE;
664 bool shorten_x2_lines = right == OUTP_L_DOUBLE;
665 bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
666 int vert_line_ofs = double_horz ? double_line_ofs : 0;
667 int yc = (y0 + y3) / 2;
668 int y1 = yc - vert_line_ofs;
669 int y2 = yc + vert_line_ofs;
672 horz_line (this, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
675 horz_line (this, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
676 horz_line (this, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
680 vert_line (this, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
683 vert_line (this, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
684 vert_line (this, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
688 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
689 and with the given JUSTIFICATION for THIS driver. */
691 draw_text (struct outp_driver *this,
692 const char *string, int x, int y, int max_width,
693 enum outp_justification justification)
695 struct outp_text text;
698 text.font = OUTP_PROPORTIONAL;
699 text.justification = justification;
700 text.string = ss_cstr (string);
702 text.v = this->font_height;
705 this->class->text_metrics (this, &text, &width, NULL);
706 this->class->text_draw (this, &text);
710 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
711 and with the given JUSTIFICATION for THIS driver. */
713 text_width (struct outp_driver *this, const char *string, enum outp_font font)
715 struct outp_text text;
719 text.justification = OUTP_LEFT;
720 text.string = ss_cstr (string);
722 text.v = this->font_height;
725 this->class->text_metrics (this, &text, &width, NULL);
729 /* Writes LEFT left-justified and RIGHT right-justified within
730 (X0...X1) at Y. LEFT or RIGHT or both may be null. */
732 draw_header_line (struct outp_driver *this,
733 const char *left, const char *right,
734 int x0, int x1, int y)
738 right_width = (draw_text (this, right, x0, y, x1 - x0, OUTP_RIGHT)
739 + this->prop_em_width);
741 draw_text (this, left, x0, y, x1 - x0 - right_width, OUTP_LEFT);
744 /* Draw top of page headers for THIS driver. */
746 draw_headers (struct outp_driver *this)
748 struct xr_driver_ext *ext = this->ext;
753 y = -3 * this->font_height;
754 x0 = this->prop_em_width;
755 x1 = this->width - this->prop_em_width;
758 cairo_rectangle (ext->cairo, 0, xr_to_pt (y), xr_to_pt (this->width),
759 xr_to_pt (2 * (this->font_height
760 + ext->line_width + ext->line_gutter)));
761 cairo_save (ext->cairo);
762 cairo_set_source_rgb (ext->cairo, 0.9, 0.9, 0.9);
763 cairo_fill_preserve (ext->cairo);
764 cairo_restore (ext->cairo);
765 cairo_stroke (ext->cairo);
767 y += ext->line_width + ext->line_gutter;
769 r1 = xasprintf (_("%s - Page %d"), get_start_date (), ext->page_number);
770 r2 = xasprintf ("%s - %s", version, host_system);
772 draw_header_line (this, outp_title, r1, x0, x1, y);
773 y += this->font_height;
775 draw_header_line (this, outp_subtitle, r2, x0, x1, y);
781 /* Format TEXT on THIS driver.
782 If DRAW is nonzero, draw the text.
783 The width of the widest line is stored into *WIDTH, if WIDTH
785 The total height of the text written is stored into *HEIGHT,
786 if HEIGHT is nonnull. */
788 text (struct outp_driver *this, const struct outp_text *text, bool draw,
789 int *width, int *height)
791 struct xr_driver_ext *ext = this->ext;
792 struct xr_font *font = &ext->fonts[text->font];
794 pango_layout_set_text (font->layout,
795 text->string.string, text->string.length);
796 pango_layout_set_alignment (
798 (text->justification == OUTP_RIGHT ? PANGO_ALIGN_RIGHT
799 : text->justification == OUTP_LEFT ? PANGO_ALIGN_LEFT
800 : PANGO_ALIGN_CENTER));
801 pango_layout_set_width (font->layout, text->h == INT_MAX ? -1 : text->h);
802 pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD_CHAR);
803 /* XXX need to limit number of lines to those that fit in text->v. */
808 if (text->justification != OUTP_LEFT && text->h != INT_MAX)
811 pango_layout_get_size (font->layout, &w, &h);
812 excess = text->h - w;
815 if (text->justification == OUTP_CENTER)
821 cairo_save (ext->cairo);
822 cairo_translate (ext->cairo, xr_to_pt (text->x), xr_to_pt (text->y));
823 pango_cairo_show_layout (ext->cairo, font->layout);
824 cairo_restore (ext->cairo);
827 if (width != NULL || height != NULL)
830 pango_layout_get_size (font->layout, &w, &h);
839 xr_text_metrics (struct outp_driver *this, const struct outp_text *t,
840 int *width, int *height)
842 text (this, t, false, width, height);
846 xr_text_draw (struct outp_driver *this, const struct outp_text *t)
848 text (this, t, true, NULL, NULL);
851 /* Attempts to load FONT, initializing its other members based on
852 its 'string' member and the information in THIS. Returns true
853 if successful, otherwise false. */
855 load_font (struct outp_driver *this, struct xr_font *font)
857 struct xr_driver_ext *x = this->ext;
858 PangoContext *context;
859 PangoLanguage *language;
861 font->desc = pango_font_description_from_string (font->string);
862 if (font->desc == NULL)
864 error (0, 0, _("\"%s\": bad font specification"), font->string);
867 pango_font_description_set_absolute_size (font->desc, this->font_height);
869 font->layout = pango_cairo_create_layout (x->cairo);
870 pango_layout_set_font_description (font->layout, font->desc);
872 language = pango_language_get_default ();
873 context = pango_layout_get_context (font->layout);
874 font->metrics = pango_context_get_metrics (context, font->desc, language);
881 free_font (struct xr_font *font)
884 if (font->desc != NULL)
885 pango_font_description_free (font->desc);
886 pango_font_metrics_unref (font->metrics);
887 g_object_unref (font->layout);
890 /* Cairo driver class. */
891 const struct outp_class cairo_class =