1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 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/cast.h"
21 #include "libpspp/message.h"
22 #include "libpspp/str.h"
23 #include "libpspp/string-map.h"
24 #include "data/file-handle-def.h"
25 #include "output/cairo-fsm.h"
26 #include "output/cairo-pager.h"
27 #include "output/driver-provider.h"
28 #include "output/options.h"
29 #include "output/table.h"
31 #include <cairo/cairo-pdf.h>
32 #include <cairo/cairo-ps.h>
33 #include <cairo/cairo-svg.h>
35 #include <cairo/cairo.h>
38 #include <pango/pango-font.h>
41 #include "gl/c-strcase.h"
42 #include "gl/xalloc.h"
45 #define _(msgid) gettext (msgid)
47 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
51 /* The unit used for internal measurements is inch/(72 * XR_POINT).
52 (Thus, XR_POINT units represent one point.) */
53 #define XR_POINT PANGO_SCALE
55 /* Conversions to and from points. */
59 return x / (double) XR_POINT;
71 /* Cairo output driver. */
74 struct output_driver driver;
76 enum xr_output_type output_type;
77 struct xr_fsm_style *fsm_style;
78 struct xr_page_style *page_style;
79 struct xr_pager *pager;
82 /* This is the surface where we're currently drawing. It is always
85 If 'trim' is true, this is a special Cairo "recording surface" that we
86 are using to save output temporarily just to find out the bounding box,
87 then later replay it into the destination surface.
91 - For output to a PDF or PostScript file, it is the same pointer as
94 - For output to a PNG file, it is an image surface.
96 - For output to an SVG file, it is a recording surface.
98 cairo_surface_t *drawing_surface;
100 /* - For output to a PDF or PostScript file, this is the surface for the
101 PDF or PostScript file where the output is ultimately going.
103 - For output to a PNG file, this is NULL, because Cairo has very
104 limited support for PNG. Cairo can't open a PNG file for writing as
105 a surface, it can only save an existing surface to a PNG file.
107 - For output to a SVG file, this is NULL, because Cairo does not
108 permit resizing the SVG page size after creating the file, whereas
109 this driver needs to do that sometimes. Also, SVG is not multi-page
110 (according to https://wiki.inkscape.org/wiki/index.php/Multipage).
112 cairo_surface_t *dest_surface;
114 /* Used only in file names, for PNG and SVG output where we can only write
115 one page per file. */
119 static const struct output_driver_class cairo_driver_class;
122 /* Output driver basics. */
124 static struct xr_driver *
125 xr_driver_cast (struct output_driver *driver)
127 assert (driver->class == &cairo_driver_class);
128 return UP_CAST (driver, struct xr_driver, driver);
131 static struct driver_option *
132 opt (struct output_driver *d, struct string_map *options, const char *key,
133 const char *default_value)
135 return driver_option_get (d, options, key, default_value);
138 static PangoFontDescription *
139 parse_font (const char *font, int default_size, bool bold, bool italic)
141 if (!c_strcasecmp (font, "Monospaced"))
144 PangoFontDescription *desc = pango_font_description_from_string (font);
148 /* If the font description didn't include an explicit font size, then set it
149 to DEFAULT_SIZE, which is in inch/72000 units. */
150 if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
151 pango_font_description_set_size (desc,
152 (default_size / 1000.0) * PANGO_SCALE);
154 pango_font_description_set_weight (desc, (bold
156 : PANGO_WEIGHT_NORMAL));
157 pango_font_description_set_style (desc, (italic
159 : PANGO_STYLE_NORMAL));
164 static PangoFontDescription *
165 parse_font_option (struct output_driver *d, struct string_map *options,
166 const char *key, const char *default_value,
167 int default_size, bool bold, bool italic)
169 char *string = parse_string (opt (d, options, key, default_value));
170 PangoFontDescription *desc = parse_font (string, default_size, bold, italic);
173 msg (MW, _("`%s': bad font specification"), string);
175 /* Fall back to DEFAULT_VALUE, which had better be a valid font
177 desc = parse_font (default_value, default_size, bold, italic);
178 assert (desc != NULL);
185 static struct xr_driver *
186 xr_allocate (const char *name, int device_type,
187 enum xr_output_type output_type, struct string_map *o)
189 struct xr_driver *xr = xzalloc (sizeof *xr);
190 struct output_driver *d = &xr->driver;
192 output_driver_init (d, &cairo_driver_class, name, device_type);
193 xr->output_type = output_type;
195 /* Scale factor from inch/72000 to inch/(72 * XR_POINT). */
196 const double scale = XR_POINT / 1000.;
198 int paper[TABLE_N_AXES];
199 parse_paper_size (opt (d, o, "paper-size", ""), &paper[H], &paper[V]);
200 for (int a = 0; a < TABLE_N_AXES; a++)
203 int margins[TABLE_N_AXES][2];
204 margins[H][0] = parse_dimension (opt (d, o, "left-margin", ".5in")) * scale;
205 margins[H][1] = parse_dimension (opt (d, o, "right-margin", ".5in")) * scale;
206 margins[V][0] = parse_dimension (opt (d, o, "top-margin", ".5in")) * scale;
207 margins[V][1] = parse_dimension (opt (d, o, "bottom-margin", ".5in")) * scale;
209 int size[TABLE_N_AXES];
210 for (int a = 0; a < TABLE_N_AXES; a++)
211 size[a] = paper[a] - margins[a][0] - margins[a][1];
213 int min_break[TABLE_N_AXES];
214 min_break[H] = parse_dimension (opt (d, o, "min-hbreak", NULL)) * scale;
215 min_break[V] = parse_dimension (opt (d, o, "min-vbreak", NULL)) * scale;
216 for (int a = 0; a < TABLE_N_AXES; a++)
217 if (min_break[a] <= 0)
218 min_break[a] = size[a] / 2;
220 int font_size = parse_int (opt (d, o, "font-size", "10000"), 1000, 1000000);
221 PangoFontDescription *fixed_font = parse_font_option
222 (d, o, "fixed-font", "monospace", font_size, false, false);
223 PangoFontDescription *proportional_font = parse_font_option (
224 d, o, "prop-font", "sans serif", font_size, false, false);
226 struct cell_color fg = parse_color (opt (d, o, "foreground-color", "black"));
228 bool systemcolors = parse_boolean (opt (d, o, "systemcolors", "false"));
231 = parse_dimension (opt (d, o, "object-spacing", NULL)) * scale;
232 if (object_spacing <= 0)
233 object_spacing = XR_POINT * 12;
235 const char *default_resolution = (output_type == XR_PNG ? "96" : "72");
236 int font_resolution = parse_int (opt (d, o, "font-resolution",
237 default_resolution), 10, 1000);
239 xr->trim = parse_boolean (opt (d, o, "trim", "false"));
241 /* Cairo 1.16.0 has a bug that causes crashes if outlines are enabled at the
242 same time as trimming:
243 https://lists.cairographics.org/archives/cairo/2020-December/029151.html
244 For now, just disable the outline if trimming is enabled. */
246 = (output_type == XR_PDF
247 && parse_boolean (opt (d, o, "outline", xr->trim ? "false" : "true")));
249 xr->page_style = xmalloc (sizeof *xr->page_style);
250 *xr->page_style = (struct xr_page_style) {
254 [H] = { margins[H][0], margins[H][1], },
255 [V] = { margins[V][0], margins[V][1], },
258 .initial_page_number = 1,
259 .object_spacing = object_spacing,
260 .include_outline = include_outline,
263 xr->fsm_style = xmalloc (sizeof *xr->fsm_style);
264 *xr->fsm_style = (struct xr_fsm_style) {
266 .size = { [H] = size[H], [V] = size[V] },
267 .min_break = { [H] = min_break[H], [V] = min_break[V] },
269 [XR_FONT_PROPORTIONAL] = proportional_font,
270 [XR_FONT_FIXED] = fixed_font,
273 .use_system_colors = systemcolors,
274 .font_resolution = font_resolution,
280 static struct output_driver *
281 xr_create (struct file_handle *fh, enum settings_output_devices device_type,
282 struct string_map *o, enum xr_output_type output_type)
284 const char *file_name = fh_get_file_name (fh);
285 struct xr_driver *xr = xr_allocate (file_name, device_type, output_type, o);
287 double paper[TABLE_N_AXES];
288 for (int a = 0; a < TABLE_N_AXES; a++)
289 paper[a] = xr_to_pt (xr_page_style_paper_size (xr->page_style,
293 = (output_type == XR_PDF
294 ? cairo_pdf_surface_create (file_name, paper[H], paper[V])
295 : output_type == XR_PS
296 ? cairo_ps_surface_create (file_name, paper[H], paper[V])
298 if (xr->dest_surface)
300 cairo_status_t status = cairo_surface_status (xr->dest_surface);
301 if (status != CAIRO_STATUS_SUCCESS)
303 msg (ME, _("error opening output file `%s': %s"),
304 file_name, cairo_status_to_string (status));
310 = (xr->trim || output_type == XR_SVG
311 ? cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA,
312 &(cairo_rectangle_t) {
314 .height = paper[V] })
315 : output_type == XR_PNG
316 ? cairo_image_surface_create (CAIRO_FORMAT_ARGB32, paper[H], paper[V])
324 output_driver_destroy (&xr->driver);
328 static struct output_driver *
329 xr_pdf_create (struct file_handle *fh, enum settings_output_devices device_type,
330 struct string_map *o)
332 return xr_create (fh, device_type, o, XR_PDF);
335 static struct output_driver *
336 xr_ps_create (struct file_handle *fh, enum settings_output_devices device_type,
337 struct string_map *o)
339 return xr_create (fh, device_type, o, XR_PS);
342 static struct output_driver *
343 xr_svg_create (struct file_handle *fh, enum settings_output_devices device_type,
344 struct string_map *o)
346 return xr_create (fh, device_type, o, XR_SVG);
349 static struct output_driver *
350 xr_png_create (struct file_handle *fh, enum settings_output_devices device_type,
351 struct string_map *o)
353 return xr_create (fh, device_type, o, XR_PNG);
357 xr_set_surface_size (cairo_surface_t *surface, enum xr_output_type output_type,
358 double width, double height)
363 cairo_pdf_surface_set_size (surface, width, height);
367 cairo_ps_surface_set_size (surface, width, height);
377 xr_copy_surface (cairo_surface_t *dst, cairo_surface_t *src,
380 cairo_t *cr = cairo_create (dst);
381 cairo_set_source_surface (cr, src, x, y);
387 clear_rectangle (cairo_surface_t *surface,
388 double x0, double y0, double x1, double y1)
390 cairo_t *cr = cairo_create (surface);
391 cairo_set_source_rgb (cr, 1, 1, 1);
393 cairo_rectangle (cr, x0, y0, x1 - x0, y1 - y0);
399 xr_report_error (cairo_status_t status, const char *file_name)
401 if (status != CAIRO_STATUS_SUCCESS)
402 fprintf (stderr, "%s: %s\n", file_name, cairo_status_to_string (status));
406 xr_finish_page (struct xr_driver *xr)
408 xr_pager_finish_page (xr->pager);
412 - If the destination is PDF or PostScript, set the dest surface size, copy
413 ink extent, show_page.
415 - If the destination is PNG, create image surface, copy ink extent,
416 cairo_surface_write_to_png(), destroy image surface.
418 - If the destination is SVG, create svg surface, copy ink extent, close.
420 then destroy drawing_surface and make a new one.
424 - If the destination is PDF or PostScript, show_page.
426 - If the destination is PNG, cairo_surface_write_to_png(), destroy image
427 surface, create new image surface.
429 - If the destination is SVG, create svg surface, copy whole thing, close.
432 double paper[TABLE_N_AXES];
433 for (int a = 0; a < TABLE_N_AXES; a++)
434 paper[a] = xr_to_pt (xr_page_style_paper_size (
435 xr->page_style, xr->fsm_style, a));
438 char *file_name = (xr->page_number > 1
439 ? xasprintf ("%s-%d", xr->driver.name, xr->page_number)
444 /* Get the bounding box for the drawing surface. */
445 double ofs[TABLE_N_AXES], size[TABLE_N_AXES];
446 cairo_recording_surface_ink_extents (xr->drawing_surface,
449 const int (*margins)[2] = xr->page_style->margins;
450 for (int a = 0; a < TABLE_N_AXES; a++)
452 double scale = XR_POINT;
453 size[a] += (margins[a][0] + margins[a][1]) / scale;
454 ofs[a] = -ofs[a] + margins[a][0] / scale;
457 switch (xr->output_type)
461 xr_set_surface_size (xr->dest_surface, xr->output_type,
463 xr_copy_surface (xr->dest_surface, xr->drawing_surface,
465 cairo_surface_show_page (xr->dest_surface);
470 cairo_surface_t *svg = cairo_svg_surface_create (
471 file_name, size[H], size[V]);
472 xr_copy_surface (svg, xr->drawing_surface, ofs[H], ofs[V]);
473 xr_report_error (cairo_surface_status (svg), file_name);
474 cairo_surface_destroy (svg);
480 cairo_surface_t *png = cairo_image_surface_create (
481 CAIRO_FORMAT_ARGB32, size[H], size[V]);
482 clear_rectangle (png, 0, 0, size[H], size[V]);
483 xr_copy_surface (png, xr->drawing_surface, ofs[H], ofs[V]);
484 xr_report_error (cairo_surface_write_to_png (png, file_name),
486 cairo_surface_destroy (png);
491 /* Destroy the recording surface and create a fresh one of the same
493 cairo_surface_destroy (xr->drawing_surface);
494 xr->drawing_surface = cairo_recording_surface_create (
495 CAIRO_CONTENT_COLOR_ALPHA,
496 &(cairo_rectangle_t) { .width = paper[H], .height = paper[V] });
500 switch (xr->output_type)
504 cairo_surface_show_page (xr->dest_surface);
509 cairo_surface_t *svg = cairo_svg_surface_create (
510 file_name, paper[H], paper[V]);
511 xr_copy_surface (svg, xr->drawing_surface, 0.0, 0.0);
512 xr_report_error (cairo_surface_status (svg), file_name);
513 cairo_surface_destroy (svg);
518 xr_report_error (cairo_surface_write_to_png (xr->drawing_surface,
519 file_name), file_name);
520 cairo_surface_destroy (xr->drawing_surface);
521 xr->drawing_surface = cairo_image_surface_create (
522 CAIRO_FORMAT_ARGB32, paper[H], paper[V]);
527 if (file_name != xr->driver.name)
532 xr_destroy (struct output_driver *driver)
534 struct xr_driver *xr = xr_driver_cast (driver);
539 xr_pager_destroy (xr->pager);
541 if (xr->drawing_surface && xr->drawing_surface != xr->dest_surface)
542 cairo_surface_destroy (xr->drawing_surface);
543 if (xr->dest_surface)
545 cairo_surface_finish (xr->dest_surface);
546 cairo_status_t status = cairo_surface_status (xr->dest_surface);
547 if (status != CAIRO_STATUS_SUCCESS)
548 fprintf (stderr, _("error drawing output for %s driver: %s\n"),
549 output_driver_get_name (driver),
550 cairo_status_to_string (status));
551 cairo_surface_destroy (xr->dest_surface);
554 xr_page_style_unref (xr->page_style);
555 xr_fsm_style_unref (xr->fsm_style);
560 xr_update_page_setup (struct output_driver *driver,
561 const struct page_setup *setup)
563 struct xr_driver *xr = xr_driver_cast (driver);
565 const double scale = 72 * XR_POINT;
567 int swap = setup->orientation == PAGE_LANDSCAPE;
568 enum table_axis h = H ^ swap;
569 enum table_axis v = V ^ swap;
571 struct xr_page_style *old_ps = xr->page_style;
572 xr->page_style = xmalloc (sizeof *xr->page_style);
573 *xr->page_style = (struct xr_page_style) {
577 [H] = { setup->margins[h][0] * scale, setup->margins[h][1] * scale },
578 [V] = { setup->margins[v][0] * scale, setup->margins[v][1] * scale },
581 .initial_page_number = setup->initial_page_number,
582 .object_spacing = setup->object_spacing * 72 * XR_POINT,
583 .include_outline = old_ps->include_outline,
585 for (size_t i = 0; i < 2; i++)
586 page_heading_copy (&xr->page_style->headings[i], &setup->headings[i]);
587 xr_page_style_unref (old_ps);
589 struct xr_fsm_style *old_fs = xr->fsm_style;
590 xr->fsm_style = xmalloc (sizeof *xr->fsm_style);
591 *xr->fsm_style = (struct xr_fsm_style) {
593 .size = { [H] = setup->paper[H] * scale, [V] = setup->paper[V] * scale },
595 [H] = setup->paper[H] * scale / 2,
596 [V] = setup->paper[V] * scale / 2,
599 .use_system_colors = old_fs->use_system_colors,
600 .font_resolution = old_fs->font_resolution,
602 for (size_t i = 0; i < XR_N_FONTS; i++)
603 xr->fsm_style->fonts[i] = pango_font_description_copy (old_fs->fonts[i]);
604 xr_fsm_style_unref (old_fs);
606 xr_set_surface_size (xr->dest_surface, xr->output_type,
607 setup->paper[H] * 72.0, setup->paper[V] * 72.0);
611 xr_submit (struct output_driver *driver, const struct output_item *output_item)
613 struct xr_driver *xr = xr_driver_cast (driver);
615 if (is_page_setup_item (output_item))
618 xr_update_page_setup (driver,
619 to_page_setup_item (output_item)->page_setup);
625 xr->pager = xr_pager_create (xr->page_style, xr->fsm_style);
626 xr_pager_add_page (xr->pager, cairo_create (xr->drawing_surface));
629 xr_pager_add_item (xr->pager, output_item);
630 while (xr_pager_needs_new_page (xr->pager))
633 xr_pager_add_page (xr->pager, cairo_create (xr->drawing_surface));
637 struct output_driver_factory pdf_driver_factory =
638 { "pdf", "pspp.pdf", xr_pdf_create };
639 struct output_driver_factory ps_driver_factory =
640 { "ps", "pspp.ps", xr_ps_create };
641 struct output_driver_factory svg_driver_factory =
642 { "svg", "pspp.svg", xr_svg_create };
643 struct output_driver_factory png_driver_factory =
644 { "png", "pspp.png", xr_png_create };
646 static const struct output_driver_class cairo_driver_class =