output: Make table_item a pivot_table, table_cell a pivot_value.
[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/table.h"
30
31 #include <cairo/cairo-pdf.h>
32 #include <cairo/cairo-ps.h>
33 #include <cairo/cairo-svg.h>
34
35 #include <cairo/cairo.h>
36 #include <inttypes.h>
37 #include <math.h>
38 #include <pango/pango-font.h>
39 #include <stdlib.h>
40
41 #include "gl/c-strcase.h"
42 #include "gl/xalloc.h"
43
44 #include "gettext.h"
45 #define _(msgid) gettext (msgid)
46
47 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
48 #define H TABLE_HORZ
49 #define V TABLE_VERT
50
51 /* The unit used for internal measurements is inch/(72 * XR_POINT).
52    (Thus, XR_POINT units represent one point.) */
53 #define XR_POINT PANGO_SCALE
54
55 /* Conversions to and from points. */
56 static double
57 xr_to_pt (int x)
58 {
59   return x / (double) XR_POINT;
60 }
61
62 /* Output types. */
63 enum xr_output_type
64   {
65     XR_PDF,
66     XR_PS,
67     XR_SVG,
68     XR_PNG
69   };
70
71 /* Cairo output driver. */
72 struct xr_driver
73   {
74     struct output_driver driver;
75
76     enum xr_output_type output_type;
77     struct xr_fsm_style *fsm_style;
78     struct xr_page_style *page_style;
79     struct xr_pager *pager;
80     bool trim;
81
82     /* This is the surface where we're currently drawing.  It is always
83        nonnull.
84
85        If 'trim' is true, this is a special Cairo "recording surface" that we
86        are using to save output temporarily just to find out the bounding box,
87        then later replay it into the destination surface.
88
89        If 'trim' is false:
90
91          - For output to a PDF or PostScript file, it is the same pointer as
92            'dest_surface'.
93
94          - For output to a PNG file, it is an image surface.
95
96          - For output to an SVG file, it is a recording surface.
97     */
98     cairo_surface_t *drawing_surface;
99
100     /* - For output to a PDF or PostScript file, this is the surface for the
101          PDF or PostScript file where the output is ultimately going.
102
103        - For output to a PNG file, this is NULL, because Cairo has very
104          limited support for PNG.  Cairo can't open a PNG file for writing as
105          a surface, it can only save an existing surface to a PNG file.
106
107        - For output to a SVG file, this is NULL, because Cairo does not
108          permit resizing the SVG page size after creating the file, whereas
109          this driver needs to do that sometimes.  Also, SVG is not multi-page
110          (according to https://wiki.inkscape.org/wiki/index.php/Multipage).
111     */
112     cairo_surface_t *dest_surface;
113
114     /* Used only in file names, for PNG and SVG output where we can only write
115        one page per file. */
116     int page_number;
117   };
118
119 static const struct output_driver_class cairo_driver_class;
120
121 \f
122 /* Output driver basics. */
123
124 static struct xr_driver *
125 xr_driver_cast (struct output_driver *driver)
126 {
127   assert (driver->class == &cairo_driver_class);
128   return UP_CAST (driver, struct xr_driver, driver);
129 }
130
131 static struct driver_option *
132 opt (struct output_driver *d, struct string_map *options, const char *key,
133      const char *default_value)
134 {
135   return driver_option_get (d, options, key, default_value);
136 }
137
138 static PangoFontDescription *
139 parse_font (const char *font, int default_size, bool bold, bool italic)
140 {
141   if (!c_strcasecmp (font, "Monospaced"))
142     font = "Monospace";
143
144   PangoFontDescription *desc = pango_font_description_from_string (font);
145   if (desc == NULL)
146     return NULL;
147
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);
153
154   pango_font_description_set_weight (desc, (bold
155                                             ? PANGO_WEIGHT_BOLD
156                                             : PANGO_WEIGHT_NORMAL));
157   pango_font_description_set_style (desc, (italic
158                                            ? PANGO_STYLE_ITALIC
159                                            : PANGO_STYLE_NORMAL));
160
161   return desc;
162 }
163
164 static PangoFontDescription *
165 parse_font_option (struct output_driver *d, struct string_map *options,
166                    const char *key, const char *default_value,
167                    int default_size, bool bold, bool italic)
168 {
169   char *string = parse_string (opt (d, options, key, default_value));
170   PangoFontDescription *desc = parse_font (string, default_size, bold, italic);
171   if (!desc)
172     {
173       msg (MW, _("`%s': bad font specification"), string);
174
175       /* Fall back to DEFAULT_VALUE, which had better be a valid font
176          description. */
177       desc = parse_font (default_value, default_size, bold, italic);
178       assert (desc != NULL);
179     }
180   free (string);
181
182   return desc;
183 }
184
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)
188 {
189   struct xr_driver *xr = xzalloc (sizeof *xr);
190   struct output_driver *d = &xr->driver;
191
192   output_driver_init (d, &cairo_driver_class, name, device_type);
193   xr->output_type = output_type;
194
195   /* Scale factor from inch/72000 to inch/(72 * XR_POINT). */
196   const double scale = XR_POINT / 1000.;
197
198   int paper[TABLE_N_AXES];
199   parse_paper_size (opt (d, o, "paper-size", ""), &paper[H], &paper[V]);
200   for (int a = 0; a < TABLE_N_AXES; a++)
201     paper[a] *= scale;
202
203   int margins[TABLE_N_AXES][2];
204   margins[H][0] = parse_dimension (opt (d, o, "left-margin", ".5in")) * scale;
205   margins[H][1] = parse_dimension (opt (d, o, "right-margin", ".5in")) * scale;
206   margins[V][0] = parse_dimension (opt (d, o, "top-margin", ".5in")) * scale;
207   margins[V][1] = parse_dimension (opt (d, o, "bottom-margin", ".5in")) * scale;
208
209   int size[TABLE_N_AXES];
210   for (int a = 0; a < TABLE_N_AXES; a++)
211     size[a] = paper[a] - margins[a][0] - margins[a][1];
212
213   int min_break[TABLE_N_AXES];
214   min_break[H] = parse_dimension (opt (d, o, "min-hbreak", NULL)) * scale;
215   min_break[V] = parse_dimension (opt (d, o, "min-vbreak", NULL)) * scale;
216   for (int a = 0; a < TABLE_N_AXES; a++)
217     if (min_break[a] <= 0)
218       min_break[a] = size[a] / 2;
219
220   int font_size = parse_int (opt (d, o, "font-size", "10000"), 1000, 1000000);
221   PangoFontDescription *font = parse_font_option (
222     d, o, "prop-font", "Sans Serif", font_size, false, false);
223
224   struct cell_color fg = parse_color (opt (d, o, "foreground-color", "black"));
225
226   bool systemcolors = parse_boolean (opt (d, o, "systemcolors", "false"));
227
228   int object_spacing
229     = parse_dimension (opt (d, o, "object-spacing", NULL)) * scale;
230   if (object_spacing <= 0)
231     object_spacing = XR_POINT * 12;
232
233   const char *default_resolution = (output_type == XR_PNG ? "96" : "72");
234   int font_resolution = parse_int (opt (d, o, "font-resolution",
235                                         default_resolution), 10, 1000);
236
237   xr->trim = parse_boolean (opt (d, o, "trim", "false"));
238
239   /* Cairo 1.16.0 has a bug that causes crashes if outlines are enabled at the
240      same time as trimming:
241      https://lists.cairographics.org/archives/cairo/2020-December/029151.html
242      For now, just disable the outline if trimming is enabled. */
243   bool include_outline
244     = (output_type == XR_PDF
245        && parse_boolean (opt (d, o, "outline", xr->trim ? "false" : "true")));
246
247   xr->page_style = xmalloc (sizeof *xr->page_style);
248   *xr->page_style = (struct xr_page_style) {
249     .ref_cnt = 1,
250
251     .margins = {
252       [H] = { margins[H][0], margins[H][1], },
253       [V] = { margins[V][0], margins[V][1], },
254     },
255
256     .initial_page_number = 1,
257     .include_outline = include_outline,
258   };
259
260   xr->fsm_style = xmalloc (sizeof *xr->fsm_style);
261   *xr->fsm_style = (struct xr_fsm_style) {
262     .ref_cnt = 1,
263     .size = { [H] = size[H], [V] = size[V] },
264     .min_break = { [H] = min_break[H], [V] = min_break[V] },
265     .font = font,
266     .fg = fg,
267     .use_system_colors = systemcolors,
268     .object_spacing = object_spacing,
269     .font_resolution = font_resolution,
270   };
271
272   return xr;
273 }
274
275 static struct output_driver *
276 xr_create (struct file_handle *fh, enum settings_output_devices device_type,
277            struct string_map *o, enum xr_output_type output_type)
278 {
279   const char *file_name = fh_get_file_name (fh);
280   struct xr_driver *xr = xr_allocate (file_name, device_type, output_type, o);
281
282   double paper[TABLE_N_AXES];
283   for (int a = 0; a < TABLE_N_AXES; a++)
284     paper[a] = xr_to_pt (xr_page_style_paper_size (xr->page_style,
285                                                    xr->fsm_style, a));
286
287   xr->dest_surface
288     = (output_type == XR_PDF
289        ? cairo_pdf_surface_create (file_name, paper[H], paper[V])
290        : output_type == XR_PS
291        ? cairo_ps_surface_create (file_name, paper[H], paper[V])
292        : NULL);
293   if (xr->dest_surface)
294     {
295       cairo_status_t status = cairo_surface_status (xr->dest_surface);
296       if (status != CAIRO_STATUS_SUCCESS)
297         {
298           msg (ME, _("error opening output file `%s': %s"),
299                file_name, cairo_status_to_string (status));
300           goto error;
301         }
302     }
303
304   xr->drawing_surface
305     = (xr->trim || output_type == XR_SVG
306        ? cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA,
307                                          &(cairo_rectangle_t) {
308                                            .width = paper[H],
309                                            .height = paper[V] })
310        : output_type == XR_PNG
311        ? cairo_image_surface_create (CAIRO_FORMAT_ARGB32, paper[H], paper[V])
312        : xr->dest_surface);
313
314   fh_unref (fh);
315   return &xr->driver;
316
317  error:
318   fh_unref (fh);
319   output_driver_destroy (&xr->driver);
320   return NULL;
321 }
322
323 static struct output_driver *
324 xr_pdf_create (struct  file_handle *fh, enum settings_output_devices device_type,
325                struct string_map *o)
326 {
327   return xr_create (fh, device_type, o, XR_PDF);
328 }
329
330 static struct output_driver *
331 xr_ps_create (struct  file_handle *fh, enum settings_output_devices device_type,
332                struct string_map *o)
333 {
334   return xr_create (fh, device_type, o, XR_PS);
335 }
336
337 static struct output_driver *
338 xr_svg_create (struct file_handle *fh, enum settings_output_devices device_type,
339                struct string_map *o)
340 {
341   return xr_create (fh, device_type, o, XR_SVG);
342 }
343
344 static struct output_driver *
345 xr_png_create (struct file_handle *fh, enum settings_output_devices device_type,
346                struct string_map *o)
347 {
348   return xr_create (fh, device_type, o, XR_PNG);
349 }
350
351 static void
352 xr_set_surface_size (cairo_surface_t *surface, enum xr_output_type output_type,
353                      double width, double height)
354 {
355   switch (output_type)
356     {
357     case XR_PDF:
358       cairo_pdf_surface_set_size (surface, width, height);
359       break;
360
361     case XR_PS:
362       cairo_ps_surface_set_size (surface, width, height);
363       break;
364
365     case XR_SVG:
366     case XR_PNG:
367       NOT_REACHED ();
368     }
369 }
370
371 static void
372 xr_copy_surface (cairo_surface_t *dst, cairo_surface_t *src,
373                  double x, double y)
374 {
375   cairo_t *cr = cairo_create (dst);
376   cairo_set_source_surface (cr, src, x, y);
377   cairo_paint (cr);
378   cairo_destroy (cr);
379 }
380
381 static void
382 clear_rectangle (cairo_surface_t *surface,
383                  double x0, double y0, double x1, double y1)
384 {
385   cairo_t *cr = cairo_create (surface);
386   cairo_set_source_rgb (cr, 1, 1, 1);
387   cairo_new_path (cr);
388   cairo_rectangle (cr, x0, y0, x1 - x0, y1 - y0);
389   cairo_fill (cr);
390   cairo_destroy (cr);
391 }
392
393 static void
394 xr_report_error (cairo_status_t status, const char *file_name)
395 {
396   if (status != CAIRO_STATUS_SUCCESS)
397     fprintf (stderr,  "%s: %s\n", file_name, cairo_status_to_string (status));
398 }
399
400 static void
401 xr_finish_page (struct xr_driver *xr)
402 {
403   xr_pager_finish_page (xr->pager);
404
405   /* For 'trim' true:
406
407     - If the destination is PDF or PostScript, set the dest surface size, copy
408       ink extent, show_page.
409
410     - If the destination is PNG, create image surface, copy ink extent,
411       cairo_surface_write_to_png(), destroy image surface.
412
413     - If the destination is SVG, create svg surface, copy ink extent, close.
414
415     then destroy drawing_surface and make a new one.
416
417     For 'trim' false:
418
419     - If the destination is PDF or PostScript, show_page.
420
421     - If the destination is PNG, cairo_surface_write_to_png(), destroy image
422       surface, create new image surface.
423
424     - If the destination is SVG, create svg surface, copy whole thing, close.
425
426     */
427   double paper[TABLE_N_AXES];
428   for (int a = 0; a < TABLE_N_AXES; a++)
429     paper[a] = xr_to_pt (xr_page_style_paper_size (
430                            xr->page_style, xr->fsm_style, a));
431
432   xr->page_number++;
433   char *file_name = (xr->page_number > 1
434                      ? xasprintf ("%s-%d", xr->driver.name, xr->page_number)
435                      : xr->driver.name);
436
437   if (xr->trim)
438     {
439       /* Get the bounding box for the drawing surface. */
440       double ofs[TABLE_N_AXES], size[TABLE_N_AXES];
441       cairo_recording_surface_ink_extents (xr->drawing_surface,
442                                            &ofs[H], &ofs[V],
443                                            &size[H], &size[V]);
444       const int (*margins)[2] = xr->page_style->margins;
445       for (int a = 0; a < TABLE_N_AXES; a++)
446         {
447           double scale = XR_POINT;
448           size[a] += (margins[a][0] + margins[a][1]) / scale;
449           ofs[a] = -ofs[a] + margins[a][0] / scale;
450         }
451
452       switch (xr->output_type)
453         {
454         case XR_PDF:
455         case XR_PS:
456           xr_set_surface_size (xr->dest_surface, xr->output_type,
457                                size[H], size[V]);
458           xr_copy_surface (xr->dest_surface, xr->drawing_surface,
459                            ofs[H], ofs[V]);
460           cairo_surface_show_page (xr->dest_surface);
461           break;
462
463         case XR_SVG:
464           {
465             cairo_surface_t *svg = cairo_svg_surface_create (
466               file_name, size[H], size[V]);
467             xr_copy_surface (svg, xr->drawing_surface, ofs[H], ofs[V]);
468             xr_report_error (cairo_surface_status (svg), file_name);
469             cairo_surface_destroy (svg);
470           }
471           break;
472
473         case XR_PNG:
474           {
475             cairo_surface_t *png = cairo_image_surface_create (
476               CAIRO_FORMAT_ARGB32, size[H], size[V]);
477             clear_rectangle (png, 0, 0, size[H], size[V]);
478             xr_copy_surface (png, xr->drawing_surface, ofs[H], ofs[V]);
479             xr_report_error (cairo_surface_write_to_png (png, file_name),
480                              file_name);
481             cairo_surface_destroy (png);
482           }
483           break;
484         }
485
486       /* Destroy the recording surface and create a fresh one of the same
487          size. */
488       cairo_surface_destroy (xr->drawing_surface);
489       xr->drawing_surface = cairo_recording_surface_create (
490         CAIRO_CONTENT_COLOR_ALPHA,
491         &(cairo_rectangle_t) { .width = paper[H], .height = paper[V] });
492     }
493   else
494     {
495       switch (xr->output_type)
496         {
497         case XR_PDF:
498         case XR_PS:
499           cairo_surface_show_page (xr->dest_surface);
500           break;
501
502         case XR_SVG:
503           {
504             cairo_surface_t *svg = cairo_svg_surface_create (
505               file_name, paper[H], paper[V]);
506             xr_copy_surface (svg, xr->drawing_surface, 0.0, 0.0);
507             xr_report_error (cairo_surface_status (svg), file_name);
508             cairo_surface_destroy (svg);
509           }
510           break;
511
512         case XR_PNG:
513           xr_report_error (cairo_surface_write_to_png (xr->drawing_surface,
514                                                        file_name), file_name);
515           cairo_surface_destroy (xr->drawing_surface);
516           xr->drawing_surface = cairo_image_surface_create (
517             CAIRO_FORMAT_ARGB32, paper[H], paper[V]);
518           break;
519         }
520     }
521
522   if (file_name != xr->driver.name)
523     free (file_name);
524 }
525
526 static void
527 xr_destroy (struct output_driver *driver)
528 {
529   struct xr_driver *xr = xr_driver_cast (driver);
530
531   if (xr->pager)
532     xr_finish_page (xr);
533
534   xr_pager_destroy (xr->pager);
535
536   if (xr->drawing_surface && xr->drawing_surface != xr->dest_surface)
537     cairo_surface_destroy (xr->drawing_surface);
538   if (xr->dest_surface)
539     {
540       cairo_surface_finish (xr->dest_surface);
541       cairo_status_t status = cairo_surface_status (xr->dest_surface);
542       if (status != CAIRO_STATUS_SUCCESS)
543         fprintf (stderr,  _("error drawing output for %s driver: %s\n"),
544                  output_driver_get_name (driver),
545                  cairo_status_to_string (status));
546       cairo_surface_destroy (xr->dest_surface);
547     }
548
549   xr_page_style_unref (xr->page_style);
550   xr_fsm_style_unref (xr->fsm_style);
551   free (xr);
552 }
553
554 static void
555 xr_update_page_setup (struct output_driver *driver,
556                       const struct page_setup *setup)
557 {
558   struct xr_driver *xr = xr_driver_cast (driver);
559
560   const double scale = 72 * XR_POINT;
561
562   int swap = setup->orientation == PAGE_LANDSCAPE;
563   enum table_axis h = H ^ swap;
564   enum table_axis v = V ^ swap;
565
566   struct xr_page_style *old_ps = xr->page_style;
567   xr->page_style = xmalloc (sizeof *xr->page_style);
568   *xr->page_style = (struct xr_page_style) {
569     .ref_cnt = 1,
570
571     .margins = {
572       [H] = { setup->margins[h][0] * scale, setup->margins[h][1] * scale },
573       [V] = { setup->margins[v][0] * scale, setup->margins[v][1] * scale },
574     },
575
576     .initial_page_number = setup->initial_page_number,
577     .include_outline = old_ps->include_outline,
578   };
579   for (size_t i = 0; i < 2; i++)
580     page_heading_copy (&xr->page_style->headings[i], &setup->headings[i]);
581   xr_page_style_unref (old_ps);
582
583   struct xr_fsm_style *old_fs = xr->fsm_style;
584   xr->fsm_style = xmalloc (sizeof *xr->fsm_style);
585   *xr->fsm_style = (struct xr_fsm_style) {
586     .ref_cnt = 1,
587     .size = { [H] = setup->paper[H] * scale, [V] = setup->paper[V] * scale },
588     .min_break = {
589       [H] = setup->paper[H] * scale / 2,
590       [V] = setup->paper[V] * scale / 2,
591     },
592     .font = pango_font_description_copy (old_fs->font),
593     .fg = old_fs->fg,
594     .use_system_colors = old_fs->use_system_colors,
595     .object_spacing = setup->object_spacing * 72 * XR_POINT,
596     .font_resolution = old_fs->font_resolution,
597   };
598   xr_fsm_style_unref (old_fs);
599
600   xr_set_surface_size (xr->dest_surface, xr->output_type,
601                        setup->paper[H] * 72.0, setup->paper[V] * 72.0);
602 }
603
604 static void
605 xr_submit (struct output_driver *driver, const struct output_item *output_item)
606 {
607   struct xr_driver *xr = xr_driver_cast (driver);
608
609   if (is_page_setup_item (output_item))
610     {
611       if (!xr->pager)
612         xr_update_page_setup (driver,
613                               to_page_setup_item (output_item)->page_setup);
614       return;
615     }
616
617   if (!xr->pager)
618     {
619       xr->pager = xr_pager_create (xr->page_style, xr->fsm_style);
620       xr_pager_add_page (xr->pager, cairo_create (xr->drawing_surface));
621     }
622
623   xr_pager_add_item (xr->pager, output_item);
624   while (xr_pager_needs_new_page (xr->pager))
625     {
626       xr_finish_page (xr);
627       xr_pager_add_page (xr->pager, cairo_create (xr->drawing_surface));
628     }
629 }
630 \f
631 struct output_driver_factory pdf_driver_factory =
632   { "pdf", "pspp.pdf", xr_pdf_create };
633 struct output_driver_factory ps_driver_factory =
634   { "ps", "pspp.ps", xr_ps_create };
635 struct output_driver_factory svg_driver_factory =
636   { "svg", "pspp.svg", xr_svg_create };
637 struct output_driver_factory png_driver_factory =
638   { "png", "pspp.png", xr_png_create };
639
640 static const struct output_driver_class cairo_driver_class =
641 {
642   "cairo",
643   xr_destroy,
644   xr_submit,
645   NULL,
646 };