cairo: Add support for png and trim.
authorBen Pfaff <blp@cs.stanford.edu>
Sat, 19 Dec 2020 08:12:28 +0000 (00:12 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Mon, 21 Dec 2020 00:04:32 +0000 (16:04 -0800)
This also allows dropping the librsvg dependency.

INSTALL
NEWS
configure.ac
doc/invoking.texi
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/driver.c
src/ui/gui/psppire-output-view.c

diff --git a/INSTALL b/INSTALL
index 66d9355fced5b9db208e9870653f5fc673c600e2..96191b9782e7a0c8ae1a1a6bf73442d81914eaed 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -128,10 +128,6 @@ Other optional packages:
       PSPP to test the Perl module more thoroughly.  It is not needed
       to build or use the Perl module.
 
-    * librsvg enables 300 dpi copy and paste operation. Without librsvg
-      the copy action will only provide images with default resolution
-      which is often 96dpi. This only affects bitmap image formats.
-
 Basic Installation
 ==================
 
diff --git a/NEWS b/NEWS
index 204d773f17df087c23dd8fe4e35463ff26bb0edd..98b24a4fef8e3b1291d3adcf1b2dcfd556b24e12 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -9,8 +9,6 @@ Changes from 1.4.1 to 1.5.2:
  * An error in the displayed signficance of oneway anova
    contrasts tests has been corrected.
 
- * PSPP can now write its results directly to a TeX source file.
-
  * Added Drag-N-Drop in output view.
 
  * The Explore GUI dialog supports the "Plots" subdialog. Boxplots, Q-Q Plots
@@ -20,7 +18,14 @@ Changes from 1.4.1 to 1.5.2:
    The new interface provides the user with a preview of the data to be imported
    and interactive methods to select the desired ranges.
 
- * The html output driver has a new option "bare".
+ * Output driver changes:
+
+   - New drivers for output to TeX source files and to PNG files.
+
+   - New option "trim" to remove empty space from PDF, PostScript, SVG,
+     and PNG output files.
+
+   - The HTML output driver has a new option "bare".
 
  * New features in pspp-output:
 
index e62a77504d01186f444a2debf8dea18e0f0736f3..d98631fcfcd08b184d6c223db679684d90ef1936 100644 (file)
@@ -142,10 +142,6 @@ if test "$with_cairo" != no && test "$with_gui" != "no"; then
     [PSPP_REQUIRED_PREREQ([spread-sheet-widget 0.7 (or use --without-gui)])])
   PKG_CHECK_VAR([SPREAD_SHEET_WIDGET_LIBDIR], [spread-sheet-widget], [libdir])
 
-  PKG_CHECK_MODULES([LIBRSVG], [librsvg-2.0 >= 2.44],
-    [AC_DEFINE([HAVE_RSVG], 1, [Define to 1 if librsvg is available])],
-    [PSPP_OPTIONAL_PREREQ([librsvg >= 2.44 required for high dpi Copy and Paste])])
-
   AC_ARG_VAR([GLIB_GENMARSHAL])
   AC_CHECK_PROGS([GLIB_GENMARSHAL], [glib-genmarshal])
   if test "x$GLIB_GENMARSHAL" = x; then
index 016659377da7a52d34e984bc31082267f26a6143..4800fc6997991f341ce52758dfc52f2440a3ff56 100644 (file)
@@ -22,7 +22,7 @@ interface.
 
 @menu
 * Main Options::
-* PDF PostScript and SVG Output Options::
+* PDF PostScript SVG and PNG Output Options::
 * Plain Text Output Options::
 * TeX Output Options::
 * HTML Output Options::
@@ -205,25 +205,41 @@ Invoke heuristics to assist with testing @pspp{}.  For use
 by @command{make check} and similar scripts.
 @end table
 
-@node PDF PostScript and SVG Output Options
-@section PDF, PostScript, and SVG Output Options
+@node PDF PostScript SVG and PNG Output Options
+@section PDF, PostScript, SVG, and PNG Output Options
 @cindex PDF
 @cindex Postscript
 @cindex SVG
-
-To produce output in PDF, PostScript, and SVG formats, specify
-@option{-o @var{file}} on the @pspp{} command line, optionally followed
-by any of the options shown in the table below to customize the output
-format.
-
-PDF, PostScript, and SVG output is only available if your installation
-of @pspp{} was compiled with the Cairo library.
+@cindex PNG
+
+To produce output in PDF, PostScript, SVG, or PNG format, specify
+@option{-o @var{file}} on the @pspp{} command line, optionally
+followed by any of the options shown in the table below to customize
+the output format.  These output formats are only available if your
+installation of @pspp{} was compiled with the Cairo library.
+
+PDF, PostScript, and SVG use real units: each dimension among the
+options listed below may have a suffix @samp{mm} for millimeters,
+@samp{in} for inches, or @samp{pt} for points.  Lacking a suffix,
+numbers below 50 are assumed to be in inches and those about 50 are
+assumed to be in millimeters.
+
+PNG files are pixel-based, so dimensions in PNG output must ultimately
+be measured in pixels.  For output to these files, PSPP translates the
+specified dimensions to pixels at 72 pixels per inch.  For PNG output
+only, fonts are by default rendered larger than this, at 96 pixels per
+inch.
+
+An SVG or PNG file can only hold a single page.  When PSPP outputs
+more than one page to SVG or PNG, it creates multiple files.  It
+outputs the second page to a file named with a @code{-2} suffix, the
+third with a @code{-3} suffix, and so on.
 
 @table @asis
-@item @option{-O format=@{pdf|ps|svg@}}
+@item @option{-O format=@{pdf|ps|svg|png@}}
 Specify the output format.  This is only necessary if the file name
-given on @option{-o} does not end in @file{.pdf}, @file{.ps}, or
-@file{.svg}.
+given on @option{-o} does not end in @file{.pdf}, @file{.ps},
+@file{.svg}, or @file{.png}.
 
 @item @option{-O paper-size=@var{paper-size}}
 Paper size, as a name (@i{e.g.}@: @code{a4}, @code{letter}) or
@@ -238,11 +254,9 @@ the default paper size is read from it.  As a last resort, A4 paper is
 assumed.
 
 @item @option{-O foreground-color=@var{color}}
-@itemx @option{-O background-color=@var{color}}
-Sets @var{color} as the color to be used for the background or foreground.
-Color should be given in the format @code{#@var{RRRR}@var{GGGG}@var{BBBB}},
-where @var{RRRR}, @var{GGGG} and @var{BBBB} are 4 character hexadecimal
-representations of the red, green and blue components respectively.
+Sets @var{color} as the default color for lines and text.  Use a CSS
+color format (e.g.@: @code{#@var{rr}@var{gg}@var{bb}}) or name (e.g.@:
+@code{black}) as @var{color}.
 
 @item @option{-O orientation=@var{orientation}}
 Either @code{portrait} or @code{landscape}.  Default: @code{portrait}.
@@ -267,24 +281,20 @@ Default: proportional font @code{serif}, fixed-pitch font @code{monospace}.
 Sets the size of the default fonts, in thousandths of a point.  Default:
 10000 (10 point).
 
-@item @option{-O line-gutter=@var{dimension}}
-Sets the width of white space on either side of lines that border text
-or graphics objects.  Default: @code{1pt}.
-
-@item @option{-O line-spacing=@var{dimension}}
-Sets the spacing between the lines in a double line in a table.
-Default: @code{1pt}.
-
-@item @option{-O line-width=@var{dimension}}
-Sets the width of the lines used in tables.  Default: @code{0.5pt}.
+@item @option{-O trim=true}
+This option makes PSPP trim empty space around each page of output,
+before adding the margins.  This can make the output easier to include
+in other documents.
+
+@item @option{-O font-resolution=@var{dpi}}
+Sets the resolution for font rendering, in dots per inch.  For PDF,
+PostScript, and SVG output, the default is 72 dpi, so that a 10-point
+font is rendered with a height of 10 points.  For PNG output, the
+default is 96 dpi, so that a 10-point font is rendered with a height
+of @math{10 / 72 * 96 = 13.3} pixels.  Use a larger @var{dpi} to
+enlarge text output, or a smaller @var{dpi} to shrink it.
 @end table
 
-Each @var{dimension} value above may be specified in various units
-based on its suffix: @samp{mm} for millimeters, @samp{in} for inches,
-or @samp{pt} for points.  Lacking a suffix, numbers below 50 are
-assumed to be in inches and those about 50 are assumed to be in
-millimeters.
-
 @node Plain Text Output Options
 @section Plain Text Output Options
 
index ec7e7278809b43bcf9b009f234e8b3264e1d1118..f95ccd4c3f8f3b7e4b08a73d2cc700c46da0e3de 100644 (file)
@@ -105,7 +105,6 @@ xr_fsm_style_equals (const struct xr_fsm_style *a,
       || a->min_break[H] != b->min_break[H]
       || a->min_break[V] != b->min_break[V]
       || a->use_system_colors != b->use_system_colors
-      || a->transparent != b->transparent
       || a->font_resolution != b->font_resolution)
     return false;
 
@@ -451,7 +450,8 @@ xrr_draw_cell (void *xr_, const struct table_cell *cell, int color_idx,
   struct xr_fsm *xr = xr_;
   int w, h, brk;
 
-  if (!xr->style->transparent)
+  const struct cell_color *bg = &cell->style->font_style.bg[color_idx];
+  if ((bg->r != 255 || bg->g != 255 || bg->b != 255) && bg->alpha)
     {
       cairo_save (xr->cairo);
       int bg_clip[TABLE_N_AXES][2];
@@ -466,7 +466,7 @@ xrr_draw_cell (void *xr_, const struct table_cell *cell, int color_idx,
            bg_clip[axis][1] += spill[axis][1];
        }
       xr_clip (xr, bg_clip);
-      xr_set_source_rgba (xr->cairo, &cell->style->font_style.bg[color_idx]);
+      xr_set_source_rgba (xr->cairo, bg);
       fill_rectangle (xr,
                      bb[H][0] - spill[H][0],
                      bb[V][0] - spill[V][0],
index 37f4b8b40be32a9bd856b4e31d6705d3ccbc3b10..bf937835418b6b130d48c7b3edf5995223b59ab1 100644 (file)
@@ -48,7 +48,6 @@ struct xr_fsm_style
     PangoFontDescription *fonts[XR_N_FONTS];
     struct cell_color fg;
     bool use_system_colors;
-    bool transparent;
 
     /* Resolution, in units per inch, used for measuring font "points".  If
        this is 72.0, for example, then 1pt = 1 device unit, which is
index 36823bd9452344168722b6b7b5c4a0d81823aa1f..f23ad5faa28ca99efcf2c8c5272057dc17ac1e77 100644 (file)
@@ -279,16 +279,6 @@ xr_pager_add_page (struct xr_pager *p, cairo_t *cr)
 
   const struct xr_fsm_style *fs = p->fsm_style;
   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, fs->size[H], fs->size[V]);
-      cairo_fill (cr);
-      cairo_restore (cr);
-    }
   cairo_translate (cr,
                    xr_to_pt (ps->margins[H][0]),
                    xr_to_pt (ps->margins[V][0]));
index ab843ee30e6973d1585f303622253640d27e5526..87dcd367f74fa8aff0b99604f871b1f98ac28aba 100644 (file)
@@ -37,7 +37,6 @@ struct xr_page_style
 
     struct page_heading headings[2]; /* Top and bottom headings. */
 
-    struct cell_color bg;            /* Background color. */
     int initial_page_number;
     int object_spacing;
   };
index 4ee386d25f1e86f660cc75bf49f817fc7e57590b..a3fb1b7d13b184bf9b8eb9198ac6c816a0e375fc 100644 (file)
@@ -64,7 +64,8 @@ enum xr_output_type
   {
     XR_PDF,
     XR_PS,
-    XR_SVG
+    XR_SVG,
+    XR_PNG
   };
 
 /* Cairo output driver. */
@@ -72,10 +73,47 @@ struct xr_driver
   {
     struct output_driver driver;
 
+    enum xr_output_type output_type;
     struct xr_fsm_style *fsm_style;
     struct xr_page_style *page_style;
     struct xr_pager *pager;
-    cairo_surface_t *surface;
+    bool trim;
+
+    /* This is the surface where we're currently drawing.  It is always
+       nonnull.
+
+       If 'trim' is true, this is a special Cairo "recording surface" that we
+       are using to save output temporarily just to find out the bounding box,
+       then later replay it into the destination surface.
+
+       If 'trim' is false:
+
+         - For output to a PDF or PostScript file, it is the same pointer as
+           'dest_surface'.
+
+         - For output to a PNG file, it is an image surface.
+
+         - For output to an SVG file, it is a recording surface.
+    */
+    cairo_surface_t *drawing_surface;
+
+    /* - For output to a PDF or PostScript file, this is the surface for the
+         PDF or PostScript file where the output is ultimately going.
+
+       - For output to a PNG file, this is NULL, because Cairo has very
+         limited support for PNG.  Cairo can't open a PNG file for writing as
+         a surface, it can only save an existing surface to a PNG file.
+
+       - For output to a SVG file, this is NULL, because Cairo does not
+         permit resizing the SVG page size after creating the file, whereas
+         this driver needs to do that sometimes.  Also, SVG is not multi-page
+         (according to https://wiki.inkscape.org/wiki/index.php/Multipage).
+    */
+    cairo_surface_t *dest_surface;
+
+    /* Used only in file names, for PNG and SVG output where we can only write
+       one page per file. */
+    int page_number;
   };
 
 static const struct output_driver_class cairo_driver_class;
@@ -145,12 +183,14 @@ parse_font_option (struct output_driver *d, struct string_map *options,
 }
 
 static struct xr_driver *
-xr_allocate (const char *name, int device_type, struct string_map *o)
+xr_allocate (const char *name, int device_type,
+             enum xr_output_type output_type, struct string_map *o)
 {
   struct xr_driver *xr = xzalloc (sizeof *xr);
   struct output_driver *d = &xr->driver;
 
   output_driver_init (d, &cairo_driver_class, name, device_type);
+  xr->output_type = output_type;
 
   /* Scale factor from inch/72000 to inch/(72 * XR_POINT). */
   const double scale = XR_POINT / 1000.;
@@ -185,12 +225,6 @@ xr_allocate (const char *name, int device_type, struct string_map *o)
 
   struct cell_color fg = parse_color (opt (d, o, "foreground-color", "black"));
 
-  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")));
-
   bool systemcolors = parse_boolean (opt (d, o, "systemcolors", "false"));
 
   int object_spacing
@@ -198,6 +232,12 @@ xr_allocate (const char *name, int device_type, struct string_map *o)
   if (object_spacing <= 0)
     object_spacing = XR_POINT * 12;
 
+  const char *default_resolution = (output_type == XR_PNG ? "96" : "72");
+  int font_resolution = parse_int (opt (d, o, "font-resolution",
+                                        default_resolution), 10, 1000);
+
+  xr->trim = parse_boolean (opt (d, o, "trim", "false"));
+
   xr->page_style = xmalloc (sizeof *xr->page_style);
   *xr->page_style = (struct xr_page_style) {
     .ref_cnt = 1,
@@ -207,7 +247,6 @@ xr_allocate (const char *name, int device_type, struct string_map *o)
       [V] = { margins[V][0], margins[V][1], },
     },
 
-    .bg = bg,
     .initial_page_number = 1,
     .object_spacing = object_spacing,
   };
@@ -223,8 +262,7 @@ xr_allocate (const char *name, int device_type, struct string_map *o)
     },
     .fg = fg,
     .use_system_colors = systemcolors,
-    .transparent = transparent,
-    .font_resolution = 72.0,
+    .font_resolution = font_resolution,
   };
 
   return xr;
@@ -232,32 +270,43 @@ xr_allocate (const char *name, int device_type, struct string_map *o)
 
 static struct output_driver *
 xr_create (struct file_handle *fh, enum settings_output_devices device_type,
-           struct string_map *o, enum xr_output_type file_type)
+           struct string_map *o, enum xr_output_type output_type)
 {
   const char *file_name = fh_get_file_name (fh);
-  struct xr_driver *xr = xr_allocate (file_name, device_type, o);
+  struct xr_driver *xr = xr_allocate (file_name, device_type, output_type, o);
 
   double paper[TABLE_N_AXES];
   for (int a = 0; a < TABLE_N_AXES; a++)
     paper[a] = xr_to_pt (xr_page_style_paper_size (xr->page_style,
                                                    xr->fsm_style, a));
-  if (file_type == XR_PDF)
-    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[H], paper[V]);
-  else if (file_type == XR_SVG)
-    xr->surface = cairo_svg_surface_create (file_name, paper[H], paper[V]);
-  else
-    NOT_REACHED ();
 
-  cairo_status_t status = cairo_surface_status (xr->surface);
-  if (status != CAIRO_STATUS_SUCCESS)
+  xr->dest_surface
+    = (output_type == XR_PDF
+       ? cairo_pdf_surface_create (file_name, paper[H], paper[V])
+       : output_type == XR_PS
+       ? cairo_ps_surface_create (file_name, paper[H], paper[V])
+       : NULL);
+  if (xr->dest_surface)
     {
-      msg (ME, _("error opening output file `%s': %s"),
-           file_name, cairo_status_to_string (status));
-      goto error;
+      cairo_status_t status = cairo_surface_status (xr->dest_surface);
+      if (status != CAIRO_STATUS_SUCCESS)
+        {
+          msg (ME, _("error opening output file `%s': %s"),
+               file_name, cairo_status_to_string (status));
+          goto error;
+        }
     }
 
+  xr->drawing_surface
+    = (xr->trim || output_type == XR_SVG
+       ? cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA,
+                                         &(cairo_rectangle_t) {
+                                           .width = paper[H],
+                                           .height = paper[V] })
+       : output_type == XR_PNG
+       ? cairo_image_surface_create (CAIRO_FORMAT_ARGB32, paper[H], paper[V])
+       : xr->dest_surface);
+
   fh_unref (fh);
   return &xr->driver;
 
@@ -288,20 +337,205 @@ xr_svg_create (struct file_handle *fh, enum settings_output_devices device_type,
   return xr_create (fh, device_type, o, XR_SVG);
 }
 
+static struct output_driver *
+xr_png_create (struct file_handle *fh, enum settings_output_devices device_type,
+               struct string_map *o)
+{
+  return xr_create (fh, device_type, o, XR_PNG);
+}
+
+static void
+xr_set_surface_size (cairo_surface_t *surface, enum xr_output_type output_type,
+                     double width, double height)
+{
+  switch (output_type)
+    {
+    case XR_PDF:
+      cairo_pdf_surface_set_size (surface, width, height);
+      break;
+
+    case XR_PS:
+      cairo_ps_surface_set_size (surface, width, height);
+      break;
+
+    case XR_SVG:
+    case XR_PNG:
+      NOT_REACHED ();
+    }
+}
+
+static void
+xr_copy_surface (cairo_surface_t *dst, cairo_surface_t *src,
+                 double x, double y)
+{
+  cairo_t *cr = cairo_create (dst);
+  cairo_set_source_surface (cr, src, x, y);
+  cairo_paint (cr);
+  cairo_destroy (cr);
+}
+
+static void
+clear_rectangle (cairo_surface_t *surface,
+                 double x0, double y0, double x1, double y1)
+{
+  cairo_t *cr = cairo_create (surface);
+  cairo_set_source_rgb (cr, 1, 1, 1);
+  cairo_new_path (cr);
+  cairo_rectangle (cr, x0, y0, x1 - x0, y1 - y0);
+  cairo_fill (cr);
+  cairo_destroy (cr);
+}
+
+static void
+xr_report_error (cairo_status_t status, const char *file_name)
+{
+  if (status != CAIRO_STATUS_SUCCESS)
+    fprintf (stderr,  "%s: %s", file_name, cairo_status_to_string (status));
+}
+
+static void
+xr_finish_page (struct xr_driver *xr)
+{
+  /* For 'trim' true:
+
+    - If the destination is PDF or PostScript, set the dest surface size, copy
+      ink extent, show_page.
+
+    - If the destination is PNG, create image surface, copy ink extent,
+      cairo_surface_write_to_png(), destroy image surface.
+
+    - If the destination is SVG, create svg surface, copy ink extent, close.
+
+    then destroy drawing_surface and make a new one.
+
+    For 'trim' false:
+
+    - If the destination is PDF or PostScript, show_page.
+
+    - If the destination is PNG, cairo_surface_write_to_png(), destroy image
+      surface, create new image surface.
+
+    - If the destination is SVG, create svg surface, copy whole thing, close.
+
+    */
+  double paper[TABLE_N_AXES];
+  for (int a = 0; a < TABLE_N_AXES; a++)
+    paper[a] = xr_to_pt (xr_page_style_paper_size (
+                           xr->page_style, xr->fsm_style, a));
+
+  xr->page_number++;
+  char *file_name = (xr->page_number > 1
+                     ? xasprintf ("%s-%d", xr->driver.name, xr->page_number)
+                     : xr->driver.name);
+
+  if (xr->trim)
+    {
+      /* Get the bounding box for the drawing surface. */
+      double ofs[TABLE_N_AXES], size[TABLE_N_AXES];
+      cairo_recording_surface_ink_extents (xr->drawing_surface,
+                                           &ofs[H], &ofs[V],
+                                           &size[H], &size[V]);
+      const int (*margins)[2] = xr->page_style->margins;
+      for (int a = 0; a < TABLE_N_AXES; a++)
+        {
+          double scale = XR_POINT;
+          size[a] += (margins[a][0] + margins[a][1]) / scale;
+          ofs[a] = -ofs[a] + margins[a][0] / scale;
+        }
+
+      switch (xr->output_type)
+        {
+        case XR_PDF:
+        case XR_PS:
+          xr_set_surface_size (xr->dest_surface, xr->output_type,
+                               size[H], size[V]);
+          xr_copy_surface (xr->dest_surface, xr->drawing_surface,
+                           ofs[H], ofs[V]);
+          cairo_surface_show_page (xr->dest_surface);
+          break;
+
+        case XR_SVG:
+          {
+            cairo_surface_t *svg = cairo_svg_surface_create (
+              file_name, size[H], size[V]);
+            xr_copy_surface (svg, xr->drawing_surface, ofs[H], ofs[V]);
+            xr_report_error (cairo_surface_status (svg), file_name);
+            cairo_surface_destroy (svg);
+          }
+          break;
+
+        case XR_PNG:
+          {
+            cairo_surface_t *png = cairo_image_surface_create (
+              CAIRO_FORMAT_ARGB32, size[H], size[V]);
+            clear_rectangle (png, 0, 0, size[H], size[V]);
+            xr_copy_surface (png, xr->drawing_surface, ofs[H], ofs[V]);
+            xr_report_error (cairo_surface_write_to_png (png, file_name),
+                             file_name);
+            cairo_surface_destroy (png);
+          }
+          break;
+        }
+
+      /* Destroy the recording surface and create a fresh one of the same
+         size. */
+      cairo_surface_destroy (xr->drawing_surface);
+      xr->drawing_surface = cairo_recording_surface_create (
+        CAIRO_CONTENT_COLOR_ALPHA,
+        &(cairo_rectangle_t) { .width = paper[H], .height = paper[V] });
+    }
+  else
+    {
+      switch (xr->output_type)
+        {
+        case XR_PDF:
+        case XR_PS:
+          cairo_surface_show_page (xr->dest_surface);
+          break;
+
+        case XR_SVG:
+          {
+            cairo_surface_t *svg = cairo_svg_surface_create (
+              file_name, paper[H], paper[V]);
+            xr_copy_surface (svg, xr->drawing_surface, 0.0, 0.0);
+            xr_report_error (cairo_surface_status (svg), file_name);
+            cairo_surface_destroy (svg);
+          }
+          break;
+
+        case XR_PNG:
+          xr_report_error (cairo_surface_write_to_png (xr->drawing_surface,
+                                                       file_name), file_name);
+          cairo_surface_destroy (xr->drawing_surface);
+          xr->drawing_surface = cairo_image_surface_create (
+            CAIRO_FORMAT_ARGB32, paper[H], paper[V]);
+          break;
+        }
+    }
+
+  if (file_name != xr->driver.name)
+    free (file_name);
+}
+
 static void
 xr_destroy (struct output_driver *driver)
 {
   struct xr_driver *xr = xr_driver_cast (driver);
 
-  if (xr->surface)
+  if (xr->pager)
+    xr_finish_page (xr);
+
+  if (xr->drawing_surface && xr->drawing_surface != xr->dest_surface)
+    cairo_surface_destroy (xr->drawing_surface);
+  if (xr->dest_surface)
     {
-      cairo_surface_finish (xr->surface);
-      cairo_status_t status = cairo_surface_status (xr->surface);
+      cairo_surface_finish (xr->dest_surface);
+      cairo_status_t status = cairo_surface_status (xr->dest_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_surface_destroy (xr->dest_surface);
     }
 
   xr_pager_destroy (xr->pager);
@@ -310,15 +544,6 @@ xr_destroy (struct output_driver *driver)
   free (xr);
 }
 
-static void
-xr_flush (struct output_driver *driver)
-{
-  struct xr_driver *xr = xr_driver_cast (driver);
-
-  if (xr->surface)
-    cairo_surface_flush (xr->surface);
-}
-
 static void
 xr_update_page_setup (struct output_driver *driver,
                       const struct page_setup *ps)
@@ -341,7 +566,6 @@ xr_update_page_setup (struct output_driver *driver,
       [V] = { ps->margins[v][0] * scale, ps->margins[v][1] * scale },
     },
 
-    .bg = xr->page_style->bg,
     .initial_page_number = ps->initial_page_number,
     .object_spacing = ps->object_spacing * 72 * XR_POINT,
   };
@@ -359,15 +583,14 @@ xr_update_page_setup (struct output_driver *driver,
     },
     .fg = old_fs->fg,
     .use_system_colors = old_fs->use_system_colors,
-    .transparent = old_fs->transparent,
-    .font_resolution = 72.0,
+    .font_resolution = old_fs->font_resolution,
   };
   for (size_t i = 0; i < XR_N_FONTS; i++)
     xr->fsm_style->fonts[i] = pango_font_description_copy (old_fs->fonts[i]);
   xr_fsm_style_unref (old_fs);
 
-  cairo_pdf_surface_set_size (xr->surface, ps->paper[H] * 72.0,
-                              ps->paper[V] * 72.0);
+  xr_set_surface_size (xr->dest_surface, xr->output_type, ps->paper[H] * 72.0,
+                       ps->paper[V] * 72.0);
 }
 
 static void
@@ -386,14 +609,14 @@ xr_submit (struct output_driver *driver, const struct output_item *output_item)
   if (!xr->pager)
     {
       xr->pager = xr_pager_create (xr->page_style, xr->fsm_style);
-      xr_pager_add_page (xr->pager, cairo_create (xr->surface));
+      xr_pager_add_page (xr->pager, cairo_create (xr->drawing_surface));
     }
 
   xr_pager_add_item (xr->pager, output_item);
   while (xr_pager_needs_new_page (xr->pager))
     {
-      cairo_surface_show_page (xr->surface);
-      xr_pager_add_page (xr->pager, cairo_create (xr->surface));
+      xr_finish_page (xr);
+      xr_pager_add_page (xr->pager, cairo_create (xr->drawing_surface));
     }
 }
 \f
@@ -403,11 +626,13 @@ struct output_driver_factory ps_driver_factory =
   { "ps", "pspp.ps", xr_ps_create };
 struct output_driver_factory svg_driver_factory =
   { "svg", "pspp.svg", xr_svg_create };
+struct output_driver_factory png_driver_factory =
+  { "png", "pspp.png", xr_png_create };
 
 static const struct output_driver_class cairo_driver_class =
 {
   "cairo",
   xr_destroy,
   xr_submit,
-  xr_flush,
+  NULL,
 };
index 067bf831ae7d988388743a30d836b2932e0e9de2..cf0a535f3c059ce6ae9b05a084b76ea94925265c 100644 (file)
@@ -440,6 +440,7 @@ extern const struct output_driver_factory spv_driver_factory;
 extern const struct output_driver_factory pdf_driver_factory;
 extern const struct output_driver_factory ps_driver_factory;
 extern const struct output_driver_factory svg_driver_factory;
+extern const struct output_driver_factory png_driver_factory;
 #endif
 extern const struct output_driver_factory tex_driver_factory;
 
@@ -455,6 +456,7 @@ static const struct output_driver_factory *factories[] =
     &pdf_driver_factory,
     &ps_driver_factory,
     &svg_driver_factory,
+    &png_driver_factory,
 #endif
     &tex_driver_factory,
     NULL
index f48b1ade4f5e8fa88a057d6e3e66a3cf56ec1e15..7e341797a185f4a6c84a2f6f739323581d1303ca 100644 (file)
@@ -22,9 +22,6 @@
 #include <errno.h>
 #include <stdbool.h>
 
-#if HAVE_RSVG
-#include "librsvg/rsvg.h"
-#endif
 #include "libpspp/assertion.h"
 #include "libpspp/string-map.h"
 #include "output/cairo-fsm.h"
@@ -167,7 +164,6 @@ get_xr_fsm_style (struct psppire_output_view *view)
       [XR_FONT_FIXED] = ff,
     },
     .use_system_colors = true,
-    .transparent = true,
     .font_resolution = 96.0,
   };
 
@@ -603,32 +599,16 @@ enum {
   SELECT_FMT_ODT
 };
 
-/* Returns a pixbuf from a svg file      */
-/* You must unref the pixbuf after usage */
-static GdkPixbuf *
-derive_pixbuf_from_svg (const char *filename)
+static void
+clear_rectangle (cairo_surface_t *surface,
+                 double x0, double y0, double x1, double y1)
 {
-  GError *err = NULL;
-  GdkPixbuf *pixbuf = NULL;
-#if HAVE_RSVG
-  RsvgHandle *handle = rsvg_handle_new_from_file (filename, &err);
-  if (err == NULL)
-    {
-      rsvg_handle_set_dpi (handle, 300.0);
-      pixbuf = rsvg_handle_get_pixbuf (handle);
-      g_object_unref (handle);
-    }
-#else
-  pixbuf = gdk_pixbuf_new_from_file (filename, &err);
-#endif
-  if (err != NULL)
-    {
-      msg (ME, _("Could not open file %s during copy operation: %s"),
-          filename, err->message);
-      g_error_free (err);
-      return NULL;
-    }
-  return pixbuf;
+  cairo_t *cr = cairo_create (surface);
+  cairo_set_source_rgb (cr, 1, 1, 1);
+  cairo_new_path (cr);
+  cairo_rectangle (cr, x0, y0, x1 - x0, y1 - y0);
+  cairo_fill (cr);
+  cairo_destroy (cr);
 }
 
 static void
@@ -668,6 +648,7 @@ clipboard_get_cb (GtkClipboard     *clipboard,
 
     case SELECT_FMT_TEXT:
       string_map_insert (&options, "format", "txt");
+      string_map_insert (&options, "width", "1000");
       break;
 
     case SELECT_FMT_HTML:
@@ -708,16 +689,25 @@ clipboard_get_cb (GtkClipboard     *clipboard,
       gdk_window_end_draw_frame (win, ctx);
       cairo_region_destroy (region);
 
-      cairo_surface_t *surface = cairo_svg_surface_create (filename, w, h);
-      if (surface)
+      cairo_surface_t *surface
+        = (info == SELECT_FMT_SVG
+           ? cairo_svg_surface_create (filename, w, h)
+           : cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h));
+      clear_rectangle (surface, 0, 0, w, h);
+      cairo_t *cr2 = cairo_create (surface);
+      xr_fsm_draw_all (fsm, cr2);
+      cairo_destroy (cr2);
+      if (info == SELECT_FMT_IMG)
         {
-          cairo_t *cr = cairo_create (surface);
-          xr_fsm_draw_all (fsm, cr);
-          cairo_destroy (cr);
-          cairo_surface_destroy (surface);
+          GdkPixbuf *pixbuf = gdk_pixbuf_get_from_surface (surface,
+                                                           0, 0, w, h);
+          if (pixbuf)
+            {
+              gtk_selection_data_set_pixbuf (selection_data, pixbuf);
+              g_object_unref (pixbuf);
+            }
         }
-      else
-        g_error ("Could not create cairo svg surface with file %s", filename);
+      cairo_surface_destroy (surface);
     }
   else
     {
@@ -736,19 +726,11 @@ clipboard_get_cb (GtkClipboard     *clipboard,
       driver = NULL;
     }
 
-  if (info == SELECT_FMT_IMG)
-    {
-      GdkPixbuf *pixbuf = derive_pixbuf_from_svg (filename);
-      if (pixbuf)
-       {
-         gtk_selection_data_set_pixbuf (selection_data, pixbuf);
-         g_object_unref (pixbuf);
-       }
-    }
-  else if (g_file_get_contents (filename, &text, &length, NULL))
-    gtk_selection_data_set (selection_data, gtk_selection_data_get_target (selection_data),
-                           8,
-                           (const guchar *) text, length);
+  if (info != SELECT_FMT_IMG
+      && g_file_get_contents (filename, &text, &length, NULL))
+    gtk_selection_data_set (selection_data,
+                            gtk_selection_data_get_target (selection_data),
+                            8, (const guchar *) text, length);
 
  finish:
 
@@ -1020,7 +1002,6 @@ create_xr_print_driver (GtkPrintContext *context, struct psppire_output_view *vi
       [H] = { margins[H][0], margins[H][1] },
       [V] = { margins[V][0], margins[V][1] },
     },
-    .bg = { .alpha = 0 },
     .initial_page_number = 1,
     .object_spacing = 12 * XR_POINT,
   };
@@ -1037,7 +1018,6 @@ create_xr_print_driver (GtkPrintContext *context, struct psppire_output_view *vi
     },
     .fg = CELL_COLOR_BLACK,
     .use_system_colors = false,
-    .transparent = false,
     .font_resolution = 72.0
   };