printing works; font sizes are weird
[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, bool draw, int base_y)
147 {
148   PangoLayout *layout = pango_cairo_create_layout (cairo);
149   pango_layout_set_font_description (layout, font);
150
151   int y = 0;
152   for (size_t i = 0; i < ph->n; i++)
153     {
154       const struct page_paragraph *pp = &ph->paragraphs[i];
155
156       char *markup = output_driver_substitute_heading_vars (pp->markup,
157                                                             page_number);
158       pango_layout_set_markup (layout, markup, -1);
159       free (markup);
160
161       pango_layout_set_alignment (
162         layout,
163         (pp->halign == TABLE_HALIGN_LEFT ? PANGO_ALIGN_LEFT
164          : pp->halign == TABLE_HALIGN_CENTER ? PANGO_ALIGN_CENTER
165          : pp->halign == TABLE_HALIGN_MIXED ? PANGO_ALIGN_LEFT
166          : PANGO_ALIGN_RIGHT));
167       pango_layout_set_width (layout, xr_to_pango (width));
168       if (draw)
169         {
170           cairo_save (cairo);
171           cairo_translate (cairo, 0, xr_to_pt (y + base_y));
172           pango_cairo_show_layout (cairo, layout);
173           cairo_restore (cairo);
174         }
175
176       y += pango_to_xr (get_layout_height (layout));
177     }
178
179   g_object_unref (G_OBJECT (layout));
180
181   return y;
182 }
183
184 static void
185 xr_measure_headings (const struct xr_page_style *ps,
186                      const struct xr_fsm_style *fs,
187                      int heading_heights[2])
188 {
189   cairo_surface_t *surface = cairo_recording_surface_create (
190     CAIRO_CONTENT_COLOR, NULL);
191   cairo_t *cairo = cairo_create (surface);
192   for (int i = 0; i < 2; i++)
193     {
194       int *h = &heading_heights[i];
195       *h = xr_render_page_heading (cairo, fs->fonts[XR_FONT_PROPORTIONAL],
196                                    &ps->headings[i], -1, fs->size[H], false,
197                                    0);
198       if (*h)
199         *h += ps->object_spacing;
200     }
201   cairo_destroy (cairo);
202   cairo_surface_destroy (surface);
203 }
204
205 struct xr_pager *
206 xr_pager_create (const struct xr_page_style *ps_,
207                  const struct xr_fsm_style *fs_)
208 {
209   printf ("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
210   struct xr_page_style *ps = xr_page_style_ref (ps_);
211   struct xr_fsm_style *fs = xr_fsm_style_ref (fs_);
212
213   int heading_heights[2];
214   xr_measure_headings (ps, fs, heading_heights);
215   int total = heading_heights[0] + heading_heights[1];
216   if (total > 0 && total < fs->size[V])
217     {
218       fs = xr_fsm_style_unshare (fs);
219       ps = xr_page_style_unshare (ps);
220
221       for (int i = 0; i < 2; i++)
222         ps->margins[V][i] += heading_heights[i];
223       fs->size[V] -= total;
224     }
225
226   struct xr_pager *p = xmalloc (sizeof *p);
227   *p = (struct xr_pager) { .page_style = ps, .fsm_style = fs };
228   return p;
229 }
230
231 void
232 xr_pager_destroy (struct xr_pager *p)
233 {
234   if (p)
235     {
236       printf (">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
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   printf ("add_item\n");
262   assert (!p->item);
263   p->item = output_item_ref (item);
264   xr_pager_run (p);
265 }
266
267 bool
268 xr_pager_has_page (const struct xr_pager *p)
269 {
270   return p->cr;
271 }
272
273 void
274 xr_pager_add_page (struct xr_pager *p, cairo_t *cr)
275 {
276   printf ("add_page\n");
277   assert (!p->cr);
278   cairo_save (cr);
279   p->cr = cr;
280   p->y = 0;
281
282   const struct xr_fsm_style *fs = p->fsm_style;
283   const struct xr_page_style *ps = p->page_style;
284   const struct cell_color *bg = &ps->bg;
285   if (bg->alpha)
286     {
287       cairo_save (cr);
288       cairo_set_source_rgba (cr, bg->r / 255.0, bg->g / 255.0,
289                           bg->b / 255.0, bg->alpha / 255.0);
290       cairo_rectangle (cr, 0, 0, fs->size[H], fs->size[V]);
291       cairo_fill (cr);
292       cairo_restore (cr);
293     }
294   cairo_translate (cr,
295                    xr_to_pt (ps->margins[H][0]),
296                    xr_to_pt (ps->margins[V][0]));
297
298   const PangoFontDescription *font = fs->fonts[XR_FONT_PROPORTIONAL];
299   int page_number = p->page_index++ + ps->initial_page_number;
300   if (p->heading_heights[0])
301     xr_render_page_heading (cr, font, &ps->headings[0], page_number,
302                             fs->size[H], true, -p->heading_heights[0]);
303
304   if (p->heading_heights[1])
305     xr_render_page_heading (cr, font, &ps->headings[1], page_number,
306                             fs->size[H], true,
307                             fs->size[V] + ps->object_spacing);
308
309   xr_pager_run (p);
310 }
311
312 bool
313 xr_pager_needs_new_page (struct xr_pager *p)
314 {
315   if (p->item && (!p->cr || p->y >= p->fsm_style->size[V]))
316     {
317       if (p->cr)
318         {
319           cairo_restore (p->cr);
320           cairo_destroy (p->cr);
321           p->cr = NULL;
322         }
323       return true;
324     }
325   else
326     return false;
327 }
328
329 static void
330 xr_pager_run (struct xr_pager *p)
331 {
332   printf ("run:\n");
333   if (p->item && p->cr && p->y < p->fsm_style->size[V])
334     {
335       if (!p->fsm)
336         {
337           printf ("\tcreate fsm\n");
338           p->fsm = xr_fsm_create (p->item, p->fsm_style, p->cr);
339           if (!p->fsm)
340             {
341               printf ("\t(was null)\n");
342               output_item_unref (p->item);
343               p->item = NULL;
344
345               return;
346             }
347         }
348
349       for (;;)
350         {
351           printf ("\tdraw slice\n");
352           int spacing = p->page_style->object_spacing;
353           int chunk = xr_fsm_draw_slice (p->fsm, p->cr,
354                                          p->fsm_style->size[V] - p->y);
355           p->y += chunk + spacing;
356           cairo_translate (p->cr, 0, xr_to_pt (chunk + spacing));
357
358           if (xr_fsm_is_empty (p->fsm))
359             {
360               printf ("\tfinished object\n");
361               xr_fsm_destroy (p->fsm);
362               p->fsm = NULL;
363               output_item_unref (p->item);
364               p->item = NULL;
365               return;
366             }
367           else if (!chunk)
368             {
369               printf ("\tobject doesn't fit on page\n");
370               assert (p->y > 0);
371               p->y = INT_MAX;
372               return;
373             }
374           printf ("\tneed to draw another slice\n");
375         }
376     }
377   if (!p->item)
378     printf ("\tno item\n");
379   if (!p->cr)
380     printf ("\tno page\n");
381 }