00ac472f7c4d0229fd146b6d84585dfce28acdda
[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 *fixed_font = parse_font_option
222     (d, o, "fixed-font", "monospace", font_size, false, false);
223   PangoFontDescription *proportional_font = parse_font_option (
224     d, o, "prop-font", "sans serif", font_size, false, false);
225
226   struct cell_color fg = parse_color (opt (d, o, "foreground-color", "black"));
227
228   bool systemcolors = parse_boolean (opt (d, o, "systemcolors", "false"));
229
230   int object_spacing
231     = parse_dimension (opt (d, o, "object-spacing", NULL)) * scale;
232   if (object_spacing <= 0)
233     object_spacing = XR_POINT * 12;
234
235   const char *default_resolution = (output_type == XR_PNG ? "96" : "72");
236   int font_resolution = parse_int (opt (d, o, "font-resolution",
237                                         default_resolution), 10, 1000);
238
239   xr->trim = parse_boolean (opt (d, o, "trim", "false"));
240
241   xr->page_style = xmalloc (sizeof *xr->page_style);
242   *xr->page_style = (struct xr_page_style) {
243     .ref_cnt = 1,
244
245     .margins = {
246       [H] = { margins[H][0], margins[H][1], },
247       [V] = { margins[V][0], margins[V][1], },
248     },
249
250     .initial_page_number = 1,
251     .object_spacing = object_spacing,
252   };
253
254   xr->fsm_style = xmalloc (sizeof *xr->fsm_style);
255   *xr->fsm_style = (struct xr_fsm_style) {
256     .ref_cnt = 1,
257     .size = { [H] = size[H], [V] = size[V] },
258     .min_break = { [H] = min_break[H], [V] = min_break[V] },
259     .fonts = {
260       [XR_FONT_PROPORTIONAL] = proportional_font,
261       [XR_FONT_FIXED] = fixed_font,
262     },
263     .fg = fg,
264     .use_system_colors = systemcolors,
265     .font_resolution = font_resolution,
266   };
267
268   return xr;
269 }
270
271 static struct output_driver *
272 xr_create (struct file_handle *fh, enum settings_output_devices device_type,
273            struct string_map *o, enum xr_output_type output_type)
274 {
275   const char *file_name = fh_get_file_name (fh);
276   struct xr_driver *xr = xr_allocate (file_name, device_type, output_type, o);
277
278   double paper[TABLE_N_AXES];
279   for (int a = 0; a < TABLE_N_AXES; a++)
280     paper[a] = xr_to_pt (xr_page_style_paper_size (xr->page_style,
281                                                    xr->fsm_style, a));
282
283   xr->dest_surface
284     = (output_type == XR_PDF
285        ? cairo_pdf_surface_create (file_name, paper[H], paper[V])
286        : output_type == XR_PS
287        ? cairo_ps_surface_create (file_name, paper[H], paper[V])
288        : NULL);
289   if (xr->dest_surface)
290     {
291       cairo_status_t status = cairo_surface_status (xr->dest_surface);
292       if (status != CAIRO_STATUS_SUCCESS)
293         {
294           msg (ME, _("error opening output file `%s': %s"),
295                file_name, cairo_status_to_string (status));
296           goto error;
297         }
298     }
299
300   xr->drawing_surface
301     = (xr->trim || output_type == XR_SVG
302        ? cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA,
303                                          &(cairo_rectangle_t) {
304                                            .width = paper[H],
305                                            .height = paper[V] })
306        : output_type == XR_PNG
307        ? cairo_image_surface_create (CAIRO_FORMAT_ARGB32, paper[H], paper[V])
308        : xr->dest_surface);
309
310   fh_unref (fh);
311   return &xr->driver;
312
313  error:
314   fh_unref (fh);
315   output_driver_destroy (&xr->driver);
316   return NULL;
317 }
318
319 static struct output_driver *
320 xr_pdf_create (struct  file_handle *fh, enum settings_output_devices device_type,
321                struct string_map *o)
322 {
323   return xr_create (fh, device_type, o, XR_PDF);
324 }
325
326 static struct output_driver *
327 xr_ps_create (struct  file_handle *fh, enum settings_output_devices device_type,
328                struct string_map *o)
329 {
330   return xr_create (fh, device_type, o, XR_PS);
331 }
332
333 static struct output_driver *
334 xr_svg_create (struct file_handle *fh, enum settings_output_devices device_type,
335                struct string_map *o)
336 {
337   return xr_create (fh, device_type, o, XR_SVG);
338 }
339
340 static struct output_driver *
341 xr_png_create (struct file_handle *fh, enum settings_output_devices device_type,
342                struct string_map *o)
343 {
344   return xr_create (fh, device_type, o, XR_PNG);
345 }
346
347 static void
348 xr_set_surface_size (cairo_surface_t *surface, enum xr_output_type output_type,
349                      double width, double height)
350 {
351   switch (output_type)
352     {
353     case XR_PDF:
354       cairo_pdf_surface_set_size (surface, width, height);
355       break;
356
357     case XR_PS:
358       cairo_ps_surface_set_size (surface, width, height);
359       break;
360
361     case XR_SVG:
362     case XR_PNG:
363       NOT_REACHED ();
364     }
365 }
366
367 static void
368 xr_copy_surface (cairo_surface_t *dst, cairo_surface_t *src,
369                  double x, double y)
370 {
371   cairo_t *cr = cairo_create (dst);
372   cairo_set_source_surface (cr, src, x, y);
373   cairo_paint (cr);
374   cairo_destroy (cr);
375 }
376
377 static void
378 clear_rectangle (cairo_surface_t *surface,
379                  double x0, double y0, double x1, double y1)
380 {
381   cairo_t *cr = cairo_create (surface);
382   cairo_set_source_rgb (cr, 1, 1, 1);
383   cairo_new_path (cr);
384   cairo_rectangle (cr, x0, y0, x1 - x0, y1 - y0);
385   cairo_fill (cr);
386   cairo_destroy (cr);
387 }
388
389 static void
390 xr_report_error (cairo_status_t status, const char *file_name)
391 {
392   if (status != CAIRO_STATUS_SUCCESS)
393     fprintf (stderr,  "%s: %s\n", file_name, cairo_status_to_string (status));
394 }
395
396 static void
397 xr_finish_page (struct xr_driver *xr)
398 {
399   xr_pager_finish_page (xr->pager);
400
401   /* For 'trim' true:
402
403     - If the destination is PDF or PostScript, set the dest surface size, copy
404       ink extent, show_page.
405
406     - If the destination is PNG, create image surface, copy ink extent,
407       cairo_surface_write_to_png(), destroy image surface.
408
409     - If the destination is SVG, create svg surface, copy ink extent, close.
410
411     then destroy drawing_surface and make a new one.
412
413     For 'trim' false:
414
415     - If the destination is PDF or PostScript, show_page.
416
417     - If the destination is PNG, cairo_surface_write_to_png(), destroy image
418       surface, create new image surface.
419
420     - If the destination is SVG, create svg surface, copy whole thing, close.
421
422     */
423   double paper[TABLE_N_AXES];
424   for (int a = 0; a < TABLE_N_AXES; a++)
425     paper[a] = xr_to_pt (xr_page_style_paper_size (
426                            xr->page_style, xr->fsm_style, a));
427
428   xr->page_number++;
429   char *file_name = (xr->page_number > 1
430                      ? xasprintf ("%s-%d", xr->driver.name, xr->page_number)
431                      : xr->driver.name);
432
433   if (xr->trim)
434     {
435       /* Get the bounding box for the drawing surface. */
436       double ofs[TABLE_N_AXES], size[TABLE_N_AXES];
437       cairo_recording_surface_ink_extents (xr->drawing_surface,
438                                            &ofs[H], &ofs[V],
439                                            &size[H], &size[V]);
440       const int (*margins)[2] = xr->page_style->margins;
441       for (int a = 0; a < TABLE_N_AXES; a++)
442         {
443           double scale = XR_POINT;
444           size[a] += (margins[a][0] + margins[a][1]) / scale;
445           ofs[a] = -ofs[a] + margins[a][0] / scale;
446         }
447
448       switch (xr->output_type)
449         {
450         case XR_PDF:
451         case XR_PS:
452           xr_set_surface_size (xr->dest_surface, xr->output_type,
453                                size[H], size[V]);
454           xr_copy_surface (xr->dest_surface, xr->drawing_surface,
455                            ofs[H], ofs[V]);
456           cairo_surface_show_page (xr->dest_surface);
457           break;
458
459         case XR_SVG:
460           {
461             cairo_surface_t *svg = cairo_svg_surface_create (
462               file_name, size[H], size[V]);
463             xr_copy_surface (svg, xr->drawing_surface, ofs[H], ofs[V]);
464             xr_report_error (cairo_surface_status (svg), file_name);
465             cairo_surface_destroy (svg);
466           }
467           break;
468
469         case XR_PNG:
470           {
471             cairo_surface_t *png = cairo_image_surface_create (
472               CAIRO_FORMAT_ARGB32, size[H], size[V]);
473             clear_rectangle (png, 0, 0, size[H], size[V]);
474             xr_copy_surface (png, xr->drawing_surface, ofs[H], ofs[V]);
475             xr_report_error (cairo_surface_write_to_png (png, file_name),
476                              file_name);
477             cairo_surface_destroy (png);
478           }
479           break;
480         }
481
482       /* Destroy the recording surface and create a fresh one of the same
483          size. */
484       cairo_surface_destroy (xr->drawing_surface);
485       xr->drawing_surface = cairo_recording_surface_create (
486         CAIRO_CONTENT_COLOR_ALPHA,
487         &(cairo_rectangle_t) { .width = paper[H], .height = paper[V] });
488     }
489   else
490     {
491       switch (xr->output_type)
492         {
493         case XR_PDF:
494         case XR_PS:
495           cairo_surface_show_page (xr->dest_surface);
496           break;
497
498         case XR_SVG:
499           {
500             cairo_surface_t *svg = cairo_svg_surface_create (
501               file_name, paper[H], paper[V]);
502             xr_copy_surface (svg, xr->drawing_surface, 0.0, 0.0);
503             xr_report_error (cairo_surface_status (svg), file_name);
504             cairo_surface_destroy (svg);
505           }
506           break;
507
508         case XR_PNG:
509           xr_report_error (cairo_surface_write_to_png (xr->drawing_surface,
510                                                        file_name), file_name);
511           cairo_surface_destroy (xr->drawing_surface);
512           xr->drawing_surface = cairo_image_surface_create (
513             CAIRO_FORMAT_ARGB32, paper[H], paper[V]);
514           break;
515         }
516     }
517
518   if (file_name != xr->driver.name)
519     free (file_name);
520 }
521
522 static void
523 xr_destroy (struct output_driver *driver)
524 {
525   struct xr_driver *xr = xr_driver_cast (driver);
526
527   if (xr->pager)
528     xr_finish_page (xr);
529
530   xr_pager_destroy (xr->pager);
531
532   if (xr->drawing_surface && xr->drawing_surface != xr->dest_surface)
533     cairo_surface_destroy (xr->drawing_surface);
534   if (xr->dest_surface)
535     {
536       cairo_surface_finish (xr->dest_surface);
537       cairo_status_t status = cairo_surface_status (xr->dest_surface);
538       if (status != CAIRO_STATUS_SUCCESS)
539         fprintf (stderr,  _("error drawing output for %s driver: %s\n"),
540                  output_driver_get_name (driver),
541                  cairo_status_to_string (status));
542       cairo_surface_destroy (xr->dest_surface);
543     }
544
545   xr_page_style_unref (xr->page_style);
546   xr_fsm_style_unref (xr->fsm_style);
547   free (xr);
548 }
549
550 static void
551 xr_update_page_setup (struct output_driver *driver,
552                       const struct page_setup *ps)
553 {
554   struct xr_driver *xr = xr_driver_cast (driver);
555
556   const double scale = 72 * XR_POINT;
557
558   int swap = ps->orientation == PAGE_LANDSCAPE;
559   enum table_axis h = H ^ swap;
560   enum table_axis v = V ^ swap;
561
562   xr_page_style_unref (xr->page_style);
563   xr->page_style = xmalloc (sizeof *xr->page_style);
564   *xr->page_style = (struct xr_page_style) {
565     .ref_cnt = 1,
566
567     .margins = {
568       [H] = { ps->margins[h][0] * scale, ps->margins[h][1] * scale },
569       [V] = { ps->margins[v][0] * scale, ps->margins[v][1] * scale },
570     },
571
572     .initial_page_number = ps->initial_page_number,
573     .object_spacing = ps->object_spacing * 72 * XR_POINT,
574   };
575   for (size_t i = 0; i < 2; i++)
576     page_heading_copy (&xr->page_style->headings[i], &ps->headings[i]);
577
578   struct xr_fsm_style *old_fs = xr->fsm_style;
579   xr->fsm_style = xmalloc (sizeof *xr->fsm_style);
580   *xr->fsm_style = (struct xr_fsm_style) {
581     .ref_cnt = 1,
582     .size = { [H] = ps->paper[H] * scale, [V] = ps->paper[V] * scale },
583     .min_break = {
584       [H] = ps->paper[H] * scale / 2,
585       [V] = ps->paper[V] * scale / 2,
586     },
587     .fg = old_fs->fg,
588     .use_system_colors = old_fs->use_system_colors,
589     .font_resolution = old_fs->font_resolution,
590   };
591   for (size_t i = 0; i < XR_N_FONTS; i++)
592     xr->fsm_style->fonts[i] = pango_font_description_copy (old_fs->fonts[i]);
593   xr_fsm_style_unref (old_fs);
594
595   xr_set_surface_size (xr->dest_surface, xr->output_type, ps->paper[H] * 72.0,
596                        ps->paper[V] * 72.0);
597 }
598
599 static void
600 xr_submit (struct output_driver *driver, const struct output_item *output_item)
601 {
602   struct xr_driver *xr = xr_driver_cast (driver);
603
604   if (is_page_setup_item (output_item))
605     {
606       if (!xr->pager)
607         xr_update_page_setup (driver,
608                               to_page_setup_item (output_item)->page_setup);
609       return;
610     }
611
612   if (!xr->pager)
613     {
614       xr->pager = xr_pager_create (xr->page_style, xr->fsm_style);
615       xr_pager_add_page (xr->pager, cairo_create (xr->drawing_surface));
616     }
617
618   xr_pager_add_item (xr->pager, output_item);
619   while (xr_pager_needs_new_page (xr->pager))
620     {
621       xr_finish_page (xr);
622       xr_pager_add_page (xr->pager, cairo_create (xr->drawing_surface));
623     }
624 }
625 \f
626 struct output_driver_factory pdf_driver_factory =
627   { "pdf", "pspp.pdf", xr_pdf_create };
628 struct output_driver_factory ps_driver_factory =
629   { "ps", "pspp.ps", xr_ps_create };
630 struct output_driver_factory svg_driver_factory =
631   { "svg", "pspp.svg", xr_svg_create };
632 struct output_driver_factory png_driver_factory =
633   { "png", "pspp.png", xr_png_create };
634
635 static const struct output_driver_class cairo_driver_class =
636 {
637   "cairo",
638   xr_destroy,
639   xr_submit,
640   NULL,
641 };