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 output_driver *d, struct string_map *options, const char *key,
134 const char *default_value)
136 return driver_option_get (d, options, key, default_value);
139 static PangoFontDescription *
140 parse_font (const char *font, int default_size, bool bold, bool italic)
142 if (!c_strcasecmp (font, "Monospaced"))
145 PangoFontDescription *desc = pango_font_description_from_string (font);
149 /* If the font description didn't include an explicit font size, then set it
150 to DEFAULT_SIZE, which is in inch/72000 units. */
151 if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
152 pango_font_description_set_size (desc,
153 (default_size / 1000.0) * PANGO_SCALE);
155 pango_font_description_set_weight (desc, (bold
157 : PANGO_WEIGHT_NORMAL));
158 pango_font_description_set_style (desc, (italic
160 : PANGO_STYLE_NORMAL));
165 static PangoFontDescription *
166 parse_font_option (struct output_driver *d, struct string_map *options,
167 const char *key, const char *default_value,
168 int default_size, bool bold, bool italic)
170 char *string = parse_string (opt (d, options, key, default_value));
171 PangoFontDescription *desc = parse_font (string, default_size, bold, italic);
174 msg (MW, _("`%s': bad font specification"), string);
176 /* Fall back to DEFAULT_VALUE, which had better be a valid font
178 desc = parse_font (default_value, default_size, bold, italic);
179 assert (desc != NULL);
186 static struct xr_driver *
187 xr_allocate (const char *name, int device_type,
188 enum xr_output_type output_type, struct string_map *o)
190 struct xr_driver *xr = XZALLOC (struct xr_driver);
191 struct output_driver *d = &xr->driver;
193 output_driver_init (d, &cairo_driver_class, name, device_type);
194 xr->output_type = output_type;
196 /* Scale factor from inch/72000 to inch/(72 * XR_POINT). */
197 const double scale = XR_POINT / 1000.;
199 int paper[TABLE_N_AXES];
200 parse_paper_size (opt (d, o, "paper-size", ""), &paper[H], &paper[V]);
201 for (int a = 0; a < TABLE_N_AXES; a++)
204 int margins[TABLE_N_AXES][2];
205 margins[H][0] = parse_dimension (opt (d, o, "left-margin", ".5in")) * scale;
206 margins[H][1] = parse_dimension (opt (d, o, "right-margin", ".5in")) * scale;
207 margins[V][0] = parse_dimension (opt (d, o, "top-margin", ".5in")) * scale;
208 margins[V][1] = parse_dimension (opt (d, o, "bottom-margin", ".5in")) * scale;
210 int size[TABLE_N_AXES];
211 for (int a = 0; a < TABLE_N_AXES; a++)
212 size[a] = paper[a] - margins[a][0] - margins[a][1];
214 int min_break[TABLE_N_AXES];
215 min_break[H] = parse_dimension (opt (d, o, "min-hbreak", NULL)) * scale;
216 min_break[V] = parse_dimension (opt (d, o, "min-vbreak", NULL)) * scale;
217 for (int a = 0; a < TABLE_N_AXES; a++)
218 if (min_break[a] <= 0)
219 min_break[a] = size[a] / 2;
221 int font_size = parse_int (opt (d, o, "font-size", "10000"), 1000, 1000000);
222 PangoFontDescription *font = parse_font_option (
223 d, o, "prop-font", "Sans Serif", font_size, false, false);
225 struct cell_color fg = parse_color (opt (d, o, "foreground-color", "black"));
227 bool systemcolors = parse_boolean (opt (d, o, "systemcolors", "false"));
230 = parse_dimension (opt (d, o, "object-spacing", NULL)) * scale;
231 if (object_spacing <= 0)
232 object_spacing = XR_POINT * 12;
234 const char *default_resolution = (output_type == XR_PNG ? "96" : "72");
235 int font_resolution = parse_int (opt (d, o, "font-resolution",
236 default_resolution), 10, 1000);
238 xr->trim = parse_boolean (opt (d, o, "trim", "false"));
240 /* Cairo 1.16.0 has a bug that causes crashes if outlines are enabled at the
241 same time as trimming:
242 https://lists.cairographics.org/archives/cairo/2020-December/029151.html
243 For now, just disable the outline if trimming is enabled. */
245 = (output_type == XR_PDF
246 && parse_boolean (opt (d, o, "outline", xr->trim ? "false" : "true")));
248 xr->page_style = xmalloc (sizeof *xr->page_style);
249 *xr->page_style = (struct xr_page_style) {
253 [H] = { margins[H][0], margins[H][1], },
254 [V] = { margins[V][0], margins[V][1], },
257 .initial_page_number = 1,
258 .include_outline = include_outline,
261 xr->fsm_style = xmalloc (sizeof *xr->fsm_style);
262 *xr->fsm_style = (struct xr_fsm_style) {
264 .size = { [H] = size[H], [V] = size[V] },
265 .min_break = { [H] = min_break[H], [V] = min_break[V] },
268 .use_system_colors = systemcolors,
269 .object_spacing = object_spacing,
270 .font_resolution = font_resolution,
276 static struct output_driver *
277 xr_create (struct file_handle *fh, enum settings_output_devices device_type,
278 struct string_map *o, enum xr_output_type output_type)
280 const char *file_name = fh_get_file_name (fh);
281 struct xr_driver *xr = xr_allocate (file_name, device_type, output_type, o);
283 double paper[TABLE_N_AXES];
284 for (int a = 0; a < TABLE_N_AXES; a++)
285 paper[a] = xr_to_pt (xr_page_style_paper_size (xr->page_style,
289 = (output_type == XR_PDF
290 ? cairo_pdf_surface_create (file_name, paper[H], paper[V])
291 : output_type == XR_PS
292 ? cairo_ps_surface_create (file_name, paper[H], paper[V])
294 if (xr->dest_surface)
296 cairo_status_t status = cairo_surface_status (xr->dest_surface);
297 if (status != CAIRO_STATUS_SUCCESS)
299 msg (ME, _("error opening output file `%s': %s"),
300 file_name, cairo_status_to_string (status));
306 = (xr->trim || output_type == XR_SVG
307 ? cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA,
308 &(cairo_rectangle_t) {
310 .height = paper[V] })
311 : output_type == XR_PNG
312 ? cairo_image_surface_create (CAIRO_FORMAT_ARGB32, paper[H], paper[V])
320 output_driver_destroy (&xr->driver);
324 static struct output_driver *
325 xr_pdf_create (struct file_handle *fh, enum settings_output_devices device_type,
326 struct string_map *o)
328 return xr_create (fh, device_type, o, XR_PDF);
331 static struct output_driver *
332 xr_ps_create (struct file_handle *fh, enum settings_output_devices device_type,
333 struct string_map *o)
335 return xr_create (fh, device_type, o, XR_PS);
338 static struct output_driver *
339 xr_svg_create (struct file_handle *fh, enum settings_output_devices device_type,
340 struct string_map *o)
342 return xr_create (fh, device_type, o, XR_SVG);
345 static struct output_driver *
346 xr_png_create (struct file_handle *fh, enum settings_output_devices device_type,
347 struct string_map *o)
349 return xr_create (fh, device_type, o, XR_PNG);
353 xr_set_surface_size (cairo_surface_t *surface, enum xr_output_type output_type,
354 double width, double height)
359 cairo_pdf_surface_set_size (surface, width, height);
363 cairo_ps_surface_set_size (surface, width, height);
373 xr_copy_surface (cairo_surface_t *dst, cairo_surface_t *src,
376 cairo_t *cr = cairo_create (dst);
377 cairo_set_source_surface (cr, src, x, y);
383 clear_rectangle (cairo_surface_t *surface,
384 double x0, double y0, double x1, double y1)
386 cairo_t *cr = cairo_create (surface);
387 cairo_set_source_rgb (cr, 1, 1, 1);
389 cairo_rectangle (cr, x0, y0, x1 - x0, y1 - y0);
395 xr_report_error (cairo_status_t status, const char *file_name)
397 if (status != CAIRO_STATUS_SUCCESS)
398 fprintf (stderr, "%s: %s\n", file_name, cairo_status_to_string (status));
402 xr_finish_page (struct xr_driver *xr)
404 xr_pager_finish_page (xr->pager);
408 - If the destination is PDF or PostScript, set the dest surface size, copy
409 ink extent, show_page.
411 - If the destination is PNG, create image surface, copy ink extent,
412 cairo_surface_write_to_png(), destroy image surface.
414 - If the destination is SVG, create svg surface, copy ink extent, close.
416 then destroy drawing_surface and make a new one.
420 - If the destination is PDF or PostScript, show_page.
422 - If the destination is PNG, cairo_surface_write_to_png(), destroy image
423 surface, create new image surface.
425 - If the destination is SVG, create svg surface, copy whole thing, close.
428 double paper[TABLE_N_AXES];
429 for (int a = 0; a < TABLE_N_AXES; a++)
430 paper[a] = xr_to_pt (xr_page_style_paper_size (
431 xr->page_style, xr->fsm_style, a));
434 char *file_name = (xr->page_number > 1
435 ? xasprintf ("%s-%d", xr->driver.name, xr->page_number)
440 /* Get the bounding box for the drawing surface. */
441 double ofs[TABLE_N_AXES], size[TABLE_N_AXES];
442 cairo_recording_surface_ink_extents (xr->drawing_surface,
445 const int (*margins)[2] = xr->page_style->margins;
446 for (int a = 0; a < TABLE_N_AXES; a++)
448 double scale = XR_POINT;
449 size[a] += (margins[a][0] + margins[a][1]) / scale;
450 ofs[a] = -ofs[a] + margins[a][0] / scale;
453 switch (xr->output_type)
457 xr_set_surface_size (xr->dest_surface, xr->output_type,
459 xr_copy_surface (xr->dest_surface, xr->drawing_surface,
461 cairo_surface_show_page (xr->dest_surface);
466 cairo_surface_t *svg = cairo_svg_surface_create (
467 file_name, size[H], size[V]);
468 xr_copy_surface (svg, xr->drawing_surface, ofs[H], ofs[V]);
469 xr_report_error (cairo_surface_status (svg), file_name);
470 cairo_surface_destroy (svg);
476 cairo_surface_t *png = cairo_image_surface_create (
477 CAIRO_FORMAT_ARGB32, size[H], size[V]);
478 clear_rectangle (png, 0, 0, size[H], size[V]);
479 xr_copy_surface (png, xr->drawing_surface, ofs[H], ofs[V]);
480 xr_report_error (cairo_surface_write_to_png (png, file_name),
482 cairo_surface_destroy (png);
487 /* Destroy the recording surface and create a fresh one of the same
489 cairo_surface_destroy (xr->drawing_surface);
490 xr->drawing_surface = cairo_recording_surface_create (
491 CAIRO_CONTENT_COLOR_ALPHA,
492 &(cairo_rectangle_t) { .width = paper[H], .height = paper[V] });
496 switch (xr->output_type)
500 cairo_surface_show_page (xr->dest_surface);
505 cairo_surface_t *svg = cairo_svg_surface_create (
506 file_name, paper[H], paper[V]);
507 xr_copy_surface (svg, xr->drawing_surface, 0.0, 0.0);
508 xr_report_error (cairo_surface_status (svg), file_name);
509 cairo_surface_destroy (svg);
514 xr_report_error (cairo_surface_write_to_png (xr->drawing_surface,
515 file_name), file_name);
516 cairo_surface_destroy (xr->drawing_surface);
517 xr->drawing_surface = cairo_image_surface_create (
518 CAIRO_FORMAT_ARGB32, paper[H], paper[V]);
523 if (file_name != xr->driver.name)
528 xr_destroy (struct output_driver *driver)
530 struct xr_driver *xr = xr_driver_cast (driver);
535 xr_pager_destroy (xr->pager);
537 if (xr->drawing_surface && xr->drawing_surface != xr->dest_surface)
538 cairo_surface_destroy (xr->drawing_surface);
539 if (xr->dest_surface)
541 cairo_surface_finish (xr->dest_surface);
542 cairo_status_t status = cairo_surface_status (xr->dest_surface);
543 if (status != CAIRO_STATUS_SUCCESS)
544 fprintf (stderr, _("error drawing output for %s driver: %s\n"),
545 output_driver_get_name (driver),
546 cairo_status_to_string (status));
547 cairo_surface_destroy (xr->dest_surface);
550 xr_page_style_unref (xr->page_style);
551 xr_fsm_style_unref (xr->fsm_style);
556 xr_update_page_setup (struct output_driver *driver,
557 const struct page_setup *setup)
559 struct xr_driver *xr = xr_driver_cast (driver);
561 const double scale = 72 * XR_POINT;
563 int swap = setup->orientation == PAGE_LANDSCAPE;
564 enum table_axis h = H ^ swap;
565 enum table_axis v = V ^ swap;
567 struct xr_page_style *old_ps = xr->page_style;
568 xr->page_style = xmalloc (sizeof *xr->page_style);
569 *xr->page_style = (struct xr_page_style) {
573 [H] = { setup->margins[h][0] * scale, setup->margins[h][1] * scale },
574 [V] = { setup->margins[v][0] * scale, setup->margins[v][1] * scale },
577 .initial_page_number = setup->initial_page_number,
578 .include_outline = old_ps->include_outline,
580 for (size_t i = 0; i < 2; i++)
581 page_heading_copy (&xr->page_style->headings[i], &setup->headings[i]);
582 xr_page_style_unref (old_ps);
584 struct xr_fsm_style *old_fs = xr->fsm_style;
585 xr->fsm_style = xmalloc (sizeof *xr->fsm_style);
586 *xr->fsm_style = (struct xr_fsm_style) {
588 .size = { [H] = setup->paper[H] * scale, [V] = setup->paper[V] * scale },
590 [H] = setup->paper[H] * scale / 2,
591 [V] = setup->paper[V] * scale / 2,
593 .font = pango_font_description_copy (old_fs->font),
595 .use_system_colors = old_fs->use_system_colors,
596 .object_spacing = setup->object_spacing * 72 * XR_POINT,
597 .font_resolution = old_fs->font_resolution,
599 xr_fsm_style_unref (old_fs);
601 xr_set_surface_size (xr->dest_surface, xr->output_type,
602 setup->paper[H] * 72.0, setup->paper[V] * 72.0);
606 xr_submit (struct output_driver *driver, const struct output_item *item)
608 struct xr_driver *xr = xr_driver_cast (driver);
612 xr->pager = xr_pager_create (xr->page_style, xr->fsm_style);
613 xr_pager_add_page (xr->pager, cairo_create (xr->drawing_surface));
616 xr_pager_add_item (xr->pager, item);
617 while (xr_pager_needs_new_page (xr->pager))
620 xr_pager_add_page (xr->pager, cairo_create (xr->drawing_surface));
625 xr_setup (struct output_driver *driver, const struct page_setup *ps)
627 struct xr_driver *xr = xr_driver_cast (driver);
630 xr_update_page_setup (driver, ps);
633 struct output_driver_factory pdf_driver_factory =
634 { "pdf", "pspp.pdf", xr_pdf_create };
635 struct output_driver_factory ps_driver_factory =
636 { "ps", "pspp.ps", xr_ps_create };
637 struct output_driver_factory svg_driver_factory =
638 { "svg", "pspp.svg", xr_svg_create };
639 struct output_driver_factory png_driver_factory =
640 { "png", "pspp.png", xr_png_create };
642 static const struct output_driver_class cairo_driver_class =
645 .destroy = xr_destroy,
648 .handles_groups = true,