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. We use the same scale as Pango, for simplicity. */
69 #define XR_POINT PANGO_SCALE
70 #define XR_INCH (XR_POINT * 72)
80 /* A font for use with Cairo. */
84 PangoFontDescription *desc;
86 PangoFontMetrics *metrics;
89 /* Cairo output driver extension record. */
92 char *file_name; /* Output file name. */
93 enum xr_output_type file_type; /* Type of output file. */
96 bool draw_headers; /* Draw headers at top of page? */
97 int page_number; /* Current page number. */
99 bool portrait; /* Portrait mode? */
100 int paper_width; /* Width of paper before dropping margins. */
101 int paper_length; /* Length of paper before dropping margins. */
102 int left_margin; /* Left margin in XR units. */
103 int right_margin; /* Right margin in XR units. */
104 int top_margin; /* Top margin in XR units. */
105 int bottom_margin; /* Bottom margin in XR units. */
107 int line_gutter; /* Space around lines. */
108 int line_space; /* Space between lines. */
109 int line_width; /* Width of lines. */
111 struct xr_font fonts[OUTP_FONT_CNT];
114 static bool handle_option (struct outp_driver *this, const char *key,
115 const struct string *val);
116 static void draw_headers (struct outp_driver *this);
118 static bool load_font (struct outp_driver *this, struct xr_font *);
119 static void free_font (struct xr_font *);
120 static int text_width (struct outp_driver *, const char *, enum outp_font);
122 /* Driver initialization. */
125 xr_open_driver (struct outp_driver *this, struct substring options)
127 cairo_surface_t *surface;
128 cairo_status_t status;
129 struct xr_driver_ext *x;
130 double width_pt, length_pt;
133 this->width = this->length = 0;
134 this->font_height = XR_POINT * 10;
136 this->ext = x = xmalloc (sizeof *x);
137 x->file_name = xstrdup ("pspp.pdf");
138 x->file_type = XR_PDF;
139 x->draw_headers = true;
142 outp_get_paper_size ("", &x->paper_width, &x->paper_length);
143 x->left_margin = XR_INCH / 2;
144 x->right_margin = XR_INCH / 2;
145 x->top_margin = XR_INCH / 2;
146 x->bottom_margin = XR_INCH / 2;
147 x->line_gutter = XR_POINT;
148 x->line_space = XR_POINT;
149 x->line_width = XR_POINT / 2;
150 x->fonts[OUTP_FIXED].string = xstrdup ("monospace");
151 x->fonts[OUTP_PROPORTIONAL].string = xstrdup ("serif");
152 x->fonts[OUTP_EMPHASIS].string = xstrdup ("serif italic");
153 for (i = 0; i < OUTP_FONT_CNT; i++)
155 struct xr_font *font = &x->fonts[i];
157 font->metrics = NULL;
161 outp_parse_options (options, handle_option, this);
165 this->width = x->paper_width;
166 this->length = x->paper_length;
170 this->width = x->paper_length;
171 this->length = x->paper_width;
174 x->top_margin += 3 * this->font_height;
175 this->width -= x->left_margin + x->right_margin;
176 this->length -= x->top_margin + x->bottom_margin;
178 width_pt = x->paper_width / (double) XR_POINT;
179 length_pt = x->paper_length / (double) XR_POINT;
180 if (x->file_type == XR_PDF)
181 surface = cairo_pdf_surface_create (x->file_name, width_pt, length_pt);
182 else if (x->file_type == XR_PS)
183 surface = cairo_ps_surface_create (x->file_name, width_pt, length_pt);
184 else if (x->file_type == XR_SVG)
185 surface = cairo_svg_surface_create (x->file_name, width_pt, length_pt);
189 status = cairo_surface_status (surface);
190 if (status != CAIRO_STATUS_SUCCESS)
192 error (0, 0, _("opening output file \"%s\": %s"),
193 x->file_name, cairo_status_to_string (status));
194 cairo_surface_destroy (surface);
198 x->cairo = cairo_create (surface);
199 cairo_surface_destroy (surface);
201 cairo_scale (x->cairo, 1.0 / PANGO_SCALE, 1.0 / PANGO_SCALE);
202 cairo_translate (x->cairo, x->left_margin, x->top_margin);
203 cairo_set_line_width (x->cairo, x->line_width);
205 for (i = 0; i < OUTP_FONT_CNT; i++)
206 if (!load_font (this, &x->fonts[i]))
209 if (this->length / this->font_height < 15)
211 error (0, 0, _("The defined page is not long "
212 "enough to hold margins and headers, plus least 15 "
213 "lines of the default fonts. In fact, there's only "
214 "room for %d lines."),
215 this->length / this->font_height);
219 this->fixed_width = text_width (this, "0", OUTP_FIXED);
220 this->prop_em_width = text_width (this, "0", OUTP_PROPORTIONAL);
222 this->horiz_line_width[OUTP_L_NONE] = 0;
223 this->horiz_line_width[OUTP_L_SINGLE] = 2 * x->line_gutter + x->line_width;
224 this->horiz_line_width[OUTP_L_DOUBLE] = (2 * x->line_gutter + x->line_space
225 + 2 * x->line_width);
226 memcpy (this->vert_line_width, this->horiz_line_width,
227 sizeof this->vert_line_width);
232 this->class->close_driver (this);
237 xr_close_driver (struct outp_driver *this)
239 struct xr_driver_ext *x = this->ext;
243 if (x->cairo != NULL)
245 cairo_status_t status;
247 cairo_surface_finish (cairo_get_target (x->cairo));
248 status = cairo_status (x->cairo);
249 if (status != CAIRO_STATUS_SUCCESS)
250 error (0, 0, _("writing output file \"%s\": %s"),
251 x->file_name, cairo_status_to_string (status));
252 cairo_destroy (x->cairo);
256 for (i = 0; i < OUTP_FONT_CNT; i++)
257 free_font (&x->fonts[i]);
263 /* Generic option types. */
277 /* All the options that the Cairo driver supports. */
278 static const struct outp_option option_tab[] =
280 {"output-file", output_file_arg,0},
281 {"output-type", output_type_arg,0},
282 {"paper-size", paper_size_arg, 0},
283 {"orientation", orientation_arg,0},
285 {"headers", boolean_arg, 1},
287 {"prop-font", string_arg, OUTP_PROPORTIONAL},
288 {"emph-font", string_arg, OUTP_EMPHASIS},
289 {"fixed-font", string_arg, OUTP_FIXED},
291 {"left-margin", dimension_arg, 0},
292 {"right-margin", dimension_arg, 1},
293 {"top-margin", dimension_arg, 2},
294 {"bottom-margin", dimension_arg, 3},
295 {"font-size", dimension_arg, 4},
296 {"line-width", dimension_arg, 5},
297 {"line-gutter", dimension_arg, 6},
298 {"line-width", dimension_arg, 7},
303 handle_option (struct outp_driver *this, const char *key,
304 const struct string *val)
306 struct xr_driver_ext *x = this->ext;
308 char *value = ds_cstr (val);
310 switch (outp_match_keyword (key, option_tab, &subcat))
314 _("unknown configuration parameter `%s' for Cairo device "
317 case output_file_arg:
319 x->file_name = xstrdup (value);
321 case output_type_arg:
322 if (!strcmp (value, "pdf"))
323 x->file_type = XR_PDF;
324 else if (!strcmp (value, "ps"))
325 x->file_type = XR_PS;
326 else if (!strcmp (value, "svg"))
327 x->file_type = XR_SVG;
330 error (0, 0, _("unknown Cairo output type \"%s\""), value);
335 outp_get_paper_size (value, &x->paper_width, &x->paper_length);
337 case orientation_arg:
338 if (!strcmp (value, "portrait"))
340 else if (!strcmp (value, "landscape"))
343 error (0, 0, _("unknown orientation `%s' (valid orientations are "
344 "`portrait' and `landscape')"), value);
347 if (!strcmp (value, "on") || !strcmp (value, "true")
348 || !strcmp (value, "yes") || atoi (value))
349 x->draw_headers = true;
350 else if (!strcmp (value, "off") || !strcmp (value, "false")
351 || !strcmp (value, "no") || !strcmp (value, "0"))
352 x->draw_headers = false;
355 error (0, 0, _("boolean value expected for %s"), key);
361 int dimension = outp_evaluate_dimension (value);
368 x->left_margin = dimension;
371 x->right_margin = dimension;
374 x->top_margin = dimension;
377 x->bottom_margin = dimension;
380 this->font_height = dimension;
383 x->line_width = dimension;
386 x->line_gutter = dimension;
389 x->line_width = dimension;
397 free (x->fonts[subcat].string);
398 x->fonts[subcat].string = ds_xstrdup (val);
407 /* Basic file operations. */
410 xr_open_page (struct outp_driver *this)
412 struct xr_driver_ext *x = this->ext;
421 xr_close_page (struct outp_driver *this)
423 struct xr_driver_ext *x = this->ext;
424 cairo_show_page (x->cairo);
428 xr_submit (struct outp_driver *this UNUSED, struct som_entity *s)
439 /* Draws a line from (x0,y0) to (x1,y1). */
441 dump_line (struct outp_driver *this, int x0, int y0, int x1, int y1)
443 struct xr_driver_ext *x = this->ext;
444 cairo_new_path (x->cairo);
445 cairo_move_to (x->cairo, x0, y0);
446 cairo_line_to (x->cairo, x1, y1);
447 cairo_stroke (x->cairo);
450 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
451 shortening it to X0...X1 if SHORTEN is true.
452 Draws a horizontal line X1...X3 at Y if RIGHT says so,
453 shortening it to X2...X3 if SHORTEN is true. */
455 horz_line (struct outp_driver *this,
456 int x0, int x1, int x2, int x3, int y,
457 enum outp_line_style left, enum outp_line_style right,
460 if (left != OUTP_L_NONE && right != OUTP_L_NONE && !shorten)
461 dump_line (this, x0, y, x3, y);
464 if (left != OUTP_L_NONE)
465 dump_line (this, x0, y, shorten ? x1 : x2, y);
466 if (right != OUTP_L_NONE)
467 dump_line (this, shorten ? x2 : x1, y, x3, y);
471 /* Draws a vertical line Y0...Y2 at X if TOP says so,
472 shortening it to Y0...Y1 if SHORTEN is true.
473 Draws a vertical line Y1...Y3 at X if BOTTOM says so,
474 shortening it to Y2...Y3 if SHORTEN is true. */
476 vert_line (struct outp_driver *this,
477 int y0, int y1, int y2, int y3, int x,
478 enum outp_line_style top, enum outp_line_style bottom,
481 if (top != OUTP_L_NONE && bottom != OUTP_L_NONE && !shorten)
482 dump_line (this, x, y0, x, y3);
485 if (top != OUTP_L_NONE)
486 dump_line (this, x, y0, x, shorten ? y1 : y2);
487 if (bottom != OUTP_L_NONE)
488 dump_line (this, x, shorten ? y2 : y1, x, y3);
492 /* Draws a generalized intersection of lines in the rectangle
493 (X0,Y0)-(X3,Y3). The line coming from the top to the center
494 is of style TOP, from left to center of style LEFT, from
495 bottom to center of style BOTTOM, and from right to center of
498 xr_line (struct outp_driver *this,
499 int x0, int y0, int x3, int y3,
500 enum outp_line_style top, enum outp_line_style left,
501 enum outp_line_style bottom, enum outp_line_style right)
503 /* The algorithm here is somewhat subtle, to allow it to handle
504 all the kinds of intersections that we need.
506 Three additional ordinates are assigned along the x axis. The
507 first is xc, midway between x0 and x3. The others are x1 and
508 x2; for a single vertical line these are equal to xc, and for
509 a double vertical line they are the ordinates of the left and
510 right half of the double line.
512 yc, y1, and y2 are assigned similarly along the y axis.
514 The following diagram shows the coordinate system and output
515 for double top and bottom lines, single left line, and no
519 y0 ________________________
525 y1 = y2 = yc |######### # |
530 y3 |________#_____#_______|
532 struct xr_driver_ext *ext = this->ext;
534 /* Offset from center of each line in a pair of double lines. */
535 int double_line_ofs = (ext->line_space + ext->line_width) / 2;
537 /* Are the lines along each axis single or double?
538 (It doesn't make sense to have different kinds of line on the
539 same axis, so we don't try to gracefully handle that case.) */
540 bool double_vert = top == OUTP_L_DOUBLE || bottom == OUTP_L_DOUBLE;
541 bool double_horz = left == OUTP_L_DOUBLE || right == OUTP_L_DOUBLE;
543 /* When horizontal lines are doubled,
544 the left-side line along y1 normally runs from x0 to x2,
545 and the right-side line along y1 from x3 to x1.
546 If the top-side line is also doubled, we shorten the y1 lines,
547 so that the left-side line runs only to x1,
548 and the right-side line only to x2.
549 Otherwise, the horizontal line at y = y1 below would cut off
550 the intersection, which looks ugly:
552 y0 ________________________
557 y1 |######### ########|
560 y2 |######################|
563 y3 |______________________|
564 It is more of a judgment call when the horizontal line is
565 single. We actually choose to cut off the line anyhow, as
566 shown in the first diagram above.
568 bool shorten_y1_lines = top == OUTP_L_DOUBLE;
569 bool shorten_y2_lines = bottom == OUTP_L_DOUBLE;
570 bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
571 int horz_line_ofs = double_vert ? double_line_ofs : 0;
572 int xc = (x0 + x3) / 2;
573 int x1 = xc - horz_line_ofs;
574 int x2 = xc + horz_line_ofs;
576 bool shorten_x1_lines = left == OUTP_L_DOUBLE;
577 bool shorten_x2_lines = right == OUTP_L_DOUBLE;
578 bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
579 int vert_line_ofs = double_horz ? double_line_ofs : 0;
580 int yc = (y0 + y3) / 2;
581 int y1 = yc - vert_line_ofs;
582 int y2 = yc + vert_line_ofs;
585 horz_line (this, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
588 horz_line (this, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
589 horz_line (this, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
593 vert_line (this, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
596 vert_line (this, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
597 vert_line (this, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
601 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
602 and with the given JUSTIFICATION for THIS driver. */
604 draw_text (struct outp_driver *this,
605 const char *string, int x, int y, int max_width,
606 enum outp_justification justification)
608 struct outp_text text;
611 text.font = OUTP_PROPORTIONAL;
612 text.justification = justification;
613 text.string = ss_cstr (string);
615 text.v = this->font_height;
618 this->class->text_metrics (this, &text, &width, NULL);
619 this->class->text_draw (this, &text);
623 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
624 and with the given JUSTIFICATION for THIS driver. */
626 text_width (struct outp_driver *this, const char *string, enum outp_font font)
628 struct outp_text text;
632 text.justification = OUTP_LEFT;
633 text.string = ss_cstr (string);
635 text.v = this->font_height;
638 this->class->text_metrics (this, &text, &width, NULL);
642 /* Writes LEFT left-justified and RIGHT right-justified within
643 (X0...X1) at Y. LEFT or RIGHT or both may be null. */
645 draw_header_line (struct outp_driver *this,
646 const char *left, const char *right,
647 int x0, int x1, int y)
651 right_width = (draw_text (this, right, x0, y, x1 - x0, OUTP_RIGHT)
652 + this->prop_em_width);
654 draw_text (this, left, x0, y, x1 - x0 - right_width, OUTP_LEFT);
657 /* Draw top of page headers for THIS driver. */
659 draw_headers (struct outp_driver *this)
661 struct xr_driver_ext *ext = this->ext;
666 y = -3 * this->font_height;
667 x0 = this->prop_em_width;
668 x1 = this->width - this->prop_em_width;
671 cairo_rectangle (ext->cairo, 0, y, this->width,
672 2 * (this->font_height
673 + ext->line_width + ext->line_gutter));
674 cairo_save (ext->cairo);
675 cairo_set_source_rgb (ext->cairo, 0.9, 0.9, 0.9);
676 cairo_fill_preserve (ext->cairo);
677 cairo_restore (ext->cairo);
678 cairo_stroke (ext->cairo);
680 y += ext->line_width + ext->line_gutter;
682 r1 = xasprintf (_("%s - Page %d"), get_start_date (), ext->page_number);
683 r2 = xasprintf ("%s - %s", version, host_system);
685 draw_header_line (this, outp_title, r1, x0, x1, y);
686 y += this->font_height;
688 draw_header_line (this, outp_subtitle, r2, x0, x1, y);
694 /* Format TEXT on THIS driver.
695 If DRAW is nonzero, draw the text.
696 The width of the widest line is stored into *WIDTH, if WIDTH
698 The total height of the text written is stored into *HEIGHT,
699 if HEIGHT is nonnull. */
701 text (struct outp_driver *this, const struct outp_text *text, bool draw,
702 int *width, int *height)
704 struct xr_driver_ext *ext = this->ext;
705 struct xr_font *font = &ext->fonts[text->font];
707 pango_layout_set_text (font->layout,
708 text->string.string, text->string.length);
709 pango_layout_set_alignment (
711 (text->justification == OUTP_RIGHT ? PANGO_ALIGN_RIGHT
712 : text->justification == OUTP_LEFT ? PANGO_ALIGN_LEFT
713 : PANGO_ALIGN_CENTER));
714 pango_layout_set_width (font->layout, text->h == INT_MAX ? -1 : text->h);
715 pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD_CHAR);
716 /* XXX need to limit number of lines to those that fit in text->v. */
721 if (text->justification != OUTP_LEFT && text->h != INT_MAX)
724 pango_layout_get_size (font->layout, &w, &h);
725 excess = text->h - w;
728 if (text->justification == OUTP_CENTER)
734 cairo_save (ext->cairo);
735 cairo_translate (ext->cairo, text->x, text->y);
736 cairo_scale (ext->cairo, PANGO_SCALE, PANGO_SCALE);
737 pango_cairo_show_layout (ext->cairo, font->layout);
738 cairo_restore (ext->cairo);
739 pango_cairo_update_layout (ext->cairo, font->layout);
742 if (width != NULL || height != NULL)
745 pango_layout_get_size (font->layout, &w, &h);
754 xr_text_metrics (struct outp_driver *this, const struct outp_text *t,
755 int *width, int *height)
757 text (this, t, false, width, height);
761 xr_text_draw (struct outp_driver *this, const struct outp_text *t)
763 assert (this->page_open);
764 text (this, t, true, NULL, NULL);
768 xr_chart_initialise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
773 /* XXX libplot doesn't support Cairo yet. */
778 xr_chart_finalise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
781 /* XXX libplot doesn't support Cairo yet. */
785 /* Attempts to load FONT, initializing its other members based on
786 its 'string' member and the information in THIS. Returns true
787 if successful, otherwise false. */
789 load_font (struct outp_driver *this, struct xr_font *font)
791 struct xr_driver_ext *x = this->ext;
792 PangoContext *context;
793 PangoLanguage *language;
795 font->desc = pango_font_description_from_string (font->string);
796 if (font->desc == NULL)
798 error (0, 0, _("\"%s\": bad font specification"), font->string);
801 pango_font_description_set_absolute_size (font->desc, this->font_height);
803 font->layout = pango_cairo_create_layout (x->cairo);
804 pango_cairo_update_layout (x->cairo, font->layout);
805 pango_layout_set_font_description (font->layout, font->desc);
807 language = pango_language_get_default ();
808 context = pango_layout_get_context (font->layout);
809 font->metrics = pango_context_get_metrics (context, font->desc, language);
816 free_font (struct xr_font *font)
819 if (font->desc != NULL)
820 pango_font_description_free (font->desc);
821 pango_font_metrics_unref (font->metrics);
822 g_object_unref (font->layout);
825 /* Cairo driver class. */
826 const struct outp_class cairo_class =