a85054ebccc0ca5f6fd78d8a5264df8ac0556ddd
[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 "output/cairo.h"
20
21 #include "libpspp/assertion.h"
22 #include "libpspp/cast.h"
23 #include "libpspp/hash-functions.h"
24 #include "libpspp/message.h"
25 #include "libpspp/pool.h"
26 #include "libpspp/start-date.h"
27 #include "libpspp/str.h"
28 #include "libpspp/string-map.h"
29 #include "libpspp/version.h"
30 #include "data/file-handle-def.h"
31 #include "output/cairo-chart.h"
32 #include "output/cairo-fsm.h"
33 #include "output/cairo-pager.h"
34 #include "output/chart-item-provider.h"
35 #include "output/driver-provider.h"
36 #include "output/group-item.h"
37 #include "output/message-item.h"
38 #include "output/options.h"
39 #include "output/page-eject-item.h"
40 #include "output/page-setup-item.h"
41 #include "output/render.h"
42 #include "output/table-item.h"
43 #include "output/table.h"
44 #include "output/text-item.h"
45
46 #include <cairo/cairo-pdf.h>
47 #include <cairo/cairo-ps.h>
48 #include <cairo/cairo-svg.h>
49
50 #include <cairo/cairo.h>
51 #include <inttypes.h>
52 #include <math.h>
53 #include <pango/pango-font.h>
54 #include <pango/pango-layout.h>
55 #include <pango/pango.h>
56 #include <pango/pangocairo.h>
57 #include <stdlib.h>
58
59 #include "gl/c-ctype.h"
60 #include "gl/c-strcase.h"
61 #include "gl/intprops.h"
62 #include "gl/minmax.h"
63 #include "gl/xalloc.h"
64
65 #include "gettext.h"
66 #define _(msgid) gettext (msgid)
67
68 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
69 #define H TABLE_HORZ
70 #define V TABLE_VERT
71
72 /* The unit used for internal measurements is inch/(72 * XR_POINT).
73    (Thus, XR_POINT units represent one point.) */
74 #define XR_POINT PANGO_SCALE
75
76 /* Conversions to and from points. */
77 static double
78 xr_to_pt (int x)
79 {
80   return x / (double) XR_POINT;
81 }
82
83 /* Dimensions for drawing lines in tables. */
84 #define XR_LINE_WIDTH (XR_POINT / 2) /* Width of an ordinary line. */
85 #define XR_LINE_SPACE XR_POINT       /* Space between double lines. */
86
87 /* Output types. */
88 enum xr_output_type
89   {
90     XR_PDF,
91     XR_PS,
92     XR_SVG
93   };
94
95 /* Cairo output driver. */
96 struct xr_driver
97   {
98     struct output_driver driver;
99
100     struct xr_fsm_style *fsm_style;
101     struct xr_page_style *page_style;
102     struct xr_pager *pager;
103
104     /* Internal state. */
105     cairo_surface_t *surface;
106     int page_number;            /* Current page number. */
107     int y;
108     struct xr_fsm *fsm;
109   };
110
111 static const struct output_driver_class cairo_driver_class;
112
113 \f
114 /* Output driver basics. */
115
116 static struct xr_driver *
117 xr_driver_cast (struct output_driver *driver)
118 {
119   assert (driver->class == &cairo_driver_class);
120   return UP_CAST (driver, struct xr_driver, driver);
121 }
122
123 static struct driver_option *
124 opt (struct output_driver *d, struct string_map *options, const char *key,
125      const char *default_value)
126 {
127   return driver_option_get (d, options, key, default_value);
128 }
129
130 static PangoFontDescription *
131 parse_font (const char *font, int default_size, bool bold, bool italic)
132 {
133   if (!c_strcasecmp (font, "Monospaced"))
134     font = "Monospace";
135
136   PangoFontDescription *desc = pango_font_description_from_string (font);
137   if (desc == NULL)
138     return NULL;
139
140   /* If the font description didn't include an explicit font size, then set it
141      to DEFAULT_SIZE, which is in inch/72000 units. */
142   if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
143     pango_font_description_set_size (desc,
144                                      (default_size / 1000.0) * PANGO_SCALE);
145
146   pango_font_description_set_weight (desc, (bold
147                                             ? PANGO_WEIGHT_BOLD
148                                             : PANGO_WEIGHT_NORMAL));
149   pango_font_description_set_style (desc, (italic
150                                            ? PANGO_STYLE_ITALIC
151                                            : PANGO_STYLE_NORMAL));
152
153   return desc;
154 }
155
156 static PangoFontDescription *
157 parse_font_option (struct output_driver *d, struct string_map *options,
158                    const char *key, const char *default_value,
159                    int default_size, bool bold, bool italic)
160 {
161   char *string = parse_string (opt (d, options, key, default_value));
162   PangoFontDescription *desc = parse_font (string, default_size, bold, italic);
163   if (!desc)
164     {
165       msg (MW, _("`%s': bad font specification"), string);
166
167       /* Fall back to DEFAULT_VALUE, which had better be a valid font
168          description. */
169       desc = parse_font (default_value, default_size, bold, italic);
170       assert (desc != NULL);
171     }
172   free (string);
173
174   return desc;
175 }
176
177 /* FONT_SCALE is a nasty kluge for an issue that does not make sense.  On any
178    surface other than a screen (e.g. for output to PDF or PS or SVG), the fonts
179    are way too big by default.  A "9-point" font seems to appear about 16
180    points tall.  We use a scale factor for these surfaces to help, but the
181    underlying issue is a mystery. */
182 static struct xr_driver *
183 xr_allocate (const char *name, int device_type, struct string_map *o,
184              double font_scale)
185 {
186   struct xr_driver *xr = xzalloc (sizeof *xr);
187   struct output_driver *d = &xr->driver;
188
189   output_driver_init (d, &cairo_driver_class, name, device_type);
190
191   /* Scale factor from inch/72000 to inch/(72 * XR_POINT). */
192   const double scale = XR_POINT / 1000.;
193
194   int paper[TABLE_N_AXES];
195   parse_paper_size (opt (d, o, "paper-size", ""), &paper[H], &paper[V]);
196   for (int a = 0; a < TABLE_N_AXES; a++)
197     paper[a] *= scale;
198
199   int margins[TABLE_N_AXES][2];
200   margins[H][0] = parse_dimension (opt (d, o, "left-margin", ".5in")) * scale;
201   margins[H][1] = parse_dimension (opt (d, o, "right-margin", ".5in")) * scale;
202   margins[V][0] = parse_dimension (opt (d, o, "top-margin", ".5in")) * scale;
203   margins[V][1] = parse_dimension (opt (d, o, "bottom-margin", ".5in")) * scale;
204
205   int size[TABLE_N_AXES];
206   for (int a = 0; a < TABLE_N_AXES; a++)
207     size[a] = paper[a] - margins[a][0] - margins[a][1];
208
209   int min_break[TABLE_N_AXES];
210   min_break[H] = parse_dimension (opt (d, o, "min-hbreak", NULL)) * scale;
211   min_break[V] = parse_dimension (opt (d, o, "min-vbreak", NULL)) * scale;
212   for (int a = 0; a < TABLE_N_AXES; a++)
213     if (min_break[a] <= 0)
214       min_break[a] = size[a] / 2;
215
216   int font_size = parse_int (opt (d, o, "font-size", "10000"), 1000, 1000000);
217   PangoFontDescription *fixed_font = parse_font_option
218     (d, o, "fixed-font", "monospace", font_size, false, false);
219   PangoFontDescription *proportional_font = parse_font_option (
220     d, o, "prop-font", "sans serif", font_size, false, false);
221
222   struct cell_color fg = parse_color (opt (d, o, "foreground-color", "black"));
223
224   bool transparent = parse_boolean (opt (d, o, "transparent", "false"));
225   struct cell_color bg = (transparent
226                           ? (struct cell_color) { .alpha = 0 }
227                           : parse_color (opt (d, o, "background-color",
228                                               "white")));
229
230   bool systemcolors = parse_boolean (opt (d, o, "systemcolors", "false"));
231
232   int object_spacing
233     = parse_dimension (opt (d, o, "object-spacing", NULL)) * scale;
234   if (object_spacing <= 0)
235     object_spacing = XR_POINT * 12;
236
237   xr->page_style = xmalloc (sizeof *xr->page_style);
238   *xr->page_style = (struct xr_page_style) {
239     .ref_cnt = 1,
240
241     .margins = {
242       [H] = { margins[H][0], margins[H][1], },
243       [V] = { margins[V][0], margins[V][1], },
244     },
245
246     .bg = bg,
247     .initial_page_number = 1,
248     .object_spacing = object_spacing,
249   };
250
251   xr->fsm_style = xmalloc (sizeof *xr->fsm_style);
252   *xr->fsm_style = (struct xr_fsm_style) {
253     .ref_cnt = 1,
254     .size = { [H] = size[H], [V] = size[V] },
255     .min_break = { [H] = min_break[H], [V] = min_break[V] },
256     .fonts = {
257       [XR_FONT_PROPORTIONAL] = proportional_font,
258       [XR_FONT_FIXED] = fixed_font,
259     },
260     .fg = fg,
261     .use_system_colors = systemcolors,
262     .transparent = transparent,
263     .font_scale = font_scale,
264   };
265
266   return xr;
267 }
268
269 static struct output_driver *
270 xr_create (struct file_handle *fh, enum settings_output_devices device_type,
271            struct string_map *o, enum xr_output_type file_type)
272 {
273   const char *file_name = fh_get_file_name (fh);
274   struct xr_driver *xr = xr_allocate (file_name, device_type, o, 72.0 / 128.0);
275
276   double paper[TABLE_N_AXES];
277   for (int a = 0; a < TABLE_N_AXES; a++)
278     paper[a] = xr_to_pt (xr_page_style_paper_size (xr->page_style,
279                                                    xr->fsm_style, a));
280   if (file_type == XR_PDF)
281     xr->surface = cairo_pdf_surface_create (file_name, paper[H], paper[V]);
282   else if (file_type == XR_PS)
283     xr->surface = cairo_ps_surface_create (file_name, paper[H], paper[V]);
284   else if (file_type == XR_SVG)
285     xr->surface = cairo_svg_surface_create (file_name, paper[H], paper[V]);
286   else
287     NOT_REACHED ();
288
289   cairo_status_t status = cairo_surface_status (xr->surface);
290   if (status != CAIRO_STATUS_SUCCESS)
291     {
292       msg (ME, _("error opening output file `%s': %s"),
293            file_name, cairo_status_to_string (status));
294       goto error;
295     }
296
297   fh_unref (fh);
298   return &xr->driver;
299
300  error:
301   fh_unref (fh);
302   output_driver_destroy (&xr->driver);
303   return NULL;
304 }
305
306 static struct output_driver *
307 xr_pdf_create (struct  file_handle *fh, enum settings_output_devices device_type,
308                struct string_map *o)
309 {
310   return xr_create (fh, device_type, o, XR_PDF);
311 }
312
313 static struct output_driver *
314 xr_ps_create (struct  file_handle *fh, enum settings_output_devices device_type,
315                struct string_map *o)
316 {
317   return xr_create (fh, device_type, o, XR_PS);
318 }
319
320 static struct output_driver *
321 xr_svg_create (struct file_handle *fh, enum settings_output_devices device_type,
322                struct string_map *o)
323 {
324   return xr_create (fh, device_type, o, XR_SVG);
325 }
326
327 static void
328 xr_destroy (struct output_driver *driver)
329 {
330   struct xr_driver *xr = xr_driver_cast (driver);
331
332   if (xr->surface)
333     {
334       cairo_surface_finish (xr->surface);
335       cairo_status_t status = cairo_surface_status (xr->surface);
336       if (status != CAIRO_STATUS_SUCCESS)
337         fprintf (stderr,  _("error drawing output for %s driver: %s"),
338                  output_driver_get_name (driver),
339                  cairo_status_to_string (status));
340       cairo_surface_destroy (xr->surface);
341     }
342
343   xr_pager_destroy (xr->pager);
344   xr_page_style_unref (xr->page_style);
345   xr_fsm_style_unref (xr->fsm_style);
346   free (xr);
347 }
348
349 static void
350 xr_flush (struct output_driver *driver)
351 {
352   struct xr_driver *xr = xr_driver_cast (driver);
353
354   if (xr->surface)
355     cairo_surface_flush (xr->surface);
356 }
357
358 static void
359 xr_update_page_setup (struct output_driver *driver,
360                       const struct page_setup *ps)
361 {
362   struct xr_driver *xr = xr_driver_cast (driver);
363
364   const double scale = 72 * XR_POINT;
365
366   int swap = ps->orientation == PAGE_LANDSCAPE;
367   enum table_axis h = H ^ swap;
368   enum table_axis v = V ^ swap;
369
370   struct xr_page_style *page_style = xmalloc (sizeof *page_style);
371   *page_style = (struct xr_page_style) {
372     .ref_cnt = 1,
373
374     .margins = {
375       [H] = { ps->margins[h][0] * scale, ps->margins[h][1] * scale },
376       [V] = { ps->margins[v][0] * scale, ps->margins[v][1] * scale },
377     },
378
379     .bg = xr->page_style->bg,
380     .initial_page_number = ps->initial_page_number,
381     .object_spacing = ps->object_spacing * 72 * XR_POINT,
382   };
383
384   for (size_t i = 0; i < 2; i++)
385     page_heading_copy (&page_style->headings[i], &ps->headings[i]);
386
387   xr_page_style_unref (xr->page_style);
388   xr->page_style = page_style;
389 }
390
391 static void
392 xr_submit (struct output_driver *driver, const struct output_item *output_item)
393 {
394   struct xr_driver *xr = xr_driver_cast (driver);
395
396   if (is_page_setup_item (output_item))
397     {
398       xr_update_page_setup (driver,
399                             to_page_setup_item (output_item)->page_setup);
400       return;
401     }
402
403   if (!xr->pager)
404     {
405       xr->pager = xr_pager_create (xr->page_style, xr->fsm_style);
406       xr_pager_add_page (xr->pager, cairo_create (xr->surface));
407     }
408
409   xr_pager_add_item (xr->pager, output_item);
410   while (xr_pager_needs_new_page (xr->pager))
411     {
412       cairo_surface_show_page (xr->surface);
413       xr_pager_add_page (xr->pager, cairo_create (xr->surface));
414     }
415 }
416 \f
417 struct output_driver_factory pdf_driver_factory =
418   { "pdf", "pspp.pdf", xr_pdf_create };
419 struct output_driver_factory ps_driver_factory =
420   { "ps", "pspp.ps", xr_ps_create };
421 struct output_driver_factory svg_driver_factory =
422   { "svg", "pspp.svg", xr_svg_create };
423
424 static const struct output_driver_class cairo_driver_class =
425 {
426   "cairo",
427   xr_destroy,
428   xr_submit,
429   xr_flush,
430 };