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;
122 static void xr_update_page_setup (struct output_driver *,
123 const struct page_setup *);
125 /* Output driver basics. */
127 static struct xr_driver *
128 xr_driver_cast (struct output_driver *driver)
130 assert (driver->class == &cairo_driver_class);
131 return UP_CAST (driver, struct xr_driver, driver);
134 static struct driver_option
135 opt (struct driver_options *options, const char *key, const char *default_value)
137 return driver_option_get (options, key, default_value);
140 static PangoFontDescription *
141 parse_font (const char *font, int default_size, bool bold, bool italic)
143 if (!c_strcasecmp (font, "Monospaced"))
146 PangoFontDescription *desc = pango_font_description_from_string (font);
150 /* If the font description didn't include an explicit font size, then set it
151 to DEFAULT_SIZE, which is in inch/72000 units. */
152 if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
153 pango_font_description_set_size (desc,
154 (default_size / 1000.0) * PANGO_SCALE);
156 pango_font_description_set_weight (desc, (bold
158 : PANGO_WEIGHT_NORMAL));
159 pango_font_description_set_style (desc, (italic
161 : PANGO_STYLE_NORMAL));
166 static PangoFontDescription *
167 parse_font_option (struct driver_options *options,
168 const char *key, const char *default_value,
169 int default_size, bool bold, bool italic)
171 char *string = parse_string (opt (options, key, default_value));
172 PangoFontDescription *desc = parse_font (string, default_size, bold, italic);
175 msg (MW, _("`%s': bad font specification"), string);
177 /* Fall back to DEFAULT_VALUE, which had better be a valid font
179 desc = parse_font (default_value, default_size, bold, italic);
180 assert (desc != NULL);
187 /* Scale INCHES into inch/(72 * XR_POINT). */
189 scale (double inches)
191 return inches * 72 * XR_POINT + 0.5;
194 static struct xr_driver *
195 xr_allocate (const char *name, int device_type,
196 enum xr_output_type output_type, struct driver_options *o)
198 int font_size = parse_int (opt (o, "font-size", "10000"), 1000, 1000000);
199 PangoFontDescription *font = parse_font_option (
200 o, "prop-font", "Sans Serif", font_size, false, false);
202 struct cell_color fg = parse_color (opt (o, "foreground-color", "black"));
204 bool systemcolors = parse_boolean (opt (o, "systemcolors", "false"));
206 const char *default_resolution = (output_type == XR_PNG ? "96" : "72");
207 int font_resolution = parse_int (opt (o, "font-resolution",
208 default_resolution), 10, 1000);
210 bool trim = parse_boolean (opt (o, "trim", "false"));
212 /* Cairo 1.16.0 has a bug that causes crashes if outlines are enabled at the
213 same time as trimming:
214 https://lists.cairographics.org/archives/cairo/2020-December/029151.html
215 For now, just disable the outline if trimming is enabled. */
217 = (output_type == XR_PDF
218 && parse_boolean (opt (o, "outline", trim ? "false" : "true")));
220 struct xr_page_style *page_style = xmalloc (sizeof *page_style);
221 *page_style = (struct xr_page_style) {
223 .initial_page_number = 1,
224 .include_outline = include_outline,
227 struct xr_fsm_style *fsm_style = xmalloc (sizeof *fsm_style);
228 *fsm_style = (struct xr_fsm_style) {
232 .use_system_colors = systemcolors,
233 .font_resolution = font_resolution,
236 struct xr_driver *xr = xmalloc (sizeof *xr);
237 *xr = (struct xr_driver) {
239 .class = &cairo_driver_class,
240 .name = xstrdup (name),
241 .device_type = device_type,
243 .output_type = output_type,
244 .fsm_style = fsm_style,
245 .page_style = page_style,
252 static struct output_driver *
253 xr_create (struct file_handle *fh, enum settings_output_devices device_type,
254 struct driver_options *o, enum xr_output_type output_type)
256 const char *file_name = fh_get_file_name (fh);
257 struct xr_driver *xr = xr_allocate (file_name, device_type, output_type, o);
259 struct page_setup *ps = page_setup_parse (o);
260 xr_update_page_setup (&xr->driver, ps);
262 double paper[TABLE_N_AXES];
263 for (int a = 0; a < TABLE_N_AXES; a++)
264 paper[a] = ps->paper[a] * 72.0;
265 page_setup_destroy (ps);
268 = (output_type == XR_PDF
269 ? cairo_pdf_surface_create (file_name, paper[H], paper[V])
270 : output_type == XR_PS
271 ? cairo_ps_surface_create (file_name, paper[H], paper[V])
273 if (xr->dest_surface)
275 cairo_status_t status = cairo_surface_status (xr->dest_surface);
276 if (status != CAIRO_STATUS_SUCCESS)
278 msg (ME, _("error opening output file `%s': %s"),
279 file_name, cairo_status_to_string (status));
285 = (xr->trim || output_type == XR_SVG
286 ? cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA,
287 &(cairo_rectangle_t) {
289 .height = paper[V] })
290 : output_type == XR_PNG
291 ? cairo_image_surface_create (CAIRO_FORMAT_ARGB32, paper[H], paper[V])
299 output_driver_destroy (&xr->driver);
303 static struct output_driver *
304 xr_pdf_create (struct file_handle *fh, enum settings_output_devices device_type,
305 struct driver_options *o)
307 return xr_create (fh, device_type, o, XR_PDF);
310 static struct output_driver *
311 xr_ps_create (struct file_handle *fh, enum settings_output_devices device_type,
312 struct driver_options *o)
314 return xr_create (fh, device_type, o, XR_PS);
317 static struct output_driver *
318 xr_svg_create (struct file_handle *fh, enum settings_output_devices device_type,
319 struct driver_options *o)
321 return xr_create (fh, device_type, o, XR_SVG);
324 static struct output_driver *
325 xr_png_create (struct file_handle *fh, enum settings_output_devices device_type,
326 struct driver_options *o)
328 return xr_create (fh, device_type, o, XR_PNG);
332 xr_set_surface_size (cairo_surface_t *surface, enum xr_output_type output_type,
333 double width, double height)
338 cairo_pdf_surface_set_size (surface, width, height);
342 cairo_ps_surface_set_size (surface, width, height);
352 xr_copy_surface (cairo_surface_t *dst, cairo_surface_t *src,
355 cairo_t *cr = cairo_create (dst);
356 cairo_set_source_surface (cr, src, x, y);
362 clear_rectangle (cairo_surface_t *surface,
363 double x0, double y0, double x1, double y1)
365 cairo_t *cr = cairo_create (surface);
366 cairo_set_source_rgb (cr, 1, 1, 1);
368 cairo_rectangle (cr, x0, y0, x1 - x0, y1 - y0);
374 xr_report_error (cairo_status_t status, const char *file_name)
376 if (status != CAIRO_STATUS_SUCCESS)
377 fprintf (stderr, "%s: %s\n", file_name, cairo_status_to_string (status));
381 xr_finish_page (struct xr_driver *xr)
383 xr_pager_finish_page (xr->pager);
387 - If the destination is PDF or PostScript, set the dest surface size, copy
388 ink extent, show_page.
390 - If the destination is PNG, create image surface, copy ink extent,
391 cairo_surface_write_to_png(), destroy image surface.
393 - If the destination is SVG, create svg surface, copy ink extent, close.
395 then destroy drawing_surface and make a new one.
399 - If the destination is PDF or PostScript, show_page.
401 - If the destination is PNG, cairo_surface_write_to_png(), destroy image
402 surface, create new image surface.
404 - If the destination is SVG, create svg surface, copy whole thing, close.
407 double paper[TABLE_N_AXES];
408 for (int a = 0; a < TABLE_N_AXES; a++)
409 paper[a] = xr_to_pt (xr_page_style_paper_size (
410 xr->page_style, xr->fsm_style, a));
413 char *file_name = (xr->page_number > 1
414 ? xasprintf ("%s-%d", xr->driver.name, xr->page_number)
419 /* Get the bounding box for the drawing surface. */
420 double ofs[TABLE_N_AXES], size[TABLE_N_AXES];
421 cairo_recording_surface_ink_extents (xr->drawing_surface,
424 const int (*margins)[2] = xr->page_style->margins;
425 for (int a = 0; a < TABLE_N_AXES; a++)
427 double scale = XR_POINT;
428 size[a] += (margins[a][0] + margins[a][1]) / scale;
429 ofs[a] = -ofs[a] + margins[a][0] / scale;
432 switch (xr->output_type)
436 xr_set_surface_size (xr->dest_surface, xr->output_type,
438 xr_copy_surface (xr->dest_surface, xr->drawing_surface,
440 cairo_surface_show_page (xr->dest_surface);
445 cairo_surface_t *svg = cairo_svg_surface_create (
446 file_name, size[H], size[V]);
447 xr_copy_surface (svg, xr->drawing_surface, ofs[H], ofs[V]);
448 xr_report_error (cairo_surface_status (svg), file_name);
449 cairo_surface_destroy (svg);
455 cairo_surface_t *png = cairo_image_surface_create (
456 CAIRO_FORMAT_ARGB32, size[H], size[V]);
457 clear_rectangle (png, 0, 0, size[H], size[V]);
458 xr_copy_surface (png, xr->drawing_surface, ofs[H], ofs[V]);
459 xr_report_error (cairo_surface_write_to_png (png, file_name),
461 cairo_surface_destroy (png);
466 /* Destroy the recording surface and create a fresh one of the same
468 cairo_surface_destroy (xr->drawing_surface);
469 xr->drawing_surface = cairo_recording_surface_create (
470 CAIRO_CONTENT_COLOR_ALPHA,
471 &(cairo_rectangle_t) { .width = paper[H], .height = paper[V] });
475 switch (xr->output_type)
479 cairo_surface_show_page (xr->dest_surface);
484 cairo_surface_t *svg = cairo_svg_surface_create (
485 file_name, paper[H], paper[V]);
486 xr_copy_surface (svg, xr->drawing_surface, 0.0, 0.0);
487 xr_report_error (cairo_surface_status (svg), file_name);
488 cairo_surface_destroy (svg);
493 xr_report_error (cairo_surface_write_to_png (xr->drawing_surface,
494 file_name), file_name);
495 cairo_surface_destroy (xr->drawing_surface);
496 xr->drawing_surface = cairo_image_surface_create (
497 CAIRO_FORMAT_ARGB32, paper[H], paper[V]);
502 if (file_name != xr->driver.name)
507 xr_destroy (struct output_driver *driver)
509 struct xr_driver *xr = xr_driver_cast (driver);
514 xr_pager_destroy (xr->pager);
516 if (xr->drawing_surface && xr->drawing_surface != xr->dest_surface)
517 cairo_surface_destroy (xr->drawing_surface);
518 if (xr->dest_surface)
520 cairo_surface_finish (xr->dest_surface);
521 cairo_status_t status = cairo_surface_status (xr->dest_surface);
522 if (status != CAIRO_STATUS_SUCCESS)
523 fprintf (stderr, _("error drawing output for %s driver: %s\n"),
524 output_driver_get_name (driver),
525 cairo_status_to_string (status));
526 cairo_surface_destroy (xr->dest_surface);
529 xr_page_style_unref (xr->page_style);
530 xr_fsm_style_unref (xr->fsm_style);
535 xr_update_page_setup (struct output_driver *driver,
536 const struct page_setup *ps)
538 struct xr_driver *xr = xr_driver_cast (driver);
540 int swap = ps->orientation == PAGE_LANDSCAPE;
541 enum table_axis h = H ^ swap;
542 enum table_axis v = V ^ swap;
544 int size[TABLE_N_AXES];
545 for (int a = 0; a < TABLE_N_AXES; a++)
546 size[a] = scale (ps->paper[a] - ps->margins[a][0] - ps->margins[a][1]);
548 struct xr_page_style *old_ps = xr->page_style;
549 xr->page_style = xmalloc (sizeof *xr->page_style);
550 *xr->page_style = (struct xr_page_style) {
554 [H] = { scale (ps->margins[h][0]), scale (ps->margins[h][1]) },
555 [V] = { scale (ps->margins[v][0]), scale (ps->margins[v][1]) },
558 .initial_page_number = ps->initial_page_number,
559 .include_outline = old_ps->include_outline,
561 for (size_t i = 0; i < 2; i++)
562 page_heading_copy (&xr->page_style->headings[i], &ps->headings[i]);
563 xr_page_style_unref (old_ps);
565 struct xr_fsm_style *old_fs = xr->fsm_style;
566 xr->fsm_style = xmalloc (sizeof *xr->fsm_style);
567 *xr->fsm_style = (struct xr_fsm_style) {
569 .size = { [H] = size[H], [V] = size[V] },
574 .font = pango_font_description_copy (old_fs->font),
576 .use_system_colors = old_fs->use_system_colors,
577 .object_spacing = scale (ps->object_spacing),
578 .font_resolution = old_fs->font_resolution,
580 xr_fsm_style_unref (old_fs);
583 && (xr->output_type == XR_PDF || xr->output_type == XR_PS))
584 xr_set_surface_size (xr->dest_surface, xr->output_type,
585 ps->paper[H] * 72.0, ps->paper[V] * 72.0);
589 xr_submit (struct output_driver *driver, const struct output_item *item)
591 struct xr_driver *xr = xr_driver_cast (driver);
595 xr->pager = xr_pager_create (xr->page_style, xr->fsm_style);
596 xr_pager_add_page (xr->pager, cairo_create (xr->drawing_surface));
599 xr_pager_add_item (xr->pager, item);
600 while (xr_pager_needs_new_page (xr->pager))
603 xr_pager_add_page (xr->pager, cairo_create (xr->drawing_surface));
608 xr_setup (struct output_driver *driver, const struct page_setup *ps)
610 struct xr_driver *xr = xr_driver_cast (driver);
613 xr_update_page_setup (driver, ps);
616 struct output_driver_factory pdf_driver_factory =
617 { "pdf", "pspp.pdf", xr_pdf_create };
618 struct output_driver_factory ps_driver_factory =
619 { "ps", "pspp.ps", xr_ps_create };
620 struct output_driver_factory svg_driver_factory =
621 { "svg", "pspp.svg", xr_svg_create };
622 struct output_driver_factory png_driver_factory =
623 { "png", "pspp.png", xr_png_create };
625 static const struct output_driver_class cairo_driver_class =
628 .destroy = xr_destroy,
631 .handles_groups = true,