cairo: Fix crashes and bugs in 'setup' call.
[pspp] / src / output / cairo.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free Software Foundation, Inc.
3
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.
8
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.
13
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/>. */
16
17 #include <config.h>
18
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"
31
32 #include <cairo/cairo-pdf.h>
33 #include <cairo/cairo-ps.h>
34 #include <cairo/cairo-svg.h>
35
36 #include <cairo/cairo.h>
37 #include <inttypes.h>
38 #include <math.h>
39 #include <pango/pango-font.h>
40 #include <stdlib.h>
41
42 #include "gl/c-strcase.h"
43 #include "gl/xalloc.h"
44
45 #include "gettext.h"
46 #define _(msgid) gettext (msgid)
47
48 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
49 #define H TABLE_HORZ
50 #define V TABLE_VERT
51
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
55
56 /* Conversions to and from points. */
57 static double
58 xr_to_pt (int x)
59 {
60   return x / (double) XR_POINT;
61 }
62
63 /* Output types. */
64 enum xr_output_type
65   {
66     XR_PDF,
67     XR_PS,
68     XR_SVG,
69     XR_PNG
70   };
71
72 /* Cairo output driver. */
73 struct xr_driver
74   {
75     struct output_driver driver;
76
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;
81     bool trim;
82
83     /* This is the surface where we're currently drawing.  It is always
84        nonnull.
85
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.
89
90        If 'trim' is false:
91
92          - For output to a PDF or PostScript file, it is the same pointer as
93            'dest_surface'.
94
95          - For output to a PNG file, it is an image surface.
96
97          - For output to an SVG file, it is a recording surface.
98     */
99     cairo_surface_t *drawing_surface;
100
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.
103
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.
107
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).
112     */
113     cairo_surface_t *dest_surface;
114
115     /* Used only in file names, for PNG and SVG output where we can only write
116        one page per file. */
117     int page_number;
118   };
119
120 static const struct output_driver_class cairo_driver_class;
121
122 static void xr_update_page_setup (struct output_driver *,
123                                   const struct page_setup *);
124 \f
125 /* Output driver basics. */
126
127 static struct xr_driver *
128 xr_driver_cast (struct output_driver *driver)
129 {
130   assert (driver->class == &cairo_driver_class);
131   return UP_CAST (driver, struct xr_driver, driver);
132 }
133
134 static struct driver_option
135 opt (struct driver_options *options, const char *key, const char *default_value)
136 {
137   return driver_option_get (options, key, default_value);
138 }
139
140 static PangoFontDescription *
141 parse_font (const char *font, int default_size, bool bold, bool italic)
142 {
143   if (!c_strcasecmp (font, "Monospaced"))
144     font = "Monospace";
145
146   PangoFontDescription *desc = pango_font_description_from_string (font);
147   if (desc == NULL)
148     return NULL;
149
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);
155
156   pango_font_description_set_weight (desc, (bold
157                                             ? PANGO_WEIGHT_BOLD
158                                             : PANGO_WEIGHT_NORMAL));
159   pango_font_description_set_style (desc, (italic
160                                            ? PANGO_STYLE_ITALIC
161                                            : PANGO_STYLE_NORMAL));
162
163   return desc;
164 }
165
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)
170 {
171   char *string = parse_string (opt (options, key, default_value));
172   PangoFontDescription *desc = parse_font (string, default_size, bold, italic);
173   if (!desc)
174     {
175       msg (MW, _("`%s': bad font specification"), string);
176
177       /* Fall back to DEFAULT_VALUE, which had better be a valid font
178          description. */
179       desc = parse_font (default_value, default_size, bold, italic);
180       assert (desc != NULL);
181     }
182   free (string);
183
184   return desc;
185 }
186
187 /* Scale INCHES into inch/(72 * XR_POINT). */
188 static int
189 scale (double inches)
190 {
191   return inches * 72 * XR_POINT + 0.5;
192 }
193
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)
197 {
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);
201
202   struct cell_color fg = parse_color (opt (o, "foreground-color", "black"));
203
204   bool systemcolors = parse_boolean (opt (o, "systemcolors", "false"));
205
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);
209
210   bool trim = parse_boolean (opt (o, "trim", "false"));
211
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. */
216   bool include_outline
217     = (output_type == XR_PDF
218        && parse_boolean (opt (o, "outline", trim ? "false" : "true")));
219
220   struct xr_page_style *page_style = xmalloc (sizeof *page_style);
221   *page_style = (struct xr_page_style) {
222     .ref_cnt = 1,
223     .initial_page_number = 1,
224     .include_outline = include_outline,
225   };
226
227   struct xr_fsm_style *fsm_style = xmalloc (sizeof *fsm_style);
228   *fsm_style = (struct xr_fsm_style) {
229     .ref_cnt = 1,
230     .font = font,
231     .fg = fg,
232     .use_system_colors = systemcolors,
233     .font_resolution = font_resolution,
234   };
235
236   struct xr_driver *xr = xmalloc (sizeof *xr);
237   *xr = (struct xr_driver) {
238     .driver = {
239       .class = &cairo_driver_class,
240       .name = xstrdup (name),
241       .device_type = device_type,
242     },
243     .output_type = output_type,
244     .fsm_style = fsm_style,
245     .page_style = page_style,
246     .trim = trim,
247   };
248
249   return xr;
250 }
251
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)
255 {
256   const char *file_name = fh_get_file_name (fh);
257   struct xr_driver *xr = xr_allocate (file_name, device_type, output_type, o);
258
259   struct page_setup *ps = page_setup_parse (o);
260   xr_update_page_setup (&xr->driver, ps);
261
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);
266
267   xr->dest_surface
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])
272        : NULL);
273   if (xr->dest_surface)
274     {
275       cairo_status_t status = cairo_surface_status (xr->dest_surface);
276       if (status != CAIRO_STATUS_SUCCESS)
277         {
278           msg (ME, _("error opening output file `%s': %s"),
279                file_name, cairo_status_to_string (status));
280           goto error;
281         }
282     }
283
284   xr->drawing_surface
285     = (xr->trim || output_type == XR_SVG
286        ? cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA,
287                                          &(cairo_rectangle_t) {
288                                            .width = paper[H],
289                                            .height = paper[V] })
290        : output_type == XR_PNG
291        ? cairo_image_surface_create (CAIRO_FORMAT_ARGB32, paper[H], paper[V])
292        : xr->dest_surface);
293
294   fh_unref (fh);
295   return &xr->driver;
296
297  error:
298   fh_unref (fh);
299   output_driver_destroy (&xr->driver);
300   return NULL;
301 }
302
303 static struct output_driver *
304 xr_pdf_create (struct file_handle *fh, enum settings_output_devices device_type,
305                struct driver_options *o)
306 {
307   return xr_create (fh, device_type, o, XR_PDF);
308 }
309
310 static struct output_driver *
311 xr_ps_create (struct file_handle *fh, enum settings_output_devices device_type,
312               struct driver_options *o)
313 {
314   return xr_create (fh, device_type, o, XR_PS);
315 }
316
317 static struct output_driver *
318 xr_svg_create (struct file_handle *fh, enum settings_output_devices device_type,
319                struct driver_options *o)
320 {
321   return xr_create (fh, device_type, o, XR_SVG);
322 }
323
324 static struct output_driver *
325 xr_png_create (struct file_handle *fh, enum settings_output_devices device_type,
326                struct driver_options *o)
327 {
328   return xr_create (fh, device_type, o, XR_PNG);
329 }
330
331 static void
332 xr_set_surface_size (cairo_surface_t *surface, enum xr_output_type output_type,
333                      double width, double height)
334 {
335   switch (output_type)
336     {
337     case XR_PDF:
338       cairo_pdf_surface_set_size (surface, width, height);
339       break;
340
341     case XR_PS:
342       cairo_ps_surface_set_size (surface, width, height);
343       break;
344
345     case XR_SVG:
346     case XR_PNG:
347       NOT_REACHED ();
348     }
349 }
350
351 static void
352 xr_copy_surface (cairo_surface_t *dst, cairo_surface_t *src,
353                  double x, double y)
354 {
355   cairo_t *cr = cairo_create (dst);
356   cairo_set_source_surface (cr, src, x, y);
357   cairo_paint (cr);
358   cairo_destroy (cr);
359 }
360
361 static void
362 clear_rectangle (cairo_surface_t *surface,
363                  double x0, double y0, double x1, double y1)
364 {
365   cairo_t *cr = cairo_create (surface);
366   cairo_set_source_rgb (cr, 1, 1, 1);
367   cairo_new_path (cr);
368   cairo_rectangle (cr, x0, y0, x1 - x0, y1 - y0);
369   cairo_fill (cr);
370   cairo_destroy (cr);
371 }
372
373 static void
374 xr_report_error (cairo_status_t status, const char *file_name)
375 {
376   if (status != CAIRO_STATUS_SUCCESS)
377     fprintf (stderr,  "%s: %s\n", file_name, cairo_status_to_string (status));
378 }
379
380 static void
381 xr_finish_page (struct xr_driver *xr)
382 {
383   xr_pager_finish_page (xr->pager);
384
385   /* For 'trim' true:
386
387     - If the destination is PDF or PostScript, set the dest surface size, copy
388       ink extent, show_page.
389
390     - If the destination is PNG, create image surface, copy ink extent,
391       cairo_surface_write_to_png(), destroy image surface.
392
393     - If the destination is SVG, create svg surface, copy ink extent, close.
394
395     then destroy drawing_surface and make a new one.
396
397     For 'trim' false:
398
399     - If the destination is PDF or PostScript, show_page.
400
401     - If the destination is PNG, cairo_surface_write_to_png(), destroy image
402       surface, create new image surface.
403
404     - If the destination is SVG, create svg surface, copy whole thing, close.
405
406     */
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));
411
412   xr->page_number++;
413   char *file_name = (xr->page_number > 1
414                      ? xasprintf ("%s-%d", xr->driver.name, xr->page_number)
415                      : xr->driver.name);
416
417   if (xr->trim)
418     {
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,
422                                            &ofs[H], &ofs[V],
423                                            &size[H], &size[V]);
424       const int (*margins)[2] = xr->page_style->margins;
425       for (int a = 0; a < TABLE_N_AXES; a++)
426         {
427           double scale = XR_POINT;
428           size[a] += (margins[a][0] + margins[a][1]) / scale;
429           ofs[a] = -ofs[a] + margins[a][0] / scale;
430         }
431
432       switch (xr->output_type)
433         {
434         case XR_PDF:
435         case XR_PS:
436           xr_set_surface_size (xr->dest_surface, xr->output_type,
437                                size[H], size[V]);
438           xr_copy_surface (xr->dest_surface, xr->drawing_surface,
439                            ofs[H], ofs[V]);
440           cairo_surface_show_page (xr->dest_surface);
441           break;
442
443         case XR_SVG:
444           {
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);
450           }
451           break;
452
453         case XR_PNG:
454           {
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),
460                              file_name);
461             cairo_surface_destroy (png);
462           }
463           break;
464         }
465
466       /* Destroy the recording surface and create a fresh one of the same
467          size. */
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] });
472     }
473   else
474     {
475       switch (xr->output_type)
476         {
477         case XR_PDF:
478         case XR_PS:
479           cairo_surface_show_page (xr->dest_surface);
480           break;
481
482         case XR_SVG:
483           {
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);
489           }
490           break;
491
492         case XR_PNG:
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]);
498           break;
499         }
500     }
501
502   if (file_name != xr->driver.name)
503     free (file_name);
504 }
505
506 static void
507 xr_destroy (struct output_driver *driver)
508 {
509   struct xr_driver *xr = xr_driver_cast (driver);
510
511   if (xr->pager)
512     xr_finish_page (xr);
513
514   xr_pager_destroy (xr->pager);
515
516   if (xr->drawing_surface && xr->drawing_surface != xr->dest_surface)
517     cairo_surface_destroy (xr->drawing_surface);
518   if (xr->dest_surface)
519     {
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);
527     }
528
529   xr_page_style_unref (xr->page_style);
530   xr_fsm_style_unref (xr->fsm_style);
531   free (xr);
532 }
533
534 static void
535 xr_update_page_setup (struct output_driver *driver,
536                       const struct page_setup *ps)
537 {
538   struct xr_driver *xr = xr_driver_cast (driver);
539
540   int swap = ps->orientation == PAGE_LANDSCAPE;
541   enum table_axis h = H ^ swap;
542   enum table_axis v = V ^ swap;
543
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]);
547
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) {
551     .ref_cnt = 1,
552
553     .margins = {
554       [H] = { scale (ps->margins[h][0]), scale (ps->margins[h][1]) },
555       [V] = { scale (ps->margins[v][0]), scale (ps->margins[v][1]) },
556     },
557
558     .initial_page_number = ps->initial_page_number,
559     .include_outline = old_ps->include_outline,
560   };
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);
564
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) {
568     .ref_cnt = 1,
569     .size = { [H] = size[H], [V] = size[V] },
570     .min_break = {
571       [H] = size[H] / 2,
572       [V] = size[V] / 2,
573     },
574     .font = pango_font_description_copy (old_fs->font),
575     .fg = old_fs->fg,
576     .use_system_colors = old_fs->use_system_colors,
577     .object_spacing = scale (ps->object_spacing),
578     .font_resolution = old_fs->font_resolution,
579   };
580   xr_fsm_style_unref (old_fs);
581
582   if (xr->dest_surface
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);
586 }
587
588 static void
589 xr_submit (struct output_driver *driver, const struct output_item *item)
590 {
591   struct xr_driver *xr = xr_driver_cast (driver);
592
593   if (!xr->pager)
594     {
595       xr->pager = xr_pager_create (xr->page_style, xr->fsm_style);
596       xr_pager_add_page (xr->pager, cairo_create (xr->drawing_surface));
597     }
598
599   xr_pager_add_item (xr->pager, item);
600   while (xr_pager_needs_new_page (xr->pager))
601     {
602       xr_finish_page (xr);
603       xr_pager_add_page (xr->pager, cairo_create (xr->drawing_surface));
604     }
605 }
606
607 static void
608 xr_setup (struct output_driver *driver, const struct page_setup *ps)
609 {
610   struct xr_driver *xr = xr_driver_cast (driver);
611
612   if (!xr->pager)
613     xr_update_page_setup (driver, ps);
614 }
615 \f
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 };
624
625 static const struct output_driver_class cairo_driver_class =
626 {
627   .name = "cairo",
628   .destroy = xr_destroy,
629   .submit = xr_submit,
630   .setup = xr_setup,
631   .handles_groups = true,
632 };