font resolution works!
[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 static struct xr_driver *
178 xr_allocate (const char *name, int device_type, struct string_map *o)
179 {
180   struct xr_driver *xr = xzalloc (sizeof *xr);
181   struct output_driver *d = &xr->driver;
182
183   output_driver_init (d, &cairo_driver_class, name, device_type);
184
185   /* Scale factor from inch/72000 to inch/(72 * XR_POINT). */
186   const double scale = XR_POINT / 1000.;
187
188   int paper[TABLE_N_AXES];
189   parse_paper_size (opt (d, o, "paper-size", ""), &paper[H], &paper[V]);
190   for (int a = 0; a < TABLE_N_AXES; a++)
191     paper[a] *= scale;
192
193   int margins[TABLE_N_AXES][2];
194   margins[H][0] = parse_dimension (opt (d, o, "left-margin", ".5in")) * scale;
195   margins[H][1] = parse_dimension (opt (d, o, "right-margin", ".5in")) * scale;
196   margins[V][0] = parse_dimension (opt (d, o, "top-margin", ".5in")) * scale;
197   margins[V][1] = parse_dimension (opt (d, o, "bottom-margin", ".5in")) * scale;
198
199   int size[TABLE_N_AXES];
200   for (int a = 0; a < TABLE_N_AXES; a++)
201     size[a] = paper[a] - margins[a][0] - margins[a][1];
202
203   int min_break[TABLE_N_AXES];
204   min_break[H] = parse_dimension (opt (d, o, "min-hbreak", NULL)) * scale;
205   min_break[V] = parse_dimension (opt (d, o, "min-vbreak", NULL)) * scale;
206   for (int a = 0; a < TABLE_N_AXES; a++)
207     if (min_break[a] <= 0)
208       min_break[a] = size[a] / 2;
209
210   int font_size = parse_int (opt (d, o, "font-size", "10000"), 1000, 1000000);
211   PangoFontDescription *fixed_font = parse_font_option
212     (d, o, "fixed-font", "monospace", font_size, false, false);
213   PangoFontDescription *proportional_font = parse_font_option (
214     d, o, "prop-font", "sans serif", font_size, false, false);
215
216   struct cell_color fg = parse_color (opt (d, o, "foreground-color", "black"));
217
218   bool transparent = parse_boolean (opt (d, o, "transparent", "false"));
219   struct cell_color bg = (transparent
220                           ? (struct cell_color) { .alpha = 0 }
221                           : parse_color (opt (d, o, "background-color",
222                                               "white")));
223
224   bool systemcolors = parse_boolean (opt (d, o, "systemcolors", "false"));
225
226   int object_spacing
227     = parse_dimension (opt (d, o, "object-spacing", NULL)) * scale;
228   if (object_spacing <= 0)
229     object_spacing = XR_POINT * 12;
230
231   xr->page_style = xmalloc (sizeof *xr->page_style);
232   *xr->page_style = (struct xr_page_style) {
233     .ref_cnt = 1,
234
235     .margins = {
236       [H] = { margins[H][0], margins[H][1], },
237       [V] = { margins[V][0], margins[V][1], },
238     },
239
240     .bg = bg,
241     .initial_page_number = 1,
242     .object_spacing = object_spacing,
243   };
244
245   xr->fsm_style = xmalloc (sizeof *xr->fsm_style);
246   *xr->fsm_style = (struct xr_fsm_style) {
247     .ref_cnt = 1,
248     .size = { [H] = size[H], [V] = size[V] },
249     .min_break = { [H] = min_break[H], [V] = min_break[V] },
250     .fonts = {
251       [XR_FONT_PROPORTIONAL] = proportional_font,
252       [XR_FONT_FIXED] = fixed_font,
253     },
254     .fg = fg,
255     .use_system_colors = systemcolors,
256     .transparent = transparent,
257     .font_resolution = 72.0,
258   };
259
260   return xr;
261 }
262
263 static struct output_driver *
264 xr_create (struct file_handle *fh, enum settings_output_devices device_type,
265            struct string_map *o, enum xr_output_type file_type)
266 {
267   const char *file_name = fh_get_file_name (fh);
268   struct xr_driver *xr = xr_allocate (file_name, device_type, o);
269
270   double paper[TABLE_N_AXES];
271   for (int a = 0; a < TABLE_N_AXES; a++)
272     paper[a] = xr_to_pt (xr_page_style_paper_size (xr->page_style,
273                                                    xr->fsm_style, a));
274   if (file_type == XR_PDF)
275     xr->surface = cairo_pdf_surface_create (file_name, paper[H], paper[V]);
276   else if (file_type == XR_PS)
277     xr->surface = cairo_ps_surface_create (file_name, paper[H], paper[V]);
278   else if (file_type == XR_SVG)
279     xr->surface = cairo_svg_surface_create (file_name, paper[H], paper[V]);
280   else
281     NOT_REACHED ();
282
283   cairo_status_t status = cairo_surface_status (xr->surface);
284   if (status != CAIRO_STATUS_SUCCESS)
285     {
286       msg (ME, _("error opening output file `%s': %s"),
287            file_name, cairo_status_to_string (status));
288       goto error;
289     }
290
291   fh_unref (fh);
292   return &xr->driver;
293
294  error:
295   fh_unref (fh);
296   output_driver_destroy (&xr->driver);
297   return NULL;
298 }
299
300 static struct output_driver *
301 xr_pdf_create (struct  file_handle *fh, enum settings_output_devices device_type,
302                struct string_map *o)
303 {
304   return xr_create (fh, device_type, o, XR_PDF);
305 }
306
307 static struct output_driver *
308 xr_ps_create (struct  file_handle *fh, enum settings_output_devices device_type,
309                struct string_map *o)
310 {
311   return xr_create (fh, device_type, o, XR_PS);
312 }
313
314 static struct output_driver *
315 xr_svg_create (struct file_handle *fh, enum settings_output_devices device_type,
316                struct string_map *o)
317 {
318   return xr_create (fh, device_type, o, XR_SVG);
319 }
320
321 static void
322 xr_destroy (struct output_driver *driver)
323 {
324   struct xr_driver *xr = xr_driver_cast (driver);
325
326   if (xr->surface)
327     {
328       cairo_surface_finish (xr->surface);
329       cairo_status_t status = cairo_surface_status (xr->surface);
330       if (status != CAIRO_STATUS_SUCCESS)
331         fprintf (stderr,  _("error drawing output for %s driver: %s"),
332                  output_driver_get_name (driver),
333                  cairo_status_to_string (status));
334       cairo_surface_destroy (xr->surface);
335     }
336
337   xr_pager_destroy (xr->pager);
338   xr_page_style_unref (xr->page_style);
339   xr_fsm_style_unref (xr->fsm_style);
340   free (xr);
341 }
342
343 static void
344 xr_flush (struct output_driver *driver)
345 {
346   struct xr_driver *xr = xr_driver_cast (driver);
347
348   if (xr->surface)
349     cairo_surface_flush (xr->surface);
350 }
351
352 static void
353 xr_update_page_setup (struct output_driver *driver,
354                       const struct page_setup *ps)
355 {
356   struct xr_driver *xr = xr_driver_cast (driver);
357
358   const double scale = 72 * XR_POINT;
359
360   int swap = ps->orientation == PAGE_LANDSCAPE;
361   enum table_axis h = H ^ swap;
362   enum table_axis v = V ^ swap;
363
364   struct xr_page_style *page_style = xmalloc (sizeof *page_style);
365   *page_style = (struct xr_page_style) {
366     .ref_cnt = 1,
367
368     .margins = {
369       [H] = { ps->margins[h][0] * scale, ps->margins[h][1] * scale },
370       [V] = { ps->margins[v][0] * scale, ps->margins[v][1] * scale },
371     },
372
373     .bg = xr->page_style->bg,
374     .initial_page_number = ps->initial_page_number,
375     .object_spacing = ps->object_spacing * 72 * XR_POINT,
376   };
377
378   for (size_t i = 0; i < 2; i++)
379     page_heading_copy (&page_style->headings[i], &ps->headings[i]);
380
381   xr_page_style_unref (xr->page_style);
382   xr->page_style = page_style;
383 }
384
385 static void
386 xr_submit (struct output_driver *driver, const struct output_item *output_item)
387 {
388   struct xr_driver *xr = xr_driver_cast (driver);
389
390   if (is_page_setup_item (output_item))
391     {
392       xr_update_page_setup (driver,
393                             to_page_setup_item (output_item)->page_setup);
394       return;
395     }
396
397   if (!xr->pager)
398     {
399       xr->pager = xr_pager_create (xr->page_style, xr->fsm_style);
400       xr_pager_add_page (xr->pager, cairo_create (xr->surface));
401     }
402
403   xr_pager_add_item (xr->pager, output_item);
404   while (xr_pager_needs_new_page (xr->pager))
405     {
406       cairo_surface_show_page (xr->surface);
407       xr_pager_add_page (xr->pager, cairo_create (xr->surface));
408     }
409 }
410 \f
411 struct output_driver_factory pdf_driver_factory =
412   { "pdf", "pspp.pdf", xr_pdf_create };
413 struct output_driver_factory ps_driver_factory =
414   { "ps", "pspp.ps", xr_ps_create };
415 struct output_driver_factory svg_driver_factory =
416   { "svg", "pspp.svg", xr_svg_create };
417
418 static const struct output_driver_class cairo_driver_class =
419 {
420   "cairo",
421   xr_destroy,
422   xr_submit,
423   xr_flush,
424 };