From f8659933d48c5682010d1e1f04ae7acb5cbcd611 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Thu, 7 Jan 2021 22:49:24 -0800 Subject: [PATCH] Add support for PNG images in .spv files. This also allows us to save charts to .spv files as images. --- doc/dev/spv-file-format.texi | 28 ++++++++ src/output/ascii.c | 20 ++++++ src/output/automake.mk | 2 + src/output/cairo-chart.c | 62 ++++++++++-------- src/output/cairo-chart.h | 6 ++ src/output/cairo-fsm.c | 67 +++++++++++++++++++ src/output/html.c | 12 ++++ src/output/image-item.c | 93 +++++++++++++++++++++++++++ src/output/image-item.h | 96 ++++++++++++++++++++++++++++ src/output/spv-driver.c | 16 +++++ src/output/spv/spv-dump.c | 4 +- src/output/spv/spv-writer.c | 42 ++++++++++++ src/output/spv/spv-writer.h | 8 +++ src/output/spv/spv.c | 79 ++++++++++++++++++----- src/output/spv/spv.h | 21 ++++-- src/output/spv/structure-xml.grammar | 2 +- src/output/tex.c | 13 ++++ src/ui/gui/psppire-window.c | 7 ++ utilities/pspp-output.c | 29 ++++++--- 19 files changed, 550 insertions(+), 57 deletions(-) create mode 100644 src/output/image-item.c create mode 100644 src/output/image-item.h diff --git a/doc/dev/spv-file-format.texi b/doc/dev/spv-file-format.texi index ebeac668cb..b27037cce7 100644 --- a/doc/dev/spv-file-format.texi +++ b/doc/dev/spv-file-format.texi @@ -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 diff --git a/src/output/ascii.c b/src/output/ascii.c index 46275bc363..b5c9c004b9 100644 --- a/src/output/ascii.c +++ b/src/output/ascii.c @@ -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); diff --git a/src/output/automake.mk b/src/output/automake.mk index fb476f4649..3e0b8f46f3 100644 --- a/src/output/automake.mk +++ b/src/output/automake.mk @@ -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 \ diff --git a/src/output/cairo-chart.c b/src/output/cairo-chart.c index 62459d6f57..11175292ec 100644 --- a/src/output/cairo-chart.c +++ b/src/output/cairo-chart.c @@ -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, diff --git a/src/output/cairo-chart.h b/src/output/cairo-chart.h index bbcc606d31..f02fd4efac 100644 --- a/src/output/cairo-chart.h +++ b/src/output/cairo-chart.h @@ -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, diff --git a/src/output/cairo-fsm.c b/src/output/cairo-fsm.c index 1671edac4f..2b47615be5 100644 --- a/src/output/cairo-fsm.c +++ b/src/output/cairo-fsm.c @@ -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; diff --git a/src/output/html.c b/src/output/html.c index ecaf729dd7..e865642a6e 100644 --- a/src/output/html.c +++ b/src/output/html.c @@ -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, "", 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 index 0000000000..62efe7b5c3 --- /dev/null +++ b/src/output/image-item.c @@ -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 . */ + +#include + +#include "output/image-item.h" + +#include +#include + +#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; +} + +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 index 0000000000..91d7c2e131 --- /dev/null +++ b/src/output/image-item.h @@ -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 . */ + +#ifndef OUTPUT_IMAGE_ITEM_H +#define OUTPUT_IMAGE_ITEM_H 1 + +#ifdef HAVE_CAIRO +#include +#endif +#include +#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 *); + +/* This boilerplate for image_item, a subclass of output_item, was + autogenerated by mk-class-boilerplate. */ + +#include +#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 *); + +#endif /* output/image-item.h */ diff --git a/src/output/spv-driver.c b/src/output/spv-driver.c index 0c9d1412a5..0fb0928372 100644 --- a/src/output/spv-driver.c +++ b/src/output/spv-driver.c @@ -22,6 +22,10 @@ #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 (); diff --git a/src/output/spv/spv-dump.c b/src/output/spv/spv-dump.c index 70d2a18791..db8a60d9ed 100644 --- a/src/output/spv/spv-dump.c +++ b/src/output/spv/spv-dump.c @@ -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: diff --git a/src/output/spv/spv-writer.c b/src/output/spv/spv-writer.c index c27368aa63..c5a600571a 100644 --- a/src/output/spv/spv-writer.c +++ b/src/output/spv/spv-writer.c @@ -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) { diff --git a/src/output/spv/spv-writer.h b/src/output/spv/spv-writer.h index 94c70f7185..4126a8606d 100644 --- a/src/output/spv/spv-writer.h +++ b/src/output/spv/spv-writer.h @@ -22,6 +22,10 @@ struct pivot_table; struct spv_writer; struct text_item; +#ifdef HAVE_CAIRO +#include +#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 */ diff --git a/src/output/spv/spv.c b/src/output/spv/spv.c index 29c26c275b..574f068f54 100644 --- a/src/output/spv/spv.c +++ b/src/output/spv/spv.c @@ -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 (); diff --git a/src/output/spv/spv.h b/src/output/spv/spv.h index d1706f4e7e..fdcd9f9f8d 100644 --- a/src/output/spv/spv.h +++ b/src/output/spv/spv.h @@ -29,6 +29,11 @@ #include #include #include + +#ifdef HAVE_CAIRO +#include +#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 *); diff --git a/src/output/spv/structure-xml.grammar b/src/output/spv/structure-xml.grammar index 09288e8c7e..d185316536 100644 --- a/src/output/spv/structure-xml.grammar +++ b/src/output/spv/structure-xml.grammar @@ -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 diff --git a/src/output/tex.c b/src/output/tex.c index 2dcfcfb0c9..7c4247f1a1 100644 --- a/src/output/tex.c +++ b/src/output/tex.c @@ -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); diff --git a/src/ui/gui/psppire-window.c b/src/ui/gui/psppire-window.c index fe4119dd37..7ad2c5a384 100644 --- a/src/ui/gui/psppire-window.c +++ b/src/ui/gui/psppire-window.c @@ -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)); diff --git a/utilities/pspp-output.c b/utilities/pspp-output.c index 6e8c3323a8..56a46f001d 100644 --- a/utilities/pspp-output.c +++ b/utilities/pspp-output.c @@ -16,6 +16,9 @@ #include +#ifdef HAVE_CAIRO +#include +#endif #include #include #include @@ -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'); } -- 2.30.2