cairo: Add support for png and trim.
[pspp] / src / output / cairo-pager.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2009, 2010, 2014, 2020 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-pager.h"
20
21 #include <math.h>
22 #include <pango/pango-layout.h>
23 #include <pango/pangocairo.h>
24
25 #include "output/driver-provider.h"
26
27 #include "gl/xalloc.h"
28
29 #include "gettext.h"
30 #define _(msgid) gettext (msgid)
31
32 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
33 #define H TABLE_HORZ
34 #define V TABLE_VERT
35 \f
36 struct xr_page_style *
37 xr_page_style_ref (const struct xr_page_style *ps_)
38 {
39   struct xr_page_style *ps = CONST_CAST (struct xr_page_style *, ps_);
40   assert (ps->ref_cnt > 0);
41   ps->ref_cnt++;
42   return ps;
43 }
44
45 struct xr_page_style *
46 xr_page_style_unshare (struct xr_page_style *old)
47 {
48   assert (old->ref_cnt > 0);
49   if (old->ref_cnt == 1)
50     return old;
51
52   xr_page_style_unref (old);
53
54   struct xr_page_style *new = xmemdup (old, sizeof *old);
55   new->ref_cnt = 1;
56   for (int i = 0; i < 2; i++)
57     page_heading_copy (&new->headings[i], &old->headings[i]);
58
59   return new;
60 }
61
62 void
63 xr_page_style_unref (struct xr_page_style *ps)
64 {
65   if (ps)
66     {
67       assert (ps->ref_cnt > 0);
68       if (!--ps->ref_cnt)
69         {
70           for (int i = 0; i < 2; i++)
71             page_heading_uninit (&ps->headings[i]);
72           free (ps);
73         }
74     }
75 }
76
77 bool
78 xr_page_style_equals (const struct xr_page_style *a,
79                       const struct xr_page_style *b)
80 {
81   for (int i = 0; i < TABLE_N_AXES; i++)
82     for (int j = 0; j < 2; j++)
83       if (a->margins[i][j] != b->margins[i][j])
84         return false;
85
86   for (int i = 0; i < 2; i++)
87     if (!page_heading_equals (&a->headings[i], &b->headings[i]))
88       return false;
89
90   return (a->initial_page_number == b->initial_page_number
91           && a->object_spacing == b->object_spacing);
92 }
93 \f
94 struct xr_pager
95   {
96     struct xr_page_style *page_style;
97     struct xr_fsm_style *fsm_style;
98     int page_index;
99     int heading_heights[2];
100
101     /* Current output item. */
102     struct xr_fsm *fsm;
103     struct output_item *item;
104
105     /* Current output page. */
106     cairo_t *cr;
107     int y;
108   };
109
110 static void xr_pager_run (struct xr_pager *);
111
112 /* Conversions to and from points. */
113 static double
114 xr_to_pt (int x)
115 {
116   return x / (double) XR_POINT;
117 }
118
119 static int
120 pango_to_xr (int pango)
121 {
122   return (XR_POINT != PANGO_SCALE
123           ? ceil (pango * (1. * XR_POINT / PANGO_SCALE))
124           : pango);
125 }
126
127 static int
128 xr_to_pango (int xr)
129 {
130   return (XR_POINT != PANGO_SCALE
131           ? ceil (xr * (1. / XR_POINT * PANGO_SCALE))
132           : xr);
133 }
134
135 static int
136 get_layout_height (PangoLayout *layout)
137 {
138   int w, h;
139   pango_layout_get_size (layout, &w, &h);
140   return h;
141 }
142
143 static int
144 xr_render_page_heading (cairo_t *cairo, const PangoFontDescription *font,
145                         const struct page_heading *ph, int page_number,
146                         int width, int base_y, double font_resolution)
147 {
148   PangoContext *context = pango_cairo_create_context (cairo);
149   pango_cairo_context_set_resolution (context, font_resolution);
150   PangoLayout *layout = pango_layout_new (context);
151   g_object_unref (context);
152
153   pango_layout_set_font_description (layout, font);
154
155   int y = 0;
156   for (size_t i = 0; i < ph->n; i++)
157     {
158       const struct page_paragraph *pp = &ph->paragraphs[i];
159
160       char *markup = output_driver_substitute_heading_vars (pp->markup,
161                                                             page_number);
162       pango_layout_set_markup (layout, markup, -1);
163       free (markup);
164
165       pango_layout_set_alignment (
166         layout,
167         (pp->halign == TABLE_HALIGN_LEFT ? PANGO_ALIGN_LEFT
168          : pp->halign == TABLE_HALIGN_CENTER ? PANGO_ALIGN_CENTER
169          : pp->halign == TABLE_HALIGN_MIXED ? PANGO_ALIGN_LEFT
170          : PANGO_ALIGN_RIGHT));
171       pango_layout_set_width (layout, xr_to_pango (width));
172
173       cairo_save (cairo);
174       cairo_translate (cairo, 0, xr_to_pt (y + base_y));
175       pango_cairo_show_layout (cairo, layout);
176       cairo_restore (cairo);
177
178       y += pango_to_xr (get_layout_height (layout));
179     }
180
181   g_object_unref (G_OBJECT (layout));
182
183   return y;
184 }
185
186 static void
187 xr_measure_headings (const struct xr_page_style *ps,
188                      const struct xr_fsm_style *fs,
189                      int heading_heights[2])
190 {
191   cairo_surface_t *surface = cairo_recording_surface_create (
192     CAIRO_CONTENT_COLOR, NULL);
193   cairo_t *cairo = cairo_create (surface);
194   for (int i = 0; i < 2; i++)
195     {
196       int *h = &heading_heights[i];
197       *h = xr_render_page_heading (cairo, fs->fonts[XR_FONT_PROPORTIONAL],
198                                    &ps->headings[i], -1, fs->size[H], 0,
199                                    fs->font_resolution);
200       if (*h)
201         *h += ps->object_spacing;
202     }
203   cairo_destroy (cairo);
204   cairo_surface_destroy (surface);
205 }
206
207 struct xr_pager *
208 xr_pager_create (const struct xr_page_style *ps_,
209                  const struct xr_fsm_style *fs_)
210 {
211   struct xr_page_style *ps = xr_page_style_ref (ps_);
212   struct xr_fsm_style *fs = xr_fsm_style_ref (fs_);
213
214   int heading_heights[2];
215   xr_measure_headings (ps, fs, heading_heights);
216   int total = heading_heights[0] + heading_heights[1];
217   if (total > 0 && total < fs->size[V])
218     {
219       fs = xr_fsm_style_unshare (fs);
220       ps = xr_page_style_unshare (ps);
221
222       for (int i = 0; i < 2; i++)
223         ps->margins[V][i] += heading_heights[i];
224       fs->size[V] -= total;
225     }
226
227   struct xr_pager *p = xmalloc (sizeof *p);
228   *p = (struct xr_pager) { .page_style = ps, .fsm_style = fs };
229   return p;
230 }
231
232 void
233 xr_pager_destroy (struct xr_pager *p)
234 {
235   if (p)
236     {
237       xr_page_style_unref (p->page_style);
238       xr_fsm_style_unref (p->fsm_style);
239
240       xr_fsm_destroy (p->fsm);
241       output_item_unref (p->item);
242
243       if (p->cr)
244         {
245           cairo_restore (p->cr);
246           cairo_destroy (p->cr);
247         }
248       free (p);
249     }
250 }
251
252 bool
253 xr_pager_has_item (const struct xr_pager *p)
254 {
255   return p->item != NULL;
256 }
257
258 void
259 xr_pager_add_item (struct xr_pager *p, const struct output_item *item)
260 {
261   assert (!p->item);
262   p->item = output_item_ref (item);
263   xr_pager_run (p);
264 }
265
266 bool
267 xr_pager_has_page (const struct xr_pager *p)
268 {
269   return p->cr;
270 }
271
272 void
273 xr_pager_add_page (struct xr_pager *p, cairo_t *cr)
274 {
275   assert (!p->cr);
276   cairo_save (cr);
277   p->cr = cr;
278   p->y = 0;
279
280   const struct xr_fsm_style *fs = p->fsm_style;
281   const struct xr_page_style *ps = p->page_style;
282   cairo_translate (cr,
283                    xr_to_pt (ps->margins[H][0]),
284                    xr_to_pt (ps->margins[V][0]));
285
286   const PangoFontDescription *font = fs->fonts[XR_FONT_PROPORTIONAL];
287   int page_number = p->page_index++ + ps->initial_page_number;
288   if (p->heading_heights[0])
289     xr_render_page_heading (cr, font, &ps->headings[0], page_number,
290                             fs->size[H], -p->heading_heights[0],
291                             fs->font_resolution);
292
293   if (p->heading_heights[1])
294     xr_render_page_heading (cr, font, &ps->headings[1], page_number,
295                             fs->size[H], fs->size[V] + ps->object_spacing,
296                             fs->font_resolution);
297
298   xr_pager_run (p);
299 }
300
301 bool
302 xr_pager_needs_new_page (struct xr_pager *p)
303 {
304   if (p->item && (!p->cr || p->y >= p->fsm_style->size[V]))
305     {
306       if (p->cr)
307         {
308           cairo_restore (p->cr);
309           cairo_destroy (p->cr);
310           p->cr = NULL;
311         }
312       return true;
313     }
314   else
315     return false;
316 }
317
318 static void
319 xr_pager_run (struct xr_pager *p)
320 {
321   if (p->item && p->cr && p->y < p->fsm_style->size[V])
322     {
323       if (!p->fsm)
324         {
325           p->fsm = xr_fsm_create (p->item, p->fsm_style, p->cr);
326           if (!p->fsm)
327             {
328               output_item_unref (p->item);
329               p->item = NULL;
330
331               return;
332             }
333         }
334
335       for (;;)
336         {
337           int spacing = p->page_style->object_spacing;
338           int chunk = xr_fsm_draw_slice (p->fsm, p->cr,
339                                          p->fsm_style->size[V] - p->y);
340           p->y += chunk + spacing;
341           cairo_translate (p->cr, 0, xr_to_pt (chunk + spacing));
342
343           if (xr_fsm_is_empty (p->fsm))
344             {
345               xr_fsm_destroy (p->fsm);
346               p->fsm = NULL;
347               output_item_unref (p->item);
348               p->item = NULL;
349               return;
350             }
351           else if (!chunk)
352             {
353               assert (p->y > 0);
354               p->y = INT_MAX;
355               return;
356             }
357         }
358     }
359 }