something odd with extra-large fixed font in export and print output
[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/chart-item-provider.h"
34 #include "output/charts/boxplot.h"
35 #include "output/charts/np-plot.h"
36 #include "output/charts/piechart.h"
37 #include "output/charts/barchart.h"
38 #include "output/charts/plot-hist.h"
39 #include "output/charts/roc-chart.h"
40 #include "output/charts/spreadlevel-plot.h"
41 #include "output/charts/scree.h"
42 #include "output/charts/scatterplot.h"
43 #include "output/driver-provider.h"
44 #include "output/group-item.h"
45 #include "output/message-item.h"
46 #include "output/options.h"
47 #include "output/page-eject-item.h"
48 #include "output/page-setup-item.h"
49 #include "output/render.h"
50 #include "output/table-item.h"
51 #include "output/table.h"
52 #include "output/text-item.h"
53
54 #include <cairo/cairo-pdf.h>
55 #include <cairo/cairo-ps.h>
56 #include <cairo/cairo-svg.h>
57
58 #include <cairo/cairo.h>
59 #include <inttypes.h>
60 #include <math.h>
61 #include <pango/pango-font.h>
62 #include <pango/pango-layout.h>
63 #include <pango/pango.h>
64 #include <pango/pangocairo.h>
65 #include <stdlib.h>
66
67 #include "gl/c-ctype.h"
68 #include "gl/c-strcase.h"
69 #include "gl/intprops.h"
70 #include "gl/minmax.h"
71 #include "gl/xalloc.h"
72
73 #include "gettext.h"
74 #define _(msgid) gettext (msgid)
75
76 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
77 #define H TABLE_HORZ
78 #define V TABLE_VERT
79
80 /* The unit used for internal measurements is inch/(72 * XR_POINT).
81    (Thus, XR_POINT units represent one point.) */
82 #define XR_POINT PANGO_SCALE
83
84 /* Conversions to and from points. */
85 static double
86 xr_to_pt (int x)
87 {
88   return x / (double) XR_POINT;
89 }
90
91 /* Conversion from 1/96" units ("pixels") to Cairo/Pango units. */
92 static int
93 px_to_xr (int x)
94 {
95   return x * (PANGO_SCALE * 72 / 96);
96 }
97
98 /* Dimensions for drawing lines in tables. */
99 #define XR_LINE_WIDTH (XR_POINT / 2) /* Width of an ordinary line. */
100 #define XR_LINE_SPACE XR_POINT       /* Space between double lines. */
101
102 /* Output types. */
103 enum xr_output_type
104   {
105     XR_PDF,
106     XR_PS,
107     XR_SVG
108   };
109
110 /* A font for use with Cairo. */
111 struct xr_font
112   {
113     PangoFontDescription *desc;
114     PangoLayout *layout;
115   };
116
117 /* Cairo output driver. */
118 struct xr_driver
119   {
120     struct output_driver driver;
121
122     /* User parameters. */
123     struct xr_font fonts[XR_N_FONTS];
124
125     int width;                  /* Page width minus margins. */
126     int length;                 /* Page length minus margins and header. */
127
128     int left_margin;            /* Left margin in inch/(72 * XR_POINT). */
129     int right_margin;           /* Right margin in inch/(72 * XR_POINT). */
130     int top_margin;             /* Top margin in inch/(72 * XR_POINT). */
131     int bottom_margin;          /* Bottom margin in inch/(72 * XR_POINT). */
132
133     int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */
134     int object_spacing;         /* Space between output objects. */
135
136     struct cell_color bg;       /* Background color */
137     struct cell_color fg;       /* Foreground color */
138     bool transparent;           /* true -> do not render background */
139     bool systemcolors;          /* true -> do not change colors     */
140
141     int initial_page_number;
142
143     struct page_heading headings[2]; /* Top and bottom headings. */
144     int headings_height[2];
145
146     /* Internal state. */
147     struct render_params *params;
148     struct xr_fsm_style *style;
149     double font_scale;
150     int char_width, char_height;
151     cairo_t *cairo;
152     cairo_surface_t *surface;
153     int page_number;            /* Current page number. */
154     int y;
155     struct xr_fsm *fsm;
156   };
157
158 static const struct output_driver_class cairo_driver_class;
159
160 static void xr_driver_destroy_fsm (struct xr_driver *);
161 static void xr_driver_run_fsm (struct xr_driver *);
162
163 static void xr_draw_line (void *, int bb[TABLE_N_AXES][2],
164                           enum render_line_style styles[TABLE_N_AXES][2],
165                           struct cell_color colors[TABLE_N_AXES][2]);
166 static void xr_measure_cell_width (void *, const struct table_cell *,
167                                    int *min, int *max);
168 static int xr_measure_cell_height (void *, const struct table_cell *,
169                                    int width);
170 static void xr_draw_cell (void *, const struct table_cell *, int color_idx,
171                           int bb[TABLE_N_AXES][2], int valign_offset,
172                           int spill[TABLE_N_AXES][2],
173                           int clip[TABLE_N_AXES][2]);
174 static int xr_adjust_break (void *, const struct table_cell *,
175                             int width, int height);
176 \f
177 /* Output driver basics. */
178
179 static struct xr_driver *
180 xr_driver_cast (struct output_driver *driver)
181 {
182   assert (driver->class == &cairo_driver_class);
183   return UP_CAST (driver, struct xr_driver, driver);
184 }
185
186 static struct driver_option *
187 opt (struct output_driver *d, struct string_map *options, const char *key,
188      const char *default_value)
189 {
190   return driver_option_get (d, options, key, default_value);
191 }
192
193 static PangoFontDescription *
194 parse_font (const char *font, int default_size, bool bold, bool italic)
195 {
196   if (!c_strcasecmp (font, "Monospaced"))
197     font = "Monospace";
198
199   PangoFontDescription *desc = pango_font_description_from_string (font);
200   if (desc == NULL)
201     return NULL;
202
203   /* If the font description didn't include an explicit font size, then set it
204      to DEFAULT_SIZE, which is in inch/72000 units. */
205   if (!(pango_font_description_get_set_fields (desc) & PANGO_FONT_MASK_SIZE))
206     pango_font_description_set_size (desc,
207                                      (default_size / 1000.0) * PANGO_SCALE);
208
209   pango_font_description_set_weight (desc, (bold
210                                             ? PANGO_WEIGHT_BOLD
211                                             : PANGO_WEIGHT_NORMAL));
212   pango_font_description_set_style (desc, (italic
213                                            ? PANGO_STYLE_ITALIC
214                                            : PANGO_STYLE_NORMAL));
215
216   return desc;
217 }
218
219 static PangoFontDescription *
220 parse_font_option (struct output_driver *d, struct string_map *options,
221                    const char *key, const char *default_value,
222                    int default_size, bool bold, bool italic)
223 {
224   char *string = parse_string (opt (d, options, key, default_value));
225   PangoFontDescription *desc = parse_font (string, default_size, bold, italic);
226   if (!desc)
227     {
228       msg (MW, _("`%s': bad font specification"), string);
229
230       /* Fall back to DEFAULT_VALUE, which had better be a valid font
231          description. */
232       desc = parse_font (default_value, default_size, bold, italic);
233       assert (desc != NULL);
234     }
235   free (string);
236
237   return desc;
238 }
239
240 static void
241 apply_options (struct xr_driver *xr, struct string_map *o)
242 {
243   struct output_driver *d = &xr->driver;
244
245   /* In inch/72000 units used by parse_paper_size() and parse_dimension(). */
246   int left_margin, right_margin;
247   int top_margin, bottom_margin;
248   int paper_width, paper_length;
249   int font_size;
250   int min_break[TABLE_N_AXES];
251
252   /* Scale factor from inch/72000 to inch/(72 * XR_POINT). */
253   const double scale = XR_POINT / 1000.;
254
255   int i;
256
257   for (i = 0; i < XR_N_FONTS; i++)
258     {
259       struct xr_font *font = &xr->fonts[i];
260
261       if (font->desc != NULL)
262         pango_font_description_free (font->desc);
263     }
264
265   font_size = parse_int (opt (d, o, "font-size", "10000"), 1000, 1000000);
266   xr->fonts[XR_FONT_FIXED].desc = parse_font_option
267     (d, o, "fixed-font", "monospace", font_size, false, false);
268   xr->fonts[XR_FONT_PROPORTIONAL].desc = parse_font_option (
269     d, o, "prop-font", "sans serif", font_size, false, false);
270
271   xr->fg = parse_color (opt (d, o, "foreground-color", "#000000000000"));
272   xr->bg = parse_color (opt (d, o, "background-color", "#FFFFFFFFFFFF"));
273
274   xr->transparent = parse_boolean (opt (d, o, "transparent", "false"));
275   xr->systemcolors = parse_boolean (opt (d, o, "systemcolors", "false"));
276
277   /* Get dimensions.  */
278   parse_paper_size (opt (d, o, "paper-size", ""), &paper_width, &paper_length);
279   left_margin = parse_dimension (opt (d, o, "left-margin", ".5in"));
280   right_margin = parse_dimension (opt (d, o, "right-margin", ".5in"));
281   top_margin = parse_dimension (opt (d, o, "top-margin", ".5in"));
282   bottom_margin = parse_dimension (opt (d, o, "bottom-margin", ".5in"));
283
284   min_break[H] = parse_dimension (opt (d, o, "min-hbreak", NULL)) * scale;
285   min_break[V] = parse_dimension (opt (d, o, "min-vbreak", NULL)) * scale;
286
287   int object_spacing = (parse_dimension (opt (d, o, "object-spacing", NULL))
288                         * scale);
289
290   /* Convert to inch/(XR_POINT * 72). */
291   xr->left_margin = left_margin * scale;
292   xr->right_margin = right_margin * scale;
293   xr->top_margin = top_margin * scale;
294   xr->bottom_margin = bottom_margin * scale;
295   xr->width = (paper_width - left_margin - right_margin) * scale;
296   xr->length = (paper_length - top_margin - bottom_margin) * scale;
297   xr->min_break[H] = min_break[H] >= 0 ? min_break[H] : xr->width / 2;
298   xr->min_break[V] = min_break[V] >= 0 ? min_break[V] : xr->length / 2;
299   xr->object_spacing = object_spacing >= 0 ? object_spacing : XR_POINT * 12;
300
301   /* There are no headings so headings_height can stay 0. */
302 }
303
304 static struct xr_driver *
305 xr_allocate (const char *name, int device_type, struct string_map *o,
306              double font_scale)
307 {
308   struct xr_driver *xr = xzalloc (sizeof *xr);
309   struct output_driver *d = &xr->driver;
310
311   output_driver_init (d, &cairo_driver_class, name, device_type);
312
313   /* This is a nasty kluge for an issue that does not make sense.  On any
314      surface other than a screen (e.g. for output to PDF or PS or SVG), the
315      fonts are way too big by default.  A "9-point" font seems to appear about
316      16 points tall.  We use a scale factor for these surfaces to help, but the
317      underlying issue is a mystery. */
318   xr->font_scale = font_scale;
319
320   apply_options (xr, o);
321
322   return xr;
323 }
324
325 static int
326 pango_to_xr (int pango)
327 {
328   return (XR_POINT != PANGO_SCALE
329           ? ceil (pango * (1. * XR_POINT / PANGO_SCALE))
330           : pango);
331 }
332
333 static int
334 xr_to_pango (int xr)
335 {
336   return (XR_POINT != PANGO_SCALE
337           ? ceil (xr * (1. / XR_POINT * PANGO_SCALE))
338           : xr);
339 }
340
341 static void
342 xr_measure_fonts (cairo_t *cairo, const struct xr_font fonts[XR_N_FONTS],
343                   int *char_width, int *char_height)
344 {
345   *char_width = 0;
346   *char_height = 0;
347   for (int i = 0; i < XR_N_FONTS; i++)
348     {
349       PangoLayout *layout = pango_cairo_create_layout (cairo);
350       pango_layout_set_font_description (layout, fonts[i].desc);
351
352       pango_layout_set_text (layout, "0", 1);
353
354       int cw, ch;
355       pango_layout_get_size (layout, &cw, &ch);
356       *char_width = MAX (*char_width, pango_to_xr (cw));
357       *char_height = MAX (*char_height, pango_to_xr (ch));
358
359       g_object_unref (G_OBJECT (layout));
360     }
361 }
362
363 static int
364 get_layout_height (PangoLayout *layout)
365 {
366   int w, h;
367   pango_layout_get_size (layout, &w, &h);
368   return h;
369 }
370
371 static int
372 xr_render_page_heading (cairo_t *cairo, const PangoFontDescription *font,
373                         const struct page_heading *ph, int page_number,
374                         int width, bool draw, int base_y)
375 {
376   PangoLayout *layout = pango_cairo_create_layout (cairo);
377   pango_layout_set_font_description (layout, font);
378
379   int y = 0;
380   for (size_t i = 0; i < ph->n; i++)
381     {
382       const struct page_paragraph *pp = &ph->paragraphs[i];
383
384       char *markup = output_driver_substitute_heading_vars (pp->markup,
385                                                             page_number);
386       pango_layout_set_markup (layout, markup, -1);
387       free (markup);
388
389       pango_layout_set_alignment (
390         layout,
391         (pp->halign == TABLE_HALIGN_LEFT ? PANGO_ALIGN_LEFT
392          : pp->halign == TABLE_HALIGN_CENTER ? PANGO_ALIGN_CENTER
393          : pp->halign == TABLE_HALIGN_MIXED ? PANGO_ALIGN_LEFT
394          : PANGO_ALIGN_RIGHT));
395       pango_layout_set_width (layout, xr_to_pango (width));
396       if (draw)
397         {
398           cairo_save (cairo);
399           cairo_translate (cairo, 0, xr_to_pt (y + base_y));
400           pango_cairo_show_layout (cairo, layout);
401           cairo_restore (cairo);
402         }
403
404       y += pango_to_xr (get_layout_height (layout));
405     }
406
407   g_object_unref (G_OBJECT (layout));
408
409   return y;
410 }
411
412 static int
413 xr_measure_headings (cairo_surface_t *surface,
414                      const PangoFontDescription *font,
415                      const struct page_heading headings[2],
416                      int width, int object_spacing, int height[2])
417 {
418   cairo_t *cairo = cairo_create (surface);
419   int total = 0;
420   for (int i = 0; i < 2; i++)
421     {
422       int h = xr_render_page_heading (cairo, font, &headings[i], -1,
423                                       width, false, 0);
424
425       /* If the top heading is nonempty, add some space below it. */
426       if (h && i == 0)
427         h += object_spacing;
428
429       if (height)
430         height[i] = h;
431       total += h;
432     }
433   cairo_destroy (cairo);
434   return total;
435 }
436
437 static bool
438 xr_check_fonts (cairo_surface_t *surface,
439                 const struct xr_font fonts[XR_N_FONTS],
440                 int usable_width, int usable_length)
441 {
442   cairo_t *cairo = cairo_create (surface);
443   int char_width, char_height;
444   xr_measure_fonts (cairo, fonts, &char_width, &char_height);
445   cairo_destroy (cairo);
446
447   bool ok = true;
448   enum { MIN_WIDTH = 3, MIN_LENGTH = 3 };
449   if (usable_width / char_width < MIN_WIDTH)
450     {
451       msg (ME, _("The defined page is not wide enough to hold at least %d "
452                  "characters in the default font.  In fact, there's only "
453                  "room for %d characters."),
454            MIN_WIDTH, usable_width / char_width);
455       ok = false;
456     }
457   if (usable_length / char_height < MIN_LENGTH)
458     {
459       msg (ME, _("The defined page is not long enough to hold at least %d "
460                  "lines in the default font.  In fact, there's only "
461                  "room for %d lines."),
462            MIN_LENGTH, usable_length / char_height);
463       ok = false;
464     }
465   return ok;
466 }
467
468 static void
469 xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
470 {
471   xr->cairo = cairo;
472
473   cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
474
475   xr_measure_fonts (xr->cairo, xr->fonts, &xr->char_width, &xr->char_height);
476
477   for (int i = 0; i < XR_N_FONTS; i++)
478     {
479       struct xr_font *font = &xr->fonts[i];
480       font->layout = pango_cairo_create_layout (cairo);
481       pango_layout_set_font_description (font->layout, font->desc);
482     }
483
484   if (xr->params == NULL)
485     {
486       static const struct render_ops xr_render_ops = {
487         .draw_line = xr_draw_line,
488         .measure_cell_width = xr_measure_cell_width,
489         .measure_cell_height = xr_measure_cell_height,
490         .adjust_break = xr_adjust_break,
491         .draw_cell = xr_draw_cell,
492       };
493
494       xr->params = xmalloc (sizeof *xr->params);
495       xr->params->ops = &xr_render_ops;
496       xr->params->aux = xr;
497       xr->params->size[H] = xr->width;
498       xr->params->size[V] = xr->length;
499       xr->params->font_size[H] = xr->char_width;
500       xr->params->font_size[V] = xr->char_height;
501
502       enum { LW = XR_LINE_WIDTH, LS = XR_LINE_SPACE };
503       static const int xr_line_widths[RENDER_N_LINES] =
504         {
505           [RENDER_LINE_NONE] = 0,
506           [RENDER_LINE_SINGLE] = LW,
507           [RENDER_LINE_DASHED] = LW,
508           [RENDER_LINE_THICK] = LW * 2,
509           [RENDER_LINE_THIN] = LW / 2,
510           [RENDER_LINE_DOUBLE] = 2 * LW + LS,
511         };
512       xr->params->line_widths = xr_line_widths;
513
514       for (int i = 0; i < TABLE_N_AXES; i++)
515         xr->params->min_break[i] = xr->min_break[i];
516       xr->params->supports_margins = true;
517       xr->params->rtl = render_direction_rtl ();
518     }
519
520   if (xr->style == NULL)
521     {
522       xr->style = xmalloc (sizeof *xr->style);
523       *xr->style = (struct xr_fsm_style) {
524         .ref_cnt = 1,
525         .size = { [H] = xr->width, [V] = xr->length },
526         .min_break = { [H] = xr->min_break[H], [V] = xr->min_break[V] },
527         .use_system_colors = xr->systemcolors,
528         .transparent = xr->transparent,
529         .font_scale = xr->font_scale,
530       };
531
532       for (size_t i = 0; i < XR_N_FONTS; i++)
533         xr->style->fonts[i] = pango_font_description_copy (xr->fonts[i].desc);
534       printf ("%s\n%s\n",
535               pango_font_description_to_string (xr->fonts[0].desc),
536               pango_font_description_to_string (xr->fonts[1].desc));
537     }
538
539   if (!xr->systemcolors)
540     cairo_set_source_rgb (xr->cairo,
541                           xr->fg.r / 255.0, xr->fg.g / 255.0, xr->fg.b / 255.0);
542 }
543
544 static struct output_driver *
545 xr_create (struct file_handle *fh, enum settings_output_devices device_type,
546            struct string_map *o, enum xr_output_type file_type)
547 {
548   const char *file_name = fh_get_file_name (fh);
549   struct xr_driver *xr = xr_allocate (file_name, device_type, o, 72.0 / 128.0);
550   double width_pt = xr_to_pt (xr->width + xr->left_margin + xr->right_margin);
551   double length_pt = xr_to_pt (xr->length + xr->top_margin + xr->bottom_margin);
552   if (file_type == XR_PDF)
553     xr->surface = cairo_pdf_surface_create (file_name, width_pt, length_pt);
554   else if (file_type == XR_PS)
555     xr->surface = cairo_ps_surface_create (file_name, width_pt, length_pt);
556   else if (file_type == XR_SVG)
557     xr->surface = cairo_svg_surface_create (file_name, width_pt, length_pt);
558   else
559     NOT_REACHED ();
560
561   cairo_status_t status = cairo_surface_status (xr->surface);
562   if (status != CAIRO_STATUS_SUCCESS)
563     {
564       msg (ME, _("error opening output file `%s': %s"),
565            file_name, cairo_status_to_string (status));
566       goto error;
567     }
568
569   if (!xr_check_fonts (xr->surface, xr->fonts, xr->width, xr->length))
570     goto error;
571
572   fh_unref (fh);
573   return &xr->driver;
574
575  error:
576   fh_unref (fh);
577   output_driver_destroy (&xr->driver);
578   return NULL;
579 }
580
581 static struct output_driver *
582 xr_pdf_create (struct  file_handle *fh, enum settings_output_devices device_type,
583                struct string_map *o)
584 {
585   return xr_create (fh, device_type, o, XR_PDF);
586 }
587
588 static struct output_driver *
589 xr_ps_create (struct  file_handle *fh, enum settings_output_devices device_type,
590                struct string_map *o)
591 {
592   return xr_create (fh, device_type, o, XR_PS);
593 }
594
595 static struct output_driver *
596 xr_svg_create (struct file_handle *fh, enum settings_output_devices device_type,
597                struct string_map *o)
598 {
599   return xr_create (fh, device_type, o, XR_SVG);
600 }
601
602 static void
603 xr_destroy (struct output_driver *driver)
604 {
605   struct xr_driver *xr = xr_driver_cast (driver);
606   size_t i;
607
608   xr_driver_destroy_fsm (xr);
609
610   if (xr->cairo != NULL)
611     {
612       cairo_surface_finish (xr->surface);
613       cairo_status_t status = cairo_status (xr->cairo);
614       if (status != CAIRO_STATUS_SUCCESS)
615         fprintf (stderr,  _("error drawing output for %s driver: %s"),
616                  output_driver_get_name (driver),
617                  cairo_status_to_string (status));
618       cairo_surface_destroy (xr->surface);
619
620       cairo_destroy (xr->cairo);
621     }
622
623   for (i = 0; i < XR_N_FONTS; i++)
624     {
625       struct xr_font *font = &xr->fonts[i];
626
627       if (font->desc != NULL)
628         pango_font_description_free (font->desc);
629       if (font->layout != NULL)
630         g_object_unref (font->layout);
631     }
632
633   xr_fsm_style_unref (xr->style);
634   free (xr->params);
635   free (xr);
636 }
637
638 static void
639 xr_flush (struct output_driver *driver)
640 {
641   struct xr_driver *xr = xr_driver_cast (driver);
642
643   cairo_surface_flush (cairo_get_target (xr->cairo));
644 }
645
646 static void
647 xr_update_page_setup (struct output_driver *driver,
648                       const struct page_setup *ps)
649 {
650   struct xr_driver *xr = xr_driver_cast (driver);
651
652   xr->initial_page_number = ps->initial_page_number;
653   xr->object_spacing = ps->object_spacing * 72 * XR_POINT;
654
655   if (xr->cairo)
656     return;
657
658   int usable[TABLE_N_AXES];
659   for (int i = 0; i < 2; i++)
660     usable[i] = (ps->paper[i]
661                  - (ps->margins[i][0] + ps->margins[i][1])) * 72 * XR_POINT;
662
663   int headings_height[2];
664   usable[V] -= xr_measure_headings (
665     xr->surface, xr->fonts[XR_FONT_PROPORTIONAL].desc, ps->headings,
666     usable[H], xr->object_spacing, headings_height);
667
668   enum table_axis h = ps->orientation == PAGE_LANDSCAPE;
669   enum table_axis v = !h;
670   if (!xr_check_fonts (xr->surface, xr->fonts, usable[h], usable[v]))
671     return;
672
673   for (int i = 0; i < 2; i++)
674     {
675       page_heading_uninit (&xr->headings[i]);
676       page_heading_copy (&xr->headings[i], &ps->headings[i]);
677       xr->headings_height[i] = headings_height[i];
678     }
679   xr->width = usable[h];
680   xr->length = usable[v];
681   xr->left_margin = ps->margins[h][0] * 72 * XR_POINT;
682   xr->right_margin = ps->margins[h][1] * 72 * XR_POINT;
683   xr->top_margin = ps->margins[v][0] * 72 * XR_POINT;
684   xr->bottom_margin = ps->margins[v][1] * 72 * XR_POINT;
685   cairo_pdf_surface_set_size (xr->surface,
686                               ps->paper[h] * 72.0, ps->paper[v] * 72.0);
687 }
688
689 static void
690 xr_submit (struct output_driver *driver, const struct output_item *output_item)
691 {
692   struct xr_driver *xr = xr_driver_cast (driver);
693
694   if (is_page_setup_item (output_item))
695     {
696       xr_update_page_setup (driver,
697                             to_page_setup_item (output_item)->page_setup);
698       return;
699     }
700
701   if (!xr->cairo)
702     {
703       xr->page_number = xr->initial_page_number - 1;
704       xr_set_cairo (xr, cairo_create (xr->surface));
705       cairo_save (xr->cairo);
706       xr_driver_next_page (xr, xr->cairo);
707     }
708
709   xr_driver_output_item (xr, output_item);
710   while (xr_driver_need_new_page (xr))
711     {
712       cairo_restore (xr->cairo);
713       cairo_show_page (xr->cairo);
714       cairo_save (xr->cairo);
715       xr_driver_next_page (xr, xr->cairo);
716     }
717 }
718 \f
719 /* Functions for rendering a series of output items to a series of Cairo
720    contexts, with pagination.
721
722    Used by PSPPIRE for printing, and by the basic Cairo output driver above as
723    its underlying implementation.
724
725    See the big comment in cairo.h for intended usage. */
726
727 /* Gives new page CAIRO to XR for output. */
728 void
729 xr_driver_next_page (struct xr_driver *xr, cairo_t *cairo)
730 {
731   if (!xr->transparent)
732     {
733       cairo_save (cairo);
734       cairo_set_source_rgb (cairo,
735                             xr->bg.r / 255.0, xr->bg.g / 255.0, xr->bg.b / 255.0);
736       cairo_rectangle (cairo, 0, 0, xr->width, xr->length);
737       cairo_fill (cairo);
738       cairo_restore (cairo);
739     }
740   cairo_translate (cairo,
741                    xr_to_pt (xr->left_margin),
742                    xr_to_pt (xr->top_margin + xr->headings_height[0]));
743
744   xr->page_number++;
745   xr->cairo = cairo;
746   xr->y = 0;
747
748   xr_render_page_heading (xr->cairo, xr->fonts[XR_FONT_PROPORTIONAL].desc,
749                           &xr->headings[0], xr->page_number, xr->width, true,
750                           -xr->headings_height[0]);
751   xr_render_page_heading (xr->cairo, xr->fonts[XR_FONT_PROPORTIONAL].desc,
752                           &xr->headings[1], xr->page_number, xr->width, true,
753                           xr->length);
754
755   xr_driver_run_fsm (xr);
756 }
757
758 /* Start rendering OUTPUT_ITEM to XR.  Only valid if XR is not in the middle of
759    rendering a previous output item, that is, only if xr_driver_need_new_page()
760    returns false. */
761 void
762 xr_driver_output_item (struct xr_driver *xr,
763                        const struct output_item *output_item)
764 {
765   assert (xr->fsm == NULL);
766   xr->fsm = xr_fsm_create (output_item, xr->style, xr->cairo);
767   xr_driver_run_fsm (xr);
768 }
769
770 /* Returns true if XR is in the middle of rendering an output item and needs a
771    new page to be appended using xr_driver_next_page() to make progress,
772    otherwise false. */
773 bool
774 xr_driver_need_new_page (const struct xr_driver *xr)
775 {
776   return xr->fsm != NULL;
777 }
778
779 /* Returns true if the current page doesn't have any content yet. */
780 bool
781 xr_driver_is_page_blank (const struct xr_driver *xr)
782 {
783   return xr->y == 0;
784 }
785
786 static void
787 xr_driver_destroy_fsm (struct xr_driver *xr)
788 {
789   xr_fsm_destroy (xr->fsm);
790   xr->fsm = NULL;
791 }
792
793 static void
794 xr_driver_run_fsm (struct xr_driver *xr)
795 {
796   if (xr->fsm != NULL)
797     {
798       cairo_save (xr->cairo);
799       cairo_translate (xr->cairo, 0, xr_to_pt (xr->y));
800       int used = xr_fsm_draw_slice (xr->fsm, xr->cairo, xr->length - xr->y);
801       printf ("draw slice at %d -> size %d\n", xr->y, used);
802       xr->y += used;
803       cairo_restore (xr->cairo);
804
805       if (xr_fsm_is_empty (xr->fsm))
806         xr_driver_destroy_fsm (xr);
807     }
808 }
809 \f
810 static void
811 xr_layout_cell (struct xr_driver *, const struct table_cell *,
812                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
813                 int *width, int *height, int *brk);
814
815 static void
816 set_source_rgba (cairo_t *cairo, const struct cell_color *color)
817 {
818   cairo_set_source_rgba (cairo,
819                          color->r / 255., color->g / 255., color->b / 255.,
820                          color->alpha / 255.);
821 }
822
823 static void
824 dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1, int style,
825            const struct cell_color *color)
826 {
827   cairo_new_path (xr->cairo);
828   if (!xr->systemcolors)
829     set_source_rgba (xr->cairo, color);
830   cairo_set_line_width (
831     xr->cairo,
832     xr_to_pt (style == RENDER_LINE_THICK ? XR_LINE_WIDTH * 2
833               : style == RENDER_LINE_THIN ? XR_LINE_WIDTH / 2
834               : XR_LINE_WIDTH));
835   cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0 + xr->y));
836   cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1 + xr->y));
837   cairo_stroke (xr->cairo);
838 }
839
840 static void UNUSED
841 dump_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
842 {
843   cairo_new_path (xr->cairo);
844   cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
845   cairo_close_path (xr->cairo);
846   cairo_stroke (xr->cairo);
847   cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0 + xr->y));
848   cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y0 + xr->y));
849   cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1 + xr->y));
850   cairo_line_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y1 + xr->y));
851 }
852
853 static void
854 fill_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
855 {
856   cairo_new_path (xr->cairo);
857   cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
858   cairo_rectangle (xr->cairo,
859                    xr_to_pt (x0), xr_to_pt (y0 + xr->y),
860                    xr_to_pt (x1 - x0), xr_to_pt (y1 - y0));
861   cairo_fill (xr->cairo);
862 }
863
864 /* Draws a horizontal line X0...X2 at Y if LEFT says so,
865    shortening it to X0...X1 if SHORTEN is true.
866    Draws a horizontal line X1...X3 at Y if RIGHT says so,
867    shortening it to X2...X3 if SHORTEN is true. */
868 static void
869 horz_line (struct xr_driver *xr, int x0, int x1, int x2, int x3, int y,
870            enum render_line_style left, enum render_line_style right,
871            const struct cell_color *left_color,
872            const struct cell_color *right_color,
873            bool shorten)
874 {
875   if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten
876       && cell_color_equal (left_color, right_color))
877     dump_line (xr, x0, y, x3, y, left, left_color);
878   else
879     {
880       if (left != RENDER_LINE_NONE)
881         dump_line (xr, x0, y, shorten ? x1 : x2, y, left, left_color);
882       if (right != RENDER_LINE_NONE)
883         dump_line (xr, shorten ? x2 : x1, y, x3, y, right, right_color);
884     }
885 }
886
887 /* Draws a vertical line Y0...Y2 at X if TOP says so,
888    shortening it to Y0...Y1 if SHORTEN is true.
889    Draws a vertical line Y1...Y3 at X if BOTTOM says so,
890    shortening it to Y2...Y3 if SHORTEN is true. */
891 static void
892 vert_line (struct xr_driver *xr, int y0, int y1, int y2, int y3, int x,
893            enum render_line_style top, enum render_line_style bottom,
894            const struct cell_color *top_color,
895            const struct cell_color *bottom_color,
896            bool shorten)
897 {
898   if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten
899       && cell_color_equal (top_color, bottom_color))
900     dump_line (xr, x, y0, x, y3, top, top_color);
901   else
902     {
903       if (top != RENDER_LINE_NONE)
904         dump_line (xr, x, y0, x, shorten ? y1 : y2, top, top_color);
905       if (bottom != RENDER_LINE_NONE)
906         dump_line (xr, x, shorten ? y2 : y1, x, y3, bottom, bottom_color);
907     }
908 }
909
910 static void
911 xr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
912               enum render_line_style styles[TABLE_N_AXES][2],
913               struct cell_color colors[TABLE_N_AXES][2])
914 {
915   const int x0 = bb[H][0];
916   const int y0 = bb[V][0];
917   const int x3 = bb[H][1];
918   const int y3 = bb[V][1];
919   const int top = styles[H][0];
920   const int bottom = styles[H][1];
921
922   int start_side = render_direction_rtl();
923   int end_side = !start_side;
924   const int start_of_line = styles[V][start_side];
925   const int end_of_line   = styles[V][end_side];
926   const struct cell_color *top_color = &colors[H][0];
927   const struct cell_color *bottom_color = &colors[H][1];
928   const struct cell_color *start_color = &colors[V][start_side];
929   const struct cell_color *end_color = &colors[V][end_side];
930
931   /* The algorithm here is somewhat subtle, to allow it to handle
932      all the kinds of intersections that we need.
933
934      Three additional ordinates are assigned along the x axis.  The
935      first is xc, midway between x0 and x3.  The others are x1 and
936      x2; for a single vertical line these are equal to xc, and for
937      a double vertical line they are the ordinates of the left and
938      right half of the double line.
939
940      yc, y1, and y2 are assigned similarly along the y axis.
941
942      The following diagram shows the coordinate system and output
943      for double top and bottom lines, single left line, and no
944      right line:
945
946                  x0       x1 xc  x2      x3
947                y0 ________________________
948                   |        #     #       |
949                   |        #     #       |
950                   |        #     #       |
951                   |        #     #       |
952                   |        #     #       |
953      y1 = y2 = yc |#########     #       |
954                   |        #     #       |
955                   |        #     #       |
956                   |        #     #       |
957                   |        #     #       |
958                y3 |________#_____#_______|
959   */
960   struct xr_driver *xr = xr_;
961
962   /* Offset from center of each line in a pair of double lines. */
963   int double_line_ofs = (XR_LINE_SPACE + XR_LINE_WIDTH) / 2;
964
965   /* Are the lines along each axis single or double?
966      (It doesn't make sense to have different kinds of line on the
967      same axis, so we don't try to gracefully handle that case.) */
968   bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
969   bool double_horz = start_of_line == RENDER_LINE_DOUBLE || end_of_line == RENDER_LINE_DOUBLE;
970
971   /* When horizontal lines are doubled,
972      the left-side line along y1 normally runs from x0 to x2,
973      and the right-side line along y1 from x3 to x1.
974      If the top-side line is also doubled, we shorten the y1 lines,
975      so that the left-side line runs only to x1,
976      and the right-side line only to x2.
977      Otherwise, the horizontal line at y = y1 below would cut off
978      the intersection, which looks ugly:
979                x0       x1     x2      x3
980              y0 ________________________
981                 |        #     #       |
982                 |        #     #       |
983                 |        #     #       |
984                 |        #     #       |
985              y1 |#########     ########|
986                 |                      |
987                 |                      |
988              y2 |######################|
989                 |                      |
990                 |                      |
991              y3 |______________________|
992      It is more of a judgment call when the horizontal line is
993      single.  We actually choose to cut off the line anyhow, as
994      shown in the first diagram above.
995   */
996   bool shorten_y1_lines = top == RENDER_LINE_DOUBLE;
997   bool shorten_y2_lines = bottom == RENDER_LINE_DOUBLE;
998   bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
999   int horz_line_ofs = double_vert ? double_line_ofs : 0;
1000   int xc = (x0 + x3) / 2;
1001   int x1 = xc - horz_line_ofs;
1002   int x2 = xc + horz_line_ofs;
1003
1004   bool shorten_x1_lines = start_of_line == RENDER_LINE_DOUBLE;
1005   bool shorten_x2_lines = end_of_line == RENDER_LINE_DOUBLE;
1006   bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
1007   int vert_line_ofs = double_horz ? double_line_ofs : 0;
1008   int yc = (y0 + y3) / 2;
1009   int y1 = yc - vert_line_ofs;
1010   int y2 = yc + vert_line_ofs;
1011
1012   if (!double_horz)
1013     horz_line (xr, x0, x1, x2, x3, yc, start_of_line, end_of_line,
1014                start_color, end_color, shorten_yc_line);
1015   else
1016     {
1017       horz_line (xr, x0, x1, x2, x3, y1, start_of_line, end_of_line,
1018                  start_color, end_color, shorten_y1_lines);
1019       horz_line (xr, x0, x1, x2, x3, y2, start_of_line, end_of_line,
1020                  start_color, end_color, shorten_y2_lines);
1021     }
1022
1023   if (!double_vert)
1024     vert_line (xr, y0, y1, y2, y3, xc, top, bottom, top_color, bottom_color,
1025                shorten_xc_line);
1026   else
1027     {
1028       vert_line (xr, y0, y1, y2, y3, x1, top, bottom, top_color, bottom_color,
1029                  shorten_x1_lines);
1030       vert_line (xr, y0, y1, y2, y3, x2, top, bottom, top_color, bottom_color,
1031                  shorten_x2_lines);
1032     }
1033 }
1034
1035 static void
1036 xr_measure_cell_width (void *xr_, const struct table_cell *cell,
1037                        int *min_width, int *max_width)
1038 {
1039   struct xr_driver *xr = xr_;
1040   int bb[TABLE_N_AXES][2];
1041   int clip[TABLE_N_AXES][2];
1042   int h;
1043
1044   bb[H][0] = 0;
1045   bb[H][1] = INT_MAX;
1046   bb[V][0] = 0;
1047   bb[V][1] = INT_MAX;
1048   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
1049   xr_layout_cell (xr, cell, bb, clip, max_width, &h, NULL);
1050
1051   bb[H][1] = 1;
1052   xr_layout_cell (xr, cell, bb, clip, min_width, &h, NULL);
1053
1054   if (*min_width > 0)
1055     *min_width += px_to_xr (cell->style->cell_style.margin[H][0]
1056                             + cell->style->cell_style.margin[H][1]);
1057   if (*max_width > 0)
1058     *max_width += px_to_xr (cell->style->cell_style.margin[H][0]
1059                             + cell->style->cell_style.margin[H][1]);
1060 }
1061
1062 static int
1063 xr_measure_cell_height (void *xr_, const struct table_cell *cell, int width)
1064 {
1065   struct xr_driver *xr = xr_;
1066   int bb[TABLE_N_AXES][2];
1067   int clip[TABLE_N_AXES][2];
1068   int w, h;
1069
1070   bb[H][0] = 0;
1071   bb[H][1] = width - px_to_xr (cell->style->cell_style.margin[H][0]
1072                                + cell->style->cell_style.margin[H][1]);
1073   bb[V][0] = 0;
1074   bb[V][1] = INT_MAX;
1075   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
1076   xr_layout_cell (xr, cell, bb, clip, &w, &h, NULL);
1077   h += px_to_xr (cell->style->cell_style.margin[V][0]
1078                  + cell->style->cell_style.margin[V][1]);
1079   return h;
1080 }
1081
1082 static void xr_clip (struct xr_driver *, int clip[TABLE_N_AXES][2]);
1083
1084 static void
1085 xr_draw_cell (void *xr_, const struct table_cell *cell, int color_idx,
1086               int bb[TABLE_N_AXES][2], int valign_offset,
1087               int spill[TABLE_N_AXES][2],
1088               int clip[TABLE_N_AXES][2])
1089 {
1090   struct xr_driver *xr = xr_;
1091   int w, h, brk;
1092
1093   if (!xr->transparent)
1094     {
1095       cairo_save (xr->cairo);
1096       int bg_clip[TABLE_N_AXES][2];
1097       for (int axis = 0; axis < TABLE_N_AXES; axis++)
1098         {
1099           bg_clip[axis][0] = clip[axis][0];
1100           if (bb[axis][0] == clip[axis][0])
1101             bg_clip[axis][0] -= spill[axis][0];
1102
1103           bg_clip[axis][1] = clip[axis][1];
1104           if (bb[axis][1] == clip[axis][1])
1105             bg_clip[axis][1] += spill[axis][1];
1106         }
1107       xr_clip (xr, bg_clip);
1108       set_source_rgba (xr->cairo, &cell->style->font_style.bg[color_idx]);
1109       fill_rectangle (xr,
1110                       bb[H][0] - spill[H][0],
1111                       bb[V][0] - spill[V][0],
1112                       bb[H][1] + spill[H][1],
1113                       bb[V][1] + spill[V][1]);
1114       cairo_restore (xr->cairo);
1115     }
1116   cairo_save (xr->cairo);
1117   if (!xr->systemcolors)
1118     set_source_rgba (xr->cairo, &cell->style->font_style.fg[color_idx]);
1119
1120   bb[V][0] += valign_offset;
1121
1122   for (int axis = 0; axis < TABLE_N_AXES; axis++)
1123     {
1124       bb[axis][0] += px_to_xr (cell->style->cell_style.margin[axis][0]);
1125       bb[axis][1] -= px_to_xr (cell->style->cell_style.margin[axis][1]);
1126     }
1127   if (bb[H][0] < bb[H][1] && bb[V][0] < bb[V][1])
1128     xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
1129   cairo_restore (xr->cairo);
1130 }
1131
1132 static int
1133 xr_adjust_break (void *xr_, const struct table_cell *cell,
1134                  int width, int height)
1135 {
1136   struct xr_driver *xr = xr_;
1137   int bb[TABLE_N_AXES][2];
1138   int clip[TABLE_N_AXES][2];
1139   int w, h, brk;
1140
1141   if (xr_measure_cell_height (xr_, cell, width) < height)
1142     return -1;
1143
1144   bb[H][0] = 0;
1145   bb[H][1] = width - px_to_xr (cell->style->cell_style.margin[H][0]
1146                                + cell->style->cell_style.margin[H][1]);
1147   if (bb[H][1] <= 0)
1148     return 0;
1149   bb[V][0] = 0;
1150   bb[V][1] = height - px_to_xr (cell->style->cell_style.margin[V][0]
1151                                 + cell->style->cell_style.margin[V][1]);
1152   clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
1153   xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
1154   return brk;
1155 }
1156 \f
1157 static void
1158 xr_clip (struct xr_driver *xr, int clip[TABLE_N_AXES][2])
1159 {
1160   if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX)
1161     {
1162       double x0 = xr_to_pt (clip[H][0]);
1163       double y0 = xr_to_pt (clip[V][0] + xr->y);
1164       double x1 = xr_to_pt (clip[H][1]);
1165       double y1 = xr_to_pt (clip[V][1] + xr->y);
1166
1167       cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0);
1168       cairo_clip (xr->cairo);
1169     }
1170 }
1171
1172 static void
1173 add_attr (PangoAttrList *list, PangoAttribute *attr,
1174           guint start_index, guint end_index)
1175 {
1176   attr->start_index = start_index;
1177   attr->end_index = end_index;
1178   pango_attr_list_insert (list, attr);
1179 }
1180
1181 static void
1182 markup_escape (struct string *out, unsigned int options,
1183                const char *in, size_t len)
1184 {
1185   if (!(options & TAB_MARKUP))
1186     {
1187       ds_put_substring (out, ss_buffer (in, len == -1 ? strlen (in) : len));
1188       return;
1189     }
1190
1191   while (len-- > 0)
1192     {
1193       int c = *in++;
1194       switch (c)
1195         {
1196         case 0:
1197           return;
1198         case '&':
1199           ds_put_cstr (out, "&amp;");
1200           break;
1201         case '<':
1202           ds_put_cstr (out, "&lt;");
1203           break;
1204         case '>':
1205           ds_put_cstr (out, "&gt;");
1206           break;
1207         default:
1208           ds_put_byte (out, c);
1209           break;
1210         }
1211     }
1212 }
1213
1214 static int
1215 get_layout_dimension (PangoLayout *layout, enum table_axis axis)
1216 {
1217   int size[TABLE_N_AXES];
1218   pango_layout_get_size (layout, &size[H], &size[V]);
1219   return size[axis];
1220 }
1221
1222 static int
1223 xr_layout_cell_text (struct xr_driver *xr, const struct table_cell *cell,
1224                      int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
1225                      int *widthp, int *brk)
1226 {
1227   const struct font_style *font_style = &cell->style->font_style;
1228   const struct cell_style *cell_style = &cell->style->cell_style;
1229   unsigned int options = cell->options;
1230
1231   enum table_axis X = options & TAB_ROTATE ? V : H;
1232   enum table_axis Y = !X;
1233   int R = options & TAB_ROTATE ? 0 : 1;
1234
1235   struct xr_font *font = (options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED]
1236                           : &xr->fonts[XR_FONT_PROPORTIONAL]);
1237   struct xr_font local_font;
1238   if (font_style->typeface)
1239     {
1240       PangoFontDescription *desc = parse_font (
1241         font_style->typeface,
1242         font_style->size ? font_style->size * 1000 * xr->font_scale : 10000,
1243         font_style->bold, font_style->italic);
1244       if (desc)
1245         {
1246           PangoLayout *layout = pango_cairo_create_layout (xr->cairo);
1247           pango_layout_set_font_description (layout, desc);
1248
1249           local_font.desc = desc;
1250           local_font.layout = layout;
1251           font = &local_font;
1252         }
1253     }
1254
1255   const char *text = cell->text;
1256   enum table_halign halign = table_halign_interpret (
1257     cell_style->halign, cell->options & TAB_NUMERIC);
1258   if (cell_style->halign == TABLE_HALIGN_DECIMAL && !(options & TAB_ROTATE))
1259     {
1260       int margin_adjustment = -px_to_xr (cell_style->decimal_offset);
1261
1262       const char *decimal = strrchr (text, cell_style->decimal_char);
1263       if (decimal)
1264         {
1265           pango_layout_set_text (font->layout, decimal, strlen (decimal));
1266           pango_layout_set_width (font->layout, -1);
1267           margin_adjustment += get_layout_dimension (font->layout, H);
1268         }
1269
1270       if (margin_adjustment < 0)
1271         bb[H][1] += margin_adjustment;
1272     }
1273
1274   struct string tmp = DS_EMPTY_INITIALIZER;
1275   PangoAttrList *attrs = NULL;
1276
1277   /* Deal with an oddity of the Unicode line-breaking algorithm (or perhaps in
1278      Pango's implementation of it): it will break after a period or a comma
1279      that precedes a digit, e.g. in ".000" it will break after the period.
1280      This code looks for such a situation and inserts a U+2060 WORD JOINER
1281      to prevent the break.
1282
1283      This isn't necessary when the decimal point is between two digits
1284      (e.g. "0.000" won't be broken) or when the display width is not limited so
1285      that word wrapping won't happen.
1286
1287      It isn't necessary to look for more than one period or comma, as would
1288      happen with grouping like 1,234,567.89 or 1.234.567,89 because if groups
1289      are present then there will always be a digit on both sides of every
1290      period and comma. */
1291   if (options & TAB_MARKUP)
1292     {
1293       PangoAttrList *new_attrs;
1294       char *new_text;
1295       if (pango_parse_markup (text, -1, 0, &new_attrs, &new_text, NULL, NULL))
1296         {
1297           attrs = new_attrs;
1298           tmp.ss = ss_cstr (new_text);
1299           tmp.capacity = tmp.ss.length;
1300         }
1301       else
1302         {
1303           /* XXX should we report the error? */
1304           ds_put_cstr (&tmp, text);
1305         }
1306     }
1307   else if (options & TAB_ROTATE || bb[H][1] != INT_MAX)
1308     {
1309       const char *decimal = text + strcspn (text, ".,");
1310       if (decimal[0]
1311           && c_isdigit (decimal[1])
1312           && (decimal == text || !c_isdigit (decimal[-1])))
1313         {
1314           ds_extend (&tmp, strlen (text) + 16);
1315           markup_escape (&tmp, options, text, decimal - text + 1);
1316           ds_put_unichar (&tmp, 0x2060 /* U+2060 WORD JOINER */);
1317           markup_escape (&tmp, options, decimal + 1, -1);
1318         }
1319     }
1320
1321   if (font_style->underline)
1322     {
1323       if (!attrs)
1324         attrs = pango_attr_list_new ();
1325       pango_attr_list_insert (attrs, pango_attr_underline_new (
1326                                 PANGO_UNDERLINE_SINGLE));
1327     }
1328
1329   if (cell->n_footnotes || cell->n_subscripts || cell->superscript)
1330     {
1331       /* If we haven't already put TEXT into tmp, do it now. */
1332       if (ds_is_empty (&tmp))
1333         {
1334           ds_extend (&tmp, strlen (text) + 16);
1335           markup_escape (&tmp, options, text, -1);
1336         }
1337
1338       size_t subscript_ofs = ds_length (&tmp);
1339       for (size_t i = 0; i < cell->n_subscripts; i++)
1340         {
1341           if (i)
1342             ds_put_byte (&tmp, ',');
1343           ds_put_cstr (&tmp, cell->subscripts[i]);
1344         }
1345
1346       size_t superscript_ofs = ds_length (&tmp);
1347       if (cell->superscript)
1348         ds_put_cstr (&tmp, cell->superscript);
1349
1350       size_t footnote_ofs = ds_length (&tmp);
1351       for (size_t i = 0; i < cell->n_footnotes; i++)
1352         {
1353           if (i)
1354             ds_put_byte (&tmp, ',');
1355           ds_put_cstr (&tmp, cell->footnotes[i]->marker);
1356         }
1357
1358       /* Allow footnote markers to occupy the right margin.  That way, numbers
1359          in the column are still aligned. */
1360       if (cell->n_footnotes && halign == TABLE_HALIGN_RIGHT)
1361         {
1362           /* Measure the width of the footnote marker, so we know how much we
1363              need to make room for. */
1364           pango_layout_set_text (font->layout, ds_cstr (&tmp) + footnote_ofs,
1365                                  ds_length (&tmp) - footnote_ofs);
1366
1367           PangoAttrList *fn_attrs = pango_attr_list_new ();
1368           pango_attr_list_insert (
1369             fn_attrs, pango_attr_scale_new (PANGO_SCALE_SMALL));
1370           pango_attr_list_insert (fn_attrs, pango_attr_rise_new (3000));
1371           pango_layout_set_attributes (font->layout, fn_attrs);
1372           pango_attr_list_unref (fn_attrs);
1373           int footnote_width = get_layout_dimension (font->layout, X);
1374
1375           /* Bound the adjustment by the width of the right margin. */
1376           int right_margin = px_to_xr (cell_style->margin[X][R]);
1377           int footnote_adjustment = MIN (footnote_width, right_margin);
1378
1379           /* Adjust the bounding box. */
1380           if (options & TAB_ROTATE)
1381             footnote_adjustment = -footnote_adjustment;
1382           bb[X][R] += footnote_adjustment;
1383
1384           /* Clean up. */
1385           pango_layout_set_attributes (font->layout, NULL);
1386         }
1387
1388       /* Set attributes. */
1389       if (!attrs)
1390         attrs = pango_attr_list_new ();
1391       add_attr (attrs, pango_attr_font_desc_new (font->desc), subscript_ofs,
1392                 PANGO_ATTR_INDEX_TO_TEXT_END);
1393       add_attr (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL),
1394                 subscript_ofs, PANGO_ATTR_INDEX_TO_TEXT_END);
1395       if (cell->n_subscripts)
1396         add_attr (attrs, pango_attr_rise_new (-3000), subscript_ofs,
1397                   superscript_ofs - subscript_ofs);
1398       if (cell->superscript || cell->n_footnotes)
1399         add_attr (attrs, pango_attr_rise_new (3000), superscript_ofs,
1400                   PANGO_ATTR_INDEX_TO_TEXT_END);
1401     }
1402
1403   /* Set the attributes, if any. */
1404   if (attrs)
1405     {
1406       pango_layout_set_attributes (font->layout, attrs);
1407       pango_attr_list_unref (attrs);
1408     }
1409
1410   /* Set the text. */
1411   if (ds_is_empty (&tmp))
1412     pango_layout_set_text (font->layout, text, -1);
1413   else
1414     pango_layout_set_text (font->layout, ds_cstr (&tmp), ds_length (&tmp));
1415   ds_destroy (&tmp);
1416
1417   pango_layout_set_alignment (font->layout,
1418                               (halign == TABLE_HALIGN_RIGHT ? PANGO_ALIGN_RIGHT
1419                                : halign == TABLE_HALIGN_LEFT ? PANGO_ALIGN_LEFT
1420                                : PANGO_ALIGN_CENTER));
1421   pango_layout_set_width (
1422     font->layout,
1423     bb[X][1] == INT_MAX ? -1 : xr_to_pango (bb[X][1] - bb[X][0]));
1424   pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD);
1425
1426   if (clip[H][0] != clip[H][1])
1427     {
1428       cairo_save (xr->cairo);
1429       if (!(options & TAB_ROTATE))
1430         xr_clip (xr, clip);
1431       if (options & TAB_ROTATE)
1432         {
1433           cairo_translate (xr->cairo,
1434                            xr_to_pt (bb[H][0]),
1435                            xr_to_pt (bb[V][1] + xr->y));
1436           cairo_rotate (xr->cairo, -M_PI_2);
1437         }
1438       else
1439         cairo_translate (xr->cairo,
1440                          xr_to_pt (bb[H][0]),
1441                          xr_to_pt (bb[V][0] + xr->y));
1442       pango_cairo_show_layout (xr->cairo, font->layout);
1443
1444       /* If enabled, this draws a blue rectangle around the extents of each
1445          line of text, which can be rather useful for debugging layout
1446          issues. */
1447       if (0)
1448         {
1449           PangoLayoutIter *iter;
1450           iter = pango_layout_get_iter (font->layout);
1451           do
1452             {
1453               PangoRectangle extents;
1454
1455               pango_layout_iter_get_line_extents (iter, &extents, NULL);
1456               cairo_save (xr->cairo);
1457               cairo_set_source_rgb (xr->cairo, 1, 0, 0);
1458               dump_rectangle (xr,
1459                               pango_to_xr (extents.x),
1460                               pango_to_xr (extents.y) - xr->y,
1461                               pango_to_xr (extents.x + extents.width),
1462                               pango_to_xr (extents.y + extents.height) - xr->y);
1463               cairo_restore (xr->cairo);
1464             }
1465           while (pango_layout_iter_next_line (iter));
1466           pango_layout_iter_free (iter);
1467         }
1468
1469       cairo_restore (xr->cairo);
1470     }
1471
1472   int size[TABLE_N_AXES];
1473   pango_layout_get_size (font->layout, &size[H], &size[V]);
1474   int w = pango_to_xr (size[X]);
1475   int h = pango_to_xr (size[Y]);
1476   if (w > *widthp)
1477     *widthp = w;
1478   if (bb[V][0] + h >= bb[V][1] && !(options & TAB_ROTATE))
1479     {
1480       PangoLayoutIter *iter;
1481       int best = 0;
1482
1483       /* Choose a breakpoint between lines instead of in the middle of one. */
1484       iter = pango_layout_get_iter (font->layout);
1485       do
1486         {
1487           PangoRectangle extents;
1488           int y0, y1;
1489           int bottom;
1490
1491           pango_layout_iter_get_line_extents (iter, NULL, &extents);
1492           pango_layout_iter_get_line_yrange (iter, &y0, &y1);
1493           extents.x = pango_to_xr (extents.x);
1494           extents.y = pango_to_xr (y0);
1495           extents.width = pango_to_xr (extents.width);
1496           extents.height = pango_to_xr (y1 - y0);
1497           bottom = bb[V][0] + extents.y + extents.height;
1498           if (bottom < bb[V][1])
1499             {
1500               if (brk && clip[H][0] != clip[H][1])
1501                 best = bottom;
1502               if (brk)
1503                 *brk = bottom;
1504             }
1505           else
1506             break;
1507         }
1508       while (pango_layout_iter_next_line (iter));
1509       pango_layout_iter_free (iter);
1510
1511       /* If enabled, draws a green line across the chosen breakpoint, which can
1512          be useful for debugging issues with breaking.  */
1513       if (0)
1514         {
1515           if (best)
1516             dump_line (xr, -xr->left_margin, best,
1517                        xr->width + xr->right_margin, best,
1518                        RENDER_LINE_SINGLE,
1519                        &(struct cell_color) CELL_COLOR (0, 255, 0));
1520         }
1521     }
1522
1523   pango_layout_set_attributes (font->layout, NULL);
1524
1525   if (font == &local_font)
1526     {
1527       g_object_unref (G_OBJECT (font->layout));
1528       pango_font_description_free (font->desc);
1529     }
1530
1531   return h;
1532 }
1533
1534 static void
1535 xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell,
1536                 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
1537                 int *width, int *height, int *brk)
1538 {
1539   *width = 0;
1540   *height = 0;
1541
1542   /* If enabled, draws a blue rectangle around the cell extents, which can be
1543      useful for debugging layout. */
1544   if (0)
1545     {
1546       if (clip[H][0] != clip[H][1])
1547         {
1548           cairo_save (xr->cairo);
1549           cairo_set_source_rgb (xr->cairo, 0, 0, 1);
1550           dump_rectangle (xr, bb[H][0], bb[V][0], bb[H][1], bb[V][1]);
1551           cairo_restore (xr->cairo);
1552         }
1553     }
1554
1555   if (brk)
1556     *brk = bb[V][0];
1557   *height = xr_layout_cell_text (xr, cell, bb, clip, width, brk);
1558 }
1559 \f
1560 struct output_driver_factory pdf_driver_factory =
1561   { "pdf", "pspp.pdf", xr_pdf_create };
1562 struct output_driver_factory ps_driver_factory =
1563   { "ps", "pspp.ps", xr_ps_create };
1564 struct output_driver_factory svg_driver_factory =
1565   { "svg", "pspp.svg", xr_svg_create };
1566
1567 static const struct output_driver_class cairo_driver_class =
1568 {
1569   "cairo",
1570   xr_destroy,
1571   xr_submit,
1572   xr_flush,
1573 };
1574 \f
1575 struct xr_driver *
1576 xr_driver_create (cairo_t *cairo, struct string_map *options)
1577 {
1578   struct xr_driver *xr = xr_allocate ("cairo", 0, options, 1.0);
1579   xr_set_cairo (xr, cairo);
1580   return xr;
1581 }
1582
1583 /* Destroy XR, which should have been created with xr_driver_create().  Any
1584    cairo_t added to XR is not destroyed, because it is owned by the client. */
1585 void
1586 xr_driver_destroy (struct xr_driver *xr)
1587 {
1588   if (xr != NULL)
1589     {
1590       xr->cairo = NULL;
1591       output_driver_destroy (&xr->driver);
1592     }
1593 }
1594 static void
1595 xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
1596                double x, double y, double width, double height)
1597 {
1598   struct xrchart_geometry geom;
1599
1600   cairo_save (cr);
1601   cairo_translate (cr, x, y + height);
1602   cairo_scale (cr, 1.0, -1.0);
1603   xrchart_geometry_init (cr, &geom, width, height);
1604   if (is_boxplot (chart_item))
1605     xrchart_draw_boxplot (chart_item, cr, &geom);
1606   else if (is_histogram_chart (chart_item))
1607     xrchart_draw_histogram (chart_item, cr, &geom);
1608   else if (is_np_plot_chart (chart_item))
1609     xrchart_draw_np_plot (chart_item, cr, &geom);
1610   else if (is_piechart (chart_item))
1611     xrchart_draw_piechart (chart_item, cr, &geom);
1612   else if (is_barchart (chart_item))
1613     xrchart_draw_barchart (chart_item, cr, &geom);
1614   else if (is_roc_chart (chart_item))
1615     xrchart_draw_roc (chart_item, cr, &geom);
1616   else if (is_scree (chart_item))
1617     xrchart_draw_scree (chart_item, cr, &geom);
1618   else if (is_spreadlevel_plot_chart (chart_item))
1619     xrchart_draw_spreadlevel (chart_item, cr, &geom);
1620   else if (is_scatterplot_chart (chart_item))
1621     xrchart_draw_scatterplot (chart_item, cr, &geom);
1622   else
1623     NOT_REACHED ();
1624   xrchart_geometry_free (cr, &geom);
1625
1626   cairo_restore (cr);
1627 }
1628
1629 char *
1630 xr_draw_png_chart (const struct chart_item *item,
1631                    const char *file_name_template, int number,
1632                    const struct cell_color *fg,
1633                    const struct cell_color *bg)
1634 {
1635   const int width = 640;
1636   const int length = 480;
1637
1638   cairo_surface_t *surface;
1639   cairo_status_t status;
1640   const char *number_pos;
1641   char *file_name;
1642   cairo_t *cr;
1643
1644   number_pos = strchr (file_name_template, '#');
1645   if (number_pos != NULL)
1646     file_name = xasprintf ("%.*s%d%s.png", (int) (number_pos - file_name_template),
1647                            file_name_template, number, number_pos + 1);
1648   else
1649     file_name = xasprintf ("%s.png", file_name_template);
1650
1651   surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, length);
1652   cr = cairo_create (surface);
1653
1654   cairo_set_source_rgb (cr, bg->r / 255.0, bg->g / 255.0, bg->b / 255.0);
1655   cairo_paint (cr);
1656
1657   cairo_set_source_rgb (cr, fg->r / 255.0, fg->g / 255.0, fg->b / 255.0);
1658
1659   xr_draw_chart (item, cr, 0.0, 0.0, width, length);
1660
1661   status = cairo_surface_write_to_png (surface, file_name);
1662   if (status != CAIRO_STATUS_SUCCESS)
1663     msg (ME, _("error writing output file `%s': %s"),
1664            file_name, cairo_status_to_string (status));
1665
1666   cairo_destroy (cr);
1667   cairo_surface_destroy (surface);
1668
1669   return file_name;
1670 }
1671
1672
1673 char *
1674 xr_draw_eps_chart (const struct chart_item *item,
1675                    const char *file_name_template, int number,
1676                    const struct cell_color *fg,
1677                    const struct cell_color *bg)
1678 {
1679   const int width = 640;
1680   const int length = 480;
1681
1682   cairo_surface_t *surface;
1683   const char *number_pos;
1684   char *file_name;
1685   cairo_t *cr;
1686
1687   number_pos = strchr (file_name_template, '#');
1688   if (number_pos != NULL)
1689     file_name = xasprintf ("%.*s%d%s.eps", (int) (number_pos - file_name_template),
1690                            file_name_template, number, number_pos + 1);
1691   else
1692     file_name = xasprintf ("%s.eps", file_name_template);
1693
1694   surface = cairo_ps_surface_create (file_name, width, length);
1695   cairo_ps_surface_set_eps (surface, true);
1696   cr = cairo_create (surface);
1697
1698   cairo_set_source_rgb (cr, bg->r / 255.0, bg->g / 255.0, bg->b / 255.0);
1699   cairo_paint (cr);
1700
1701   cairo_set_source_rgb (cr, fg->r / 255.0, fg->g / 255.0, fg->b / 255.0);
1702
1703   xr_draw_chart (item, cr, 0.0, 0.0, width, length);
1704
1705   cairo_destroy (cr);
1706   cairo_surface_destroy (surface);
1707
1708   return file_name;
1709 }
1710