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 (const char *name, int types, struct substring options)
140 cairo_surface_t *surface;
141 cairo_status_t status;
142 struct outp_driver *this;
143 struct xr_driver_ext *x;
144 double width_pt, length_pt;
147 this = outp_allocate_driver (&cairo_class, name, types);
148 this->width = this->length = 0;
149 this->font_height = XR_POINT * 10;
151 this->ext = x = xmalloc (sizeof *x);
152 x->file_name = xstrdup ("pspp.pdf");
153 x->file_type = XR_PDF;
154 x->draw_headers = true;
157 outp_get_paper_size ("", &x->paper_width, &x->paper_length);
158 x->left_margin = XR_INCH / 2;
159 x->right_margin = XR_INCH / 2;
160 x->top_margin = XR_INCH / 2;
161 x->bottom_margin = XR_INCH / 2;
162 x->line_gutter = XR_POINT;
163 x->line_space = XR_POINT;
164 x->line_width = XR_POINT / 2;
165 x->fonts[OUTP_FIXED].string = xstrdup ("monospace");
166 x->fonts[OUTP_PROPORTIONAL].string = xstrdup ("serif");
167 x->fonts[OUTP_EMPHASIS].string = xstrdup ("serif italic");
168 for (i = 0; i < OUTP_FONT_CNT; i++)
170 struct xr_font *font = &x->fonts[i];
172 font->metrics = NULL;
176 outp_parse_options (options, handle_option, this);
178 width_pt = x->paper_width / 1000.0;
179 length_pt = x->paper_length / 1000.0;
182 this->width = pt_to_xr (width_pt);
183 this->length = pt_to_xr (length_pt);
187 this->width = pt_to_xr (width_pt);
188 this->length = pt_to_xr (length_pt);
191 x->top_margin += 3 * this->font_height;
192 this->width -= x->left_margin + x->right_margin;
193 this->length -= x->top_margin + x->bottom_margin;
195 if (x->file_type == XR_PDF)
196 surface = cairo_pdf_surface_create (x->file_name, width_pt, length_pt);
197 else if (x->file_type == XR_PS)
198 surface = cairo_ps_surface_create (x->file_name, width_pt, length_pt);
199 else if (x->file_type == XR_SVG)
200 surface = cairo_svg_surface_create (x->file_name, width_pt, length_pt);
204 status = cairo_surface_status (surface);
205 if (status != CAIRO_STATUS_SUCCESS)
207 error (0, 0, _("opening output file \"%s\": %s"),
208 x->file_name, cairo_status_to_string (status));
209 cairo_surface_destroy (surface);
213 x->cairo = cairo_create (surface);
214 cairo_surface_destroy (surface);
216 cairo_translate (x->cairo,
217 xr_to_pt (x->left_margin),
218 xr_to_pt (x->top_margin));
219 cairo_set_line_width (x->cairo, xr_to_pt (x->line_width));
221 for (i = 0; i < OUTP_FONT_CNT; i++)
222 if (!load_font (this, &x->fonts[i]))
225 if (this->length / this->font_height < 15)
227 error (0, 0, _("The defined page is not long "
228 "enough to hold margins and headers, plus least 15 "
229 "lines of the default fonts. In fact, there's only "
230 "room for %d lines."),
231 this->length / this->font_height);
235 this->fixed_width = text_width (this, "0", OUTP_FIXED);
236 this->prop_em_width = text_width (this, "0", OUTP_PROPORTIONAL);
238 this->horiz_line_width[OUTP_L_NONE] = 0;
239 this->horiz_line_width[OUTP_L_SINGLE] = 2 * x->line_gutter + x->line_width;
240 this->horiz_line_width[OUTP_L_DOUBLE] = (2 * x->line_gutter + x->line_space
241 + 2 * x->line_width);
242 memcpy (this->vert_line_width, this->horiz_line_width,
243 sizeof this->vert_line_width);
245 outp_register_driver (this);
249 this->class->close_driver (this);
250 outp_free_driver (this);
255 xr_close_driver (struct outp_driver *this)
257 struct xr_driver_ext *x = this->ext;
261 if (x->cairo != NULL)
263 cairo_status_t status;
265 cairo_surface_finish (cairo_get_target (x->cairo));
266 status = cairo_status (x->cairo);
267 if (status != CAIRO_STATUS_SUCCESS)
268 error (0, 0, _("writing output file \"%s\": %s"),
269 x->file_name, cairo_status_to_string (status));
270 cairo_destroy (x->cairo);
274 for (i = 0; i < OUTP_FONT_CNT; i++)
275 free_font (&x->fonts[i]);
281 /* Generic option types. */
295 /* All the options that the Cairo driver supports. */
296 static const struct outp_option option_tab[] =
298 {"output-file", output_file_arg,0},
299 {"output-type", output_type_arg,0},
300 {"paper-size", paper_size_arg, 0},
301 {"orientation", orientation_arg,0},
303 {"headers", boolean_arg, 1},
305 {"prop-font", string_arg, OUTP_PROPORTIONAL},
306 {"emph-font", string_arg, OUTP_EMPHASIS},
307 {"fixed-font", string_arg, OUTP_FIXED},
309 {"left-margin", dimension_arg, 0},
310 {"right-margin", dimension_arg, 1},
311 {"top-margin", dimension_arg, 2},
312 {"bottom-margin", dimension_arg, 3},
313 {"font-size", dimension_arg, 4},
314 {"line-width", dimension_arg, 5},
315 {"line-gutter", dimension_arg, 6},
316 {"line-width", dimension_arg, 7},
321 handle_option (struct outp_driver *this, const char *key,
322 const struct string *val)
324 struct xr_driver_ext *x = this->ext;
326 char *value = ds_cstr (val);
328 switch (outp_match_keyword (key, option_tab, &subcat))
332 _("unknown configuration parameter `%s' for Cairo device "
335 case output_file_arg:
337 x->file_name = xstrdup (value);
339 case output_type_arg:
340 if (!strcmp (value, "pdf"))
341 x->file_type = XR_PDF;
342 else if (!strcmp (value, "ps"))
343 x->file_type = XR_PS;
344 else if (!strcmp (value, "svg"))
345 x->file_type = XR_SVG;
348 error (0, 0, _("unknown Cairo output type \"%s\""), value);
353 outp_get_paper_size (value, &x->paper_width, &x->paper_length);
355 case orientation_arg:
356 if (!strcmp (value, "portrait"))
358 else if (!strcmp (value, "landscape"))
361 error (0, 0, _("unknown orientation `%s' (valid orientations are "
362 "`portrait' and `landscape')"), value);
365 if (!strcmp (value, "on") || !strcmp (value, "true")
366 || !strcmp (value, "yes") || atoi (value))
367 x->draw_headers = true;
368 else if (!strcmp (value, "off") || !strcmp (value, "false")
369 || !strcmp (value, "no") || !strcmp (value, "0"))
370 x->draw_headers = false;
373 error (0, 0, _("boolean value expected for %s"), key);
379 int dimension = outp_evaluate_dimension (value);
386 x->left_margin = dimension;
389 x->right_margin = dimension;
392 x->top_margin = dimension;
395 x->bottom_margin = dimension;
398 this->font_height = dimension;
401 x->line_width = dimension;
404 x->line_gutter = dimension;
407 x->line_width = dimension;
415 free (x->fonts[subcat].string);
416 x->fonts[subcat].string = ds_xstrdup (val);
425 /* Basic file operations. */
428 xr_open_page (struct outp_driver *this)
430 struct xr_driver_ext *x = this->ext;
439 xr_close_page (struct outp_driver *this)
441 struct xr_driver_ext *x = this->ext;
442 cairo_show_page (x->cairo);
446 xr_submit (struct outp_driver *this UNUSED, struct som_entity *s)
457 /* Draws a line from (x0,y0) to (x1,y1). */
459 dump_line (struct outp_driver *this, int x0, int y0, int x1, int y1)
461 struct xr_driver_ext *x = this->ext;
462 cairo_new_path (x->cairo);
463 cairo_move_to (x->cairo, xr_to_pt (x0), xr_to_pt (y0));
464 cairo_line_to (x->cairo, xr_to_pt (x1), xr_to_pt (y1));
465 cairo_stroke (x->cairo);
468 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
469 shortening it to X0...X1 if SHORTEN is true.
470 Draws a horizontal line X1...X3 at Y if RIGHT says so,
471 shortening it to X2...X3 if SHORTEN is true. */
473 horz_line (struct outp_driver *this,
474 int x0, int x1, int x2, int x3, int y,
475 enum outp_line_style left, enum outp_line_style right,
478 if (left != OUTP_L_NONE && right != OUTP_L_NONE && !shorten)
479 dump_line (this, x0, y, x3, y);
482 if (left != OUTP_L_NONE)
483 dump_line (this, x0, y, shorten ? x1 : x2, y);
484 if (right != OUTP_L_NONE)
485 dump_line (this, shorten ? x2 : x1, y, x3, y);
489 /* Draws a vertical line Y0...Y2 at X if TOP says so,
490 shortening it to Y0...Y1 if SHORTEN is true.
491 Draws a vertical line Y1...Y3 at X if BOTTOM says so,
492 shortening it to Y2...Y3 if SHORTEN is true. */
494 vert_line (struct outp_driver *this,
495 int y0, int y1, int y2, int y3, int x,
496 enum outp_line_style top, enum outp_line_style bottom,
499 if (top != OUTP_L_NONE && bottom != OUTP_L_NONE && !shorten)
500 dump_line (this, x, y0, x, y3);
503 if (top != OUTP_L_NONE)
504 dump_line (this, x, y0, x, shorten ? y1 : y2);
505 if (bottom != OUTP_L_NONE)
506 dump_line (this, x, shorten ? y2 : y1, x, y3);
510 /* Draws a generalized intersection of lines in the rectangle
511 (X0,Y0)-(X3,Y3). The line coming from the top to the center
512 is of style TOP, from left to center of style LEFT, from
513 bottom to center of style BOTTOM, and from right to center of
516 xr_line (struct outp_driver *this,
517 int x0, int y0, int x3, int y3,
518 enum outp_line_style top, enum outp_line_style left,
519 enum outp_line_style bottom, enum outp_line_style right)
521 /* The algorithm here is somewhat subtle, to allow it to handle
522 all the kinds of intersections that we need.
524 Three additional ordinates are assigned along the x axis. The
525 first is xc, midway between x0 and x3. The others are x1 and
526 x2; for a single vertical line these are equal to xc, and for
527 a double vertical line they are the ordinates of the left and
528 right half of the double line.
530 yc, y1, and y2 are assigned similarly along the y axis.
532 The following diagram shows the coordinate system and output
533 for double top and bottom lines, single left line, and no
537 y0 ________________________
543 y1 = y2 = yc |######### # |
548 y3 |________#_____#_______|
550 struct xr_driver_ext *ext = this->ext;
552 /* Offset from center of each line in a pair of double lines. */
553 int double_line_ofs = (ext->line_space + ext->line_width) / 2;
555 /* Are the lines along each axis single or double?
556 (It doesn't make sense to have different kinds of line on the
557 same axis, so we don't try to gracefully handle that case.) */
558 bool double_vert = top == OUTP_L_DOUBLE || bottom == OUTP_L_DOUBLE;
559 bool double_horz = left == OUTP_L_DOUBLE || right == OUTP_L_DOUBLE;
561 /* When horizontal lines are doubled,
562 the left-side line along y1 normally runs from x0 to x2,
563 and the right-side line along y1 from x3 to x1.
564 If the top-side line is also doubled, we shorten the y1 lines,
565 so that the left-side line runs only to x1,
566 and the right-side line only to x2.
567 Otherwise, the horizontal line at y = y1 below would cut off
568 the intersection, which looks ugly:
570 y0 ________________________
575 y1 |######### ########|
578 y2 |######################|
581 y3 |______________________|
582 It is more of a judgment call when the horizontal line is
583 single. We actually choose to cut off the line anyhow, as
584 shown in the first diagram above.
586 bool shorten_y1_lines = top == OUTP_L_DOUBLE;
587 bool shorten_y2_lines = bottom == OUTP_L_DOUBLE;
588 bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
589 int horz_line_ofs = double_vert ? double_line_ofs : 0;
590 int xc = (x0 + x3) / 2;
591 int x1 = xc - horz_line_ofs;
592 int x2 = xc + horz_line_ofs;
594 bool shorten_x1_lines = left == OUTP_L_DOUBLE;
595 bool shorten_x2_lines = right == OUTP_L_DOUBLE;
596 bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
597 int vert_line_ofs = double_horz ? double_line_ofs : 0;
598 int yc = (y0 + y3) / 2;
599 int y1 = yc - vert_line_ofs;
600 int y2 = yc + vert_line_ofs;
603 horz_line (this, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
606 horz_line (this, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
607 horz_line (this, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
611 vert_line (this, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
614 vert_line (this, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
615 vert_line (this, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
619 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
620 and with the given JUSTIFICATION for THIS driver. */
622 draw_text (struct outp_driver *this,
623 const char *string, int x, int y, int max_width,
624 enum outp_justification justification)
626 struct outp_text text;
629 text.font = OUTP_PROPORTIONAL;
630 text.justification = justification;
631 text.string = ss_cstr (string);
633 text.v = this->font_height;
636 this->class->text_metrics (this, &text, &width, NULL);
637 this->class->text_draw (this, &text);
641 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
642 and with the given JUSTIFICATION for THIS driver. */
644 text_width (struct outp_driver *this, const char *string, enum outp_font font)
646 struct outp_text text;
650 text.justification = OUTP_LEFT;
651 text.string = ss_cstr (string);
653 text.v = this->font_height;
656 this->class->text_metrics (this, &text, &width, NULL);
660 /* Writes LEFT left-justified and RIGHT right-justified within
661 (X0...X1) at Y. LEFT or RIGHT or both may be null. */
663 draw_header_line (struct outp_driver *this,
664 const char *left, const char *right,
665 int x0, int x1, int y)
669 right_width = (draw_text (this, right, x0, y, x1 - x0, OUTP_RIGHT)
670 + this->prop_em_width);
672 draw_text (this, left, x0, y, x1 - x0 - right_width, OUTP_LEFT);
675 /* Draw top of page headers for THIS driver. */
677 draw_headers (struct outp_driver *this)
679 struct xr_driver_ext *ext = this->ext;
684 y = -3 * this->font_height;
685 x0 = this->prop_em_width;
686 x1 = this->width - this->prop_em_width;
689 cairo_rectangle (ext->cairo, 0, xr_to_pt (y), xr_to_pt (this->width),
690 xr_to_pt (2 * (this->font_height
691 + ext->line_width + ext->line_gutter)));
692 cairo_save (ext->cairo);
693 cairo_set_source_rgb (ext->cairo, 0.9, 0.9, 0.9);
694 cairo_fill_preserve (ext->cairo);
695 cairo_restore (ext->cairo);
696 cairo_stroke (ext->cairo);
698 y += ext->line_width + ext->line_gutter;
700 r1 = xasprintf (_("%s - Page %d"), get_start_date (), ext->page_number);
701 r2 = xasprintf ("%s - %s", version, host_system);
703 draw_header_line (this, outp_title, r1, x0, x1, y);
704 y += this->font_height;
706 draw_header_line (this, outp_subtitle, r2, x0, x1, y);
712 /* Format TEXT on THIS driver.
713 If DRAW is nonzero, draw the text.
714 The width of the widest line is stored into *WIDTH, if WIDTH
716 The total height of the text written is stored into *HEIGHT,
717 if HEIGHT is nonnull. */
719 text (struct outp_driver *this, const struct outp_text *text, bool draw,
720 int *width, int *height)
722 struct xr_driver_ext *ext = this->ext;
723 struct xr_font *font = &ext->fonts[text->font];
725 pango_layout_set_text (font->layout,
726 text->string.string, text->string.length);
727 pango_layout_set_alignment (
729 (text->justification == OUTP_RIGHT ? PANGO_ALIGN_RIGHT
730 : text->justification == OUTP_LEFT ? PANGO_ALIGN_LEFT
731 : PANGO_ALIGN_CENTER));
732 pango_layout_set_width (font->layout, text->h == INT_MAX ? -1 : text->h);
733 pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD_CHAR);
734 /* XXX need to limit number of lines to those that fit in text->v. */
739 if (text->justification != OUTP_LEFT && text->h != INT_MAX)
742 pango_layout_get_size (font->layout, &w, &h);
743 excess = text->h - w;
746 if (text->justification == OUTP_CENTER)
752 cairo_save (ext->cairo);
753 cairo_translate (ext->cairo, xr_to_pt (text->x), xr_to_pt (text->y));
754 pango_cairo_show_layout (ext->cairo, font->layout);
755 cairo_restore (ext->cairo);
758 if (width != NULL || height != NULL)
761 pango_layout_get_size (font->layout, &w, &h);
770 xr_text_metrics (struct outp_driver *this, const struct outp_text *t,
771 int *width, int *height)
773 text (this, t, false, width, height);
777 xr_text_draw (struct outp_driver *this, const struct outp_text *t)
779 assert (this->page_open);
780 text (this, t, true, NULL, NULL);
784 xr_chart_initialise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
789 /* XXX libplot doesn't support Cairo yet. */
794 xr_chart_finalise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
797 /* XXX libplot doesn't support Cairo yet. */
801 /* Attempts to load FONT, initializing its other members based on
802 its 'string' member and the information in THIS. Returns true
803 if successful, otherwise false. */
805 load_font (struct outp_driver *this, struct xr_font *font)
807 struct xr_driver_ext *x = this->ext;
808 PangoContext *context;
809 PangoLanguage *language;
811 font->desc = pango_font_description_from_string (font->string);
812 if (font->desc == NULL)
814 error (0, 0, _("\"%s\": bad font specification"), font->string);
817 pango_font_description_set_absolute_size (font->desc, this->font_height);
819 font->layout = pango_cairo_create_layout (x->cairo);
820 pango_layout_set_font_description (font->layout, font->desc);
822 language = pango_language_get_default ();
823 context = pango_layout_get_context (font->layout);
824 font->metrics = pango_context_get_metrics (context, font->desc, language);
831 free_font (struct xr_font *font)
834 if (font->desc != NULL)
835 pango_font_description_free (font->desc);
836 pango_font_metrics_unref (font->metrics);
837 g_object_unref (font->layout);
840 /* Cairo driver class. */
841 const struct outp_class cairo_class =