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 *);
121 /* Driver initialization. */
124 xr_open_driver (struct outp_driver *this, struct substring options)
126 cairo_surface_t *surface;
127 cairo_status_t status;
128 struct xr_driver_ext *x;
129 double width_pt, length_pt;
132 this->width = this->length = 0;
133 this->font_height = XR_POINT * 10;
135 this->ext = x = xmalloc (sizeof *x);
136 x->file_name = xstrdup ("pspp.pdf");
137 x->file_type = XR_PDF;
138 x->draw_headers = true;
141 outp_get_paper_size ("", &x->paper_width, &x->paper_length);
142 x->left_margin = XR_INCH / 2;
143 x->right_margin = XR_INCH / 2;
144 x->top_margin = XR_INCH / 2;
145 x->bottom_margin = XR_INCH / 2;
146 x->line_gutter = XR_POINT;
147 x->line_space = XR_POINT;
148 x->line_width = XR_POINT / 2;
149 x->fonts[OUTP_FIXED].string = xstrdup ("monospace");
150 x->fonts[OUTP_PROPORTIONAL].string = xstrdup ("serif");
151 x->fonts[OUTP_EMPHASIS].string = xstrdup ("serif italic");
152 for (i = 0; i < OUTP_FONT_CNT; i++)
154 struct xr_font *font = &x->fonts[i];
156 font->metrics = NULL;
160 outp_parse_options (options, handle_option, this);
164 this->width = x->paper_width;
165 this->length = x->paper_length;
169 this->width = x->paper_length;
170 this->length = x->paper_width;
173 x->top_margin += 3 * this->font_height;
174 this->width -= x->left_margin + x->right_margin;
175 this->length -= x->top_margin + x->bottom_margin;
177 width_pt = x->paper_width / (double) XR_POINT;
178 length_pt = x->paper_length / (double) XR_POINT;
179 if (x->file_type == XR_PDF)
180 surface = cairo_pdf_surface_create (x->file_name, width_pt, length_pt);
181 else if (x->file_type == XR_PS)
182 surface = cairo_ps_surface_create (x->file_name, width_pt, length_pt);
183 else if (x->file_type == XR_SVG)
184 surface = cairo_svg_surface_create (x->file_name, width_pt, length_pt);
188 status = cairo_surface_status (surface);
189 if (status != CAIRO_STATUS_SUCCESS)
191 error (0, 0, _("opening output file \"%s\": %s"),
192 x->file_name, cairo_status_to_string (status));
193 cairo_surface_destroy (surface);
197 x->cairo = cairo_create (surface);
198 cairo_surface_destroy (surface);
200 cairo_scale (x->cairo, 1.0 / PANGO_SCALE, 1.0 / PANGO_SCALE);
201 cairo_translate (x->cairo, x->left_margin, x->top_margin);
202 cairo_set_line_width (x->cairo, x->line_width);
204 for (i = 0; i < OUTP_FONT_CNT; i++)
205 if (!load_font (this, &x->fonts[i]))
208 if (this->length / this->font_height < 15)
210 error (0, 0, _("The defined page is not long "
211 "enough to hold margins and headers, plus least 15 "
212 "lines of the default fonts. In fact, there's only "
213 "room for %d lines."),
214 this->length / this->font_height);
218 this->fixed_width = pango_font_metrics_get_approximate_char_width (
219 x->fonts[OUTP_FIXED].metrics);
220 this->prop_em_width = pango_font_metrics_get_approximate_char_width (
221 x->fonts[OUTP_PROPORTIONAL].metrics);
223 this->horiz_line_width[OUTP_L_NONE] = 0;
224 this->horiz_line_width[OUTP_L_SINGLE] = 2 * x->line_gutter + x->line_width;
225 this->horiz_line_width[OUTP_L_DOUBLE] = (2 * x->line_gutter + x->line_space
226 + 2 * x->line_width);
227 memcpy (this->vert_line_width, this->horiz_line_width,
228 sizeof this->vert_line_width);
233 this->class->close_driver (this);
238 xr_close_driver (struct outp_driver *this)
240 struct xr_driver_ext *x = this->ext;
244 if (x->cairo != NULL)
246 cairo_status_t status;
248 cairo_surface_finish (cairo_get_target (x->cairo));
249 status = cairo_status (x->cairo);
250 if (status != CAIRO_STATUS_SUCCESS)
251 error (0, 0, _("writing output file \"%s\": %s"),
252 x->file_name, cairo_status_to_string (status));
253 cairo_destroy (x->cairo);
257 for (i = 0; i < OUTP_FONT_CNT; i++)
258 free_font (&x->fonts[i]);
264 /* Generic option types. */
278 /* All the options that the Cairo driver supports. */
279 static const struct outp_option option_tab[] =
281 {"output-file", output_file_arg,0},
282 {"output-type", output_type_arg,0},
283 {"paper-size", paper_size_arg, 0},
284 {"orientation", orientation_arg,0},
286 {"headers", boolean_arg, 1},
288 {"prop-font", string_arg, OUTP_PROPORTIONAL},
289 {"emph-font", string_arg, OUTP_EMPHASIS},
290 {"fixed-font", string_arg, OUTP_FIXED},
292 {"left-margin", dimension_arg, 0},
293 {"right-margin", dimension_arg, 1},
294 {"top-margin", dimension_arg, 2},
295 {"bottom-margin", dimension_arg, 3},
296 {"font-size", dimension_arg, 4},
297 {"line-width", dimension_arg, 5},
298 {"line-gutter", dimension_arg, 6},
299 {"line-width", dimension_arg, 7},
304 handle_option (struct outp_driver *this, const char *key,
305 const struct string *val)
307 struct xr_driver_ext *x = this->ext;
309 char *value = ds_cstr (val);
311 switch (outp_match_keyword (key, option_tab, &subcat))
315 _("unknown configuration parameter `%s' for Cairo device "
318 case output_file_arg:
320 x->file_name = xstrdup (value);
322 case output_type_arg:
323 if (!strcmp (value, "pdf"))
324 x->file_type = XR_PDF;
325 else if (!strcmp (value, "ps"))
326 x->file_type = XR_PS;
327 else if (!strcmp (value, "svg"))
328 x->file_type = XR_SVG;
331 error (0, 0, _("unknown Cairo output type \"%s\""), value);
336 outp_get_paper_size (value, &x->paper_width, &x->paper_length);
338 case orientation_arg:
339 if (!strcmp (value, "portrait"))
341 else if (!strcmp (value, "landscape"))
344 error (0, 0, _("unknown orientation `%s' (valid orientations are "
345 "`portrait' and `landscape')"), value);
348 if (!strcmp (value, "on") || !strcmp (value, "true")
349 || !strcmp (value, "yes") || atoi (value))
350 x->draw_headers = true;
351 else if (!strcmp (value, "off") || !strcmp (value, "false")
352 || !strcmp (value, "no") || !strcmp (value, "0"))
353 x->draw_headers = false;
356 error (0, 0, _("boolean value expected for %s"), key);
362 int dimension = outp_evaluate_dimension (value);
369 x->left_margin = dimension;
372 x->right_margin = dimension;
375 x->top_margin = dimension;
378 x->bottom_margin = dimension;
381 this->font_height = dimension;
384 x->line_width = dimension;
387 x->line_gutter = dimension;
390 x->line_width = dimension;
398 free (x->fonts[subcat].string);
399 x->fonts[subcat].string = ds_xstrdup (val);
408 /* Basic file operations. */
411 xr_open_page (struct outp_driver *this)
413 struct xr_driver_ext *x = this->ext;
422 xr_close_page (struct outp_driver *this)
424 struct xr_driver_ext *x = this->ext;
425 cairo_show_page (x->cairo);
429 xr_submit (struct outp_driver *this UNUSED, struct som_entity *s)
440 /* Draws a line from (x0,y0) to (x1,y1). */
442 dump_line (struct outp_driver *this, int x0, int y0, int x1, int y1)
444 struct xr_driver_ext *x = this->ext;
445 cairo_new_path (x->cairo);
446 cairo_move_to (x->cairo, x0, y0);
447 cairo_line_to (x->cairo, x1, y1);
448 cairo_stroke (x->cairo);
451 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
452 shortening it to X0...X1 if SHORTEN is true.
453 Draws a horizontal line X1...X3 at Y if RIGHT says so,
454 shortening it to X2...X3 if SHORTEN is true. */
456 horz_line (struct outp_driver *this,
457 int x0, int x1, int x2, int x3, int y,
458 enum outp_line_style left, enum outp_line_style right,
461 if (left != OUTP_L_NONE && right != OUTP_L_NONE && !shorten)
462 dump_line (this, x0, y, x3, y);
465 if (left != OUTP_L_NONE)
466 dump_line (this, x0, y, shorten ? x1 : x2, y);
467 if (right != OUTP_L_NONE)
468 dump_line (this, shorten ? x2 : x1, y, x3, y);
472 /* Draws a vertical line Y0...Y2 at X if TOP says so,
473 shortening it to Y0...Y1 if SHORTEN is true.
474 Draws a vertical line Y1...Y3 at X if BOTTOM says so,
475 shortening it to Y2...Y3 if SHORTEN is true. */
477 vert_line (struct outp_driver *this,
478 int y0, int y1, int y2, int y3, int x,
479 enum outp_line_style top, enum outp_line_style bottom,
482 if (top != OUTP_L_NONE && bottom != OUTP_L_NONE && !shorten)
483 dump_line (this, x, y0, x, y3);
486 if (top != OUTP_L_NONE)
487 dump_line (this, x, y0, x, shorten ? y1 : y2);
488 if (bottom != OUTP_L_NONE)
489 dump_line (this, x, shorten ? y2 : y1, x, y3);
493 /* Draws a generalized intersection of lines in the rectangle
494 (X0,Y0)-(X3,Y3). The line coming from the top to the center
495 is of style TOP, from left to center of style LEFT, from
496 bottom to center of style BOTTOM, and from right to center of
499 xr_line (struct outp_driver *this,
500 int x0, int y0, int x3, int y3,
501 enum outp_line_style top, enum outp_line_style left,
502 enum outp_line_style bottom, enum outp_line_style right)
504 /* The algorithm here is somewhat subtle, to allow it to handle
505 all the kinds of intersections that we need.
507 Three additional ordinates are assigned along the x axis. The
508 first is xc, midway between x0 and x3. The others are x1 and
509 x2; for a single vertical line these are equal to xc, and for
510 a double vertical line they are the ordinates of the left and
511 right half of the double line.
513 yc, y1, and y2 are assigned similarly along the y axis.
515 The following diagram shows the coordinate system and output
516 for double top and bottom lines, single left line, and no
520 y0 ________________________
526 y1 = y2 = yc |######### # |
531 y3 |________#_____#_______|
533 struct xr_driver_ext *ext = this->ext;
535 /* Offset from center of each line in a pair of double lines. */
536 int double_line_ofs = (ext->line_space + ext->line_width) / 2;
538 /* Are the lines along each axis single or double?
539 (It doesn't make sense to have different kinds of line on the
540 same axis, so we don't try to gracefully handle that case.) */
541 bool double_vert = top == OUTP_L_DOUBLE || bottom == OUTP_L_DOUBLE;
542 bool double_horz = left == OUTP_L_DOUBLE || right == OUTP_L_DOUBLE;
544 /* When horizontal lines are doubled,
545 the left-side line along y1 normally runs from x0 to x2,
546 and the right-side line along y1 from x3 to x1.
547 If the top-side line is also doubled, we shorten the y1 lines,
548 so that the left-side line runs only to x1,
549 and the right-side line only to x2.
550 Otherwise, the horizontal line at y = y1 below would cut off
551 the intersection, which looks ugly:
553 y0 ________________________
558 y1 |######### ########|
561 y2 |######################|
564 y3 |______________________|
565 It is more of a judgment call when the horizontal line is
566 single. We actually choose to cut off the line anyhow, as
567 shown in the first diagram above.
569 bool shorten_y1_lines = top == OUTP_L_DOUBLE;
570 bool shorten_y2_lines = bottom == OUTP_L_DOUBLE;
571 bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
572 int horz_line_ofs = double_vert ? double_line_ofs : 0;
573 int xc = (x0 + x3) / 2;
574 int x1 = xc - horz_line_ofs;
575 int x2 = xc + horz_line_ofs;
577 bool shorten_x1_lines = left == OUTP_L_DOUBLE;
578 bool shorten_x2_lines = right == OUTP_L_DOUBLE;
579 bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
580 int vert_line_ofs = double_horz ? double_line_ofs : 0;
581 int yc = (y0 + y3) / 2;
582 int y1 = yc - vert_line_ofs;
583 int y2 = yc + vert_line_ofs;
586 horz_line (this, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
589 horz_line (this, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
590 horz_line (this, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
594 vert_line (this, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
597 vert_line (this, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
598 vert_line (this, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
602 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
603 and with the given JUSTIFICATION for THIS driver. */
605 draw_text (struct outp_driver *this,
606 const char *string, int x, int y, int max_width,
607 enum outp_justification justification)
609 struct outp_text text;
612 text.font = OUTP_PROPORTIONAL;
613 text.justification = justification;
614 text.string = ss_cstr (string);
616 text.v = this->font_height;
619 this->class->text_metrics (this, &text, &width, NULL);
620 this->class->text_draw (this, &text);
624 /* Writes LEFT left-justified and RIGHT right-justified within
625 (X0...X1) at Y. LEFT or RIGHT or both may be null. */
627 draw_header_line (struct outp_driver *this,
628 const char *left, const char *right,
629 int x0, int x1, int y)
633 right_width = (draw_text (this, right, x0, y, x1 - x0, OUTP_RIGHT)
634 + this->prop_em_width);
636 draw_text (this, left, x0, y, x1 - x0 - right_width, OUTP_LEFT);
639 /* Draw top of page headers for THIS driver. */
641 draw_headers (struct outp_driver *this)
643 struct xr_driver_ext *ext = this->ext;
648 y = -3 * this->font_height;
649 x0 = this->prop_em_width;
650 x1 = this->width - this->prop_em_width;
653 /* XXX coordinates below might not be right, both in terms of
654 the Y transformation and width/height versus x2/y2. */
655 cairo_rectangle (ext->cairo, 0, y,
656 this->width, 2 * this->font_height + ext->line_gutter);
657 cairo_save (ext->cairo);
658 cairo_set_source_rgb (ext->cairo, 0.9, 0.9, 0.9);
659 cairo_fill_preserve (ext->cairo);
660 cairo_restore (ext->cairo);
661 cairo_stroke (ext->cairo);
663 y += ext->line_width + ext->line_gutter;
665 r1 = xasprintf (_("%s - Page %d"), get_start_date (), ext->page_number);
666 r2 = xasprintf ("%s - %s", version, host_system);
668 draw_header_line (this, outp_title, r1, x0, x1, y);
669 y += this->font_height;
671 draw_header_line (this, outp_subtitle, r2, x0, x1, y);
677 /* Format TEXT on THIS driver.
678 If DRAW is nonzero, draw the text.
679 The width of the widest line is stored into *WIDTH, if WIDTH
681 The total height of the text written is stored into *HEIGHT,
682 if HEIGHT is nonnull. */
684 text (struct outp_driver *this, const struct outp_text *text, bool draw,
685 int *width, int *height)
687 struct xr_driver_ext *ext = this->ext;
688 struct xr_font *font = &ext->fonts[text->font];
690 pango_layout_set_text (font->layout,
691 text->string.string, text->string.length);
692 pango_layout_set_alignment (
694 (text->justification == OUTP_RIGHT ? PANGO_ALIGN_RIGHT
695 : text->justification == OUTP_LEFT ? PANGO_ALIGN_LEFT
696 : PANGO_ALIGN_CENTER));
697 pango_layout_set_width (font->layout, text->h == INT_MAX ? -1 : text->h);
698 pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD_CHAR);
699 /* XXX need to limit number of lines to those that fit in text->v. */
704 if (text->justification != OUTP_LEFT && text->h != INT_MAX)
707 pango_layout_get_size (font->layout, &w, &h);
708 excess = text->h - w;
711 if (text->justification == OUTP_CENTER)
717 cairo_save (ext->cairo);
718 cairo_translate (ext->cairo, text->x, text->y);
719 cairo_scale (ext->cairo, PANGO_SCALE, PANGO_SCALE);
720 pango_cairo_show_layout (ext->cairo, font->layout);
721 cairo_restore (ext->cairo);
722 pango_cairo_update_layout (ext->cairo, font->layout);
725 if (width != NULL || height != NULL)
728 pango_layout_get_size (font->layout, &w, &h);
737 xr_text_metrics (struct outp_driver *this, const struct outp_text *t,
738 int *width, int *height)
740 text (this, t, false, width, height);
744 xr_text_draw (struct outp_driver *this, const struct outp_text *t)
746 assert (this->page_open);
747 text (this, t, true, NULL, NULL);
751 xr_chart_initialise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
756 /* XXX libplot doesn't support Cairo yet. */
761 xr_chart_finalise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
764 /* XXX libplot doesn't support Cairo yet. */
768 /* Attempts to load FONT, initializing its other members based on
769 its 'string' member and the information in THIS. Returns true
770 if successful, otherwise false. */
772 load_font (struct outp_driver *this, struct xr_font *font)
774 struct xr_driver_ext *x = this->ext;
775 PangoContext *context;
776 PangoLanguage *language;
778 font->desc = pango_font_description_from_string (font->string);
779 if (font->desc == NULL)
781 error (0, 0, _("\"%s\": bad font specification"), font->string);
784 pango_font_description_set_size (font->desc, this->font_height * 5 / 6);
786 font->layout = pango_cairo_create_layout (x->cairo);
787 pango_layout_set_font_description (font->layout, font->desc);
788 pango_layout_set_spacing (font->layout, this->font_height / 6);
790 language = pango_language_get_default ();
791 context = pango_layout_get_context (font->layout);
792 font->metrics = pango_context_get_metrics (context, font->desc, language);
799 free_font (struct xr_font *font)
802 if (font->desc != NULL)
803 pango_font_description_free (font->desc);
804 pango_font_metrics_unref (font->metrics);
805 g_object_unref (font->layout);
808 /* Cairo driver class. */
809 const struct outp_class cairo_class =