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 driver_options *options, const char *key, const char *default_value)
135 return driver_option_get (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 driver_options *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 /* Scale INCHES into inch/(72 * XR_POINT). */
187 scale (double inches)
189 return inches * 72 * XR_POINT + 0.5;
192 static struct xr_driver *
193 xr_allocate (const char *name, int device_type,
194 enum xr_output_type output_type, struct driver_options *o)
196 struct page_setup *ps = page_setup_parse (o);
198 int size[TABLE_N_AXES];
199 for (int a = 0; a < TABLE_N_AXES; a++)
200 size[a] = scale (ps->paper[a] - ps->margins[a][0] - ps->margins[a][1]);
202 int min_break[TABLE_N_AXES];
203 min_break[H] = scale (parse_dimension (opt (o, "min-hbreak", NULL)));
204 min_break[V] = scale (parse_dimension (opt (o, "min-vbreak", NULL)));
205 for (int a = 0; a < TABLE_N_AXES; a++)
206 if (min_break[a] <= 0)
207 min_break[a] = size[a] / 2;
209 int object_spacing = scale (ps->object_spacing);
210 if (object_spacing <= 0)
211 object_spacing = scale (12.0 / 72.0);
213 int font_size = parse_int (opt (o, "font-size", "10000"), 1000, 1000000);
214 PangoFontDescription *font = parse_font_option (
215 o, "prop-font", "Sans Serif", font_size, false, false);
217 struct cell_color fg = parse_color (opt (o, "foreground-color", "black"));
219 bool systemcolors = parse_boolean (opt (o, "systemcolors", "false"));
221 const char *default_resolution = (output_type == XR_PNG ? "96" : "72");
222 int font_resolution = parse_int (opt (o, "font-resolution",
223 default_resolution), 10, 1000);
225 bool trim = parse_boolean (opt (o, "trim", "false"));
227 /* Cairo 1.16.0 has a bug that causes crashes if outlines are enabled at the
228 same time as trimming:
229 https://lists.cairographics.org/archives/cairo/2020-December/029151.html
230 For now, just disable the outline if trimming is enabled. */
232 = (output_type == XR_PDF
233 && parse_boolean (opt (o, "outline", trim ? "false" : "true")));
235 struct xr_page_style *page_style = xmalloc (sizeof *page_style);
236 *page_style = (struct xr_page_style) {
240 [H] = { scale (ps->margins[H][0]), scale (ps->margins[H][1]) },
241 [V] = { scale (ps->margins[V][0]), scale (ps->margins[V][1]) },
244 .initial_page_number = 1,
245 .include_outline = include_outline,
248 struct xr_fsm_style *fsm_style = xmalloc (sizeof *fsm_style);
249 *fsm_style = (struct xr_fsm_style) {
251 .size = { [H] = size[H], [V] = size[V] },
252 .min_break = { [H] = min_break[H], [V] = min_break[V] },
255 .use_system_colors = systemcolors,
256 .object_spacing = object_spacing,
257 .font_resolution = font_resolution,
260 struct xr_driver *xr = xmalloc (sizeof *xr);
261 *xr = (struct xr_driver) {
263 .class = &cairo_driver_class,
264 .name = xstrdup (name),
265 .device_type = device_type,
267 .output_type = output_type,
268 .fsm_style = fsm_style,
269 .page_style = page_style,
273 page_setup_destroy (ps);
278 static struct output_driver *
279 xr_create (struct file_handle *fh, enum settings_output_devices device_type,
280 struct driver_options *o, enum xr_output_type output_type)
282 const char *file_name = fh_get_file_name (fh);
283 struct xr_driver *xr = xr_allocate (file_name, device_type, output_type, o);
285 double paper[TABLE_N_AXES];
286 for (int a = 0; a < TABLE_N_AXES; a++)
287 paper[a] = xr_to_pt (xr_page_style_paper_size (xr->page_style,
291 = (output_type == XR_PDF
292 ? cairo_pdf_surface_create (file_name, paper[H], paper[V])
293 : output_type == XR_PS
294 ? cairo_ps_surface_create (file_name, paper[H], paper[V])
296 if (xr->dest_surface)
298 cairo_status_t status = cairo_surface_status (xr->dest_surface);
299 if (status != CAIRO_STATUS_SUCCESS)
301 msg (ME, _("error opening output file `%s': %s"),
302 file_name, cairo_status_to_string (status));
308 = (xr->trim || output_type == XR_SVG
309 ? cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA,
310 &(cairo_rectangle_t) {
312 .height = paper[V] })
313 : output_type == XR_PNG
314 ? cairo_image_surface_create (CAIRO_FORMAT_ARGB32, paper[H], paper[V])
322 output_driver_destroy (&xr->driver);
326 static struct output_driver *
327 xr_pdf_create (struct file_handle *fh, enum settings_output_devices device_type,
328 struct driver_options *o)
330 return xr_create (fh, device_type, o, XR_PDF);
333 static struct output_driver *
334 xr_ps_create (struct file_handle *fh, enum settings_output_devices device_type,
335 struct driver_options *o)
337 return xr_create (fh, device_type, o, XR_PS);
340 static struct output_driver *
341 xr_svg_create (struct file_handle *fh, enum settings_output_devices device_type,
342 struct driver_options *o)
344 return xr_create (fh, device_type, o, XR_SVG);
347 static struct output_driver *
348 xr_png_create (struct file_handle *fh, enum settings_output_devices device_type,
349 struct driver_options *o)
351 return xr_create (fh, device_type, o, XR_PNG);
355 xr_set_surface_size (cairo_surface_t *surface, enum xr_output_type output_type,
356 double width, double height)
361 cairo_pdf_surface_set_size (surface, width, height);
365 cairo_ps_surface_set_size (surface, width, height);
375 xr_copy_surface (cairo_surface_t *dst, cairo_surface_t *src,
378 cairo_t *cr = cairo_create (dst);
379 cairo_set_source_surface (cr, src, x, y);
385 clear_rectangle (cairo_surface_t *surface,
386 double x0, double y0, double x1, double y1)
388 cairo_t *cr = cairo_create (surface);
389 cairo_set_source_rgb (cr, 1, 1, 1);
391 cairo_rectangle (cr, x0, y0, x1 - x0, y1 - y0);
397 xr_report_error (cairo_status_t status, const char *file_name)
399 if (status != CAIRO_STATUS_SUCCESS)
400 fprintf (stderr, "%s: %s\n", file_name, cairo_status_to_string (status));
404 xr_finish_page (struct xr_driver *xr)
406 xr_pager_finish_page (xr->pager);
410 - If the destination is PDF or PostScript, set the dest surface size, copy
411 ink extent, show_page.
413 - If the destination is PNG, create image surface, copy ink extent,
414 cairo_surface_write_to_png(), destroy image surface.
416 - If the destination is SVG, create svg surface, copy ink extent, close.
418 then destroy drawing_surface and make a new one.
422 - If the destination is PDF or PostScript, show_page.
424 - If the destination is PNG, cairo_surface_write_to_png(), destroy image
425 surface, create new image surface.
427 - If the destination is SVG, create svg surface, copy whole thing, close.
430 double paper[TABLE_N_AXES];
431 for (int a = 0; a < TABLE_N_AXES; a++)
432 paper[a] = xr_to_pt (xr_page_style_paper_size (
433 xr->page_style, xr->fsm_style, a));
436 char *file_name = (xr->page_number > 1
437 ? xasprintf ("%s-%d", xr->driver.name, xr->page_number)
442 /* Get the bounding box for the drawing surface. */
443 double ofs[TABLE_N_AXES], size[TABLE_N_AXES];
444 cairo_recording_surface_ink_extents (xr->drawing_surface,
447 const int (*margins)[2] = xr->page_style->margins;
448 for (int a = 0; a < TABLE_N_AXES; a++)
450 double scale = XR_POINT;
451 size[a] += (margins[a][0] + margins[a][1]) / scale;
452 ofs[a] = -ofs[a] + margins[a][0] / scale;
455 switch (xr->output_type)
459 xr_set_surface_size (xr->dest_surface, xr->output_type,
461 xr_copy_surface (xr->dest_surface, xr->drawing_surface,
463 cairo_surface_show_page (xr->dest_surface);
468 cairo_surface_t *svg = cairo_svg_surface_create (
469 file_name, size[H], size[V]);
470 xr_copy_surface (svg, xr->drawing_surface, ofs[H], ofs[V]);
471 xr_report_error (cairo_surface_status (svg), file_name);
472 cairo_surface_destroy (svg);
478 cairo_surface_t *png = cairo_image_surface_create (
479 CAIRO_FORMAT_ARGB32, size[H], size[V]);
480 clear_rectangle (png, 0, 0, size[H], size[V]);
481 xr_copy_surface (png, xr->drawing_surface, ofs[H], ofs[V]);
482 xr_report_error (cairo_surface_write_to_png (png, file_name),
484 cairo_surface_destroy (png);
489 /* Destroy the recording surface and create a fresh one of the same
491 cairo_surface_destroy (xr->drawing_surface);
492 xr->drawing_surface = cairo_recording_surface_create (
493 CAIRO_CONTENT_COLOR_ALPHA,
494 &(cairo_rectangle_t) { .width = paper[H], .height = paper[V] });
498 switch (xr->output_type)
502 cairo_surface_show_page (xr->dest_surface);
507 cairo_surface_t *svg = cairo_svg_surface_create (
508 file_name, paper[H], paper[V]);
509 xr_copy_surface (svg, xr->drawing_surface, 0.0, 0.0);
510 xr_report_error (cairo_surface_status (svg), file_name);
511 cairo_surface_destroy (svg);
516 xr_report_error (cairo_surface_write_to_png (xr->drawing_surface,
517 file_name), file_name);
518 cairo_surface_destroy (xr->drawing_surface);
519 xr->drawing_surface = cairo_image_surface_create (
520 CAIRO_FORMAT_ARGB32, paper[H], paper[V]);
525 if (file_name != xr->driver.name)
530 xr_destroy (struct output_driver *driver)
532 struct xr_driver *xr = xr_driver_cast (driver);
537 xr_pager_destroy (xr->pager);
539 if (xr->drawing_surface && xr->drawing_surface != xr->dest_surface)
540 cairo_surface_destroy (xr->drawing_surface);
541 if (xr->dest_surface)
543 cairo_surface_finish (xr->dest_surface);
544 cairo_status_t status = cairo_surface_status (xr->dest_surface);
545 if (status != CAIRO_STATUS_SUCCESS)
546 fprintf (stderr, _("error drawing output for %s driver: %s\n"),
547 output_driver_get_name (driver),
548 cairo_status_to_string (status));
549 cairo_surface_destroy (xr->dest_surface);
552 xr_page_style_unref (xr->page_style);
553 xr_fsm_style_unref (xr->fsm_style);
558 xr_update_page_setup (struct output_driver *driver,
559 const struct page_setup *setup)
561 struct xr_driver *xr = xr_driver_cast (driver);
563 const double scale = 72 * XR_POINT;
565 int swap = setup->orientation == PAGE_LANDSCAPE;
566 enum table_axis h = H ^ swap;
567 enum table_axis v = V ^ swap;
569 struct xr_page_style *old_ps = xr->page_style;
570 xr->page_style = xmalloc (sizeof *xr->page_style);
571 *xr->page_style = (struct xr_page_style) {
575 [H] = { setup->margins[h][0] * scale, setup->margins[h][1] * scale },
576 [V] = { setup->margins[v][0] * scale, setup->margins[v][1] * scale },
579 .initial_page_number = setup->initial_page_number,
580 .include_outline = old_ps->include_outline,
582 for (size_t i = 0; i < 2; i++)
583 page_heading_copy (&xr->page_style->headings[i], &setup->headings[i]);
584 xr_page_style_unref (old_ps);
586 struct xr_fsm_style *old_fs = xr->fsm_style;
587 xr->fsm_style = xmalloc (sizeof *xr->fsm_style);
588 *xr->fsm_style = (struct xr_fsm_style) {
590 .size = { [H] = setup->paper[H] * scale, [V] = setup->paper[V] * scale },
592 [H] = setup->paper[H] * scale / 2,
593 [V] = setup->paper[V] * scale / 2,
595 .font = pango_font_description_copy (old_fs->font),
597 .use_system_colors = old_fs->use_system_colors,
598 .object_spacing = setup->object_spacing * 72 * XR_POINT,
599 .font_resolution = old_fs->font_resolution,
601 xr_fsm_style_unref (old_fs);
603 xr_set_surface_size (xr->dest_surface, xr->output_type,
604 setup->paper[H] * 72.0, setup->paper[V] * 72.0);
608 xr_submit (struct output_driver *driver, const struct output_item *item)
610 struct xr_driver *xr = xr_driver_cast (driver);
614 xr->pager = xr_pager_create (xr->page_style, xr->fsm_style);
615 xr_pager_add_page (xr->pager, cairo_create (xr->drawing_surface));
618 xr_pager_add_item (xr->pager, item);
619 while (xr_pager_needs_new_page (xr->pager))
622 xr_pager_add_page (xr->pager, cairo_create (xr->drawing_surface));
627 xr_setup (struct output_driver *driver, const struct page_setup *ps)
629 struct xr_driver *xr = xr_driver_cast (driver);
632 xr_update_page_setup (driver, ps);
635 struct output_driver_factory pdf_driver_factory =
636 { "pdf", "pspp.pdf", xr_pdf_create };
637 struct output_driver_factory ps_driver_factory =
638 { "ps", "pspp.ps", xr_ps_create };
639 struct output_driver_factory svg_driver_factory =
640 { "svg", "pspp.svg", xr_svg_create };
641 struct output_driver_factory png_driver_factory =
642 { "png", "pspp.png", xr_png_create };
644 static const struct output_driver_class cairo_driver_class =
647 .destroy = xr_destroy,
650 .handles_groups = true,