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 (void *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 (name, 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 (void *this_, const char *key,
322 const struct string *val)
324 struct outp_driver *this = this_;
325 struct xr_driver_ext *x = this->ext;
327 char *value = ds_cstr (val);
329 switch (outp_match_keyword (key, option_tab, &subcat))
333 _("unknown configuration parameter `%s' for Cairo device "
336 case output_file_arg:
338 x->file_name = xstrdup (value);
340 case output_type_arg:
341 if (!strcmp (value, "pdf"))
342 x->file_type = XR_PDF;
343 else if (!strcmp (value, "ps"))
344 x->file_type = XR_PS;
345 else if (!strcmp (value, "svg"))
346 x->file_type = XR_SVG;
349 error (0, 0, _("unknown Cairo output type \"%s\""), value);
354 outp_get_paper_size (value, &x->paper_width, &x->paper_length);
356 case orientation_arg:
357 if (!strcmp (value, "portrait"))
359 else if (!strcmp (value, "landscape"))
362 error (0, 0, _("unknown orientation `%s' (valid orientations are "
363 "`portrait' and `landscape')"), value);
366 if (!strcmp (value, "on") || !strcmp (value, "true")
367 || !strcmp (value, "yes") || atoi (value))
368 x->draw_headers = true;
369 else if (!strcmp (value, "off") || !strcmp (value, "false")
370 || !strcmp (value, "no") || !strcmp (value, "0"))
371 x->draw_headers = false;
374 error (0, 0, _("boolean value expected for %s"), key);
380 int dimension = outp_evaluate_dimension (value);
387 x->left_margin = dimension;
390 x->right_margin = dimension;
393 x->top_margin = dimension;
396 x->bottom_margin = dimension;
399 this->font_height = dimension;
402 x->line_width = dimension;
405 x->line_gutter = dimension;
408 x->line_width = dimension;
416 free (x->fonts[subcat].string);
417 x->fonts[subcat].string = ds_xstrdup (val);
426 /* Basic file operations. */
429 xr_open_page (struct outp_driver *this)
431 struct xr_driver_ext *x = this->ext;
440 xr_close_page (struct outp_driver *this)
442 struct xr_driver_ext *x = this->ext;
443 cairo_show_page (x->cairo);
447 xr_submit (struct outp_driver *this UNUSED, struct som_entity *s)
458 /* Draws a line from (x0,y0) to (x1,y1). */
460 dump_line (struct outp_driver *this, int x0, int y0, int x1, int y1)
462 struct xr_driver_ext *x = this->ext;
463 cairo_new_path (x->cairo);
464 cairo_move_to (x->cairo, xr_to_pt (x0), xr_to_pt (y0));
465 cairo_line_to (x->cairo, xr_to_pt (x1), xr_to_pt (y1));
466 cairo_stroke (x->cairo);
469 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
470 shortening it to X0...X1 if SHORTEN is true.
471 Draws a horizontal line X1...X3 at Y if RIGHT says so,
472 shortening it to X2...X3 if SHORTEN is true. */
474 horz_line (struct outp_driver *this,
475 int x0, int x1, int x2, int x3, int y,
476 enum outp_line_style left, enum outp_line_style right,
479 if (left != OUTP_L_NONE && right != OUTP_L_NONE && !shorten)
480 dump_line (this, x0, y, x3, y);
483 if (left != OUTP_L_NONE)
484 dump_line (this, x0, y, shorten ? x1 : x2, y);
485 if (right != OUTP_L_NONE)
486 dump_line (this, shorten ? x2 : x1, y, x3, y);
490 /* Draws a vertical line Y0...Y2 at X if TOP says so,
491 shortening it to Y0...Y1 if SHORTEN is true.
492 Draws a vertical line Y1...Y3 at X if BOTTOM says so,
493 shortening it to Y2...Y3 if SHORTEN is true. */
495 vert_line (struct outp_driver *this,
496 int y0, int y1, int y2, int y3, int x,
497 enum outp_line_style top, enum outp_line_style bottom,
500 if (top != OUTP_L_NONE && bottom != OUTP_L_NONE && !shorten)
501 dump_line (this, x, y0, x, y3);
504 if (top != OUTP_L_NONE)
505 dump_line (this, x, y0, x, shorten ? y1 : y2);
506 if (bottom != OUTP_L_NONE)
507 dump_line (this, x, shorten ? y2 : y1, x, y3);
511 /* Draws a generalized intersection of lines in the rectangle
512 (X0,Y0)-(X3,Y3). The line coming from the top to the center
513 is of style TOP, from left to center of style LEFT, from
514 bottom to center of style BOTTOM, and from right to center of
517 xr_line (struct outp_driver *this,
518 int x0, int y0, int x3, int y3,
519 enum outp_line_style top, enum outp_line_style left,
520 enum outp_line_style bottom, enum outp_line_style right)
522 /* The algorithm here is somewhat subtle, to allow it to handle
523 all the kinds of intersections that we need.
525 Three additional ordinates are assigned along the x axis. The
526 first is xc, midway between x0 and x3. The others are x1 and
527 x2; for a single vertical line these are equal to xc, and for
528 a double vertical line they are the ordinates of the left and
529 right half of the double line.
531 yc, y1, and y2 are assigned similarly along the y axis.
533 The following diagram shows the coordinate system and output
534 for double top and bottom lines, single left line, and no
538 y0 ________________________
544 y1 = y2 = yc |######### # |
549 y3 |________#_____#_______|
551 struct xr_driver_ext *ext = this->ext;
553 /* Offset from center of each line in a pair of double lines. */
554 int double_line_ofs = (ext->line_space + ext->line_width) / 2;
556 /* Are the lines along each axis single or double?
557 (It doesn't make sense to have different kinds of line on the
558 same axis, so we don't try to gracefully handle that case.) */
559 bool double_vert = top == OUTP_L_DOUBLE || bottom == OUTP_L_DOUBLE;
560 bool double_horz = left == OUTP_L_DOUBLE || right == OUTP_L_DOUBLE;
562 /* When horizontal lines are doubled,
563 the left-side line along y1 normally runs from x0 to x2,
564 and the right-side line along y1 from x3 to x1.
565 If the top-side line is also doubled, we shorten the y1 lines,
566 so that the left-side line runs only to x1,
567 and the right-side line only to x2.
568 Otherwise, the horizontal line at y = y1 below would cut off
569 the intersection, which looks ugly:
571 y0 ________________________
576 y1 |######### ########|
579 y2 |######################|
582 y3 |______________________|
583 It is more of a judgment call when the horizontal line is
584 single. We actually choose to cut off the line anyhow, as
585 shown in the first diagram above.
587 bool shorten_y1_lines = top == OUTP_L_DOUBLE;
588 bool shorten_y2_lines = bottom == OUTP_L_DOUBLE;
589 bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
590 int horz_line_ofs = double_vert ? double_line_ofs : 0;
591 int xc = (x0 + x3) / 2;
592 int x1 = xc - horz_line_ofs;
593 int x2 = xc + horz_line_ofs;
595 bool shorten_x1_lines = left == OUTP_L_DOUBLE;
596 bool shorten_x2_lines = right == OUTP_L_DOUBLE;
597 bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
598 int vert_line_ofs = double_horz ? double_line_ofs : 0;
599 int yc = (y0 + y3) / 2;
600 int y1 = yc - vert_line_ofs;
601 int y2 = yc + vert_line_ofs;
604 horz_line (this, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
607 horz_line (this, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
608 horz_line (this, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
612 vert_line (this, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
615 vert_line (this, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
616 vert_line (this, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
620 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
621 and with the given JUSTIFICATION for THIS driver. */
623 draw_text (struct outp_driver *this,
624 const char *string, int x, int y, int max_width,
625 enum outp_justification justification)
627 struct outp_text text;
630 text.font = OUTP_PROPORTIONAL;
631 text.justification = justification;
632 text.string = ss_cstr (string);
634 text.v = this->font_height;
637 this->class->text_metrics (this, &text, &width, NULL);
638 this->class->text_draw (this, &text);
642 /* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
643 and with the given JUSTIFICATION for THIS driver. */
645 text_width (struct outp_driver *this, const char *string, enum outp_font font)
647 struct outp_text text;
651 text.justification = OUTP_LEFT;
652 text.string = ss_cstr (string);
654 text.v = this->font_height;
657 this->class->text_metrics (this, &text, &width, NULL);
661 /* Writes LEFT left-justified and RIGHT right-justified within
662 (X0...X1) at Y. LEFT or RIGHT or both may be null. */
664 draw_header_line (struct outp_driver *this,
665 const char *left, const char *right,
666 int x0, int x1, int y)
670 right_width = (draw_text (this, right, x0, y, x1 - x0, OUTP_RIGHT)
671 + this->prop_em_width);
673 draw_text (this, left, x0, y, x1 - x0 - right_width, OUTP_LEFT);
676 /* Draw top of page headers for THIS driver. */
678 draw_headers (struct outp_driver *this)
680 struct xr_driver_ext *ext = this->ext;
685 y = -3 * this->font_height;
686 x0 = this->prop_em_width;
687 x1 = this->width - this->prop_em_width;
690 cairo_rectangle (ext->cairo, 0, xr_to_pt (y), xr_to_pt (this->width),
691 xr_to_pt (2 * (this->font_height
692 + ext->line_width + ext->line_gutter)));
693 cairo_save (ext->cairo);
694 cairo_set_source_rgb (ext->cairo, 0.9, 0.9, 0.9);
695 cairo_fill_preserve (ext->cairo);
696 cairo_restore (ext->cairo);
697 cairo_stroke (ext->cairo);
699 y += ext->line_width + ext->line_gutter;
701 r1 = xasprintf (_("%s - Page %d"), get_start_date (), ext->page_number);
702 r2 = xasprintf ("%s - %s", version, host_system);
704 draw_header_line (this, outp_title, r1, x0, x1, y);
705 y += this->font_height;
707 draw_header_line (this, outp_subtitle, r2, x0, x1, y);
713 /* Format TEXT on THIS driver.
714 If DRAW is nonzero, draw the text.
715 The width of the widest line is stored into *WIDTH, if WIDTH
717 The total height of the text written is stored into *HEIGHT,
718 if HEIGHT is nonnull. */
720 text (struct outp_driver *this, const struct outp_text *text, bool draw,
721 int *width, int *height)
723 struct xr_driver_ext *ext = this->ext;
724 struct xr_font *font = &ext->fonts[text->font];
726 pango_layout_set_text (font->layout,
727 text->string.string, text->string.length);
728 pango_layout_set_alignment (
730 (text->justification == OUTP_RIGHT ? PANGO_ALIGN_RIGHT
731 : text->justification == OUTP_LEFT ? PANGO_ALIGN_LEFT
732 : PANGO_ALIGN_CENTER));
733 pango_layout_set_width (font->layout, text->h == INT_MAX ? -1 : text->h);
734 pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD_CHAR);
735 /* XXX need to limit number of lines to those that fit in text->v. */
740 if (text->justification != OUTP_LEFT && text->h != INT_MAX)
743 pango_layout_get_size (font->layout, &w, &h);
744 excess = text->h - w;
747 if (text->justification == OUTP_CENTER)
753 cairo_save (ext->cairo);
754 cairo_translate (ext->cairo, xr_to_pt (text->x), xr_to_pt (text->y));
755 pango_cairo_show_layout (ext->cairo, font->layout);
756 cairo_restore (ext->cairo);
759 if (width != NULL || height != NULL)
762 pango_layout_get_size (font->layout, &w, &h);
771 xr_text_metrics (struct outp_driver *this, const struct outp_text *t,
772 int *width, int *height)
774 text (this, t, false, width, height);
778 xr_text_draw (struct outp_driver *this, const struct outp_text *t)
780 assert (this->page_open);
781 text (this, t, true, NULL, NULL);
785 xr_chart_initialise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
790 /* XXX libplot doesn't support Cairo yet. */
795 xr_chart_finalise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
798 /* XXX libplot doesn't support Cairo yet. */
802 /* Attempts to load FONT, initializing its other members based on
803 its 'string' member and the information in THIS. Returns true
804 if successful, otherwise false. */
806 load_font (struct outp_driver *this, struct xr_font *font)
808 struct xr_driver_ext *x = this->ext;
809 PangoContext *context;
810 PangoLanguage *language;
812 font->desc = pango_font_description_from_string (font->string);
813 if (font->desc == NULL)
815 error (0, 0, _("\"%s\": bad font specification"), font->string);
818 pango_font_description_set_absolute_size (font->desc, this->font_height);
820 font->layout = pango_cairo_create_layout (x->cairo);
821 pango_layout_set_font_description (font->layout, font->desc);
823 language = pango_language_get_default ();
824 context = pango_layout_get_context (font->layout);
825 font->metrics = pango_context_get_metrics (context, font->desc, language);
832 free_font (struct xr_font *font)
835 if (font->desc != NULL)
836 pango_font_description_free (font->desc);
837 pango_font_metrics_unref (font->metrics);
838 g_object_unref (font->layout);
841 /* Cairo driver class. */
842 const struct outp_class cairo_class =