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);
203 for (i = 0; i < OUTP_FONT_CNT; i++)
204 if (!load_font (this, &x->fonts[i]))
207 if (this->length / this->font_height < 15)
209 error (0, 0, _("The defined page is not long "
210 "enough to hold margins and headers, plus least 15 "
211 "lines of the default fonts. In fact, there's only "
212 "room for %d lines."),
213 this->length / this->font_height);
217 this->fixed_width = pango_font_metrics_get_approximate_char_width (
218 x->fonts[OUTP_FIXED].metrics);
219 this->prop_em_width = pango_font_metrics_get_approximate_char_width (
220 x->fonts[OUTP_PROPORTIONAL].metrics);
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 LEFT left-justified and RIGHT right-justified within
624 (X0...X1) at Y. LEFT or RIGHT or both may be null. */
626 draw_header_line (struct outp_driver *this,
627 const char *left, const char *right,
628 int x0, int x1, int y)
632 right_width = (draw_text (this, right, x0, y, x1 - x0, OUTP_RIGHT)
633 + this->prop_em_width);
635 draw_text (this, left, x0, y, x1 - x0 - right_width, OUTP_LEFT);
638 /* Draw top of page headers for THIS driver. */
640 draw_headers (struct outp_driver *this)
642 struct xr_driver_ext *ext = this->ext;
647 y = -3 * this->font_height;
648 x0 = this->prop_em_width;
649 x1 = this->width - this->prop_em_width;
652 /* XXX coordinates below might not be right, both in terms of
653 the Y transformation and width/height versus x2/y2. */
654 cairo_rectangle (ext->cairo, 0, y,
655 this->width, 2 * this->font_height + ext->line_gutter);
656 cairo_save (ext->cairo);
657 cairo_set_source_rgb (ext->cairo, 0.9, 0.9, 0.9);
658 cairo_fill_preserve (ext->cairo);
659 cairo_restore (ext->cairo);
660 cairo_stroke (ext->cairo);
662 y += ext->line_width + ext->line_gutter;
664 r1 = xasprintf (_("%s - Page %d"), get_start_date (), ext->page_number);
665 r2 = xasprintf ("%s - %s", version, host_system);
667 draw_header_line (this, outp_title, r1, x0, x1, y);
668 y += this->font_height;
670 draw_header_line (this, outp_subtitle, r2, x0, x1, y);
676 /* Format TEXT on THIS driver.
677 If DRAW is nonzero, draw the text.
678 The width of the widest line is stored into *WIDTH, if WIDTH
680 The total height of the text written is stored into *HEIGHT,
681 if HEIGHT is nonnull. */
683 text (struct outp_driver *this, const struct outp_text *text, bool draw,
684 int *width, int *height)
686 struct xr_driver_ext *ext = this->ext;
687 struct xr_font *font = &ext->fonts[text->font];
689 pango_layout_set_text (font->layout,
690 text->string.string, text->string.length);
691 pango_layout_set_alignment (
693 (text->justification == OUTP_RIGHT ? PANGO_ALIGN_RIGHT
694 : text->justification == OUTP_LEFT ? PANGO_ALIGN_LEFT
695 : PANGO_ALIGN_CENTER));
696 pango_layout_set_width (font->layout, text->h == INT_MAX ? -1 : text->h);
697 pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD_CHAR);
698 /* XXX need to limit number of lines to those that fit in text->v. */
703 if (text->justification != OUTP_LEFT && text->h != INT_MAX)
706 pango_layout_get_size (font->layout, &w, &h);
707 excess = text->h - w;
710 if (text->justification == OUTP_CENTER)
716 cairo_save (ext->cairo);
717 cairo_translate (ext->cairo, text->x, text->y);
718 cairo_scale (ext->cairo, PANGO_SCALE, PANGO_SCALE);
719 pango_cairo_show_layout (ext->cairo, font->layout);
720 cairo_restore (ext->cairo);
721 pango_cairo_update_layout (ext->cairo, font->layout);
724 if (width != NULL || height != NULL)
727 pango_layout_get_size (font->layout, &w, &h);
736 xr_text_metrics (struct outp_driver *this, const struct outp_text *t,
737 int *width, int *height)
739 text (this, t, false, width, height);
743 xr_text_draw (struct outp_driver *this, const struct outp_text *t)
745 assert (this->page_open);
746 text (this, t, true, NULL, NULL);
750 xr_chart_initialise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
755 /* XXX libplot doesn't support Cairo yet. */
760 xr_chart_finalise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
763 /* XXX libplot doesn't support Cairo yet. */
767 /* Attempts to load FONT, initializing its other members based on
768 its 'string' member and the information in THIS. Returns true
769 if successful, otherwise false. */
771 load_font (struct outp_driver *this, struct xr_font *font)
773 struct xr_driver_ext *x = this->ext;
774 PangoContext *context;
775 PangoLanguage *language;
777 font->desc = pango_font_description_from_string (font->string);
778 if (font->desc == NULL)
780 error (0, 0, _("\"%s\": bad font specification"), font->string);
783 pango_font_description_set_size (font->desc, this->font_height * 5 / 6);
785 font->layout = pango_cairo_create_layout (x->cairo);
786 pango_layout_set_font_description (font->layout, font->desc);
787 pango_layout_set_spacing (font->layout, this->font_height / 6);
789 language = pango_language_get_default ();
790 context = pango_layout_get_context (font->layout);
791 font->metrics = pango_context_get_metrics (context, font->desc, language);
798 free_font (struct xr_font *font)
801 if (font->desc != NULL)
802 pango_font_description_free (font->desc);
803 pango_font_metrics_unref (font->metrics);
804 g_object_unref (font->layout);
807 /* Cairo driver class. */
808 const struct outp_class cairo_class =