Add support for PNG images in .spv files.
authorBen Pfaff <blp@cs.stanford.edu>
Fri, 8 Jan 2021 06:49:24 +0000 (22:49 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Fri, 8 Jan 2021 08:23:11 +0000 (00:23 -0800)
This also allows us to save charts to .spv files as images.

19 files changed:
doc/dev/spv-file-format.texi
src/output/ascii.c
src/output/automake.mk
src/output/cairo-chart.c
src/output/cairo-chart.h
src/output/cairo-fsm.c
src/output/html.c
src/output/image-item.c [new file with mode: 0644]
src/output/image-item.h [new file with mode: 0644]
src/output/spv-driver.c
src/output/spv/spv-dump.c
src/output/spv/spv-writer.c
src/output/spv/spv-writer.h
src/output/spv/spv.c
src/output/spv/spv.h
src/output/spv/structure-xml.grammar
src/output/tex.c
src/ui/gui/psppire-window.c
utilities/pspp-output.c

index ebeac668cb9a729c012ae0cc4c4c6de014c1d46d..b27037cce7b069580d667ca1147fa586d349aa9a 100644 (file)
@@ -72,6 +72,13 @@ Same format used for tables, with a different name.
 The structure of a chart plus its data.  Charts do not have a
 ``light'' format.
 
+@item @file{@var{prefix}_Imagegeneric.png}
+@itemx @file{@var{prefix}_PastedObjectgeneric.png}
+@itemx @file{@var{prefix}_imageData.bin}
+A PNG image referenced by an @code{object} element (in the first two
+cases) or an @code{image} element (in the final case).  @xref{SPV
+Structure object and image Elements}.
+
 @item @file{@var{prefix}_pmml.scf}
 @itemx @file{@var{prefix}_stats.scf}
 @item @file{@var{prefix}_model.xml}
@@ -294,6 +301,7 @@ information, and the CSS from the embedded HTML:
 * SPV Structure table Element::
 * SPV Structure graph Element::
 * SPV Structure model Element::
+* SPV Structure object and image Elements::
 * SPV Structure tree Element::
 * SPV Structure Path Elements::
 * SPV Structure pageSetup Element::
@@ -668,6 +676,26 @@ strings, and @code{path} names an Zip member that contains XML.
 Alternatively, @code{pmmlContainerPath} and @code{statsContainerPath}
 name Zip members with @file{.scf} extension.
 
+@node SPV Structure object and image Elements
+@subsection The @code{object} and @code{image} Elements
+
+@example
+object :type[object_type]=(unknown)? :uri => EMPTY
+
+image :VDPId :commandName => dataPath
+@end example
+
+These two elements represent an image in PNG format.  They are
+equivalent and the corpus contains examples of both.  The only
+difference is the syntax: for @code{object}, the @code{uri} attribute
+names the Zip member that contains a PNG file; for @code{image}, the
+text of the inner @code{dataPath} element names the Zip member.
+
+PSPP writes @code{object} in output but there is no strong reason to
+choose this form.
+
+The corpus only contains PNG image files.
+
 @node SPV Structure tree Element
 @subsection The @code{tree} Element
 
index 46275bc363d61a22850933b6ff5f69cb48c86559..b5c9c004b9eb2460b4fb6666ed256a0ee26f12f3 100644 (file)
@@ -52,6 +52,7 @@
 #endif
 #include "output/chart-item-provider.h"
 #include "output/driver-provider.h"
+#include "output/image-item.h"
 #include "output/message-item.h"
 #include "output/options.h"
 #include "output/pivot-output.h"
@@ -603,6 +604,25 @@ ascii_submit (struct output_driver *driver,
   if (is_table_item (output_item))
     ascii_output_table_item (a, to_table_item (output_item));
 #ifdef HAVE_CAIRO
+  else if (is_image_item (output_item) && a->chart_file_name != NULL)
+    {
+      struct image_item *image_item = to_image_item (output_item);
+      char *file_name = xr_write_png_image (
+        image_item->image, a->chart_file_name, ++a->chart_cnt);
+      if (file_name != NULL)
+        {
+          struct text_item *text_item;
+
+          text_item = text_item_create_nocopy (
+            TEXT_ITEM_LOG,
+            xasprintf (_("See %s for an image."), file_name),
+            NULL);
+
+          ascii_submit (driver, &text_item->output_item);
+          text_item_unref (text_item);
+          free (file_name);
+        }
+    }
   else if (is_chart_item (output_item) && a->chart_file_name != NULL)
     {
       struct chart_item *chart_item = to_chart_item (output_item);
index fb476f4649d6a0785e700e810423a66420811138..3e0b8f46f312fac895c9f2f3ccad41328dc076d6 100644 (file)
@@ -48,6 +48,8 @@ src_output_liboutput_la_SOURCES = \
        src/output/driver-provider.h \
        src/output/driver.c \
        src/output/driver.h \
+       src/output/image-item.c \
+       src/output/image-item.h \
        src/output/tex-glyphs.c \
        src/output/tex-glyphs.h \
        src/output/tex-parsing.c \
index 62459d6f5780ca5dc9d19439d84bb35b75fbc60b..11175292ec2daefde28b81411e4ffd4ce32149a2 100644 (file)
@@ -655,49 +655,61 @@ xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
   cairo_restore (cr);
 }
 
-char *
-xr_draw_png_chart (const struct chart_item *item,
-                   const char *file_name_template, int number,
-                  const struct cell_color *fg,
-                  const struct cell_color *bg)
+cairo_surface_t *
+xr_draw_image_chart (const struct chart_item *item,
+                     const struct cell_color *fg,
+                     const struct cell_color *bg)
 {
   const int width = 640;
   const int length = 480;
 
-  cairo_surface_t *surface;
-  cairo_status_t status;
-  const char *number_pos;
-  char *file_name;
-  cairo_t *cr;
-
-  number_pos = strchr (file_name_template, '#');
-  if (number_pos != NULL)
-    file_name = xasprintf ("%.*s%d%s.png", (int) (number_pos - file_name_template),
-                           file_name_template, number, number_pos + 1);
-  else
-    file_name = xasprintf ("%s.png", file_name_template);
-
-  surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, length);
-  cr = cairo_create (surface);
+  cairo_surface_t *surface = cairo_image_surface_create (
+    CAIRO_FORMAT_RGB24, width, length);
+  cairo_t *cr = cairo_create (surface);
 
   cairo_set_source_rgb (cr, bg->r / 255.0, bg->g / 255.0, bg->b / 255.0);
   cairo_paint (cr);
 
   cairo_set_source_rgb (cr, fg->r / 255.0, fg->g / 255.0, fg->b / 255.0);
-
   xr_draw_chart (item, cr, width, length);
 
-  status = cairo_surface_write_to_png (surface, file_name);
+  cairo_destroy (cr);
+
+  return surface;
+}
+
+char *
+xr_write_png_image (cairo_surface_t *surface,
+                    const char *file_name_template, int number)
+{
+  const char *number_pos = strchr (file_name_template, '#');
+  char *file_name;
+  if (number_pos != NULL)
+    file_name = xasprintf ("%.*s%d%s.png",
+                           (int) (number_pos - file_name_template),
+                           file_name_template, number, number_pos + 1);
+  else
+    file_name = xasprintf ("%s.png", file_name_template);
+
+  cairo_status_t status = cairo_surface_write_to_png (surface, file_name);
   if (status != CAIRO_STATUS_SUCCESS)
     msg (ME, _("error writing output file `%s': %s"),
            file_name, cairo_status_to_string (status));
 
-  cairo_destroy (cr);
-  cairo_surface_destroy (surface);
-
   return file_name;
 }
 
+char *
+xr_draw_png_chart (const struct chart_item *item,
+                   const char *file_name_template, int number,
+                  const struct cell_color *fg,
+                  const struct cell_color *bg)
+{
+  cairo_surface_t *surface = xr_draw_image_chart (item, fg, bg);
+  char *file_name = xr_write_png_image (surface, file_name_template, number);
+  cairo_surface_destroy (surface);
+  return file_name;
+}
 
 char *
 xr_draw_eps_chart (const struct chart_item *item,
index bbcc606d31ddb6216c58d3dfef61fc6662bb14e5..f02fd4efac9ef80a9007ce3429203891ae772181 100644 (file)
@@ -181,6 +181,12 @@ void xrchart_draw_scatterplot (const struct chart_item *, cairo_t *,
 void xr_draw_chart (const struct chart_item *, cairo_t *,
                     double width, double height);
 
+cairo_surface_t *xr_draw_image_chart (const struct chart_item *,
+                                      const struct cell_color *fg,
+                                      const struct cell_color *bg);
+char *xr_write_png_image (cairo_surface_t *,
+                          const char *file_name_template, int number);
+
 char *xr_draw_png_chart (const struct chart_item *,
                          const char *file_name_template, int number,
                          const struct cell_color *fg,
index 1671edac4f512f93095d0071cfc48f4a64521ae5..2b47615be58d8fd9010123267e1c6f5014168181 100644 (file)
@@ -38,6 +38,7 @@
 #include "output/charts/scree.h"
 #include "output/charts/spreadlevel-plot.h"
 #include "output/group-item.h"
+#include "output/image-item.h"
 #include "output/message-item.h"
 #include "output/page-eject-item.h"
 #include "output/page-setup-item.h"
@@ -987,6 +988,7 @@ xr_fsm_create (const struct output_item *item_,
   struct output_item *item;
   if (is_table_item (item_)
       || is_chart_item (item_)
+      || is_image_item (item_)
       || is_page_eject_item (item_))
     item = output_item_ref (item_);
   else if (is_message_item (item_))
@@ -1017,6 +1019,7 @@ xr_fsm_create (const struct output_item *item_,
     NOT_REACHED ();
   assert (is_table_item (item)
           || is_chart_item (item)
+          || is_image_item (item)
           || is_page_eject_item (item));
 
   size_t *layer_indexes = NULL;
@@ -1147,6 +1150,12 @@ xr_fsm_measure (struct xr_fsm *fsm, cairo_t *cr, int *wp, int *hp)
       w = CHART_WIDTH;
       h = CHART_HEIGHT;
     }
+  else if (is_image_item (fsm->item))
+    {
+      cairo_surface_t *image = to_image_item (fsm->item)->image;
+      w = cairo_image_surface_get_width (image);
+      h = cairo_image_surface_get_height (image);
+    }
   else
     NOT_REACHED ();
 
@@ -1171,6 +1180,18 @@ mul_XR_POINT (int x)
           : x * XR_POINT);
 }
 
+static void
+draw_image (cairo_surface_t *image, cairo_t *cr)
+{
+  cairo_save (cr);
+  cairo_set_source_surface (cr, image, 0, 0);
+  cairo_rectangle (cr, 0, 0, cairo_image_surface_get_width (image),
+                   cairo_image_surface_get_height (image));
+  cairo_clip (cr);
+  cairo_paint (cr);
+  cairo_restore (cr);
+}
+
 void
 xr_fsm_draw_region (struct xr_fsm *fsm, cairo_t *cr,
                     int x, int y, int w, int h)
@@ -1183,6 +1204,8 @@ xr_fsm_draw_region (struct xr_fsm *fsm, cairo_t *cr,
                                 mul_XR_POINT (w), mul_XR_POINT (h));
       fsm->cairo = NULL;
     }
+  else if (is_image_item (fsm->item))
+    draw_image (to_image_item (fsm->item)->image, cr);
   else if (is_chart_item (fsm->item))
     xr_draw_chart (to_chart_item (fsm->item), cr, CHART_WIDTH, CHART_HEIGHT);
   else if (is_page_eject_item (fsm->item))
@@ -1237,6 +1260,49 @@ xr_fsm_draw_chart (struct xr_fsm *fsm, int space)
   return chart_height;
 }
 
+static int
+xr_fsm_draw_image (struct xr_fsm *fsm, int space)
+{
+  cairo_surface_t *image = to_image_item (fsm->item)->image;
+  int width = cairo_image_surface_get_width (image) * XR_POINT;
+  int height = cairo_image_surface_get_height (image) * XR_POINT;
+  if (!width || !height)
+    goto error;
+
+  if (height > fsm->rp.size[V])
+    {
+      double scale = fsm->rp.size[V] / (double) height;
+      width *= scale;
+      height *= scale;
+      if (!width || !height)
+        goto error;
+
+      cairo_scale (fsm->cairo, scale, scale);
+    }
+
+  if (width > fsm->rp.size[H])
+    {
+      double scale = fsm->rp.size[H] / (double) width;
+      width *= scale;
+      height *= scale;
+      if (!width || !height)
+        goto error;
+
+      cairo_scale (fsm->cairo, scale, scale);
+    }
+
+  if (space < height)
+    return 0;
+
+  draw_image (image, fsm->cairo);
+  fsm->done = true;
+  return height;
+
+error:
+  fsm->done = true;
+  return 0;
+}
+
 static int
 xr_fsm_draw_eject (struct xr_fsm *fsm, int space)
 {
@@ -1257,6 +1323,7 @@ xr_fsm_draw_slice (struct xr_fsm *fsm, cairo_t *cr, int space)
   fsm->cairo = cr;
   int used = (is_table_item (fsm->item) ? xr_fsm_draw_table (fsm, space)
               : is_chart_item (fsm->item) ? xr_fsm_draw_chart (fsm, space)
+              : is_image_item (fsm->item) ? xr_fsm_draw_image (fsm, space)
               : is_page_eject_item (fsm->item) ? xr_fsm_draw_eject (fsm, space)
               : (abort (), 0));
   fsm->cairo = NULL;
index ecaf729dd74e4377142dc7f40ec6669ce8df0379..e865642a6e518d9bb47e3afb00f8f1600631c90e 100644 (file)
@@ -38,6 +38,7 @@
 #endif
 #include "output/chart-item.h"
 #include "output/driver-provider.h"
+#include "output/image-item.h"
 #include "output/message-item.h"
 #include "output/options.h"
 #include "output/output-item-provider.h"
@@ -266,6 +267,17 @@ html_submit (struct output_driver *driver,
       html_output_table (html, table_item);
     }
 #ifdef HAVE_CAIRO
+  else if (is_image_item (output_item) && html->chart_file_name != NULL)
+    {
+      struct image_item *image_item = to_image_item (output_item);
+      char *file_name = xr_write_png_image (
+        image_item->image, html->chart_file_name, ++html->chart_cnt);
+      if (file_name != NULL)
+        {
+          fprintf (html->file, "<img src=\"%s\">", file_name);
+          free (file_name);
+        }
+    }
   else if (is_chart_item (output_item) && html->chart_file_name != NULL)
     {
       struct chart_item *chart_item = to_chart_item (output_item);
diff --git a/src/output/image-item.c b/src/output/image-item.c
new file mode 100644 (file)
index 0000000..62efe7b
--- /dev/null
@@ -0,0 +1,93 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include "output/image-item.h"
+
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include "libpspp/cast.h"
+#include "libpspp/compiler.h"
+#include "output/driver.h"
+#include "output/output-item-provider.h"
+
+#include "gl/xalloc.h"
+#include "gl/xvasprintf.h"
+
+#ifdef HAVE_CAIRO
+/* Creates and returns a new image item containing IMAGE.  Takes ownership of
+   IMAGE. */
+struct image_item *
+image_item_create (cairo_surface_t *image)
+{
+  struct image_item *item = xmalloc (sizeof *item);
+  *item = (struct image_item) {
+    .output_item = OUTPUT_ITEM_INITIALIZER (&image_item_class),
+    .image = image,
+  };
+  return item;
+}
+#endif
+
+/* Submits ITEM to the configured output drivers, and transfers ownership to
+   the output subsystem. */
+void
+image_item_submit (struct image_item *item)
+{
+  output_submit (&item->output_item);
+}
+
+struct image_item *
+image_item_unshare (struct image_item *old)
+{
+  assert (old->output_item.ref_cnt > 0);
+  if (!image_item_is_shared (old))
+    return old;
+  image_item_unref (old);
+
+  struct image_item *new = xmalloc (sizeof *new);
+  *new = (struct image_item) {
+    .output_item = OUTPUT_ITEM_CLONE_INITIALIZER (&old->output_item),
+#ifdef HAVE_CAIRO
+    .image = cairo_surface_reference (old->image),
+#endif
+  };
+  return new;
+}
+\f
+static const char *
+image_item_get_label (const struct output_item *output_item UNUSED)
+{
+  return "Image";
+}
+
+static void
+image_item_destroy (struct output_item *output_item)
+{
+  struct image_item *item = to_image_item (output_item);
+#ifdef HAVE_CAIRO
+  cairo_surface_destroy (item->image);
+#endif
+  free (item);
+}
+
+const struct output_item_class image_item_class =
+  {
+    image_item_get_label,
+    image_item_destroy,
+  };
diff --git a/src/output/image-item.h b/src/output/image-item.h
new file mode 100644 (file)
index 0000000..91d7c2e
--- /dev/null
@@ -0,0 +1,96 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 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 <http://www.gnu.org/licenses/>. */
+
+#ifndef OUTPUT_IMAGE_ITEM_H
+#define OUTPUT_IMAGE_ITEM_H 1
+
+#ifdef HAVE_CAIRO
+#include <cairo.h>
+#endif
+#include <stdbool.h>
+#include "output/output-item.h"
+
+struct image_item
+  {
+    struct output_item output_item; /* Superclass */
+#ifdef HAVE_CAIRO
+    cairo_surface_t *image;
+#endif
+  };
+
+#ifdef HAVE_CAIRO
+struct image_item *image_item_create (cairo_surface_t *);
+#endif
+
+struct image_item *image_item_unshare (struct image_item *);
+\f
+/* This boilerplate for image_item, a subclass of output_item, was
+   autogenerated by mk-class-boilerplate. */
+
+#include <assert.h>
+#include "libpspp/cast.h"
+
+extern const struct output_item_class image_item_class;
+
+/* Returns true if SUPER is a image_item, otherwise false. */
+static inline bool
+is_image_item (const struct output_item *super)
+{
+  return super->class == &image_item_class;
+}
+
+/* Returns SUPER converted to image_item.  SUPER must be a image_item, as
+   reported by is_image_item. */
+static inline struct image_item *
+to_image_item (const struct output_item *super)
+{
+  assert (is_image_item (super));
+  return UP_CAST (super, struct image_item, output_item);
+}
+
+/* Returns INSTANCE converted to output_item. */
+static inline struct output_item *
+image_item_super (const struct image_item *instance)
+{
+  return CONST_CAST (struct output_item *, &instance->output_item);
+}
+
+/* Increments INSTANCE's reference count and returns INSTANCE. */
+static inline struct image_item *
+image_item_ref (const struct image_item *instance)
+{
+  return to_image_item (output_item_ref (&instance->output_item));
+}
+
+/* Decrements INSTANCE's reference count, then destroys INSTANCE if
+   the reference count is now zero. */
+static inline void
+image_item_unref (struct image_item *instance)
+{
+  output_item_unref (&instance->output_item);
+}
+
+/* Returns true if INSTANCE's reference count is greater than 1,
+   false otherwise. */
+static inline bool
+image_item_is_shared (const struct image_item *instance)
+{
+  return output_item_is_shared (&instance->output_item);
+}
+
+void image_item_submit (struct image_item *);
+\f
+#endif /* output/image-item.h */
index 0c9d1412a5584514d4bf45be1139bf94ae4ce7ad..0fb0928372debcdb4cdd8f1e1e1dc6689d8ebf28 100644 (file)
 
 #include "data/file-handle-def.h"
 #include "libpspp/cast.h"
+#ifdef HAVE_CAIRO
+#include "output/cairo-chart.h"
+#endif
+#include "output/chart-item.h"
 #include "output/group-item.h"
 #include "output/page-eject-item.h"
 #include "output/page-setup-item.h"
@@ -107,6 +111,18 @@ spv_submit (struct output_driver *driver,
       if (table_item->pt)
         spv_writer_put_table (spv->writer, table_item->pt);
     }
+#ifdef HAVE_CAIRO
+  else if (is_chart_item (output_item))
+    {
+      cairo_surface_t *surface = xr_draw_image_chart (
+        to_chart_item (output_item),
+        &(struct cell_color) CELL_COLOR_BLACK,
+        &(struct cell_color) CELL_COLOR_WHITE);
+      if (cairo_surface_status (surface) == CAIRO_STATUS_SUCCESS)
+        spv_writer_put_image (spv->writer, surface);
+      cairo_surface_destroy (surface);
+    }
+#endif
   else if (is_text_item (output_item))
     {
       char *command_id = output_get_command_name ();
index 70d2a18791a9af1bfb8aeda8b351b8496ab824a2..db8a60d9ed17497baa207a0b0912e7fefc44f46a 100644 (file)
@@ -74,8 +74,8 @@ spv_item_dump (const struct spv_item *item, int indentation)
       printf ("model\n");
       break;
 
-    case SPV_ITEM_OBJECT:
-      printf ("object type=\"%s\" uri=\"%s\"\n", item->object_type, item->uri);
+    case SPV_ITEM_IMAGE:
+      printf ("image in %s\n", item->png_member);
       break;
 
     case SPV_ITEM_TREE:
index c27368aa63da1c6dd4ffd5215a2fdfb77b74b094..c5a600571a8e869a4ceb5d6700c43e4a1fd0a7ce 100644 (file)
@@ -323,6 +323,48 @@ spv_writer_put_text (struct spv_writer *w, const struct text_item *text,
     spv_writer_close_file (w, "");
 }
 
+#ifdef HAVE_CAIRO
+static cairo_status_t
+write_to_zip (void *zw_, const unsigned char *data, unsigned int length)
+{
+  struct zip_writer *zw = zw_;
+
+  zip_writer_add_write (zw, data, length);
+  return CAIRO_STATUS_SUCCESS;
+}
+
+void
+spv_writer_put_image (struct spv_writer *w, cairo_surface_t *image)
+{
+  bool initial_depth = w->heading_depth;
+  if (!initial_depth)
+    spv_writer_open_file (w);
+
+  char *uri = xasprintf ("%010d_Imagegeneric.png", ++w->n_tables);
+
+  start_container (w);
+
+  start_elem (w, "label");
+  write_text (w, "Image");
+  end_elem (w);
+
+  start_elem (w, "object");
+  write_attr (w, "type", "unknown");
+  write_attr (w, "uri", uri);
+  end_elem (w); /* object */
+  end_elem (w); /* container */
+
+  if (!initial_depth)
+    spv_writer_close_file (w, "");
+
+  zip_writer_add_start (w->zw, uri);
+  cairo_surface_write_to_png_stream (image, write_to_zip, w->zw);
+  zip_writer_add_finish (w->zw);
+
+  free (uri);
+}
+#endif
+
 void
 spv_writer_eject_page (struct spv_writer *w)
 {
index 94c70f718503900aa0ea64bb91fc4c69fa6d0f08..4126a8606db273dc200277af3f870a1a305dc290 100644 (file)
@@ -22,6 +22,10 @@ struct pivot_table;
 struct spv_writer;
 struct text_item;
 
+#ifdef HAVE_CAIRO
+#include <cairo.h>
+#endif
+
 #include "libpspp/compiler.h"
 
 char *spv_writer_open (const char *filename, struct spv_writer **)
@@ -39,6 +43,10 @@ void spv_writer_put_text (struct spv_writer *, const struct text_item *,
                           const char *command_id);
 void spv_writer_put_table (struct spv_writer *, const struct pivot_table *);
 
+#ifdef HAVE_CAIRO
+void spv_writer_put_image (struct spv_writer *, cairo_surface_t *);
+#endif
+
 void spv_writer_eject_page (struct spv_writer *);
 
 #endif /* output/spv/spv-writer.h */
index 29c26c275b5bfdaa40389ddbecf0d727e58f3dd6..574f068f54077a6c69b0e46053e96bea7290fb02 100644 (file)
@@ -77,7 +77,7 @@ spv_item_type_to_string (enum spv_item_type type)
     case SPV_ITEM_TABLE: return "table";
     case SPV_ITEM_GRAPH: return "graph";
     case SPV_ITEM_MODEL: return "model";
-    case SPV_ITEM_OBJECT: return "object";
+    case SPV_ITEM_IMAGE: return "image";
     default: return "**error**";
     }
 }
@@ -140,7 +140,7 @@ spv_item_get_class (const struct spv_item *item)
     case SPV_ITEM_MODEL:
       return SPV_CLASS_MODELS;
 
-    case SPV_ITEM_OBJECT:
+    case SPV_ITEM_IMAGE:
       return SPV_CLASS_OTHER;
 
     case SPV_ITEM_TREE:
@@ -195,6 +195,53 @@ spv_item_get_text (const struct spv_item *item)
   return item->text;
 }
 
+bool
+spv_item_is_image (const struct spv_item *item)
+{
+  return item->type == SPV_ITEM_IMAGE;
+}
+
+#ifdef HAVE_CAIRO
+static cairo_status_t
+read_from_zip_member (void *zm_, unsigned char *data, unsigned int length)
+{
+  struct zip_member *zm = zm_;
+  if (!zm)
+    return CAIRO_STATUS_READ_ERROR;
+
+  while (length > 0)
+    {
+      int n = zip_member_read (zm, data, length);
+      if (n <= 0)
+        return CAIRO_STATUS_READ_ERROR;
+
+      data += n;
+      length -= n;
+    }
+
+  return CAIRO_STATUS_SUCCESS;
+}
+
+cairo_surface_t *
+spv_item_get_image (const struct spv_item *item_)
+{
+  struct spv_item *item = CONST_CAST (struct spv_item *, item_);
+  assert (spv_item_is_image (item));
+
+  if (!item->image)
+    {
+      struct zip_member *zm = zip_member_open (item->spv->zip,
+                                               item->png_member);
+      item->image = cairo_image_surface_create_from_png_stream (
+        read_from_zip_member, zm);
+      if (zm)
+        zip_member_finish (zm);
+    }
+
+  return item->image;
+}
+#endif
+
 struct spv_item *
 spv_item_next (const struct spv_item *item)
 {
@@ -267,8 +314,11 @@ spv_item_destroy (struct spv_item *item)
 
       pivot_value_destroy (item->text);
 
-      free (item->object_type);
-      free (item->uri);
+      free (item->png_member);
+#ifdef HAVE_CAIRO
+      if (item->image)
+        cairo_surface_destroy (item->image);
+#endif
 
       free (item);
     }
@@ -573,6 +623,10 @@ spv_item_load (const struct spv_item *item)
 {
   if (spv_item_is_table (item))
     spv_item_get_table (item);
+#ifdef HAVE_CAIRO
+  else if (spv_item_is_image (item))
+    spv_item_get_image (item);
+#endif
 }
 
 bool
@@ -934,24 +988,17 @@ spv_decode_container (const struct spvsx_container *c,
   else if (spvsx_is_object (content))
     {
       struct spvsx_object *object = spvsx_cast_object (content);
-      item->type = SPV_ITEM_OBJECT;
-      item->object_type = xstrdup (object->type);
-      item->uri = xstrdup (object->uri);
+      item->type = SPV_ITEM_IMAGE;
+      item->png_member = xstrdup (object->uri);
     }
   else if (spvsx_is_image (content))
     {
       struct spvsx_image *image = spvsx_cast_image (content);
-      item->type = SPV_ITEM_OBJECT;
-      item->object_type = xstrdup ("image");
-      item->uri = xstrdup (image->data_path->text);
+      item->type = SPV_ITEM_IMAGE;
+      item->png_member = xstrdup (image->data_path->text);
     }
   else if (spvsx_is_tree (content))
-    {
-      struct spvsx_tree *tree = spvsx_cast_tree (content);
-      item->type = SPV_ITEM_TREE;
-      item->object_type = xstrdup ("tree");
-      item->uri = xstrdup (tree->data_path->text);
-    }
+    item->type = SPV_ITEM_TREE;
   else
     NOT_REACHED ();
 
index d1706f4e7ecc4334c2e4d49e6f45b3fdc6335161..fdcd9f9f8daf804d172e9a14e1d4ad33f6aa7905 100644 (file)
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
+
+#ifdef HAVE_CAIRO
+#include <cairo.h>
+#endif
+
 #include "libpspp/compiler.h"
 
 struct fmt_spec;
@@ -68,7 +73,7 @@ enum spv_item_type
     SPV_ITEM_TABLE,
     SPV_ITEM_GRAPH,
     SPV_ITEM_MODEL,
-    SPV_ITEM_OBJECT,
+    SPV_ITEM_IMAGE,
     SPV_ITEM_TREE,
   };
 
@@ -138,9 +143,11 @@ struct spv_item
     /* SPV_ITEM_TEXT only.  */
     struct pivot_value *text;
 
-    /* SPV_ITEM_OBJECT only. */
-    char *object_type;
-    char *uri;
+    /* SPV_ITEM_IMAGE only. */
+    char *png_member;
+#ifdef HAVE_CAIRO
+    cairo_surface_t *image;
+#endif
   };
 
 void spv_item_format_path (const struct spv_item *, struct string *);
@@ -162,6 +169,11 @@ const struct pivot_table *spv_item_get_table (const struct spv_item *);
 bool spv_item_is_text (const struct spv_item *);
 const struct pivot_value *spv_item_get_text (const struct spv_item *);
 
+bool spv_item_is_image (const struct spv_item *);
+#ifdef HAVE_CAIRO
+cairo_surface_t *spv_item_get_image (const struct spv_item *);
+#endif
+
 bool spv_item_is_visible (const struct spv_item *);
 
 #define SPV_ITEM_FOR_EACH(ITER, ROOT) \
@@ -173,7 +185,6 @@ struct spv_item *spv_item_next (const struct spv_item *);
 const struct spv_item *spv_item_get_parent (const struct spv_item *);
 size_t spv_item_get_level (const struct spv_item *);
 
-const char *spv_item_get_member_name (const struct spv_item *);
 const char *spv_item_get_command_id (const struct spv_item *);
 const char *spv_item_get_subtype (const struct spv_item *);
 
index 09288e8c7ee3aaa1abcf6c6c330f50a719e1b68d..d185316536d88802bfdae4766afde5dcd633d548 100644 (file)
@@ -191,6 +191,6 @@ pageParagraph => pageParagraph_text
 
 text[pageParagraph_text] :type=(title | text) => TEXT
 
-object :type :uri => EMPTY
+object :type[object_type]=(unknown)? :uri => EMPTY
 
 image :VDPId :commandName => dataPath
index 2dcfcfb0c903cdcc7bf3d68fa54fcb6f38b78e8e..7c4247f1a188eeb3d531d17eb8c7c79b688ad465 100644 (file)
@@ -41,6 +41,7 @@
 #endif
 #include "output/chart-item.h"
 #include "output/driver-provider.h"
+#include "output/image-item.h"
 #include "output/message-item.h"
 #include "output/options.h"
 #include "output/output-item-provider.h"
@@ -324,6 +325,18 @@ tex_submit (struct output_driver *driver,
       tex_output_table (tex, table_item);
     }
 #ifdef HAVE_CAIRO
+  else if (is_image_item (output_item) && tex->chart_file_name != NULL)
+    {
+      struct image_item *image_item = to_image_item (output_item);
+      char *file_name = xr_write_png_image (
+        image_item->image, tex->chart_file_name, tex->chart_cnt++);
+      if (file_name != NULL)
+        {
+          shipout (&tex->token_list, "\\includegraphics{%s}\n", file_name);
+          tex->require_graphics = true;
+          free (file_name);
+        }
+    }
   else if (is_chart_item (output_item) && tex->chart_file_name != NULL)
     {
       struct chart_item *chart_item = to_chart_item (output_item);
index fe4119dd374fd8d40626693eb7184c791ff5ec16..7ad2c5a384cb9e6e84c981fdf407b9d59bd2a38a 100644 (file)
@@ -33,6 +33,7 @@
 #include "data/dataset.h"
 #include "libpspp/version.h"
 #include "output/group-item.h"
+#include "output/image-item.h"
 #include "output/pivot-table.h"
 #include "output/spv/spv.h"
 #include "output/spv/spv-output.h"
@@ -798,6 +799,12 @@ read_spv_file (const char *filename)
         spv_text_submit (items[i]);
       else if (items[i]->type == SPV_ITEM_TABLE)
         pivot_table_submit (pivot_table_ref (spv_item_get_table (items[i])));
+      else if (items[i]->type == SPV_ITEM_IMAGE)
+        {
+          cairo_surface_t *image = spv_item_get_image (items[i]);
+          image_item_submit (image_item_create (cairo_surface_reference (
+                                                  image)));
+        }
       prev_heading = heading;
     }
   dump_heading_transition (prev_heading, spv_get_root (spv));
index 6e8c3323a868172b2df204580cae90a110cc66cc..56a46f001d876c835936ecc172041730b3006197 100644 (file)
@@ -16,6 +16,9 @@
 
 #include <config.h>
 
+#ifdef HAVE_CAIRO
+#include <cairo.h>
+#endif
 #include <getopt.h>
 #include <limits.h>
 #include <stdlib.h>
@@ -29,6 +32,7 @@
 #include "libpspp/string-set.h"
 #include "output/driver.h"
 #include "output/group-item.h"
+#include "output/image-item.h"
 #include "output/page-setup-item.h"
 #include "output/pivot-table.h"
 #include "output/spv/light-binary-parser.h"
@@ -121,7 +125,11 @@ dump_item (const struct spv_item *item)
     case SPV_ITEM_MODEL:
       break;
 
-    case SPV_ITEM_OBJECT:
+    case SPV_ITEM_IMAGE:
+#ifdef HAVE_CAIRO
+      image_item_submit (image_item_create (cairo_surface_reference (
+                                              spv_item_get_image (item))));
+#endif
       break;
 
     case SPV_ITEM_TREE:
@@ -164,14 +172,19 @@ print_item_directory (const struct spv_item *item)
 
   if (!spv_item_is_visible (item))
     printf (" (hidden)");
-  if (show_member_names && (item->xml_member || item->bin_member))
+
+  if (show_member_names)
     {
-      if (item->xml_member && item->bin_member)
-        printf (" in %s and %s", item->xml_member, item->bin_member);
-      else if (item->xml_member)
-        printf (" in %s", item->xml_member);
-      else if (item->bin_member)
-        printf (" in %s", item->bin_member);
+      const char *members[] = {
+        item->xml_member,
+        item->bin_member,
+        item->png_member,
+      };
+      size_t n = 0;
+
+      for (size_t i = 0; i < sizeof members / sizeof *members; i++)
+        if (members[i])
+          printf (" %s %s", n++ == 0 ? "in" : "and", members[i]);
     }
   putchar ('\n');
 }