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);
507 xr_submit (struct outp_driver *this UNUSED, struct som_entity *s)
518 /* Draws a line from (x0,y0) to (x1,y1). */
520 dump_line (struct outp_driver *this, int x0, int y0, int x1, int y1)
522 struct xr_driver_ext *x = this->ext;
523 cairo_new_path (x->cairo);
524 cairo_move_to (x->cairo, xr_to_pt (x0), xr_to_pt (y0));
525 cairo_line_to (x->cairo, xr_to_pt (x1), xr_to_pt (y1));
526 cairo_stroke (x->cairo);
529 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
530 shortening it to X0...X1 if SHORTEN is true.
531 Draws a horizontal line X1...X3 at Y if RIGHT says so,
532 shortening it to X2...X3 if SHORTEN is true. */
534 horz_line (struct outp_driver *this,
535 int x0, int x1, int x2, int x3, int y,
536 enum outp_line_style left, enum outp_line_style right,
539 if (left != OUTP_L_NONE && right != OUTP_L_NONE && !shorten)
540 dump_line (this, x0, y, x3, y);
543 if (left != OUTP_L_NONE)
544 dump_line (this, x0, y, shorten ? x1 : x2, y);
545 if (right != OUTP_L_NONE)
546 dump_line (this, shorten ? x2 : x1, y, x3, y);
550 /* Draws a vertical line Y0...Y2 at X if TOP says so,
551 shortening it to Y0...Y1 if SHORTEN is true.
552 Draws a vertical line Y1...Y3 at X if BOTTOM says so,
553 shortening it to Y2...Y3 if SHORTEN is true. */
555 vert_line (struct outp_driver *this,
556 int y0, int y1, int y2, int y3, int x,
557 enum outp_line_style top, enum outp_line_style bottom,
560 if (top != OUTP_L_NONE && bottom != OUTP_L_NONE && !shorten)
561 dump_line (this, x, y0, x, y3);
564 if (top != OUTP_L_NONE)
565 dump_line (this, x, y0, x, shorten ? y1 : y2);
566 if (bottom != OUTP_L_NONE)
567 dump_line (this, x, shorten ? y2 : y1, x, y3);
571 /* Draws a generalized intersection of lines in the rectangle
572 (X0,Y0)-(X3,Y3). The line coming from the top to the center
573 is of style TOP, from left to center of style LEFT, from
574 bottom to center of style BOTTOM, and from right to center of
577 xr_line (struct outp_driver *this,
578 int x0, int y0, int x3, int y3,
579 enum outp_line_style top, enum outp_line_style left,
580 enum outp_line_style bottom, enum outp_line_style right)
582 /* The algorithm here is somewhat subtle, to allow it to handle
583 all the kinds of intersections that we need.
585 Three additional ordinates are assigned along the x axis. The
586 first is xc, midway between x0 and x3. The others are x1 and
587 x2; for a single vertical line these are equal to xc, and for
588 a double vertical line they are the ordinates of the left and
589 right half of the double line.
591 yc, y1, and y2 are assigned similarly along the y axis.
593 The following diagram shows the coordinate system and output
594 for double top and bottom lines, single left line, and no
598 y0 ________________________
604 y1 = y2 = yc |######### # |
609 y3 |________#_____#_______|
611 struct xr_driver_ext *ext = this->ext;
613 /* Offset from center of each line in a pair of double lines. */
614 int double_line_ofs = (ext->line_space + ext->line_width) / 2;
616 /* Are the lines along each axis single or double?
617 (It doesn't make sense to have different kinds of line on the
618 same axis, so we don't try to gracefully handle that case.) */
619 bool double_vert = top == OUTP_L_DOUBLE || bottom == OUTP_L_DOUBLE;
620 bool double_horz = left == OUTP_L_DOUBLE || right == OUTP_L_DOUBLE;
622 /* When horizontal lines are doubled,
623 the left-side line along y1 normally runs from x0 to x2,
624 and the right-side line along y1 from x3 to x1.
625 If the top-side line is also doubled, we shorten the y1 lines,
626 so that the left-side line runs only to x1,
627 and the right-side line only to x2.
628 Otherwise, the horizontal line at y = y1 below would cut off
629 the intersection, which looks ugly:
631 y0 ________________________
636 y1 |######### ########|
639 y2 |######################|
642 y3 |______________________|
643 It is more of a judgment call when the horizontal line is
644 single. We actually choose to cut off the line anyhow, as
645 shown in the first diagram above.
647 bool shorten_y1_lines = top == OUTP_L_DOUBLE;
648 bool shorten_y2_lines = bottom == OUTP_L_DOUBLE;
649 bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
650 int horz_line_ofs = double_vert ? double_line_ofs : 0;
651 int xc = (x0 + x3) / 2;
652 int x1 = xc - horz_line_ofs;
653 int x2 = xc + horz_line_ofs;
655 bool shorten_x1_lines = left == OUTP_L_DOUBLE;
656 bool shorten_x2_lines = right == OUTP_L_DOUBLE;
657 bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
658 int vert_line_ofs = double_horz ? double_line_ofs : 0;
659 int yc = (y0 + y3) / 2;
660 int y1 = yc - vert_line_ofs;
661 int y2 = yc + vert_line_ofs;
664 horz_line (this, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
667 horz_line (this, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
668 horz_line (this, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
672 vert_line (this, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
675 vert_line (this, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
676 vert_line (this, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
680 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
681 and with the given JUSTIFICATION for THIS driver. */
683 draw_text (struct outp_driver *this,
684 const char *string, int x, int y, int max_width,
685 enum outp_justification justification)
687 struct outp_text text;
690 text.font = OUTP_PROPORTIONAL;
691 text.justification = justification;
692 text.string = ss_cstr (string);
694 text.v = this->font_height;
697 this->class->text_metrics (this, &text, &width, NULL);
698 this->class->text_draw (this, &text);
702 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
703 and with the given JUSTIFICATION for THIS driver. */
705 text_width (struct outp_driver *this, const char *string, enum outp_font font)
707 struct outp_text text;
711 text.justification = OUTP_LEFT;
712 text.string = ss_cstr (string);
714 text.v = this->font_height;
717 this->class->text_metrics (this, &text, &width, NULL);
721 /* Writes LEFT left-justified and RIGHT right-justified within
722 (X0...X1) at Y. LEFT or RIGHT or both may be null. */
724 draw_header_line (struct outp_driver *this,
725 const char *left, const char *right,
726 int x0, int x1, int y)
730 right_width = (draw_text (this, right, x0, y, x1 - x0, OUTP_RIGHT)
731 + this->prop_em_width);
733 draw_text (this, left, x0, y, x1 - x0 - right_width, OUTP_LEFT);
736 /* Draw top of page headers for THIS driver. */
738 draw_headers (struct outp_driver *this)
740 struct xr_driver_ext *ext = this->ext;
745 y = -3 * this->font_height;
746 x0 = this->prop_em_width;
747 x1 = this->width - this->prop_em_width;
750 cairo_rectangle (ext->cairo, 0, xr_to_pt (y), xr_to_pt (this->width),
751 xr_to_pt (2 * (this->font_height
752 + ext->line_width + ext->line_gutter)));
753 cairo_save (ext->cairo);
754 cairo_set_source_rgb (ext->cairo, 0.9, 0.9, 0.9);
755 cairo_fill_preserve (ext->cairo);
756 cairo_restore (ext->cairo);
757 cairo_stroke (ext->cairo);
759 y += ext->line_width + ext->line_gutter;
761 r1 = xasprintf (_("%s - Page %d"), get_start_date (), ext->page_number);
762 r2 = xasprintf ("%s - %s", version, host_system);
764 draw_header_line (this, outp_title, r1, x0, x1, y);
765 y += this->font_height;
767 draw_header_line (this, outp_subtitle, r2, x0, x1, y);
773 /* Format TEXT on THIS driver.
774 If DRAW is nonzero, draw the text.
775 The width of the widest line is stored into *WIDTH, if WIDTH
777 The total height of the text written is stored into *HEIGHT,
778 if HEIGHT is nonnull. */
780 text (struct outp_driver *this, const struct outp_text *text, bool draw,
781 int *width, int *height)
783 struct xr_driver_ext *ext = this->ext;
784 struct xr_font *font = &ext->fonts[text->font];
786 pango_layout_set_text (font->layout,
787 text->string.string, text->string.length);
788 pango_layout_set_alignment (
790 (text->justification == OUTP_RIGHT ? PANGO_ALIGN_RIGHT
791 : text->justification == OUTP_LEFT ? PANGO_ALIGN_LEFT
792 : PANGO_ALIGN_CENTER));
793 pango_layout_set_width (font->layout, text->h == INT_MAX ? -1 : text->h);
794 pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD_CHAR);
795 /* XXX need to limit number of lines to those that fit in text->v. */
800 if (text->justification != OUTP_LEFT && text->h != INT_MAX)
803 pango_layout_get_size (font->layout, &w, &h);
804 excess = text->h - w;
807 if (text->justification == OUTP_CENTER)
813 cairo_save (ext->cairo);
814 cairo_translate (ext->cairo, xr_to_pt (text->x), xr_to_pt (text->y));
815 pango_cairo_show_layout (ext->cairo, font->layout);
816 cairo_restore (ext->cairo);
819 if (width != NULL || height != NULL)
822 pango_layout_get_size (font->layout, &w, &h);
831 xr_text_metrics (struct outp_driver *this, const struct outp_text *t,
832 int *width, int *height)
834 text (this, t, false, width, height);
838 xr_text_draw (struct outp_driver *this, const struct outp_text *t)
840 text (this, t, true, NULL, NULL);
844 xr_chart_initialise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
849 /* XXX libplot doesn't support Cairo yet. */
854 xr_chart_finalise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
857 /* XXX libplot doesn't support Cairo yet. */
861 /* Attempts to load FONT, initializing its other members based on
862 its 'string' member and the information in THIS. Returns true
863 if successful, otherwise false. */
865 load_font (struct outp_driver *this, struct xr_font *font)
867 struct xr_driver_ext *x = this->ext;
868 PangoContext *context;
869 PangoLanguage *language;
871 font->desc = pango_font_description_from_string (font->string);
872 if (font->desc == NULL)
874 error (0, 0, _("\"%s\": bad font specification"), font->string);
877 pango_font_description_set_absolute_size (font->desc, this->font_height);
879 font->layout = pango_cairo_create_layout (x->cairo);
880 pango_layout_set_font_description (font->layout, font->desc);
882 language = pango_language_get_default ();
883 context = pango_layout_get_context (font->layout);
884 font->metrics = pango_context_get_metrics (context, font->desc, language);
891 free_font (struct xr_font *font)
894 if (font->desc != NULL)
895 pango_font_description_free (font->desc);
896 pango_font_metrics_unref (font->metrics);
897 g_object_unref (font->layout);
900 /* Cairo driver class. */
901 const struct outp_class cairo_class =