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/output-item.h"
30 #include "output/table.h"
32 #include <cairo/cairo-pdf.h>
33 #include <cairo/cairo-ps.h>
34 #include <cairo/cairo-svg.h>
36 #include <cairo/cairo.h>
39 #include <pango/pango-font.h>
42 #include "gl/c-strcase.h"
43 #include "gl/xalloc.h"
46 #define _(msgid) gettext (msgid)
48 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
52 /* The unit used for internal measurements is inch/(72 * XR_POINT).
53 (Thus, XR_POINT units represent one point.) */
54 #define XR_POINT PANGO_SCALE
56 /* Conversions to and from points. */
60 return x / (double) XR_POINT;
72 /* Cairo output driver. */
75 struct output_driver driver;
77 enum xr_output_type output_type;
78 struct xr_fsm_style *fsm_style;
79 struct xr_page_style *page_style;
80 struct xr_pager *pager;
83 /* This is the surface where we're currently drawing. It is always
86 If 'trim' is true, this is a special Cairo "recording surface" that we
87 are using to save output temporarily just to find out the bounding box,
88 then later replay it into the destination surface.
92 - For output to a PDF or PostScript file, it is the same pointer as
95 - For output to a PNG file, it is an image surface.
97 - For output to an SVG file, it is a recording surface.
99 cairo_surface_t *drawing_surface;
101 /* - For output to a PDF or PostScript file, this is the surface for the
102 PDF or PostScript file where the output is ultimately going.
104 - For output to a PNG file, this is NULL, because Cairo has very
105 limited support for PNG. Cairo can't open a PNG file for writing as
106 a surface, it can only save an existing surface to a PNG file.
108 - For output to a SVG file, this is NULL, because Cairo does not
109 permit resizing the SVG page size after creating the file, whereas
110 this driver needs to do that sometimes. Also, SVG is not multi-page
111 (according to https://wiki.inkscape.org/wiki/index.php/Multipage).
113 cairo_surface_t *dest_surface;
115 /* Used only in file names, for PNG and SVG output where we can only write
116 one page per file. */
120 static const struct output_driver_class cairo_driver_class;
123 /* Output driver basics. */
125 static struct xr_driver *
126 xr_driver_cast (struct output_driver *driver)
128 assert (driver->class == &cairo_driver_class);
129 return UP_CAST (driver, struct xr_driver, driver);
132 static struct driver_option *
133 opt (struct string_map *options, const char *key, const char *default_value)
135 return driver_option_get ("cairo", 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 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 (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 /* Scale factor from inch/72000 to inch/(72 * XR_POINT). */
190 const double scale = XR_POINT / 1000.;
192 int paper[TABLE_N_AXES];
193 parse_paper_size (opt (o, "paper-size", ""), &paper[H], &paper[V]);
194 for (int a = 0; a < TABLE_N_AXES; a++)
197 int margins[TABLE_N_AXES][2];
198 margins[H][0] = parse_dimension (opt (o, "left-margin", ".5in")) * scale;
199 margins[H][1] = parse_dimension (opt (o, "right-margin", ".5in")) * scale;
200 margins[V][0] = parse_dimension (opt (o, "top-margin", ".5in")) * scale;
201 margins[V][1] = parse_dimension (opt (o, "bottom-margin", ".5in")) * scale;
203 int size[TABLE_N_AXES];
204 for (int a = 0; a < TABLE_N_AXES; a++)
205 size[a] = paper[a] - margins[a][0] - margins[a][1];
207 int min_break[TABLE_N_AXES];
208 min_break[H] = parse_dimension (opt (o, "min-hbreak", NULL)) * scale;
209 min_break[V] = parse_dimension (opt (o, "min-vbreak", NULL)) * scale;
210 for (int a = 0; a < TABLE_N_AXES; a++)
211 if (min_break[a] <= 0)
212 min_break[a] = size[a] / 2;
214 int font_size = parse_int (opt (o, "font-size", "10000"), 1000, 1000000);
215 PangoFontDescription *font = parse_font_option (
216 o, "prop-font", "Sans Serif", font_size, false, false);
218 struct cell_color fg = parse_color (opt (o, "foreground-color", "black"));
220 bool systemcolors = parse_boolean (opt (o, "systemcolors", "false"));
223 = parse_dimension (opt (o, "object-spacing", NULL)) * scale;
224 if (object_spacing <= 0)
225 object_spacing = XR_POINT * 12;
227 const char *default_resolution = (output_type == XR_PNG ? "96" : "72");
228 int font_resolution = parse_int (opt (o, "font-resolution",
229 default_resolution), 10, 1000);
231 bool trim = parse_boolean (opt (o, "trim", "false"));
233 /* Cairo 1.16.0 has a bug that causes crashes if outlines are enabled at the
234 same time as trimming:
235 https://lists.cairographics.org/archives/cairo/2020-December/029151.html
236 For now, just disable the outline if trimming is enabled. */
238 = (output_type == XR_PDF
239 && parse_boolean (opt (o, "outline", trim ? "false" : "true")));
241 struct xr_page_style *page_style = xmalloc (sizeof *page_style);
242 *page_style = (struct xr_page_style) {
246 [H] = { margins[H][0], margins[H][1], },
247 [V] = { margins[V][0], margins[V][1], },
250 .initial_page_number = 1,
251 .include_outline = include_outline,
254 struct xr_fsm_style *fsm_style = xmalloc (sizeof *fsm_style);
255 *fsm_style = (struct xr_fsm_style) {
257 .size = { [H] = size[H], [V] = size[V] },
258 .min_break = { [H] = min_break[H], [V] = min_break[V] },
261 .use_system_colors = systemcolors,
262 .object_spacing = object_spacing,
263 .font_resolution = font_resolution,
266 struct xr_driver *xr = xmalloc (sizeof *xr);
267 *xr = (struct xr_driver) {
269 .class = &cairo_driver_class,
270 .name = xstrdup (name),
271 .device_type = device_type,
273 .output_type = output_type,
274 .fsm_style = fsm_style,
275 .page_style = page_style,
282 static struct output_driver *
283 xr_create (struct file_handle *fh, enum settings_output_devices device_type,
284 struct string_map *o, enum xr_output_type output_type)
286 const char *file_name = fh_get_file_name (fh);
287 struct xr_driver *xr = xr_allocate (file_name, device_type, output_type, o);
289 double paper[TABLE_N_AXES];
290 for (int a = 0; a < TABLE_N_AXES; a++)
291 paper[a] = xr_to_pt (xr_page_style_paper_size (xr->page_style,
295 = (output_type == XR_PDF
296 ? cairo_pdf_surface_create (file_name, paper[H], paper[V])
297 : output_type == XR_PS
298 ? cairo_ps_surface_create (file_name, paper[H], paper[V])
300 if (xr->dest_surface)
302 cairo_status_t status = cairo_surface_status (xr->dest_surface);
303 if (status != CAIRO_STATUS_SUCCESS)
305 msg (ME, _("error opening output file `%s': %s"),
306 file_name, cairo_status_to_string (status));
312 = (xr->trim || output_type == XR_SVG
313 ? cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA,
314 &(cairo_rectangle_t) {
316 .height = paper[V] })
317 : output_type == XR_PNG
318 ? cairo_image_surface_create (CAIRO_FORMAT_ARGB32, paper[H], paper[V])
326 output_driver_destroy (&xr->driver);
330 static struct output_driver *
331 xr_pdf_create (struct file_handle *fh, enum settings_output_devices device_type,
332 struct string_map *o)
334 return xr_create (fh, device_type, o, XR_PDF);
337 static struct output_driver *
338 xr_ps_create (struct file_handle *fh, enum settings_output_devices device_type,
339 struct string_map *o)
341 return xr_create (fh, device_type, o, XR_PS);
344 static struct output_driver *
345 xr_svg_create (struct file_handle *fh, enum settings_output_devices device_type,
346 struct string_map *o)
348 return xr_create (fh, device_type, o, XR_SVG);
351 static struct output_driver *
352 xr_png_create (struct file_handle *fh, enum settings_output_devices device_type,
353 struct string_map *o)
355 return xr_create (fh, device_type, o, XR_PNG);
359 xr_set_surface_size (cairo_surface_t *surface, enum xr_output_type output_type,
360 double width, double height)
365 cairo_pdf_surface_set_size (surface, width, height);
369 cairo_ps_surface_set_size (surface, width, height);
379 xr_copy_surface (cairo_surface_t *dst, cairo_surface_t *src,
382 cairo_t *cr = cairo_create (dst);
383 cairo_set_source_surface (cr, src, x, y);
389 clear_rectangle (cairo_surface_t *surface,
390 double x0, double y0, double x1, double y1)
392 cairo_t *cr = cairo_create (surface);
393 cairo_set_source_rgb (cr, 1, 1, 1);
395 cairo_rectangle (cr, x0, y0, x1 - x0, y1 - y0);
401 xr_report_error (cairo_status_t status, const char *file_name)
403 if (status != CAIRO_STATUS_SUCCESS)
404 fprintf (stderr, "%s: %s\n", file_name, cairo_status_to_string (status));
408 xr_finish_page (struct xr_driver *xr)
410 xr_pager_finish_page (xr->pager);
414 - If the destination is PDF or PostScript, set the dest surface size, copy
415 ink extent, show_page.
417 - If the destination is PNG, create image surface, copy ink extent,
418 cairo_surface_write_to_png(), destroy image surface.
420 - If the destination is SVG, create svg surface, copy ink extent, close.
422 then destroy drawing_surface and make a new one.
426 - If the destination is PDF or PostScript, show_page.
428 - If the destination is PNG, cairo_surface_write_to_png(), destroy image
429 surface, create new image surface.
431 - If the destination is SVG, create svg surface, copy whole thing, close.
434 double paper[TABLE_N_AXES];
435 for (int a = 0; a < TABLE_N_AXES; a++)
436 paper[a] = xr_to_pt (xr_page_style_paper_size (
437 xr->page_style, xr->fsm_style, a));
440 char *file_name = (xr->page_number > 1
441 ? xasprintf ("%s-%d", xr->driver.name, xr->page_number)
446 /* Get the bounding box for the drawing surface. */
447 double ofs[TABLE_N_AXES], size[TABLE_N_AXES];
448 cairo_recording_surface_ink_extents (xr->drawing_surface,
451 const int (*margins)[2] = xr->page_style->margins;
452 for (int a = 0; a < TABLE_N_AXES; a++)
454 double scale = XR_POINT;
455 size[a] += (margins[a][0] + margins[a][1]) / scale;
456 ofs[a] = -ofs[a] + margins[a][0] / scale;
459 switch (xr->output_type)
463 xr_set_surface_size (xr->dest_surface, xr->output_type,
465 xr_copy_surface (xr->dest_surface, xr->drawing_surface,
467 cairo_surface_show_page (xr->dest_surface);
472 cairo_surface_t *svg = cairo_svg_surface_create (
473 file_name, size[H], size[V]);
474 xr_copy_surface (svg, xr->drawing_surface, ofs[H], ofs[V]);
475 xr_report_error (cairo_surface_status (svg), file_name);
476 cairo_surface_destroy (svg);
482 cairo_surface_t *png = cairo_image_surface_create (
483 CAIRO_FORMAT_ARGB32, size[H], size[V]);
484 clear_rectangle (png, 0, 0, size[H], size[V]);
485 xr_copy_surface (png, xr->drawing_surface, ofs[H], ofs[V]);
486 xr_report_error (cairo_surface_write_to_png (png, file_name),
488 cairo_surface_destroy (png);
493 /* Destroy the recording surface and create a fresh one of the same
495 cairo_surface_destroy (xr->drawing_surface);
496 xr->drawing_surface = cairo_recording_surface_create (
497 CAIRO_CONTENT_COLOR_ALPHA,
498 &(cairo_rectangle_t) { .width = paper[H], .height = paper[V] });
502 switch (xr->output_type)
506 cairo_surface_show_page (xr->dest_surface);
511 cairo_surface_t *svg = cairo_svg_surface_create (
512 file_name, paper[H], paper[V]);
513 xr_copy_surface (svg, xr->drawing_surface, 0.0, 0.0);
514 xr_report_error (cairo_surface_status (svg), file_name);
515 cairo_surface_destroy (svg);
520 xr_report_error (cairo_surface_write_to_png (xr->drawing_surface,
521 file_name), file_name);
522 cairo_surface_destroy (xr->drawing_surface);
523 xr->drawing_surface = cairo_image_surface_create (
524 CAIRO_FORMAT_ARGB32, paper[H], paper[V]);
529 if (file_name != xr->driver.name)
534 xr_destroy (struct output_driver *driver)
536 struct xr_driver *xr = xr_driver_cast (driver);
541 xr_pager_destroy (xr->pager);
543 if (xr->drawing_surface && xr->drawing_surface != xr->dest_surface)
544 cairo_surface_destroy (xr->drawing_surface);
545 if (xr->dest_surface)
547 cairo_surface_finish (xr->dest_surface);
548 cairo_status_t status = cairo_surface_status (xr->dest_surface);
549 if (status != CAIRO_STATUS_SUCCESS)
550 fprintf (stderr, _("error drawing output for %s driver: %s\n"),
551 output_driver_get_name (driver),
552 cairo_status_to_string (status));
553 cairo_surface_destroy (xr->dest_surface);
556 xr_page_style_unref (xr->page_style);
557 xr_fsm_style_unref (xr->fsm_style);
562 xr_update_page_setup (struct output_driver *driver,
563 const struct page_setup *setup)
565 struct xr_driver *xr = xr_driver_cast (driver);
567 const double scale = 72 * XR_POINT;
569 int swap = setup->orientation == PAGE_LANDSCAPE;
570 enum table_axis h = H ^ swap;
571 enum table_axis v = V ^ swap;
573 struct xr_page_style *old_ps = xr->page_style;
574 xr->page_style = xmalloc (sizeof *xr->page_style);
575 *xr->page_style = (struct xr_page_style) {
579 [H] = { setup->margins[h][0] * scale, setup->margins[h][1] * scale },
580 [V] = { setup->margins[v][0] * scale, setup->margins[v][1] * scale },
583 .initial_page_number = setup->initial_page_number,
584 .include_outline = old_ps->include_outline,
586 for (size_t i = 0; i < 2; i++)
587 page_heading_copy (&xr->page_style->headings[i], &setup->headings[i]);
588 xr_page_style_unref (old_ps);
590 struct xr_fsm_style *old_fs = xr->fsm_style;
591 xr->fsm_style = xmalloc (sizeof *xr->fsm_style);
592 *xr->fsm_style = (struct xr_fsm_style) {
594 .size = { [H] = setup->paper[H] * scale, [V] = setup->paper[V] * scale },
596 [H] = setup->paper[H] * scale / 2,
597 [V] = setup->paper[V] * scale / 2,
599 .font = pango_font_description_copy (old_fs->font),
601 .use_system_colors = old_fs->use_system_colors,
602 .object_spacing = setup->object_spacing * 72 * XR_POINT,
603 .font_resolution = old_fs->font_resolution,
605 xr_fsm_style_unref (old_fs);
607 xr_set_surface_size (xr->dest_surface, xr->output_type,
608 setup->paper[H] * 72.0, setup->paper[V] * 72.0);
612 xr_submit (struct output_driver *driver, const struct output_item *item)
614 struct xr_driver *xr = xr_driver_cast (driver);
618 xr->pager = xr_pager_create (xr->page_style, xr->fsm_style);
619 xr_pager_add_page (xr->pager, cairo_create (xr->drawing_surface));
622 xr_pager_add_item (xr->pager, item);
623 while (xr_pager_needs_new_page (xr->pager))
626 xr_pager_add_page (xr->pager, cairo_create (xr->drawing_surface));
631 xr_setup (struct output_driver *driver, const struct page_setup *ps)
633 struct xr_driver *xr = xr_driver_cast (driver);
636 xr_update_page_setup (driver, ps);
639 struct output_driver_factory pdf_driver_factory =
640 { "pdf", "pspp.pdf", xr_pdf_create };
641 struct output_driver_factory ps_driver_factory =
642 { "ps", "pspp.ps", xr_ps_create };
643 struct output_driver_factory svg_driver_factory =
644 { "svg", "pspp.svg", xr_svg_create };
645 struct output_driver_factory png_driver_factory =
646 { "png", "pspp.png", xr_png_create };
648 static const struct output_driver_class cairo_driver_class =
651 .destroy = xr_destroy,
654 .handles_groups = true,