From: Ben Pfaff Date: Sat, 12 Dec 2020 07:20:56 +0000 (-0800) Subject: cairo output works again X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=16d77d1696dbf27abc0409c77115f84f42bf3b53;p=pspp cairo output works again --- diff --git a/src/output/automake.mk b/src/output/automake.mk index d524bc8f60..61d804372f 100644 --- a/src/output/automake.mk +++ b/src/output/automake.mk @@ -95,6 +95,8 @@ src_output_liboutput_la_SOURCES += \ src/output/cairo-chart.h \ src/output/cairo-fsm.c \ src/output/cairo-fsm.h \ + src/output/cairo-pager.c \ + src/output/cairo-pager.h \ src/output/cairo.c \ src/output/cairo.h \ src/output/charts/boxplot-cairo.c \ diff --git a/src/output/cairo-fsm.c b/src/output/cairo-fsm.c index 73231dba4e..5d1b63e4f1 100644 --- a/src/output/cairo-fsm.c +++ b/src/output/cairo-fsm.c @@ -1118,10 +1118,10 @@ xr_fsm_create (const struct output_item *item_, int char_size[TABLE_N_AXES]; pango_layout_get_size (layout, &char_size[H], &char_size[V]); - for (int j = 0; j < TABLE_N_AXES; j++) + for (int a = 0; a < TABLE_N_AXES; a++) { - int csj = pango_to_xr (char_size[j]); - fsm->rp.font_size[j] = MAX (fsm->rp.font_size[j], csj); + int csa = pango_to_xr (char_size[a]); + fsm->rp.font_size[a] = MAX (fsm->rp.font_size[a], csa); } g_object_unref (G_OBJECT (layout)); @@ -1143,7 +1143,6 @@ xr_fsm_destroy (struct xr_fsm *fsm) } } - /* This is primarily meant for use with screen rendering since the result is a fixed value for charts. */ void @@ -1175,17 +1174,9 @@ xr_fsm_measure (struct xr_fsm *fsm, cairo_t *cr, int *wp, int *hp) static int xr_fsm_draw_table (struct xr_fsm *fsm, int space) { - int used = 0; - while (render_pager_has_next (fsm->p)) - { - int chunk = render_pager_draw_next (fsm->p, space - used); - if (!chunk) - return used; - - used += chunk; - cairo_translate (fsm->cairo, 0, chunk); - } - return used; + return (render_pager_has_next (fsm->p) + ? render_pager_draw_next (fsm->p, space) + : 0); } static int diff --git a/src/output/cairo-fsm.h b/src/output/cairo-fsm.h index 60a6efb489..f89acbe537 100644 --- a/src/output/cairo-fsm.h +++ b/src/output/cairo-fsm.h @@ -28,6 +28,10 @@ struct xr_fsm; struct output_item; +/* The unit used for internal measurements is inch/(72 * XR_POINT). + (Thus, XR_POINT units represent one point.) */ +#define XR_POINT PANGO_SCALE + enum xr_font_type { XR_FONT_PROPORTIONAL, @@ -42,6 +46,7 @@ struct xr_fsm_style int size[TABLE_N_AXES]; /* Page size. */ int min_break[TABLE_N_AXES]; /* Minimum cell size to allow breaking. */ PangoFontDescription *fonts[XR_N_FONTS]; + struct cell_color fg; bool use_system_colors; bool transparent; double font_scale; diff --git a/src/output/cairo-pager.c b/src/output/cairo-pager.c new file mode 100644 index 0000000000..224ad0381f --- /dev/null +++ b/src/output/cairo-pager.c @@ -0,0 +1,355 @@ +/* PSPP - a program for statistical analysis. + Copyright (C) 2009, 2010, 2014, 2020 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include + +#include "output/cairo-pager.h" + +#include +#include +#include + +#include "output/driver-provider.h" + +#include "gl/xalloc.h" + +#include "gettext.h" +#define _(msgid) gettext (msgid) + +struct xr_page_style * +xr_page_style_ref (const struct xr_page_style *ps_) +{ + struct xr_page_style *ps = CONST_CAST (struct xr_page_style *, ps_); + assert (ps->ref_cnt > 0); + ps->ref_cnt++; + return ps; +} + +void +xr_page_style_unref (struct xr_page_style *ps) +{ + if (ps) + { + assert (ps->ref_cnt > 0); + if (!--ps->ref_cnt) + { + for (int i = 0; i < 2; i++) + page_heading_uninit (&ps->headings[i]); + if (ps->font) + pango_font_description_free (ps->font); + free (ps); + } + } +} + +static bool +pfd_equals (const PangoFontDescription *a, + const PangoFontDescription *b) +{ + return a && b ? pango_font_description_equal (a, b) : !a && !b; +} + +bool +xr_page_style_equals (const struct xr_page_style *a, + const struct xr_page_style *b) +{ + for (int i = 0; i < TABLE_N_AXES; i++) + { + if (a->size[i] != b->size[i]) + return false; + + for (int j = 0; j < 2; j++) + if (a->margins[i][j] != b->margins[i][j]) + return false; + } + + for (int i = 0; i < 2; i++) + if (!page_heading_equals (&a->headings[i], &b->headings[i])) + return false; + + return (pfd_equals (a->font, b->font) + && a->font_scale == b->font_scale + && a->initial_page_number == b->initial_page_number + && a->object_spacing == b->object_spacing); +} + +struct xr_pager + { + struct xr_page_style *page_style; + int page_index; + int heading_heights[2]; + + /* Current output item. */ + struct xr_fsm *fsm; + struct xr_fsm_style *fsm_style; + struct output_item *item; + + /* Current output page. */ + cairo_t *cr; + int y; + }; + +static void xr_pager_run (struct xr_pager *); + +/* Conversions to and from points. */ +static double +xr_to_pt (int x) +{ + return x / (double) XR_POINT; +} + +static int +pango_to_xr (int pango) +{ + return (XR_POINT != PANGO_SCALE + ? ceil (pango * (1. * XR_POINT / PANGO_SCALE)) + : pango); +} + +static int +xr_to_pango (int xr) +{ + return (XR_POINT != PANGO_SCALE + ? ceil (xr * (1. / XR_POINT * PANGO_SCALE)) + : xr); +} + +static int +get_layout_height (PangoLayout *layout) +{ + int w, h; + pango_layout_get_size (layout, &w, &h); + return h; +} + +static int +xr_render_page_heading (cairo_t *cairo, const PangoFontDescription *font, + const struct page_heading *ph, int page_number, + int width, bool draw, int base_y) +{ + PangoLayout *layout = pango_cairo_create_layout (cairo); + pango_layout_set_font_description (layout, font); + + int y = 0; + for (size_t i = 0; i < ph->n; i++) + { + const struct page_paragraph *pp = &ph->paragraphs[i]; + + char *markup = output_driver_substitute_heading_vars (pp->markup, + page_number); + pango_layout_set_markup (layout, markup, -1); + free (markup); + + pango_layout_set_alignment ( + layout, + (pp->halign == TABLE_HALIGN_LEFT ? PANGO_ALIGN_LEFT + : pp->halign == TABLE_HALIGN_CENTER ? PANGO_ALIGN_CENTER + : pp->halign == TABLE_HALIGN_MIXED ? PANGO_ALIGN_LEFT + : PANGO_ALIGN_RIGHT)); + pango_layout_set_width (layout, xr_to_pango (width)); + if (draw) + { + cairo_save (cairo); + cairo_translate (cairo, 0, xr_to_pt (y + base_y)); + pango_cairo_show_layout (cairo, layout); + cairo_restore (cairo); + } + + y += pango_to_xr (get_layout_height (layout)); + } + + g_object_unref (G_OBJECT (layout)); + + return y; +} + +static void +xr_measure_headings (struct xr_pager *p) +{ + const struct xr_page_style *ps = p->page_style; + + cairo_surface_t *surface = cairo_recording_surface_create ( + CAIRO_CONTENT_COLOR, NULL); + cairo_t *cairo = cairo_create (surface); + for (int i = 0; i < 2; i++) + { + int h = xr_render_page_heading (cairo, ps->font, &ps->headings[i], -1, + ps->size[TABLE_HORZ], false, 0); + + /* If the heading is nonempty, add a margin above or below it. */ + if (h) + h += ps->object_spacing; + + p->heading_heights[i] = h; + } + cairo_destroy (cairo); + cairo_surface_destroy (surface); + + int total = p->heading_heights[0] + p->heading_heights[1]; + if (total >= ps->size[TABLE_VERT]) + p->heading_heights[0] = p->heading_heights[1] = 0; +} + +struct xr_pager * +xr_pager_create (const struct xr_page_style *ps) +{ + struct xr_pager *p = xmalloc (sizeof *p); + *p = (struct xr_pager) { .page_style = xr_page_style_ref (ps) }; + + xr_measure_headings (p); + + return p; +} + +void +xr_pager_destroy (struct xr_pager *p) +{ + if (p) + { + xr_fsm_destroy (p->fsm); + xr_fsm_style_unref (p->fsm_style); + output_item_unref (p->item); + + xr_page_style_unref (p->page_style); + if (p->cr) + cairo_destroy (p->cr); + free (p); + } +} + +bool +xr_pager_has_item (const struct xr_pager *p) +{ + return p->item != NULL; +} + +void +xr_pager_add_item (struct xr_pager *p, const struct xr_fsm_style *fsm_style, + const struct output_item *item) +{ + assert (!p->item); + p->item = output_item_ref (item); + p->fsm_style = xr_fsm_style_ref (fsm_style); + xr_pager_run (p); +} + +bool +xr_pager_has_page (const struct xr_pager *p) +{ + return p->cr; +} + +void +xr_pager_add_page (struct xr_pager *p, cairo_t *cr) +{ + assert (!p->cr); + p->cr = cr; + p->y = 0; + + const struct xr_page_style *ps = p->page_style; + const struct cell_color *bg = &ps->bg; + if (bg->alpha) + { + cairo_save (cr); + cairo_set_source_rgba (cr, bg->r / 255.0, bg->g / 255.0, + bg->b / 255.0, bg->alpha / 255.0); + cairo_rectangle (cr, 0, 0, ps->size[TABLE_HORZ], ps->size[TABLE_VERT]); + cairo_fill (cr); + cairo_restore (cr); + } + cairo_translate (cr, + xr_to_pt (ps->margins[TABLE_HORZ][0]), + xr_to_pt (ps->margins[TABLE_VERT][0])); + + int page_number = p->page_index++ + ps->initial_page_number; + if (p->heading_heights[0]) + xr_render_page_heading (cr, ps->font, &ps->headings[0], page_number, + ps->size[TABLE_HORZ], true, 0); + + if (p->heading_heights[1]) + xr_render_page_heading ( + cr, ps->font, &ps->headings[1], page_number, + ps->size[TABLE_HORZ], true, + ps->size[TABLE_VERT] - p->heading_heights[1] + ps->object_spacing); + + cairo_translate (cr, 0, xr_to_pt (p->heading_heights[0])); + + xr_pager_run (p); +} + +bool +xr_pager_needs_new_page (struct xr_pager *p) +{ + int vsize = (p->page_style->size[TABLE_VERT] + - p->heading_heights[0] - p->heading_heights[1]); + if (p->item && p->y >= vsize) + { + cairo_destroy (p->cr); + p->cr = NULL; + return true; + } + else + return false; +} + +static void +xr_pager_run (struct xr_pager *p) +{ + int vsize = (p->page_style->size[TABLE_VERT] + - p->heading_heights[0] - p->heading_heights[1]); + if (p->item && p->cr && p->y < vsize) + { + if (!p->fsm) + { + /* XXX fsm_style needs to account for heading_heights */ + p->fsm = xr_fsm_create (p->item, p->fsm_style, p->cr); + if (!p->fsm) + { + xr_fsm_style_unref (p->fsm_style); + p->fsm_style = NULL; + output_item_unref (p->item); + p->item = NULL; + + return; + } + } + + for (;;) + { + int spacing = p->page_style->object_spacing; + int chunk = xr_fsm_draw_slice (p->fsm, p->cr, vsize - p->y); + p->y += chunk + spacing; + cairo_translate (p->cr, 0, xr_to_pt (chunk + spacing)); + + if (xr_fsm_is_empty (p->fsm)) + { + xr_fsm_destroy (p->fsm); + p->fsm = NULL; + xr_fsm_style_unref (p->fsm_style); + p->fsm_style = NULL; + output_item_unref (p->item); + p->item = NULL; + return; + } + else if (!chunk) + { + assert (p->y > 0); + p->y = INT_MAX; + return; + } + } + } +} diff --git a/src/output/cairo-pager.h b/src/output/cairo-pager.h new file mode 100644 index 0000000000..2e722be6cf --- /dev/null +++ b/src/output/cairo-pager.h @@ -0,0 +1,72 @@ +/* PSPP - a program for statistical analysis. + Copyright (C) 2009, 2010, 2014, 2020 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef OUTPUT_CAIRO_PAGER_H +#define OUTPUT_CAIRO_PAGER_H 1 + +#include + +#ifdef HAVE_CAIRO + +/* Cairo output driver paginater. */ + +#include +#include +#include "output/cairo-fsm.h" +#include "output/page-setup-item.h" +#include "output/table.h" + +struct xr_page_style + { + int ref_cnt; + + int size[TABLE_N_AXES]; /* Page size minus margins. */ + int margins[TABLE_N_AXES][2]; /* Margins. */ + + struct page_heading headings[2]; /* Top and bottom headings. */ + PangoFontDescription *font; + + struct cell_color bg; /* Background color. */ + double font_scale; + int initial_page_number; + int object_spacing; + }; +struct xr_page_style *xr_page_style_ref (const struct xr_page_style *); +void xr_page_style_unref (struct xr_page_style *); +bool xr_page_style_equals (const struct xr_page_style *, + const struct xr_page_style *); +struct xr_page_style *xr_page_style_default (void); + +static inline int +xr_page_style_paper_size (const struct xr_page_style *ps, enum table_axis a) +{ + return ps->size[a] + ps->margins[a][0] + ps->margins[a][1]; +} + +struct xr_pager *xr_pager_create (const struct xr_page_style *); +void xr_pager_destroy (struct xr_pager *); + +bool xr_pager_has_item (const struct xr_pager *); +void xr_pager_add_item (struct xr_pager *, const struct xr_fsm_style *, + const struct output_item *); + +bool xr_pager_has_page (const struct xr_pager *); +void xr_pager_add_page (struct xr_pager *, cairo_t *); +bool xr_pager_needs_new_page (struct xr_pager *); + +#endif /* HAVE_CAIRO */ + +#endif /* output/cairo-pager.h */ diff --git a/src/output/cairo.c b/src/output/cairo.c index f5bf1f6a93..1673889713 100644 --- a/src/output/cairo.c +++ b/src/output/cairo.c @@ -30,6 +30,7 @@ #include "data/file-handle-def.h" #include "output/cairo-chart.h" #include "output/cairo-fsm.h" +#include "output/cairo-pager.h" #include "output/chart-item-provider.h" #include "output/driver-provider.h" #include "output/group-item.h" @@ -96,30 +97,11 @@ struct xr_driver { struct output_driver driver; - /* User parameters. */ - PangoFontDescription *fonts[XR_N_FONTS]; - - /* Measurements all in inch/(72 * XR_POINT). */ - int size[TABLE_N_AXES]; /* Page size with margins subtracted. */ - int margins[TABLE_N_AXES][2]; /* Margins. */ - int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */ - int object_spacing; /* Space between output objects. */ - - struct cell_color bg; /* Background color */ - struct cell_color fg; /* Foreground color */ - bool transparent; /* true -> do not render background */ - bool systemcolors; /* true -> do not change colors */ - - int initial_page_number; - - struct page_heading headings[2]; /* Top and bottom headings. */ - int headings_height[2]; + struct xr_fsm_style *fsm_style; + struct xr_page_style *page_style; + struct xr_pager *pager; /* Internal state. */ - struct xr_fsm_style *style; - double font_scale; - int char_width, char_height; - cairo_t *cairo; cairo_surface_t *surface; int page_number; /* Current page number. */ int y; @@ -128,8 +110,6 @@ struct xr_driver static const struct output_driver_class cairo_driver_class; -static void xr_driver_destroy_fsm (struct xr_driver *); -static void xr_driver_run_fsm (struct xr_driver *); /* Output driver basics. */ @@ -194,254 +174,100 @@ parse_font_option (struct output_driver *d, struct string_map *options, return desc; } -static void -apply_options (struct xr_driver *xr, struct string_map *o) +/* FONT_SCALE is a nasty kluge for an issue that does not make sense. On any + surface other than a screen (e.g. for output to PDF or PS or SVG), the fonts + are way too big by default. A "9-point" font seems to appear about 16 + points tall. We use a scale factor for these surfaces to help, but the + underlying issue is a mystery. */ +static struct xr_driver * +xr_allocate (const char *name, int device_type, struct string_map *o, + double font_scale) { + struct xr_driver *xr = xzalloc (sizeof *xr); struct output_driver *d = &xr->driver; - /* In inch/72000 units used by parse_paper_size() and parse_dimension(). */ + output_driver_init (d, &cairo_driver_class, name, device_type); /* Scale factor from inch/72000 to inch/(72 * XR_POINT). */ const double scale = XR_POINT / 1000.; - for (int i = 0; i < XR_N_FONTS; i++) - if (xr->fonts[i] != NULL) - pango_font_description_free (xr->fonts[i]); - - int font_size = parse_int (opt (d, o, "font-size", "10000"), 1000, 1000000); - xr->fonts[XR_FONT_FIXED] = parse_font_option - (d, o, "fixed-font", "monospace", font_size, false, false); - xr->fonts[XR_FONT_PROPORTIONAL] = parse_font_option ( - d, o, "prop-font", "sans serif", font_size, false, false); - - xr->fg = parse_color (opt (d, o, "foreground-color", "#000000000000")); - xr->bg = parse_color (opt (d, o, "background-color", "#FFFFFFFFFFFF")); - - xr->transparent = parse_boolean (opt (d, o, "transparent", "false")); - xr->systemcolors = parse_boolean (opt (d, o, "systemcolors", "false")); - - /* Get dimensions. */ int paper[TABLE_N_AXES]; parse_paper_size (opt (d, o, "paper-size", ""), &paper[H], &paper[V]); + for (int a = 0; a < TABLE_N_AXES; a++) + paper[a] *= scale; int margins[TABLE_N_AXES][2]; - margins[H][0] = parse_dimension (opt (d, o, "left-margin", ".5in")); - margins[H][1] = parse_dimension (opt (d, o, "right-margin", ".5in")); - margins[V][0] = parse_dimension (opt (d, o, "top-margin", ".5in")); - margins[V][1] = parse_dimension (opt (d, o, "bottom-margin", ".5in")); + margins[H][0] = parse_dimension (opt (d, o, "left-margin", ".5in")) * scale; + margins[H][1] = parse_dimension (opt (d, o, "right-margin", ".5in")) * scale; + margins[V][0] = parse_dimension (opt (d, o, "top-margin", ".5in")) * scale; + margins[V][1] = parse_dimension (opt (d, o, "bottom-margin", ".5in")) * scale; + + int size[TABLE_N_AXES]; + for (int a = 0; a < TABLE_N_AXES; a++) + size[a] = paper[a] - margins[a][0] - margins[a][1]; int min_break[TABLE_N_AXES]; min_break[H] = parse_dimension (opt (d, o, "min-hbreak", NULL)) * scale; min_break[V] = parse_dimension (opt (d, o, "min-vbreak", NULL)) * scale; - - int object_spacing = (parse_dimension (opt (d, o, "object-spacing", NULL)) - * scale); - - /* Convert to inch/(XR_POINT * 72). */ for (int a = 0; a < TABLE_N_AXES; a++) - { - for (int i = 0; i < 2; i++) - xr->margins[a][i] = margins[a][i] * scale; - xr->size[a] = (paper[a] - margins[a][0] - margins[a][1]) * scale; - xr->min_break[a] = min_break[a] >= 0 ? min_break[a] : xr->size[a] / 2; - } - xr->object_spacing = object_spacing >= 0 ? object_spacing : XR_POINT * 12; - - /* There are no headings so headings_height can stay 0. */ -} - -static struct xr_driver * -xr_allocate (const char *name, int device_type, struct string_map *o, - double font_scale) -{ - struct xr_driver *xr = xzalloc (sizeof *xr); - struct output_driver *d = &xr->driver; - - output_driver_init (d, &cairo_driver_class, name, device_type); - - /* This is a nasty kluge for an issue that does not make sense. On any - surface other than a screen (e.g. for output to PDF or PS or SVG), the - fonts are way too big by default. A "9-point" font seems to appear about - 16 points tall. We use a scale factor for these surfaces to help, but the - underlying issue is a mystery. */ - xr->font_scale = font_scale; - - apply_options (xr, o); - - return xr; -} - -static int -pango_to_xr (int pango) -{ - return (XR_POINT != PANGO_SCALE - ? ceil (pango * (1. * XR_POINT / PANGO_SCALE)) - : pango); -} - -static int -xr_to_pango (int xr) -{ - return (XR_POINT != PANGO_SCALE - ? ceil (xr * (1. / XR_POINT * PANGO_SCALE)) - : xr); -} - -static void -xr_measure_fonts (cairo_t *cairo, PangoFontDescription *fonts[XR_N_FONTS], - int *char_width, int *char_height) -{ - *char_width = 0; - *char_height = 0; - for (int i = 0; i < XR_N_FONTS; i++) - { - PangoLayout *layout = pango_cairo_create_layout (cairo); - pango_layout_set_font_description (layout, fonts[i]); - - pango_layout_set_text (layout, "0", 1); - - int cw, ch; - pango_layout_get_size (layout, &cw, &ch); - *char_width = MAX (*char_width, pango_to_xr (cw)); - *char_height = MAX (*char_height, pango_to_xr (ch)); + if (min_break[a] <= 0) + min_break[a] = size[a] / 2; - g_object_unref (G_OBJECT (layout)); - } -} + int font_size = parse_int (opt (d, o, "font-size", "10000"), 1000, 1000000); + PangoFontDescription *fixed_font = parse_font_option + (d, o, "fixed-font", "monospace", font_size, false, false); + PangoFontDescription *proportional_font = parse_font_option ( + d, o, "prop-font", "sans serif", font_size, false, false); -static int -get_layout_height (PangoLayout *layout) -{ - int w, h; - pango_layout_get_size (layout, &w, &h); - return h; -} + struct cell_color fg = parse_color (opt (d, o, "foreground-color", "black")); -static int -xr_render_page_heading (cairo_t *cairo, const PangoFontDescription *font, - const struct page_heading *ph, int page_number, - int width, bool draw, int base_y) -{ - PangoLayout *layout = pango_cairo_create_layout (cairo); - pango_layout_set_font_description (layout, font); + bool transparent = parse_boolean (opt (d, o, "transparent", "false")); + struct cell_color bg = (transparent + ? (struct cell_color) { .alpha = 0 } + : parse_color (opt (d, o, "background-color", + "white"))); - int y = 0; - for (size_t i = 0; i < ph->n; i++) - { - const struct page_paragraph *pp = &ph->paragraphs[i]; - - char *markup = output_driver_substitute_heading_vars (pp->markup, - page_number); - pango_layout_set_markup (layout, markup, -1); - free (markup); - - pango_layout_set_alignment ( - layout, - (pp->halign == TABLE_HALIGN_LEFT ? PANGO_ALIGN_LEFT - : pp->halign == TABLE_HALIGN_CENTER ? PANGO_ALIGN_CENTER - : pp->halign == TABLE_HALIGN_MIXED ? PANGO_ALIGN_LEFT - : PANGO_ALIGN_RIGHT)); - pango_layout_set_width (layout, xr_to_pango (width)); - if (draw) - { - cairo_save (cairo); - cairo_translate (cairo, 0, xr_to_pt (y + base_y)); - pango_cairo_show_layout (cairo, layout); - cairo_restore (cairo); - } - - y += pango_to_xr (get_layout_height (layout)); - } - - g_object_unref (G_OBJECT (layout)); + bool systemcolors = parse_boolean (opt (d, o, "systemcolors", "false")); - return y; -} + int object_spacing + = parse_dimension (opt (d, o, "object-spacing", NULL)) * scale; + if (object_spacing <= 0) + object_spacing = XR_POINT * 12; -static int -xr_measure_headings (cairo_surface_t *surface, - const PangoFontDescription *font, - const struct page_heading headings[2], - int width, int object_spacing, int height[2]) -{ - cairo_t *cairo = cairo_create (surface); - int total = 0; - for (int i = 0; i < 2; i++) - { - int h = xr_render_page_heading (cairo, font, &headings[i], -1, - width, false, 0); + xr->page_style = xmalloc (sizeof *xr->page_style); + *xr->page_style = (struct xr_page_style) { + .ref_cnt = 1, - /* If the top heading is nonempty, add some space below it. */ - if (h && i == 0) - h += object_spacing; + .size = { [H] = size[H], [V] = size[V] }, + .margins = { + [H] = { margins[H][0], margins[H][1], }, + [V] = { margins[V][0], margins[V][1], }, + }, - if (height) - height[i] = h; - total += h; - } - cairo_destroy (cairo); - return total; -} + .font = pango_font_description_copy (proportional_font), -static bool -xr_check_fonts (cairo_surface_t *surface, - PangoFontDescription *fonts[XR_N_FONTS], - int usable_width, int usable_length) -{ - cairo_t *cairo = cairo_create (surface); - int char_width, char_height; - xr_measure_fonts (cairo, fonts, &char_width, &char_height); - cairo_destroy (cairo); - - bool ok = true; - enum { MIN_WIDTH = 3, MIN_LENGTH = 3 }; - if (usable_width / char_width < MIN_WIDTH) - { - msg (ME, _("The defined page is not wide enough to hold at least %d " - "characters in the default font. In fact, there's only " - "room for %d characters."), - MIN_WIDTH, usable_width / char_width); - ok = false; - } - if (usable_length / char_height < MIN_LENGTH) - { - msg (ME, _("The defined page is not long enough to hold at least %d " - "lines in the default font. In fact, there's only " - "room for %d lines."), - MIN_LENGTH, usable_length / char_height); - ok = false; - } - return ok; -} - -static void -xr_set_cairo (struct xr_driver *xr, cairo_t *cairo) -{ - xr->cairo = cairo; - - cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH)); - - xr_measure_fonts (xr->cairo, xr->fonts, &xr->char_width, &xr->char_height); + .bg = bg, + .font_scale = font_scale, + .initial_page_number = 1, + .object_spacing = object_spacing, + }; - if (xr->style == NULL) - { - xr->style = xmalloc (sizeof *xr->style); - *xr->style = (struct xr_fsm_style) { - .ref_cnt = 1, - .size = { [H] = xr->size[H], [V] = xr->size[V] }, - .min_break = { [H] = xr->min_break[H], [V] = xr->min_break[V] }, - .use_system_colors = xr->systemcolors, - .transparent = xr->transparent, - .font_scale = xr->font_scale, - }; - - for (size_t i = 0; i < XR_N_FONTS; i++) - xr->style->fonts[i] = pango_font_description_copy (xr->fonts[i]); - } + xr->fsm_style = xmalloc (sizeof *xr->fsm_style); + *xr->fsm_style = (struct xr_fsm_style) { + .ref_cnt = 1, + .size = { [H] = size[H], [V] = size[V] }, + .min_break = { [H] = min_break[H], [V] = min_break[V] }, + .fonts = { + [XR_FONT_PROPORTIONAL] = proportional_font, + [XR_FONT_FIXED] = fixed_font, + }, + .fg = fg, + .use_system_colors = systemcolors, + .transparent = transparent, + .font_scale = font_scale, + }; - if (!xr->systemcolors) - cairo_set_source_rgb (xr->cairo, - xr->fg.r / 255.0, xr->fg.g / 255.0, xr->fg.b / 255.0); + return xr; } static struct output_driver * @@ -451,18 +277,15 @@ xr_create (struct file_handle *fh, enum settings_output_devices device_type, const char *file_name = fh_get_file_name (fh); struct xr_driver *xr = xr_allocate (file_name, device_type, o, 72.0 / 128.0); - double paper_pt[TABLE_N_AXES]; + double paper[TABLE_N_AXES]; for (int a = 0; a < TABLE_N_AXES; a++) - paper_pt[a] = xr_to_pt (xr->size[a] - + xr->margins[a][0] + xr->margins[a][1]); + paper[a] = xr_to_pt (xr_page_style_paper_size (xr->page_style, a)); if (file_type == XR_PDF) - xr->surface = cairo_pdf_surface_create (file_name, - paper_pt[H], paper_pt[V]); + xr->surface = cairo_pdf_surface_create (file_name, paper[H], paper[V]); else if (file_type == XR_PS) - xr->surface = cairo_ps_surface_create (file_name, paper_pt[H], paper_pt[V]); + xr->surface = cairo_ps_surface_create (file_name, paper[H], paper[V]); else if (file_type == XR_SVG) - xr->surface = cairo_svg_surface_create (file_name, - paper_pt[H], paper_pt[V]); + xr->surface = cairo_svg_surface_create (file_name, paper[H], paper[V]); else NOT_REACHED (); @@ -474,9 +297,6 @@ xr_create (struct file_handle *fh, enum settings_output_devices device_type, goto error; } - if (!xr_check_fonts (xr->surface, xr->fonts, xr->size[H], xr->size[V])) - goto error; - fh_unref (fh); return &xr->driver; @@ -511,28 +331,21 @@ static void xr_destroy (struct output_driver *driver) { struct xr_driver *xr = xr_driver_cast (driver); - size_t i; - xr_driver_destroy_fsm (xr); - - if (xr->cairo != NULL) + if (xr->surface) { cairo_surface_finish (xr->surface); - cairo_status_t status = cairo_status (xr->cairo); + cairo_status_t status = cairo_surface_status (xr->surface); if (status != CAIRO_STATUS_SUCCESS) fprintf (stderr, _("error drawing output for %s driver: %s"), output_driver_get_name (driver), cairo_status_to_string (status)); cairo_surface_destroy (xr->surface); - - cairo_destroy (xr->cairo); } - for (i = 0; i < XR_N_FONTS; i++) - if (xr->fonts[i] != NULL) - pango_font_description_free (xr->fonts[i]); - - xr_fsm_style_unref (xr->style); + xr_pager_destroy (xr->pager); + xr_page_style_unref (xr->page_style); + xr_fsm_style_unref (xr->fsm_style); free (xr); } @@ -541,7 +354,8 @@ xr_flush (struct output_driver *driver) { struct xr_driver *xr = xr_driver_cast (driver); - cairo_surface_flush (cairo_get_target (xr->cairo)); + if (xr->surface) + cairo_surface_flush (xr->surface); } static void @@ -550,45 +364,38 @@ xr_update_page_setup (struct output_driver *driver, { struct xr_driver *xr = xr_driver_cast (driver); - xr->initial_page_number = ps->initial_page_number; - xr->object_spacing = ps->object_spacing * 72 * XR_POINT; - - if (xr->cairo) - return; - - int size[TABLE_N_AXES]; - for (int a = 0; a < TABLE_N_AXES; a++) - { - double total_margin = ps->margins[a][0] + ps->margins[a][1]; - size[a] = (ps->paper[a] - total_margin) * 72 * XR_POINT; - } - - int headings_height[2]; - size[V] -= xr_measure_headings ( - xr->surface, xr->fonts[XR_FONT_PROPORTIONAL], ps->headings, - size[H], xr->object_spacing, headings_height); + const double scale = 72 * XR_POINT; int swap = ps->orientation == PAGE_LANDSCAPE; enum table_axis h = H ^ swap; enum table_axis v = V ^ swap; - if (!xr_check_fonts (xr->surface, xr->fonts, size[h], size[v])) - return; - for (int i = 0; i < 2; i++) - { - page_heading_uninit (&xr->headings[i]); - page_heading_copy (&xr->headings[i], &ps->headings[i]); - xr->headings_height[i] = headings_height[i]; - } + struct xr_page_style *page_style = xmalloc (sizeof *page_style); + *page_style = (struct xr_page_style) { + .ref_cnt = 1, + + .size = { + [H] = (ps->paper[h] - ps->margins[h][0] - ps->margins[h][1]) * scale, + [V] = (ps->paper[v] - ps->margins[v][0] - ps->margins[v][1]) * scale, + }, + .margins = { + [H] = { ps->margins[h][0] * scale, ps->margins[h][1] * scale }, + [V] = { ps->margins[v][0] * scale, ps->margins[v][1] * scale }, + }, + + .font = pango_font_description_copy (xr->page_style->font), + + .bg = xr->page_style->bg, + .font_scale = xr->page_style->font_scale, + .initial_page_number = ps->initial_page_number, + .object_spacing = ps->object_spacing * 72 * XR_POINT, + }; - for (int a = 0; a < TABLE_N_AXES; a++) - { - xr->size[a] = size[a ^ swap]; - for (int i = 0; i < 2; i++) - xr->margins[a][i] = ps->margins[a ^ swap][i] * 72 * XR_POINT; - } - cairo_pdf_surface_set_size (xr->surface, - ps->paper[h] * 72.0, ps->paper[v] * 72.0); + for (size_t i = 0; i < 2; i++) + page_heading_copy (&page_style->headings[i], &ps->headings[i]); + + xr_page_style_unref (xr->page_style); + xr->page_style = page_style; } static void @@ -603,111 +410,17 @@ xr_submit (struct output_driver *driver, const struct output_item *output_item) return; } - if (!xr->cairo) + if (!xr->pager) { - xr->page_number = xr->initial_page_number - 1; - xr_set_cairo (xr, cairo_create (xr->surface)); - cairo_save (xr->cairo); - xr_driver_next_page (xr, xr->cairo); + xr->pager = xr_pager_create (xr->page_style); + xr_pager_add_page (xr->pager, cairo_create (xr->surface)); } - xr_driver_output_item (xr, output_item); - while (xr_driver_need_new_page (xr)) + xr_pager_add_item (xr->pager, xr->fsm_style, output_item); + while (xr_pager_needs_new_page (xr->pager)) { - cairo_restore (xr->cairo); - cairo_show_page (xr->cairo); - cairo_save (xr->cairo); - xr_driver_next_page (xr, xr->cairo); - } -} - -/* Functions for rendering a series of output items to a series of Cairo - contexts, with pagination. - - Used by PSPPIRE for printing, and by the basic Cairo output driver above as - its underlying implementation. - - See the big comment in cairo.h for intended usage. */ - -/* Gives new page CAIRO to XR for output. */ -void -xr_driver_next_page (struct xr_driver *xr, cairo_t *cairo) -{ - if (!xr->transparent) - { - cairo_save (cairo); - cairo_set_source_rgb (cairo, - xr->bg.r / 255.0, xr->bg.g / 255.0, xr->bg.b / 255.0); - cairo_rectangle (cairo, 0, 0, xr->size[H], xr->size[V]); - cairo_fill (cairo); - cairo_restore (cairo); - } - cairo_translate (cairo, - xr_to_pt (xr->margins[H][0]), - xr_to_pt (xr->margins[V][0] + xr->headings_height[0])); - - xr->page_number++; - xr->cairo = cairo; - xr->y = 0; - - xr_render_page_heading (xr->cairo, xr->fonts[XR_FONT_PROPORTIONAL], - &xr->headings[0], xr->page_number, xr->size[H], true, - -xr->headings_height[0]); - xr_render_page_heading (xr->cairo, xr->fonts[XR_FONT_PROPORTIONAL], - &xr->headings[1], xr->page_number, xr->size[H], true, - xr->size[V]); - - xr_driver_run_fsm (xr); -} - -/* Start rendering OUTPUT_ITEM to XR. Only valid if XR is not in the middle of - rendering a previous output item, that is, only if xr_driver_need_new_page() - returns false. */ -void -xr_driver_output_item (struct xr_driver *xr, - const struct output_item *output_item) -{ - assert (xr->fsm == NULL); - xr->fsm = xr_fsm_create (output_item, xr->style, xr->cairo); - xr_driver_run_fsm (xr); -} - -/* Returns true if XR is in the middle of rendering an output item and needs a - new page to be appended using xr_driver_next_page() to make progress, - otherwise false. */ -bool -xr_driver_need_new_page (const struct xr_driver *xr) -{ - return xr->fsm != NULL; -} - -/* Returns true if the current page doesn't have any content yet. */ -bool -xr_driver_is_page_blank (const struct xr_driver *xr) -{ - return xr->y == 0; -} - -static void -xr_driver_destroy_fsm (struct xr_driver *xr) -{ - xr_fsm_destroy (xr->fsm); - xr->fsm = NULL; -} - -static void -xr_driver_run_fsm (struct xr_driver *xr) -{ - if (xr->fsm != NULL) - { - cairo_save (xr->cairo); - cairo_translate (xr->cairo, 0, xr_to_pt (xr->y)); - int used = xr_fsm_draw_slice (xr->fsm, xr->cairo, xr->size[V] - xr->y); - xr->y += used; - cairo_restore (xr->cairo); - - if (xr_fsm_is_empty (xr->fsm)) - xr_driver_destroy_fsm (xr); + cairo_surface_show_page (xr->surface); + xr_pager_add_page (xr->pager, cairo_create (xr->surface)); } } @@ -725,23 +438,3 @@ static const struct output_driver_class cairo_driver_class = xr_submit, xr_flush, }; - -struct xr_driver * -xr_driver_create (cairo_t *cairo, struct string_map *options) -{ - struct xr_driver *xr = xr_allocate ("cairo", 0, options, 1.0); - xr_set_cairo (xr, cairo); - return xr; -} - -/* Destroy XR, which should have been created with xr_driver_create(). Any - cairo_t added to XR is not destroyed, because it is owned by the client. */ -void -xr_driver_destroy (struct xr_driver *xr) -{ - if (xr != NULL) - { - xr->cairo = NULL; - output_driver_destroy (&xr->driver); - } -} diff --git a/src/output/page-setup-item.c b/src/output/page-setup-item.c index b4f8e4c4ca..45cf8c977f 100644 --- a/src/output/page-setup-item.c +++ b/src/output/page-setup-item.c @@ -25,6 +25,15 @@ #include "gl/xalloc.h" +bool +page_paragraph_equals (const struct page_paragraph *a, + const struct page_paragraph *b) +{ + return (!a || !b ? a == b + : !a->markup || !b->markup ? a->markup == b->markup + : !strcmp (a->markup, b->markup) && a->halign == b->halign); +} + void page_heading_copy (struct page_heading *dst, const struct page_heading *src) { @@ -48,6 +57,23 @@ page_heading_uninit (struct page_heading *ph) free (ph->paragraphs); } +bool +page_heading_equals (const struct page_heading *a, + const struct page_heading *b) +{ + if (!a || !b) + return a == b; + + if (a->n != b->n) + return false; + + for (size_t i = 0; i < a->n; i++) + if (!page_paragraph_equals (&a->paragraphs[i], &b->paragraphs[i])) + return false; + + return true; +} + struct page_setup * page_setup_clone (const struct page_setup *old) { diff --git a/src/output/page-setup-item.h b/src/output/page-setup-item.h index 6fbe514797..f311ad2d5a 100644 --- a/src/output/page-setup-item.h +++ b/src/output/page-setup-item.h @@ -46,6 +46,9 @@ struct page_paragraph enum table_halign halign; }; +bool page_paragraph_equals (const struct page_paragraph *, + const struct page_paragraph *); + struct page_heading { struct page_paragraph *paragraphs; @@ -54,6 +57,8 @@ struct page_heading void page_heading_copy (struct page_heading *, const struct page_heading *); void page_heading_uninit (struct page_heading *); +bool page_heading_equals (const struct page_heading *, + const struct page_heading *); struct page_setup { diff --git a/src/ui/gui/psppire-output-view.c b/src/ui/gui/psppire-output-view.c index ccf23e8c4c..38dbf1a060 100644 --- a/src/ui/gui/psppire-output-view.c +++ b/src/ui/gui/psppire-output-view.c @@ -28,7 +28,7 @@ #include "libpspp/assertion.h" #include "libpspp/string-map.h" #include "output/cairo-fsm.h" -#include "output/cairo.h" +#include "output/cairo-pager.h" #include "output/driver-provider.h" #include "output/driver.h" #include "output/chart-item.h" @@ -76,7 +76,10 @@ struct psppire_output_view /* Variables pertaining to printing */ GtkPrintSettings *print_settings; - struct xr_driver *print_xrd; + + struct xr_fsm_style *fsm_style; + struct xr_page_style *page_style; + struct xr_pager *pager; int print_item; int print_n_pages; gboolean paginated; @@ -865,24 +868,12 @@ psppire_output_view_new (GtkLayout *output, GtkTreeView *overview) GtkTreeModel *model; view = xmalloc (sizeof *view); - view->style = NULL; - view->object_spacing = 10; - view->output = output; - view->render_width = 0; - view->max_width = 0; - view->y = 0; - view->overview = overview; - view->cur_group = NULL; - view->toplevel = gtk_widget_get_toplevel (GTK_WIDGET (output)); - view->buttontime = 0; - view->items = NULL; - view->n_items = view->allocated_items = 0; - view->selected_item = NULL; - view->print_settings = NULL; - view->print_xrd = NULL; - view->print_item = 0; - view->print_n_pages = 0; - view->paginated = FALSE; + *view = (struct psppire_output_view) { + .object_spacing = 10, + .output = output, + .overview = overview, + .toplevel = gtk_widget_get_toplevel (GTK_WIDGET (output)), + }; g_signal_connect (output, "draw", G_CALLBACK (layout_draw_callback), NULL); @@ -1012,37 +1003,62 @@ get_cairo_context_from_print_context (GtkPrintContext *context) static void create_xr_print_driver (GtkPrintContext *context, struct psppire_output_view *view) { - struct string_map options; - GtkPageSetup *page_setup; - double width, height; - double left_margin; - double right_margin; - double top_margin; - double bottom_margin; - - page_setup = gtk_print_context_get_page_setup (context); - width = gtk_page_setup_get_paper_width (page_setup, GTK_UNIT_MM); - height = gtk_page_setup_get_paper_height (page_setup, GTK_UNIT_MM); - left_margin = gtk_page_setup_get_left_margin (page_setup, GTK_UNIT_MM); - right_margin = gtk_page_setup_get_right_margin (page_setup, GTK_UNIT_MM); - top_margin = gtk_page_setup_get_top_margin (page_setup, GTK_UNIT_MM); - bottom_margin = gtk_page_setup_get_bottom_margin (page_setup, GTK_UNIT_MM); + GtkPageSetup *ps = gtk_print_context_get_page_setup (context); - string_map_init (&options); - string_map_insert_nocopy (&options, xstrdup ("paper-size"), - c_xasprintf("%.2fx%.2fmm", width, height)); - string_map_insert_nocopy (&options, xstrdup ("left-margin"), - c_xasprintf ("%.2fmm", left_margin)); - string_map_insert_nocopy (&options, xstrdup ("right-margin"), - c_xasprintf ("%.2fmm", right_margin)); - string_map_insert_nocopy (&options, xstrdup ("top-margin"), - c_xasprintf ("%.2fmm", top_margin)); - string_map_insert_nocopy (&options, xstrdup ("bottom-margin"), - c_xasprintf ("%.2fmm", bottom_margin)); - - view->print_xrd = xr_driver_create (get_cairo_context_from_print_context (context), &options); - - string_map_destroy (&options); + enum { H = TABLE_HORZ, V = TABLE_VERT }; + int paper[TABLE_N_AXES] = { + [H] = gtk_page_setup_get_paper_width (ps, GTK_UNIT_POINTS) * XR_POINT, + [V] = gtk_page_setup_get_paper_height (ps, GTK_UNIT_POINTS) * XR_POINT, + }; + + int margins[TABLE_N_AXES][2] = { + [H][0] = gtk_page_setup_get_left_margin (ps, GTK_UNIT_POINTS) * XR_POINT, + [H][1] = gtk_page_setup_get_right_margin (ps, GTK_UNIT_POINTS) * XR_POINT, + [V][0] = gtk_page_setup_get_top_margin (ps, GTK_UNIT_POINTS) * XR_POINT, + [V][1] = gtk_page_setup_get_bottom_margin (ps, GTK_UNIT_POINTS) * XR_POINT, + }; + + double size[TABLE_N_AXES]; + for (int a = 0; a < TABLE_N_AXES; a++) + size[a] = paper[a] - margins[a][0] - margins[a][1]; + + PangoFontDescription *proportional_font + = pango_font_description_from_string ("Sans Serif 10"); + PangoFontDescription *fixed_font + = pango_font_description_from_string ("Monospace 10"); + + view->page_style = xmalloc (sizeof *view->page_style); + *view->page_style = (struct xr_page_style) { + .ref_cnt = 1, + + .size = { [H] = size[H], [V] = size[V] }, + .margins = { + [H] = { margins[H][0], margins[H][1] }, + [V] = { margins[V][0], margins[V][1] }, + }, + .font = pango_font_description_copy (proportional_font), + .bg = { .alpha = 0 }, + .font_scale = 72.0 / 128.0, + .initial_page_number = 1, + .object_spacing = 12 * XR_POINT, + }; + + view->fsm_style = xmalloc (sizeof *view->fsm_style); + *view->fsm_style = (struct xr_fsm_style) { + .ref_cnt = 1, + + .size = { [H] = size[H], [V] = size[V] }, + .fonts = { + [XR_FONT_PROPORTIONAL] = proportional_font, + [XR_FONT_FIXED] = fixed_font, + }, + .fg = CELL_COLOR_BLACK, + .use_system_colors = false, + .transparent = false, + .font_scale = 72.0 / 128.0 + }; + + view->pager = xr_pager_create (view->page_style); } static gboolean @@ -1058,11 +1074,12 @@ paginate (GtkPrintOperation *operation, } else if (view->print_item < view->n_items) { - xr_driver_output_item (view->print_xrd, - view->items[view->print_item++].item); - while (xr_driver_need_new_page (view->print_xrd)) + xr_pager_add_item (view->pager, view->fsm_style, + view->items[view->print_item++].item); + while (xr_pager_needs_new_page (view->pager)) { - xr_driver_next_page (view->print_xrd, get_cairo_context_from_print_context (context)); + xr_pager_add_page (view->pager, + get_cairo_context_from_print_context (context)); view->print_n_pages ++; } return FALSE; @@ -1072,8 +1089,8 @@ paginate (GtkPrintOperation *operation, gtk_print_operation_set_n_pages (operation, view->print_n_pages); /* Re-create the driver to do the real printing. */ - xr_driver_destroy (view->print_xrd); - create_xr_print_driver (context, view); + xr_pager_destroy (view->pager); + view->pager = xr_pager_create (view->page_style); view->print_item = 0; view->paginated = TRUE; @@ -1098,7 +1115,7 @@ end_print (GtkPrintOperation *operation, GtkPrintContext *context, struct psppire_output_view *view) { - xr_driver_destroy (view->print_xrd); + xr_pager_destroy (view->pager); } @@ -1108,10 +1125,12 @@ draw_page (GtkPrintOperation *operation, gint page_number, struct psppire_output_view *view) { - xr_driver_next_page (view->print_xrd, get_cairo_context_from_print_context (context)); - while (!xr_driver_need_new_page (view->print_xrd) + xr_pager_add_page (view->pager, + get_cairo_context_from_print_context (context)); + while (!xr_pager_needs_new_page (view->pager) && view->print_item < view->n_items) - xr_driver_output_item (view->print_xrd, view->items [view->print_item++].item); + xr_pager_add_item (view->pager, view->fsm_style, + view->items [view->print_item++].item); } diff --git a/tests/output/render-test.c b/tests/output/render-test.c index 6b0aa73c4a..8f95d3c2f6 100644 --- a/tests/output/render-test.c +++ b/tests/output/render-test.c @@ -54,7 +54,7 @@ static int render_txt = true; static int render_stdout = true; /* --pdf: Also render PDF output. */ -static int render_pdf; +static int render_pdf = true; /* --csv: Also render CSV output. */ static int render_csv;