summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--xps/ghostxps.h412
-rw-r--r--xps/xpsanalyze.c321
-rw-r--r--xps/xpscolor.c248
-rw-r--r--xps/xpscommon.c115
-rw-r--r--xps/xpscrc.c95
-rw-r--r--xps/xpsdoc.c277
-rw-r--r--xps/xpsfont.c533
-rw-r--r--xps/xpsglyphs.c675
-rw-r--r--xps/xpsgradient.c978
-rw-r--r--xps/xpshash.c217
-rw-r--r--xps/xpsimage.c470
-rw-r--r--xps/xpsjpeg.c143
-rw-r--r--xps/xpsjxr.c259
-rw-r--r--xps/xpsmem.c182
-rw-r--r--xps/xpsopacity.c102
-rw-r--r--xps/xpspage.c281
-rw-r--r--xps/xpspath.c1036
-rw-r--r--xps/xpspng.c293
-rw-r--r--xps/xpsresource.c204
-rw-r--r--xps/xpstiff.c1091
-rw-r--r--xps/xpstile.c399
-rw-r--r--xps/xpstop.c576
-rw-r--r--xps/xpsutf.c69
-rw-r--r--xps/xpsvisual.c62
-rw-r--r--xps/xpsxml.c353
-rw-r--r--xps/xpszip.c568
26 files changed, 9959 insertions, 0 deletions
diff --git a/xps/ghostxps.h b/xps/ghostxps.h
new file mode 100644
index 00000000..3f06a80d
--- /dev/null
+++ b/xps/ghostxps.h
@@ -0,0 +1,412 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* combined internal header for the XPS interpreter */
+
+#include "memory_.h"
+#include "math_.h"
+
+#include <stdlib.h>
+#include <ctype.h> /* for toupper() */
+
+#include "gp.h"
+
+#include "gsgc.h"
+#include "gstypes.h"
+#include "gsstate.h"
+#include "gsmatrix.h"
+#include "gscoord.h"
+#include "gsmemory.h"
+#include "gsparam.h"
+#include "gsdevice.h"
+#include "scommon.h"
+#include "gdebug.h"
+#include "gserror.h"
+#include "gserrors.h"
+#include "gspaint.h"
+#include "gspath.h"
+#include "gsimage.h"
+#include "gscspace.h"
+#include "gsptype1.h"
+#include "gscolor2.h"
+#include "gscolor3.h"
+#include "gsutil.h"
+#include "gsicc.h"
+
+#include "gstrans.h"
+
+#include "gxpath.h" /* gsshade.h depends on it */
+#include "gxfixed.h" /* gsshade.h depends on it */
+#include "gxmatrix.h" /* gxtype1.h depends on it */
+#include "gsshade.h"
+#include "gsfunc.h"
+#include "gsfunc3.h" /* we use stitching and exponential interp */
+
+#include "gxfont.h"
+#include "gxchar.h"
+#include "gxcolor2.h" /* Required for definition of gs_pattern1_instance_t */
+#include "gxtype1.h"
+#include "gxfont1.h"
+#include "gxfont42.h"
+#include "gxfcache.h"
+#include "gxistate.h"
+
+#include "gzstate.h"
+#include "gzpath.h"
+#include "gzcpath.h"
+
+#include "gsicc_manage.h"
+#include "gscms.h"
+#include "gsicc_cache.h"
+
+#include "zlib.h"
+
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+#ifndef MAX
+#define MAX(a,b) ((a) < (b) ? (b) : (a))
+#endif
+#ifndef ABS
+#define ABS(a) ((a) < 0 ? -(a) : (a))
+#endif
+
+/*
+ * XPS and ZIP contants.
+ */
+
+typedef struct xps_context_s xps_context_t;
+
+#define REL_START_PART \
+ "http://schemas.microsoft.com/xps/2005/06/fixedrepresentation"
+#define REL_REQUIRED_RESOURCE \
+ "http://schemas.microsoft.com/xps/2005/06/required-resource"
+#define REL_REQUIRED_RESOURCE_RECURSIVE \
+ "http://schemas.microsoft.com/xps/2005/06/required-resource#recursive"
+
+#define ZIP_LOCAL_FILE_SIG 0x04034b50
+#define ZIP_DATA_DESC_SIG 0x08074b50
+#define ZIP_CENTRAL_DIRECTORY_SIG 0x02014b50
+#define ZIP_END_OF_CENTRAL_DIRECTORY_SIG 0x06054b50
+
+/*
+ * Memory, and string functions.
+ */
+
+void * xps_realloc_imp(xps_context_t *ctx, void *ptr, int size, const char *func);
+
+#define xps_alloc(ctx, size) \
+ ((void*)gs_alloc_bytes(ctx->memory, size, __func__))
+#define xps_realloc(ctx, ptr, size) \
+ xps_realloc_imp(ctx, ptr, size, __func__)
+#define xps_strdup(ctx, str) \
+ xps_strdup_imp(ctx, str, __func__)
+#define xps_free(ctx, ptr) \
+ gs_free_object(ctx->memory, ptr, __func__)
+
+size_t xps_strlcpy(char *destination, const char *source, size_t size);
+size_t xps_strlcat(char *destination, const char *source, size_t size);
+int xps_strcasecmp(char *a, char *b);
+char *xps_strdup_imp(xps_context_t *ctx, const char *str, const char *function);
+void xps_absolute_path(char *output, char *base_uri, char *path, int output_size);
+
+int xps_utf8_to_ucs(int *p, const char *s, int n);
+
+/*
+ * Generic hashtable.
+ */
+
+typedef struct xps_hash_table_s xps_hash_table_t;
+
+xps_hash_table_t *xps_hash_new(xps_context_t *ctx);
+void *xps_hash_lookup(xps_hash_table_t *table, char *key);
+int xps_hash_insert(xps_context_t *ctx, xps_hash_table_t *table, char *key, void *value);
+void xps_hash_free(xps_context_t *ctx, xps_hash_table_t *table,
+ void (*free_key)(xps_context_t *ctx, void *),
+ void (*free_value)(xps_context_t *ctx, void *));
+void xps_hash_debug(xps_hash_table_t *table);
+
+/*
+ * Container parts.
+ */
+
+typedef struct xps_part_s xps_part_t;
+
+struct xps_part_s
+{
+ char *name;
+ int size;
+ int cap;
+ byte *data;
+};
+
+xps_part_t *xps_new_part(xps_context_t *ctx, char *name, int size);
+xps_part_t *xps_read_part(xps_context_t *ctx, char *partname);
+void xps_free_part(xps_context_t *ctx, xps_part_t *part);
+
+/*
+ * Document structure.
+ */
+
+typedef struct xps_document_s xps_document_t;
+typedef struct xps_page_s xps_page_t;
+
+struct xps_document_s
+{
+ char *name;
+ xps_document_t *next;
+};
+
+struct xps_page_s
+{
+ char *name;
+ int width;
+ int height;
+ xps_page_t *next;
+};
+
+int xps_parse_metadata(xps_context_t *ctx, xps_part_t *part);
+void xps_free_fixed_pages(xps_context_t *ctx);
+void xps_free_fixed_documents(xps_context_t *ctx);
+void xps_debug_fixdocseq(xps_context_t *ctx);
+
+/*
+ * Images.
+ */
+
+typedef struct xps_image_s xps_image_t;
+
+/* type for the information derived directly from the raster file format */
+
+struct xps_image_s
+{
+ int width;
+ int height;
+ int stride;
+ gs_color_space *colorspace;
+ int comps;
+ int hasalpha; /* chunky alpha */
+ int bits;
+ int xres;
+ int yres;
+ byte *samples;
+ byte *alpha; /* isolated alpha plane */
+ byte *profile;
+ int profilesize;
+};
+
+int xps_decode_jpeg(xps_context_t *ctx, byte *rbuf, int rlen, xps_image_t *image);
+int xps_decode_png(xps_context_t *ctx, byte *rbuf, int rlen, xps_image_t *image);
+int xps_decode_tiff(xps_context_t *ctx, byte *rbuf, int rlen, xps_image_t *image);
+int xps_decode_jpegxr(xps_context_t *ctx, byte *buf, int len, xps_image_t *image);
+
+int xps_png_has_alpha(xps_context_t *ctx, byte *rbuf, int rlen);
+int xps_tiff_has_alpha(xps_context_t *ctx, byte *rbuf, int rlen);
+int xps_jpegxr_has_alpha(xps_context_t *ctx, byte *buf, int len);
+
+void xps_free_image(xps_context_t *ctx, xps_image_t *image);
+
+/*
+ * Fonts.
+ */
+
+typedef struct xps_font_s xps_font_t;
+typedef struct xps_glyph_metrics_s xps_glyph_metrics_t;
+
+struct xps_font_s
+{
+ byte *data;
+ int length;
+ gs_font *font;
+
+ int subfontid;
+ int cmaptable;
+ int cmapsubcount;
+ int cmapsubtable;
+ int usepua;
+
+ /* these are for CFF opentypes only */
+ byte *cffdata;
+ byte *cffend;
+ byte *gsubrs;
+ byte *subrs;
+ byte *charstrings;
+};
+
+struct xps_glyph_metrics_s
+{
+ float hadv, vadv, vorg;
+};
+
+xps_font_t *xps_new_font(xps_context_t *ctx, byte *buf, int buflen, int index);
+void xps_free_font(xps_context_t *ctx, xps_font_t *font);
+
+int xps_count_font_encodings(xps_font_t *font);
+void xps_identify_font_encoding(xps_font_t *font, int idx, int *pid, int *eid);
+void xps_select_font_encoding(xps_font_t *font, int idx);
+int xps_encode_font_char(xps_font_t *font, int key);
+
+void xps_measure_font_glyph(xps_context_t *ctx, xps_font_t *font, int gid, xps_glyph_metrics_t *mtx);
+
+int xps_find_sfnt_table(xps_font_t *font, const char *name, int *lengthp);
+void xps_load_sfnt_name(xps_font_t *font, char *namep);
+int xps_init_truetype_font(xps_context_t *ctx, xps_font_t *font);
+int xps_init_postscript_font(xps_context_t *ctx, xps_font_t *font);
+
+void xps_debug_path(xps_context_t *ctx);
+
+/*
+ * Colorspaces and colors.
+ */
+
+gs_color_space *xps_read_icc_colorspace(xps_context_t *ctx, char *base_uri, char *profile);
+void xps_parse_color(xps_context_t *ctx, char *base_uri, char *hexstring, gs_color_space **csp, float *samples);
+void xps_set_color(xps_context_t *ctx, gs_color_space *colorspace, float *samples);
+
+/*
+ * XML document model
+ */
+
+typedef struct xps_item_s xps_item_t;
+
+xps_item_t * xps_parse_xml(xps_context_t *ctx, byte *buf, int len);
+xps_item_t * xps_next(xps_item_t *item);
+xps_item_t * xps_down(xps_item_t *item);
+char * xps_tag(xps_item_t *item);
+char * xps_att(xps_item_t *item, const char *att);
+void xps_free_item(xps_context_t *ctx, xps_item_t *item);
+void xps_debug_item(xps_item_t *item, int level);
+
+/*
+ * Resource dictionaries.
+ */
+
+typedef struct xps_resource_s xps_resource_t;
+
+struct xps_resource_s
+{
+ char *name;
+ char *base_uri; /* only used in the head nodes */
+ xps_item_t *base_xml; /* only used in the head nodes, to free the xml document */
+ xps_item_t *data;
+ xps_resource_t *next;
+ xps_resource_t *parent; /* up to the previous dict in the stack */
+};
+
+int xps_parse_resource_dictionary(xps_context_t *ctx, xps_resource_t **dictp, char *base_uri, xps_item_t *root);
+void xps_free_resource_dictionary(xps_context_t *ctx, xps_resource_t *dict);
+void xps_resolve_resource_reference(xps_context_t *ctx, xps_resource_t *dict, char **attp, xps_item_t **tagp, char **urip);
+
+void xps_debug_resource_dictionary(xps_resource_t *dict);
+
+/*
+ * Fixed page/graphics parsing.
+ */
+
+int xps_parse_fixed_page(xps_context_t *ctx, xps_part_t *part);
+int xps_parse_canvas(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *node);
+int xps_parse_path(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *node);
+int xps_parse_glyphs(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *node);
+int xps_parse_solid_color_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *node);
+int xps_parse_image_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *node);
+int xps_parse_visual_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *node);
+int xps_parse_linear_gradient_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *node);
+int xps_parse_radial_gradient_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *node);
+
+int xps_parse_tiling_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root, int (*func)(xps_context_t*, char*, xps_resource_t*, xps_item_t*, void*), void *user);
+
+void xps_parse_matrix_transform(xps_context_t *ctx, xps_item_t *root, gs_matrix *matrix);
+void xps_parse_render_transform(xps_context_t *ctx, char *text, gs_matrix *matrix);
+void xps_parse_rectangle(xps_context_t *ctx, char *text, gs_rect *rect);
+void xps_parse_abbreviated_geometry(xps_context_t *ctx, char *geom);
+void xps_parse_path_geometry(xps_context_t *ctx, xps_resource_t *dict, xps_item_t *root, int stroking);
+
+int xps_begin_opacity(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, char *opacity_att, xps_item_t *opacity_mask_tag);
+void xps_end_opacity(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, char *opacity_att, xps_item_t *opacity_mask_tag);
+
+int xps_parse_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *node);
+int xps_parse_element(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *node);
+
+void xps_clip(xps_context_t *ctx);
+void xps_fill(xps_context_t *ctx);
+void xps_bounds_in_user_space(xps_context_t *ctx, gs_rect *user);
+
+int xps_element_has_transparency(xps_context_t *ctx, char *base_uri, xps_item_t *node);
+int xps_resource_dictionary_has_transparency(xps_context_t *ctx, char *base_uri, xps_item_t *node);
+int xps_image_brush_has_transparency(xps_context_t *ctx, char *base_uri, xps_item_t *root);
+
+/*
+ * The interpreter context.
+ */
+
+typedef struct xps_entry_s xps_entry_t;
+
+struct xps_entry_s
+{
+ char *name;
+ int offset;
+ int csize;
+ int usize;
+};
+
+struct xps_context_s
+{
+ void *instance;
+ gs_memory_t *memory;
+ gs_state *pgs;
+ gs_font_dir *fontdir;
+
+ gs_color_space *gray;
+ gs_color_space *srgb;
+ gs_color_space *scrgb;
+ gs_color_space *cmyk;
+
+ char *directory;
+ FILE *file;
+ int zip_count;
+ xps_entry_t *zip_table;
+
+ char *start_part; /* fixed document sequence */
+ xps_document_t *first_fixdoc; /* first fixed document */
+ xps_document_t *last_fixdoc; /* last fixed document */
+ xps_page_t *first_page; /* first page of document */
+ xps_page_t *last_page; /* last page of document */
+
+ char *base_uri; /* base uri for parsing XML and resolving relative paths */
+ char *part_uri; /* part uri for parsing metadata relations */
+
+ /* We cache font and colorspace resources */
+ xps_hash_table_t *font_table;
+ xps_hash_table_t *colorspace_table;
+
+ /* Global toggle for transparency */
+ int use_transparency;
+
+ /* Hack to workaround ghostscript's lack of understanding
+ * the pdf 1.4 specification of Alpha only transparency groups.
+ * We have to force all colors to be grayscale whenever we are computing
+ * opacity masks.
+ */
+ int opacity_only;
+
+ /* The fill_rule is set by path parsing.
+ * It is used by clip/fill functions.
+ * 1=nonzero, 0=evenodd
+ */
+ int fill_rule;
+};
+
+int xps_process_file(xps_context_t *ctx, char *filename);
+
+/* end of page device callback foo */
+int xps_show_page(xps_context_t *ctx, int num_copies, int flush);
diff --git a/xps/xpsanalyze.c b/xps/xpsanalyze.c
new file mode 100644
index 00000000..6e2f9d3e
--- /dev/null
+++ b/xps/xpsanalyze.c
@@ -0,0 +1,321 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* XPS interpreter - analyze page checking for transparency.
+ * This is a stripped down parser that looks for alpha values < 1.0 in
+ * any part of the page.
+ */
+
+#include "ghostxps.h"
+
+static int
+xps_remote_resource_dictionary_has_transparency(xps_context_t *ctx, char *base_uri, char *source_att)
+{
+ //dputs("page has transparency: uses a remote resource; not parsed; being conservative\n");
+ return 1;
+}
+
+int
+xps_resource_dictionary_has_transparency(xps_context_t *ctx, char *base_uri, xps_item_t *root)
+{
+ char *source;
+ xps_item_t *node;
+
+ source = xps_att(root, "Source");
+ if (source)
+ return xps_remote_resource_dictionary_has_transparency(ctx, base_uri, source);
+
+ for (node = xps_down(root); node; node = xps_next(node))
+ {
+ // TODO: ... all kinds of stuff can be here, brushes, elements, whatnot
+ }
+
+ return 1;
+}
+
+static int
+xps_gradient_stops_have_transparency(xps_context_t *ctx, char *base_uri, xps_item_t *root)
+{
+ xps_item_t *node;
+ gs_color_space *colorspace;
+ char *color_att;
+ float samples[32];
+
+ for (node = xps_down(root); node; node = xps_next(node))
+ {
+ if (!strcmp(xps_tag(node), "GradientStop"))
+ {
+ color_att = xps_att(node, "Color");
+ if (color_att)
+ {
+ xps_parse_color(ctx, base_uri, color_att, &colorspace, samples);
+ if (samples[0] < 1.0)
+ {
+ //dputs("page has transparency: GradientStop has alpha\n");
+ return 1;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int
+xps_gradient_brush_has_transparency(xps_context_t *ctx, char *base_uri, xps_item_t *root)
+{
+ xps_item_t *node;
+ char *opacity_att;
+
+ opacity_att = xps_att(root, "Opacity");
+ if (opacity_att)
+ {
+ if (atof(opacity_att) < 1.0)
+ {
+ //dputs("page has transparency: GradientBrush Opacity\n");
+ return 1;
+ }
+ }
+
+ for (node = xps_down(root); node; node = xps_next(node))
+ {
+ if (!strcmp(xps_tag(node), "RadialGradientBrush.GradientStops"))
+ {
+ if (xps_gradient_stops_have_transparency(ctx, base_uri, node))
+ return 1;
+ }
+ if (!strcmp(xps_tag(node), "LinearGradientBrush.GradientStops"))
+ {
+ if (xps_gradient_stops_have_transparency(ctx, base_uri, node))
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+xps_brush_has_transparency(xps_context_t *ctx, char *base_uri, xps_item_t *root)
+{
+ char *opacity_att;
+ char *color_att;
+ xps_item_t *node;
+
+ gs_color_space *colorspace;
+ float samples[32];
+
+ if (!strcmp(xps_tag(root), "SolidColorBrush"))
+ {
+ opacity_att = xps_att(root, "Opacity");
+ if (opacity_att)
+ {
+ if (atof(opacity_att) < 1.0)
+ {
+ //dputs("page has transparency: SolidColorBrush Opacity\n");
+ return 1;
+ }
+ }
+
+ color_att = xps_att(root, "Color");
+ if (color_att)
+ {
+ xps_parse_color(ctx, base_uri, color_att, &colorspace, samples);
+ if (samples[0] < 1.0)
+ {
+ //dputs("page has transparency: SolidColorBrush Color has alpha\n");
+ return 1;
+ }
+ }
+ }
+
+ if (!strcmp(xps_tag(root), "VisualBrush"))
+ {
+ char *opacity_att = xps_att(root, "Opacity");
+ if (opacity_att)
+ {
+ if (atof(opacity_att) < 1.0)
+ {
+ //dputs("page has transparency: VisualBrush Opacity\n");
+ return 1;
+ }
+ }
+
+ for (node = xps_down(root); node; node = xps_next(node))
+ {
+ if (!strcmp(xps_tag(node), "VisualBrush.Visual"))
+ {
+ if (xps_element_has_transparency(ctx, base_uri, xps_down(node)))
+ return 1;
+ }
+ }
+ }
+
+ if (!strcmp(xps_tag(root), "ImageBrush"))
+ {
+ if (xps_image_brush_has_transparency(ctx, base_uri, root))
+ return 1;
+ }
+
+ if (!strcmp(xps_tag(root), "LinearGradientBrush"))
+ {
+ if (xps_gradient_brush_has_transparency(ctx, base_uri, root))
+ return 1;
+ }
+
+ if (!strcmp(xps_tag(root), "RadialGradientBrush"))
+ {
+ if (xps_gradient_brush_has_transparency(ctx, base_uri, root))
+ return 1;
+ }
+
+ return 0;
+}
+
+static int
+xps_path_has_transparency(xps_context_t *ctx, char *base_uri, xps_item_t *root)
+{
+ xps_item_t *node;
+
+ for (node = xps_down(root); node; node = xps_next(node))
+ {
+ if (!strcmp(xps_tag(node), "Path.OpacityMask"))
+ {
+ //dputs("page has transparency: Path.OpacityMask\n");
+ return 1;
+ }
+
+ if (!strcmp(xps_tag(node), "Path.Stroke"))
+ {
+ if (xps_brush_has_transparency(ctx, base_uri, xps_down(node)))
+ return 1;
+ }
+
+ if (!strcmp(xps_tag(node), "Path.Fill"))
+ {
+ if (xps_brush_has_transparency(ctx, base_uri, xps_down(node)))
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+xps_glyphs_has_transparency(xps_context_t *ctx, char *base_uri, xps_item_t *root)
+{
+ xps_item_t *node;
+
+ for (node = xps_down(root); node; node = xps_next(node))
+ {
+ if (!strcmp(xps_tag(node), "Glyphs.OpacityMask"))
+ {
+ //dputs("page has transparency: Glyphs.OpacityMask\n");
+ return 1;
+ }
+
+ if (!strcmp(xps_tag(node), "Glyphs.Fill"))
+ {
+ if (xps_brush_has_transparency(ctx, base_uri, xps_down(node)))
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+xps_canvas_has_transparency(xps_context_t *ctx, char *base_uri, xps_item_t *root)
+{
+ xps_item_t *node;
+
+ for (node = xps_down(root); node; node = xps_next(node))
+ {
+ if (!strcmp(xps_tag(node), "Canvas.Resources"))
+ {
+ if (xps_resource_dictionary_has_transparency(ctx, base_uri, xps_down(node)))
+ return 1;
+ }
+
+ if (!strcmp(xps_tag(node), "Canvas.OpacityMask"))
+ {
+ //dputs("page has transparency: Canvas.OpacityMask\n");
+ return 1;
+ }
+
+ if (xps_element_has_transparency(ctx, base_uri, node))
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+xps_element_has_transparency(xps_context_t *ctx, char *base_uri, xps_item_t *node)
+{
+ char *opacity_att;
+ char *stroke_att;
+ char *fill_att;
+
+ gs_color_space *colorspace;
+ float samples[32];
+
+ stroke_att = xps_att(node, "Stroke");
+ if (stroke_att)
+ {
+ xps_parse_color(ctx, base_uri, stroke_att, &colorspace, samples);
+ if (samples[0] < 1.0)
+ {
+ //dprintf1("page has transparency: Stroke alpha=%g\n", samples[0]);
+ return 1;
+ }
+ }
+
+ fill_att = xps_att(node, "Fill");
+ if (fill_att)
+ {
+ xps_parse_color(ctx, base_uri, fill_att, &colorspace, samples);
+ if (samples[0] < 1.0)
+ {
+ //dprintf1("page has transparency: Fill alpha=%g\n", samples[0]);
+ return 1;
+ }
+ }
+
+ opacity_att = xps_att(node, "Opacity");
+ if (opacity_att)
+ {
+ if (atof(opacity_att) < 1.0)
+ {
+ //dprintf1("page has transparency: Opacity=%g\n", atof(opacity_att));
+ return 1;
+ }
+ }
+
+ if (xps_att(node, "OpacityMask"))
+ {
+ //dputs("page has transparency: OpacityMask\n");
+ return 1;
+ }
+
+ if (!strcmp(xps_tag(node), "Path"))
+ if (xps_path_has_transparency(ctx, base_uri, node))
+ return 1;
+ if (!strcmp(xps_tag(node), "Glyphs"))
+ if (xps_glyphs_has_transparency(ctx, base_uri, node))
+ return 1;
+ if (!strcmp(xps_tag(node), "Canvas"))
+ if (xps_canvas_has_transparency(ctx, base_uri, node))
+ return 1;
+
+ return 0;
+}
diff --git a/xps/xpscolor.c b/xps/xpscolor.c
new file mode 100644
index 00000000..dcc7b0ca
--- /dev/null
+++ b/xps/xpscolor.c
@@ -0,0 +1,248 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* XPS interpreter - color functions */
+
+#include "ghostxps.h"
+
+#include "stream.h" /* for sizeof(stream) to work */
+
+void
+xps_set_color(xps_context_t *ctx, gs_color_space *cs, float *samples)
+{
+ gs_client_color cc;
+ int i, n;
+
+ if (ctx->opacity_only)
+ {
+ gs_setopacityalpha(ctx->pgs, 1.0);
+ gs_setgray(ctx->pgs, samples[0]);
+ }
+ else
+ {
+ n = cs_num_components(cs);
+ cc.pattern = 0;
+ for (i = 0; i < n; i++)
+ cc.paint.values[i] = samples[i + 1];
+
+ gs_setopacityalpha(ctx->pgs, samples[0]);
+ gs_setcolorspace(ctx->pgs, cs);
+ gs_setcolor(ctx->pgs, &cc);
+ }
+}
+
+static int unhex(int chr)
+{
+ const char *hextable = "0123456789ABCDEF";
+ return strchr(hextable, (toupper(chr))) - hextable;
+}
+
+static int count_commas(char *s)
+{
+ int n = 0;
+ while (*s)
+ {
+ if (*s == ',')
+ n ++;
+ s ++;
+ }
+ return n;
+}
+
+void
+xps_parse_color(xps_context_t *ctx, char *base_uri, char *string,
+ gs_color_space **csp, float *samples)
+{
+ char *p;
+ int i, n;
+ char buf[1024];
+ char *profile;
+
+ *csp = ctx->srgb;
+
+ samples[0] = 1.0;
+ samples[1] = 0.0;
+ samples[2] = 0.0;
+ samples[3] = 0.0;
+
+ if (string[0] == '#')
+ {
+ if (strlen(string) == 9)
+ {
+ samples[0] = unhex(string[1]) * 16 + unhex(string[2]);
+ samples[1] = unhex(string[3]) * 16 + unhex(string[4]);
+ samples[2] = unhex(string[5]) * 16 + unhex(string[6]);
+ samples[3] = unhex(string[7]) * 16 + unhex(string[8]);
+ }
+ else
+ {
+ samples[0] = 255.0;
+ samples[1] = unhex(string[1]) * 16 + unhex(string[2]);
+ samples[2] = unhex(string[3]) * 16 + unhex(string[4]);
+ samples[3] = unhex(string[5]) * 16 + unhex(string[6]);
+ }
+
+ samples[0] /= 255.0;
+ samples[1] /= 255.0;
+ samples[2] /= 255.0;
+ samples[3] /= 255.0;
+ }
+
+ else if (string[0] == 's' && string[1] == 'c' && string[2] == '#')
+ {
+ *csp = ctx->scrgb;
+
+ if (count_commas(string) == 2)
+ sscanf(string, "sc#%g,%g,%g", samples + 1, samples + 2, samples + 3);
+ if (count_commas(string) == 3)
+ sscanf(string, "sc#%g,%g,%g,%g", samples, samples + 1, samples + 2, samples + 3);
+ }
+
+ else if (strstr(string, "ContextColor ") == string)
+ {
+ /* Crack the string for profile name and sample values */
+ strcpy(buf, string);
+
+ profile = strchr(buf, ' ');
+ if (!profile)
+ {
+ gs_warn1("cannot find icc profile uri in '%s'", string);
+ return;
+ }
+
+ *profile++ = 0;
+ p = strchr(profile, ' ');
+ if (!p)
+ {
+ gs_warn1("cannot find component values in '%s'", profile);
+ return;
+ }
+
+ *p++ = 0;
+ n = count_commas(p) + 1;
+ i = 0;
+ while (i < n)
+ {
+ samples[i++] = atof(p);
+ p = strchr(p, ',');
+ if (!p)
+ break;
+ p ++;
+ if (*p == ' ')
+ p ++;
+ }
+ while (i < n)
+ {
+ samples[i++] = 0.0;
+ }
+
+ *csp = xps_read_icc_colorspace(ctx, base_uri, profile);
+ if (!*csp)
+ {
+ /* Default fallbacks if the ICC stuff fails */
+ switch (n)
+ {
+ case 2: *csp = ctx->gray; break; /* alpha + tint */
+ case 4: *csp = ctx->srgb; break; /* alpha + RGB */
+ case 5: *csp = ctx->cmyk; break; /* alpha + CMYK */
+ default: *csp = ctx->gray; break;
+ }
+ }
+ }
+}
+
+gs_color_space *
+xps_read_icc_colorspace(xps_context_t *ctx, char *base_uri, char *profilename)
+{
+ gs_color_space *space;
+ cmm_profile_t *profile;
+ xps_part_t *part;
+ char partname[1024];
+
+ /* Find ICC colorspace part */
+ xps_absolute_path(partname, base_uri, profilename, sizeof partname);
+
+ /* See if we cached the profile */
+ space = xps_hash_lookup(ctx->colorspace_table, partname);
+ if (!space)
+ {
+ part = xps_read_part(ctx, partname);
+
+ /* Problem finding profile. Don't fail, just use default */
+ if (!part) {
+ gs_warn1("cannot find icc profile part: %s", partname);
+ return NULL;
+ }
+
+ /* Create the profile */
+ profile = gsicc_profile_new(NULL, ctx->memory, NULL, 0);
+
+ /* Set buffer */
+ profile->buffer = part->data;
+ profile->buffer_size = part->size;
+
+ /* Parse */
+ gsicc_init_profile_info(profile);
+
+ /* Problem with profile. Don't fail, just use the default */
+ if (profile->profile_handle == NULL)
+ {
+ gsicc_profile_reference(profile, -1);
+ gs_warn1("there was a problem with the profile: %s", partname);
+ return NULL;
+ }
+
+ /* Create a new colorspace and associate with the profile */
+ gs_cspace_build_ICC(&space, NULL, ctx->memory);
+ space->cmm_icc_profile_data = profile;
+
+ /* Steal the buffer data before freeing the part */
+ part->data = NULL;
+ xps_free_part(ctx, part);
+
+ /* Add colorspace to xps color cache. */
+ xps_hash_insert(ctx, ctx->colorspace_table, xps_strdup(ctx, partname), space);
+ }
+
+ return space;
+}
+
+int
+xps_parse_solid_color_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *node)
+{
+ char *opacity_att;
+ char *color_att;
+ gs_color_space *colorspace;
+ float samples[32];
+
+ color_att = xps_att(node, "Color");
+ opacity_att = xps_att(node, "Opacity");
+
+ colorspace = ctx->srgb;
+ samples[0] = 1.0;
+ samples[1] = 0.0;
+ samples[2] = 0.0;
+ samples[3] = 0.0;
+
+ if (color_att)
+ xps_parse_color(ctx, base_uri, color_att, &colorspace, samples);
+
+ if (opacity_att)
+ samples[0] = atof(opacity_att);
+
+ xps_set_color(ctx, colorspace, samples);
+
+ xps_fill(ctx);
+
+ return 0;
+}
diff --git a/xps/xpscommon.c b/xps/xpscommon.c
new file mode 100644
index 00000000..6908b3c5
--- /dev/null
+++ b/xps/xpscommon.c
@@ -0,0 +1,115 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* XPS interpreter - common parse functions */
+
+#include "ghostxps.h"
+
+int
+xps_parse_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *node)
+{
+ if (!strcmp(xps_tag(node), "SolidColorBrush"))
+ return xps_parse_solid_color_brush(ctx, base_uri, dict, node);
+ if (!strcmp(xps_tag(node), "ImageBrush"))
+ {
+ int code = xps_parse_image_brush(ctx, base_uri, dict, node);
+ if (code)
+ gs_catch(code, "ignoring error in image brush");
+ return gs_okay;
+ }
+ if (!strcmp(xps_tag(node), "VisualBrush"))
+ return xps_parse_visual_brush(ctx, base_uri, dict, node);
+ if (!strcmp(xps_tag(node), "LinearGradientBrush"))
+ return xps_parse_linear_gradient_brush(ctx, base_uri, dict, node);
+ if (!strcmp(xps_tag(node), "RadialGradientBrush"))
+ return xps_parse_radial_gradient_brush(ctx, base_uri, dict, node);
+ return gs_throw1(-1, "unknown brush tag: %s", xps_tag(node));
+}
+
+int
+xps_parse_element(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *node)
+{
+ if (!strcmp(xps_tag(node), "Path"))
+ return xps_parse_path(ctx, base_uri, dict, node);
+ if (!strcmp(xps_tag(node), "Glyphs"))
+ return xps_parse_glyphs(ctx, base_uri, dict, node);
+ if (!strcmp(xps_tag(node), "Canvas"))
+ return xps_parse_canvas(ctx, base_uri, dict, node);
+ /* skip unknown tags (like Foo.Resources and similar) */
+ return 0;
+}
+
+void
+xps_parse_render_transform(xps_context_t *ctx, char *transform, gs_matrix *matrix)
+{
+ float args[6];
+ char *s = transform;
+ int i;
+
+ args[0] = 1.0; args[1] = 0.0;
+ args[2] = 0.0; args[3] = 1.0;
+ args[4] = 0.0; args[5] = 0.0;
+
+ for (i = 0; i < 6 && *s; i++)
+ {
+ args[i] = atof(s);
+ while (*s && *s != ',')
+ s++;
+ if (*s == ',')
+ s++;
+ }
+
+ matrix->xx = args[0]; matrix->xy = args[1];
+ matrix->yx = args[2]; matrix->yy = args[3];
+ matrix->tx = args[4]; matrix->ty = args[5];
+}
+
+void
+xps_parse_matrix_transform(xps_context_t *ctx, xps_item_t *root, gs_matrix *matrix)
+{
+ char *transform;
+
+ gs_make_identity(matrix);
+
+ if (!strcmp(xps_tag(root), "MatrixTransform"))
+ {
+ transform = xps_att(root, "Matrix");
+ if (transform)
+ xps_parse_render_transform(ctx, transform, matrix);
+ }
+}
+
+void
+xps_parse_rectangle(xps_context_t *ctx, char *text, gs_rect *rect)
+{
+ float args[4];
+ char *s = text;
+ int i;
+
+ args[0] = 0.0; args[1] = 0.0;
+ args[2] = 1.0; args[3] = 1.0;
+
+ for (i = 0; i < 4 && *s; i++)
+ {
+ args[i] = atof(s);
+ while (*s && *s != ',')
+ s++;
+ if (*s == ',')
+ s++;
+ }
+
+ rect->p.x = args[0];
+ rect->p.y = args[1];
+ rect->q.x = args[0] + args[2];
+ rect->q.y = args[1] + args[3];
+}
diff --git a/xps/xpscrc.c b/xps/xpscrc.c
new file mode 100644
index 00000000..7dea3c32
--- /dev/null
+++ b/xps/xpscrc.c
@@ -0,0 +1,95 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* XPS interpreter - CRC-32 implementation */
+
+#include "ghostxps.h"
+
+static const unsigned long crctab[256] =
+{
+ 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L,
+ 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L,
+ 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L,
+ 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL,
+ 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L,
+ 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L,
+ 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L,
+ 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL,
+ 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L,
+ 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL,
+ 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L,
+ 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L,
+ 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L,
+ 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL,
+ 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL,
+ 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L,
+ 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL,
+ 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L,
+ 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L,
+ 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L,
+ 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL,
+ 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L,
+ 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L,
+ 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL,
+ 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L,
+ 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L,
+ 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L,
+ 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L,
+ 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L,
+ 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL,
+ 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL,
+ 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L,
+ 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L,
+ 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL,
+ 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL,
+ 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L,
+ 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL,
+ 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L,
+ 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL,
+ 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L,
+ 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL,
+ 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L,
+ 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L,
+ 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL,
+ 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L,
+ 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L,
+ 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L,
+ 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L,
+ 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L,
+ 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L,
+ 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL,
+ 0x2d02ef8dL
+};
+
+#define DO1(buf) crc = crctab[((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8);
+#define DO2(buf) DO1(buf); DO1(buf);
+#define DO4(buf) DO2(buf); DO2(buf);
+#define DO8(buf) DO4(buf); DO4(buf);
+
+unsigned int
+xps_crc32(unsigned int crc, unsigned char *buf, int len)
+{
+ if (buf == NULL)
+ return 0L;
+ crc = crc ^ 0xffffffffL;
+ while (len >= 8)
+ {
+ DO8(buf);
+ len -= 8;
+ }
+ if (len)
+ {
+ do { DO1(buf); } while (--len);
+ }
+ return crc ^ 0xffffffffL;
+}
diff --git a/xps/xpsdoc.c b/xps/xpsdoc.c
new file mode 100644
index 00000000..3e9f7c96
--- /dev/null
+++ b/xps/xpsdoc.c
@@ -0,0 +1,277 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* XPS interpreter - document parsing */
+
+#include "ghostxps.h"
+
+#include <expat.h>
+
+xps_part_t *
+xps_new_part(xps_context_t *ctx, char *name, int size)
+{
+ xps_part_t *part;
+
+ part = xps_alloc(ctx, sizeof(xps_part_t));
+ part->name = xps_strdup(ctx, name);
+ part->size = size;
+ part->data = xps_alloc(ctx, size);
+
+ return part;
+}
+
+void
+xps_free_part(xps_context_t *ctx, xps_part_t *part)
+{
+ xps_free(ctx, part->name);
+ xps_free(ctx, part->data);
+ xps_free(ctx, part);
+}
+
+/*
+ * The FixedDocumentSequence and FixedDocument parts determine
+ * which parts correspond to actual pages, and the page order.
+ */
+
+void
+xps_debug_fixdocseq(xps_context_t *ctx)
+{
+ xps_document_t *fixdoc = ctx->first_fixdoc;
+ xps_page_t *page = ctx->first_page;
+
+ if (ctx->start_part)
+ dprintf1("start part %s\n", ctx->start_part);
+
+ while (fixdoc)
+ {
+ dprintf1("fixdoc %s\n", fixdoc->name);
+ fixdoc = fixdoc->next;
+ }
+
+ while (page)
+ {
+ dprintf3("page %s w=%d h=%d\n", page->name, page->width, page->height);
+ page = page->next;
+ }
+}
+
+static void
+xps_add_fixed_document(xps_context_t *ctx, char *name)
+{
+ xps_document_t *fixdoc;
+
+ /* Check for duplicates first */
+ for (fixdoc = ctx->first_fixdoc; fixdoc; fixdoc = fixdoc->next)
+ if (!strcmp(fixdoc->name, name))
+ return;
+
+ if_debug1('|', "doc: adding fixdoc %s\n", name);
+
+ fixdoc = xps_alloc(ctx, sizeof(xps_document_t));
+ fixdoc->name = xps_strdup(ctx, name);
+ fixdoc->next = NULL;
+
+ if (!ctx->first_fixdoc)
+ {
+ ctx->first_fixdoc = fixdoc;
+ ctx->last_fixdoc = fixdoc;
+ }
+ else
+ {
+ ctx->last_fixdoc->next = fixdoc;
+ ctx->last_fixdoc = fixdoc;
+ }
+}
+
+void
+xps_free_fixed_documents(xps_context_t *ctx)
+{
+ xps_document_t *node = ctx->first_fixdoc;
+ while (node)
+ {
+ xps_document_t *next = node->next;
+ xps_free(ctx, node->name);
+ xps_free(ctx, node);
+ node = next;
+ }
+ ctx->first_fixdoc = NULL;
+ ctx->last_fixdoc = NULL;
+}
+
+static void
+xps_add_fixed_page(xps_context_t *ctx, char *name, int width, int height)
+{
+ xps_page_t *page;
+
+ /* Check for duplicates first */
+ for (page = ctx->first_page; page; page = page->next)
+ if (!strcmp(page->name, name))
+ return;
+
+ if_debug1('|', "doc: adding page %s\n", name);
+
+ page = xps_alloc(ctx, sizeof(xps_page_t));
+ page->name = xps_strdup(ctx, name);
+ page->width = width;
+ page->height = height;
+ page->next = NULL;
+
+ if (!ctx->first_page)
+ {
+ ctx->first_page = page;
+ ctx->last_page = page;
+ }
+ else
+ {
+ ctx->last_page->next = page;
+ ctx->last_page = page;
+ }
+}
+
+void
+xps_free_fixed_pages(xps_context_t *ctx)
+{
+ xps_page_t *node = ctx->first_page;
+ while (node)
+ {
+ xps_page_t *next = node->next;
+ xps_free(ctx, node->name);
+ xps_free(ctx, node);
+ node = next;
+ }
+ ctx->first_page = NULL;
+ ctx->last_page = NULL;
+}
+
+/*
+ * Parse the fixed document sequence structure and _rels/.rels to find the
+ * start part. We hook up unique expat handlers for this, since we don't need
+ * the full document model.
+ */
+
+static void
+xps_parse_metadata_imp(void *zp, char *name, char **atts)
+{
+ xps_context_t *ctx = zp;
+ int i;
+
+ if (!strcmp(name, "Relationship"))
+ {
+ char tgtbuf[1024];
+ char *target = NULL;
+ char *type = NULL;
+
+ for (i = 0; atts[i]; i += 2)
+ {
+ if (!strcmp(atts[i], "Target"))
+ target = atts[i + 1];
+ if (!strcmp(atts[i], "Type"))
+ type = atts[i + 1];
+ }
+
+ if (target && type)
+ {
+ xps_absolute_path(tgtbuf, ctx->base_uri, target, sizeof tgtbuf);
+ if (!strcmp(type, REL_START_PART))
+ ctx->start_part = xps_strdup(ctx, tgtbuf);
+ }
+ }
+
+ if (!strcmp(name, "DocumentReference"))
+ {
+ char *source = NULL;
+ char srcbuf[1024];
+
+ for (i = 0; atts[i]; i += 2)
+ {
+ if (!strcmp(atts[i], "Source"))
+ source = atts[i + 1];
+ }
+
+ if (source)
+ {
+ xps_absolute_path(srcbuf, ctx->base_uri, source, sizeof srcbuf);
+ xps_add_fixed_document(ctx, srcbuf);
+ }
+ }
+
+ if (!strcmp(name, "PageContent"))
+ {
+ char *source = NULL;
+ char srcbuf[1024];
+ int width = 0;
+ int height = 0;
+
+ for (i = 0; atts[i]; i += 2)
+ {
+ if (!strcmp(atts[i], "Source"))
+ source = atts[i + 1];
+ if (!strcmp(atts[i], "Width"))
+ width = atoi(atts[i + 1]);
+ if (!strcmp(atts[i], "Height"))
+ height = atoi(atts[i + 1]);
+ }
+
+ if (source)
+ {
+ xps_absolute_path(srcbuf, ctx->base_uri, source, sizeof srcbuf);
+ xps_add_fixed_page(ctx, srcbuf, width, height);
+ }
+ }
+}
+
+int
+xps_parse_metadata(xps_context_t *ctx, xps_part_t *part)
+{
+ XML_Parser xp;
+ int code;
+ char buf[1024];
+ char *s;
+
+ /* Save directory name part */
+ xps_strlcpy(buf, part->name, sizeof buf);
+ s = strrchr(buf, '/');
+ if (s)
+ s[0] = 0;
+
+ /* _rels parts are voodoo: their URI references are from
+ * the part they are associated with, not the actual _rels
+ * part being parsed.
+ */
+ s = strstr(buf, "/_rels");
+ if (s)
+ *s = 0;
+
+ ctx->base_uri = buf;
+ ctx->part_uri = part->name;
+
+ xp = XML_ParserCreate(NULL);
+ if (!xp)
+ return gs_throw(-1, "cannot create XML parser");
+
+ XML_SetUserData(xp, ctx);
+ XML_SetParamEntityParsing(xp, XML_PARAM_ENTITY_PARSING_NEVER);
+ XML_SetStartElementHandler(xp, (XML_StartElementHandler)xps_parse_metadata_imp);
+
+ code = XML_Parse(xp, (char*)part->data, part->size, 1);
+
+ XML_ParserFree(xp);
+
+ ctx->base_uri = NULL;
+ ctx->part_uri = NULL;
+
+ if (code == 0)
+ return gs_throw1(-1, "cannot parse XML in part: %s", part->name);
+
+ return 0;
+}
diff --git a/xps/xpsfont.c b/xps/xpsfont.c
new file mode 100644
index 00000000..93adf71d
--- /dev/null
+++ b/xps/xpsfont.c
@@ -0,0 +1,533 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* XPS interpreter - general font functions */
+
+#include "ghostxps.h"
+
+static void xps_load_sfnt_cmap(xps_font_t *font);
+
+/*
+ * Big-endian memory accessor functions
+ */
+
+static inline int s16(byte *p)
+{
+ return (signed short)( (p[0] << 8) | p[1] );
+}
+
+static inline int u16(byte *p)
+{
+ return (p[0] << 8) | p[1];
+}
+
+static inline int u24(byte *p)
+{
+ return (p[0] << 16) | (p[1] << 8) | p[2];
+}
+
+static inline int u32(byte *p)
+{
+ return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
+}
+
+xps_font_t *
+xps_new_font(xps_context_t *ctx, byte *buf, int buflen, int index)
+{
+ xps_font_t *font;
+ int code;
+
+ font = xps_alloc(ctx, sizeof(xps_font_t));
+ if (!font)
+ {
+ gs_throw(-1, "out of memory");
+ return NULL;
+ }
+
+ font->data = buf;
+ font->length = buflen;
+ font->font = NULL;
+
+ font->subfontid = index;
+ font->cmaptable = 0;
+ font->cmapsubcount = 0;
+ font->cmapsubtable = 0;
+ font->usepua = 0;
+
+ font->cffdata = 0;
+ font->cffend = 0;
+ font->gsubrs = 0;
+ font->subrs = 0;
+ font->charstrings = 0;
+
+ if (memcmp(font->data, "OTTO", 4) == 0)
+ code = xps_init_postscript_font(ctx, font);
+ else if (memcmp(font->data, "\0\1\0\0", 4) == 0)
+ code = xps_init_truetype_font(ctx, font);
+ else if (memcmp(font->data, "true", 4) == 0)
+ code = xps_init_truetype_font(ctx, font);
+ else if (memcmp(font->data, "ttcf", 4) == 0)
+ code = xps_init_truetype_font(ctx, font);
+ else
+ {
+ xps_free_font(ctx, font);
+ gs_throw(-1, "not an opentype font");
+ return NULL;
+ }
+
+ if (code < 0)
+ {
+ xps_free_font(ctx, font);
+ gs_rethrow(-1, "cannot init font");
+ return NULL;
+ }
+
+ xps_load_sfnt_cmap(font);
+
+ return font;
+}
+
+void
+xps_free_font(xps_context_t *ctx, xps_font_t *font)
+{
+ if (font->font)
+ {
+ gs_font_finalize(font->font);
+ gs_free_object(ctx->memory, font->font, "font object");
+ }
+ xps_free(ctx, font);
+}
+
+/*
+ * Find the offset and length of an SFNT table.
+ * Return -1 if no table by the specified name is found.
+ */
+
+int
+xps_find_sfnt_table(xps_font_t *font, const char *name, int *lengthp)
+{
+ int offset;
+ int ntables;
+ int i;
+
+ if (font->length < 12)
+ return -1;
+
+ if (!memcmp(font->data, "ttcf", 4))
+ {
+ int nfonts = u32(font->data + 8);
+ if (font->subfontid < 0 || font->subfontid >= nfonts)
+ {
+ gs_warn("Invalid subfont ID");
+ return -1;
+ }
+ offset = u32(font->data + 12 + font->subfontid * 4);
+ }
+ else
+ {
+ offset = 0;
+ }
+
+ ntables = u16(font->data + offset + 4);
+ if (font->length < offset + 12 + ntables * 16)
+ return -1;
+
+ for (i = 0; i < ntables; i++)
+ {
+ byte *entry = font->data + offset + 12 + i * 16;
+ if (!memcmp(entry, name, 4))
+ {
+ if (lengthp)
+ *lengthp = u32(entry + 12);
+ return u32(entry + 8);
+ }
+ }
+
+ return -1;
+}
+
+/*
+ * Get the windows truetype font file name - position 4 in the name table.
+ */
+void
+xps_load_sfnt_name(xps_font_t *font, char *namep)
+{
+ byte *namedata;
+ int offset, length;
+ int format, count, stringoffset;
+ int i;
+
+ strcpy(namep, "Unknown");
+
+ offset = xps_find_sfnt_table(font, "name", &length);
+ if (offset < 0 || length < 6)
+ {
+ gs_warn("cannot find name table");
+ return;
+ }
+
+ namedata = font->data + offset;
+
+ format = u16(namedata + 0);
+ count = u16(namedata + 2);
+ stringoffset = u16(namedata + 4);
+
+ for (i = 0; i < count; i++)
+ {
+ byte *record = namedata + 6 + i * 12;
+ int pid = u16(record + 0);
+ int eid = u16(record + 2);
+ int langid = u16(record + 4);
+ int nameid = u16(record + 6);
+ length = u16(record + 8);
+ offset = u16(record + 10);
+
+ /* Mac Roman English */
+ if (pid == 1 && eid == 0 && langid == 0)
+ {
+ /* Full font name or postscript name */
+ if (nameid == 4 || nameid == 6)
+ {
+ memcpy(namep, namedata + stringoffset + offset, length);
+ namep[length] = 0;
+ }
+ }
+ }
+}
+
+/*
+ * Locate the 'cmap' table and count the number of subtables.
+ */
+
+static void
+xps_load_sfnt_cmap(xps_font_t *font)
+{
+ byte *cmapdata;
+ int offset, length;
+ int nsubtables;
+
+ offset = xps_find_sfnt_table(font, "cmap", &length);
+ if (offset < 0 || length < 4)
+ {
+ gs_warn("cannot find cmap table");
+ return;
+ }
+
+ cmapdata = font->data + offset;
+
+ nsubtables = u16(cmapdata + 2);
+ if (nsubtables < 0 || length < 4 + nsubtables * 8)
+ {
+ gs_warn("cannot find cmap sub-tables");
+ return;
+ }
+
+ font->cmaptable = offset;
+ font->cmapsubcount = nsubtables;
+ font->cmapsubtable = 0;
+}
+
+/*
+ * Return the number of cmap subtables.
+ */
+
+int
+xps_count_font_encodings(xps_font_t *font)
+{
+ return font->cmapsubcount;
+}
+
+/*
+ * Extract PlatformID and EncodingID for a cmap subtable.
+ */
+
+void
+xps_identify_font_encoding(xps_font_t *font, int idx, int *pid, int *eid)
+{
+ byte *cmapdata, *entry;
+ if (idx < 0 || idx >= font->cmapsubcount)
+ return;
+ cmapdata = font->data + font->cmaptable;
+ entry = cmapdata + 4 + idx * 8;
+ *pid = u16(entry + 0);
+ *eid = u16(entry + 2);
+}
+
+/*
+ * Select a cmap subtable for use with encoding functions.
+ */
+
+void
+xps_select_font_encoding(xps_font_t *font, int idx)
+{
+ byte *cmapdata, *entry;
+ int pid, eid;
+ if (idx < 0 || idx >= font->cmapsubcount)
+ return;
+ cmapdata = font->data + font->cmaptable;
+ entry = cmapdata + 4 + idx * 8;
+ pid = u16(entry + 0);
+ eid = u16(entry + 2);
+ font->cmapsubtable = font->cmaptable + u32(entry + 4);
+ font->usepua = (pid == 3 && eid == 0);
+}
+
+/*
+ * Encode a character using the selected cmap subtable.
+ * TODO: extend this to cover more cmap formats.
+ */
+
+static int
+xps_encode_font_char_imp(xps_font_t *font, int code)
+{
+ byte *table;
+
+ /* no cmap selected: return identity */
+ if (font->cmapsubtable <= 0)
+ return code;
+
+ table = font->data + font->cmapsubtable;
+
+ switch (u16(table))
+ {
+ case 0: /* Apple standard 1-to-1 mapping. */
+ return table[code + 6];
+
+ case 4: /* Microsoft/Adobe segmented mapping. */
+ {
+ int segCount2 = u16(table + 6);
+ byte *endCount = table + 14;
+ byte *startCount = endCount + segCount2 + 2;
+ byte *idDelta = startCount + segCount2;
+ byte *idRangeOffset = idDelta + segCount2;
+ int i2;
+
+ for (i2 = 0; i2 < segCount2 - 3; i2 += 2)
+ {
+ int delta, roff;
+ int start = u16(startCount + i2);
+ int glyph;
+
+ if ( code < start )
+ return 0;
+ if ( code > u16(endCount + i2) )
+ continue;
+ delta = s16(idDelta + i2);
+ roff = s16(idRangeOffset + i2);
+ if ( roff == 0 )
+ {
+ return ( code + delta ) & 0xffff; /* mod 65536 */
+ return 0;
+ }
+ glyph = u16(idRangeOffset + i2 + roff + ((code - start) << 1));
+ return (glyph == 0 ? 0 : glyph + delta);
+ }
+
+ /*
+ * The TrueType documentation says that the last range is
+ * always supposed to end with 0xffff, so this shouldn't
+ * happen; however, in some real fonts, it does.
+ */
+ return 0;
+ }
+
+ case 6: /* Single interval lookup. */
+ {
+ int firstCode = u16(table + 6);
+ int entryCount = u16(table + 8);
+ if ( code < firstCode || code >= firstCode + entryCount )
+ return 0;
+ return u16(table + 10 + ((code - firstCode) << 1));
+ }
+
+ case 10: /* Trimmed array (like 6) */
+ {
+ int startCharCode = u32(table + 12);
+ int numChars = u32(table + 16);
+ if ( code < startCharCode || code >= startCharCode + numChars )
+ return 0;
+ return u32(table + 20 + (code - startCharCode) * 4);
+ }
+
+ case 12: /* Segmented coverage. (like 4) */
+ {
+ int nGroups = u32(table + 12);
+ byte *group = table + 16;
+ int i;
+
+ for (i = 0; i < nGroups; i++)
+ {
+ int startCharCode = u32(group + 0);
+ int endCharCode = u32(group + 4);
+ int startGlyphID = u32(group + 8);
+ if ( code < startCharCode )
+ return 0;
+ if ( code <= endCharCode )
+ return startGlyphID + (code - startCharCode);
+ group += 12;
+ }
+
+ return 0;
+ }
+
+ case 2: /* High-byte mapping through table. */
+ case 8: /* Mixed 16-bit and 32-bit coverage (like 2) */
+ default:
+ gs_warn1("unknown cmap format: %d\n", u16(table));
+ return 0;
+ }
+
+ return 0;
+}
+
+int
+xps_encode_font_char(xps_font_t *font, int code)
+{
+ int gid = xps_encode_font_char_imp(font, code);
+ if (gid == 0 && font->usepua)
+ gid = xps_encode_font_char_imp(font, 0xF000 | code);
+ return gid;
+}
+
+/*
+ * Get glyph metrics by parsing TTF tables manually.
+ * XPS needs more and different metrics than postscript/ghostscript
+ * use so the native ghostscript functions are not adequate.
+ */
+
+void
+xps_measure_font_glyph(xps_context_t *ctx, xps_font_t *font, int gid, xps_glyph_metrics_t *mtx)
+{
+
+ int head, format, loca, glyf;
+ int ofs, len;
+ int idx, i, n;
+ int hadv, vadv, vorg;
+ int vtop, ymax, desc;
+ int scale;
+
+ /* some insane defaults */
+
+ scale = 1000; /* units-per-em */
+ hadv = 500;
+ vadv = -1000;
+ vorg = 1000;
+
+ /*
+ * Horizontal metrics are easy.
+ */
+
+ ofs = xps_find_sfnt_table(font, "hhea", &len);
+ if (ofs < 0 || len < 2 * 18)
+ {
+ gs_warn("hhea table is too short");
+ return;
+ }
+
+ vorg = s16(font->data + ofs + 4); /* ascender is default vorg */
+ desc = s16(font->data + ofs + 6); /* descender */
+ if (desc < 0)
+ desc = -desc;
+ n = u16(font->data + ofs + 17 * 2);
+
+ ofs = xps_find_sfnt_table(font, "hmtx", &len);
+ if (ofs < 0)
+ {
+ gs_warn("cannot find hmtx table");
+ return;
+ }
+
+ idx = gid;
+ if (idx > n - 1)
+ idx = n - 1;
+
+ hadv = u16(font->data + ofs + idx * 4);
+ vadv = 0;
+
+ /*
+ * Vertical metrics are hairy (with missing tables).
+ */
+
+ head = xps_find_sfnt_table(font, "head", &len);
+ if (head > 0)
+ {
+ scale = u16(font->data + head + 18); /* units per em */
+ }
+
+ ofs = xps_find_sfnt_table(font, "OS/2", &len);
+ if (ofs > 0 && len > 70)
+ {
+ vorg = s16(font->data + ofs + 68); /* sTypoAscender */
+ desc = s16(font->data + ofs + 70); /* sTypoDescender */
+ if (desc < 0)
+ desc = -desc;
+ }
+
+ ofs = xps_find_sfnt_table(font, "vhea", &len);
+ if (ofs > 0 && len >= 2 * 18)
+ {
+ n = u16(font->data + ofs + 17 * 2);
+
+ ofs = xps_find_sfnt_table(font, "vmtx", &len);
+ if (ofs < 0)
+ {
+ gs_warn("cannot find vmtx table");
+ return;
+ }
+
+ idx = gid;
+ if (idx > n - 1)
+ idx = n - 1;
+
+ vadv = u16(font->data + ofs + idx * 4);
+ vtop = u16(font->data + ofs + idx * 4 + 2);
+
+ glyf = xps_find_sfnt_table(font, "glyf", &len);
+ loca = xps_find_sfnt_table(font, "loca", &len);
+ if (head > 0 && glyf > 0 && loca > 0)
+ {
+ format = u16(font->data + head + 50); /* indexToLocaFormat */
+
+ if (format == 0)
+ ofs = u16(font->data + loca + gid * 2) * 2;
+ else
+ ofs = u32(font->data + loca + gid * 4);
+
+ ymax = u16(font->data + glyf + ofs + 8); /* yMax */
+
+ vorg = ymax + vtop;
+ }
+ }
+
+ ofs = xps_find_sfnt_table(font, "VORG", &len);
+ if (ofs > 0)
+ {
+ vorg = u16(font->data + ofs + 6);
+ n = u16(font->data + ofs + 6);
+ for (i = 0; i < n; i++)
+ {
+ if (u16(font->data + ofs + 8 + 4 * i) == gid)
+ {
+ vorg = s16(font->data + ofs + 8 + 4 * i + 2);
+ break;
+ }
+ }
+ }
+
+ if (vadv == 0)
+ vadv = vorg + desc;
+
+ mtx->hadv = hadv / (float) scale;
+ mtx->vadv = vadv / (float) scale;
+ mtx->vorg = vorg / (float) scale;
+}
diff --git a/xps/xpsglyphs.c b/xps/xpsglyphs.c
new file mode 100644
index 00000000..4709c94b
--- /dev/null
+++ b/xps/xpsglyphs.c
@@ -0,0 +1,675 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* XPS interpreter - text drawing support */
+
+#include "ghostxps.h"
+
+#include <ctype.h>
+
+#define XPS_TEXT_BUFFER_SIZE 300
+
+typedef struct xps_text_buffer_s xps_text_buffer_t;
+
+struct xps_text_buffer_s
+{
+ int count;
+ float x[XPS_TEXT_BUFFER_SIZE + 1];
+ float y[XPS_TEXT_BUFFER_SIZE + 1];
+ gs_glyph g[XPS_TEXT_BUFFER_SIZE];
+};
+
+static inline int unhex(int i)
+{
+ if (isdigit(i))
+ return i - '0';
+ return tolower(i) - 'a' + 10;
+}
+
+void
+xps_debug_path(xps_context_t *ctx)
+{
+ segment *seg;
+ curve_segment *cseg;
+
+ seg = (segment*)ctx->pgs->path->first_subpath;
+ while (seg)
+ {
+ switch (seg->type)
+ {
+ case s_start:
+ dprintf2("%g %g moveto\n",
+ fixed2float(seg->pt.x) * 0.001,
+ fixed2float(seg->pt.y) * 0.001);
+ break;
+ case s_line:
+ dprintf2("%g %g lineto\n",
+ fixed2float(seg->pt.x) * 0.001,
+ fixed2float(seg->pt.y) * 0.001);
+ break;
+ case s_line_close:
+ dputs("closepath\n");
+ break;
+ case s_curve:
+ cseg = (curve_segment*)seg;
+ dprintf6("%g %g %g %g %g %g curveto\n",
+ fixed2float(cseg->p1.x) * 0.001,
+ fixed2float(cseg->p1.y) * 0.001,
+ fixed2float(cseg->p2.x) * 0.001,
+ fixed2float(cseg->p2.y) * 0.001,
+ fixed2float(seg->pt.x) * 0.001,
+ fixed2float(seg->pt.y) * 0.001);
+ break;
+ }
+ seg = seg->next;
+ }
+}
+
+/*
+ * Some fonts in XPS are obfuscated by XOR:ing the first 32 bytes of the
+ * data with the GUID in the fontname.
+ */
+static void
+xps_deobfuscate_font_resource(xps_context_t *ctx, xps_part_t *part)
+{
+ byte buf[33];
+ byte key[16];
+ char *p;
+ int i;
+
+ p = strrchr(part->name, '/');
+ if (!p)
+ p = part->name;
+
+ for (i = 0; i < 32 && *p; p++)
+ {
+ if (isxdigit(*p))
+ buf[i++] = *p;
+ }
+ buf[i] = 0;
+
+ if (i != 32)
+ {
+ gs_warn("cannot extract GUID from obfuscated font part name");
+ return;
+ }
+
+ for (i = 0; i < 16; i++)
+ key[i] = unhex(buf[i*2+0]) * 16 + unhex(buf[i*2+1]);
+
+ for (i = 0; i < 16; i++)
+ {
+ part->data[i] ^= key[15-i];
+ part->data[i+16] ^= key[15-i];
+ }
+}
+
+static void
+xps_select_best_font_encoding(xps_font_t *font)
+{
+ static struct { int pid, eid; } xps_cmap_list[] =
+ {
+ { 3, 10 }, /* Unicode with surrogates */
+ { 3, 1 }, /* Unicode without surrogates */
+ { 3, 5 }, /* Wansung */
+ { 3, 4 }, /* Big5 */
+ { 3, 3 }, /* Prc */
+ { 3, 2 }, /* ShiftJis */
+ { 3, 0 }, /* Symbol */
+ // { 0, * }, -- Unicode (deprecated)
+ { 1, 0 },
+ { -1, -1 },
+ };
+
+ int i, k, n, pid, eid;
+
+ n = xps_count_font_encodings(font);
+ for (k = 0; xps_cmap_list[k].pid != -1; k++)
+ {
+ for (i = 0; i < n; i++)
+ {
+ xps_identify_font_encoding(font, i, &pid, &eid);
+ if (pid == xps_cmap_list[k].pid && eid == xps_cmap_list[k].eid)
+ {
+ xps_select_font_encoding(font, i);
+ return;
+ }
+ }
+ }
+
+ gs_warn("could not find a suitable cmap");
+}
+
+/*
+ * Call text drawing primitives.
+ */
+
+static int
+xps_flush_text_buffer(xps_context_t *ctx, xps_font_t *font,
+ xps_text_buffer_t *buf, int is_charpath)
+{
+ gs_text_params_t params;
+ gs_text_enum_t *textenum;
+ float x = buf->x[0];
+ float y = buf->y[0];
+ int code;
+ int i;
+
+ // dprintf1("flushing text buffer (%d glyphs)\n", buf->count);
+
+ gs_moveto(ctx->pgs, x, y);
+
+ params.operation = TEXT_FROM_GLYPHS | TEXT_REPLACE_WIDTHS;
+ if (is_charpath)
+ params.operation |= TEXT_DO_FALSE_CHARPATH;
+ else
+ params.operation |= TEXT_DO_DRAW;
+ params.data.glyphs = buf->g;
+ params.size = buf->count;
+ params.x_widths = buf->x + 1;
+ params.y_widths = buf->y + 1;
+ params.widths_size = buf->count;
+
+ for (i = 0; i < buf->count; i++)
+ {
+ buf->x[i] = buf->x[i] - x;
+ buf->y[i] = buf->y[i] - y;
+ x += buf->x[i];
+ y += buf->y[i];
+ }
+ buf->x[buf->count] = 0;
+ buf->y[buf->count] = 0;
+
+ code = gs_text_begin(ctx->pgs, &params, ctx->memory, &textenum);
+ if (code != 0)
+ return gs_throw1(-1, "cannot gs_text_begin() (%d)", code);
+
+ code = gs_text_process(textenum);
+
+ if (code != 0)
+ return gs_throw1(-1, "cannot gs_text_process() (%d)", code);
+
+ gs_text_release(textenum, "gslt font render");
+
+ buf->count = 0;
+
+ return 0;
+}
+
+/*
+ * Parse and draw an XPS <Glyphs> element.
+ *
+ * Indices syntax:
+
+ GlyphIndices = GlyphMapping ( ";" GlyphMapping )
+ GlyphMapping = ( [ClusterMapping] GlyphIndex ) [GlyphMetrics]
+ ClusterMapping = "(" ClusterCodeUnitCount [":" ClusterGlyphCount] ")"
+ ClusterCodeUnitCount = * DIGIT
+ ClusterGlyphCount = * DIGIT
+ GlyphIndex = * DIGIT
+ GlyphMetrics = "," AdvanceWidth ["," uOffset ["," vOffset]]
+ AdvanceWidth = ["+"] RealNum
+ uOffset = ["+" | "-"] RealNum
+ vOffset = ["+" | "-"] RealNum
+ RealNum = ((DIGIT ["." DIGIT]) | ("." DIGIT)) [Exponent]
+ Exponent = ( ("E"|"e") ("+"|"-") DIGIT )
+
+ */
+
+static char *
+xps_parse_digits(char *s, int *digit)
+{
+ *digit = 0;
+ while (*s >= '0' && *s <= '9')
+ {
+ *digit = *digit * 10 + (*s - '0');
+ s ++;
+ }
+ return s;
+}
+
+static inline int is_real_num_char(int c)
+{
+ return (c >= '0' && c <= '9') || c == 'e' || c == 'E' || c == '+' || c == '-' || c == '.';
+}
+
+static char *
+xps_parse_real_num(char *s, float *number)
+{
+ char buf[64];
+ char *p = buf;
+ while (is_real_num_char(*s))
+ *p++ = *s++;
+ *p = 0;
+ if (buf[0])
+ *number = atof(buf);
+ return s;
+}
+
+static char *
+xps_parse_cluster_mapping(char *s, int *code_count, int *glyph_count)
+{
+ if (*s == '(')
+ s = xps_parse_digits(s + 1, code_count);
+ if (*s == ':')
+ s = xps_parse_digits(s + 1, glyph_count);
+ if (*s == ')')
+ s ++;
+ return s;
+}
+
+static char *
+xps_parse_glyph_index(char *s, int *glyph_index)
+{
+ if (*s >= '0' && *s <= '9')
+ s = xps_parse_digits(s, glyph_index);
+ return s;
+}
+
+static char *
+xps_parse_glyph_metrics(char *s, float *advance, float *uofs, float *vofs)
+{
+ if (*s == ',')
+ s = xps_parse_real_num(s + 1, advance);
+ if (*s == ',')
+ s = xps_parse_real_num(s + 1, uofs);
+ if (*s == ',')
+ s = xps_parse_real_num(s + 1, vofs);
+ return s;
+}
+
+/*
+ * Parse unicode and indices strings and encode glyphs.
+ * Calculate metrics for positioning.
+ */
+static int
+xps_parse_glyphs_imp(xps_context_t *ctx, xps_font_t *font, float size,
+ float originx, float originy, int is_sideways, int bidi_level,
+ char *indices, char *unicode, int is_charpath)
+{
+ xps_text_buffer_t buf;
+ xps_glyph_metrics_t mtx;
+ float x = originx;
+ float y = originy;
+ char *us = unicode;
+ char *is = indices;
+ int un = 0;
+ int code;
+
+ buf.count = 0;
+
+ if (!unicode && !indices)
+ return gs_throw(-1, "no text in glyphs element");
+
+ if (us)
+ {
+ if (us[0] == '{' && us[1] == '}')
+ us = us + 2;
+ un = strlen(us);
+ }
+
+ while ((us && un > 0) || (is && *is))
+ {
+ int code_count = 1;
+ int glyph_count = 1;
+
+ if (is && *is)
+ {
+ is = xps_parse_cluster_mapping(is, &code_count, &glyph_count);
+ }
+
+ if (code_count < 1)
+ code_count = 1;
+ if (glyph_count < 1)
+ glyph_count = 1;
+
+ while (code_count > 0 || glyph_count > 0)
+ {
+ int char_code = '?';
+ int glyph_index = -1;
+ float u_offset = 0.0;
+ float v_offset = 0.0;
+ float advance;
+
+ if (glyph_count)
+ {
+ if (is && *is)
+ is = xps_parse_glyph_index(is, &glyph_index);
+ glyph_count --;
+ }
+
+ if (code_count)
+ {
+ if (us && un > 0)
+ {
+ int t = xps_utf8_to_ucs(&char_code, us, un);
+ if (t < 0)
+ return gs_rethrow(-1, "error decoding UTF-8 string");
+ us += t; un -= t;
+ }
+ code_count --;
+ }
+
+ if (glyph_index == -1)
+ glyph_index = xps_encode_font_char(font, char_code);
+
+ xps_measure_font_glyph(ctx, font, glyph_index, &mtx);
+ if (is_sideways)
+ advance = mtx.vadv * 100.0;
+ else if (bidi_level & 1)
+ advance = -mtx.hadv * 100.0;
+ else
+ advance = mtx.hadv * 100.0;
+
+ if (is && *is)
+ {
+ is = xps_parse_glyph_metrics(is, &advance, &u_offset, &v_offset);
+ if (*is == ';')
+ is ++;
+ }
+
+#if 0
+ dprintf6("glyph mapping (%d:%d)%d,%g,%g,%g\n",
+ code_count, glyph_count, glyph_index,
+ advance, u_offset, v_offset);
+#endif
+
+ if (bidi_level & 1)
+ u_offset = -mtx.hadv * 100 - u_offset;
+
+ u_offset = u_offset * 0.01 * size;
+ v_offset = v_offset * 0.01 * size;
+
+ if (buf.count == XPS_TEXT_BUFFER_SIZE)
+ {
+ code = xps_flush_text_buffer(ctx, font, &buf, is_charpath);
+ if (code)
+ return gs_rethrow(code, "cannot flush buffered text");
+ }
+
+ if (is_sideways)
+ {
+ buf.x[buf.count] = x + u_offset + (mtx.vorg * size);
+ buf.y[buf.count] = y - v_offset + (mtx.hadv * 0.5 * size);
+ }
+ else
+ {
+ buf.x[buf.count] = x + u_offset;
+ buf.y[buf.count] = y - v_offset;
+ }
+ buf.g[buf.count] = glyph_index;
+ buf.count ++;
+
+ x += advance * 0.01 * size;
+ }
+ }
+
+ if (buf.count > 0)
+ {
+ code = xps_flush_text_buffer(ctx, font, &buf, is_charpath);
+ if (code)
+ return gs_rethrow(code, "cannot flush buffered text");
+ }
+
+ return 0;
+}
+
+int
+xps_parse_glyphs(xps_context_t *ctx,
+ char *base_uri, xps_resource_t *dict, xps_item_t *root)
+{
+ xps_item_t *node;
+ int code;
+
+ char *fill_uri;
+ char *opacity_mask_uri;
+
+ char *bidi_level_att;
+ char *caret_stops_att;
+ char *fill_att;
+ char *font_size_att;
+ char *font_uri_att;
+ char *origin_x_att;
+ char *origin_y_att;
+ char *is_sideways_att;
+ char *indices_att;
+ char *unicode_att;
+ char *style_att;
+ char *transform_att;
+ char *clip_att;
+ char *opacity_att;
+ char *opacity_mask_att;
+
+ xps_item_t *transform_tag = NULL;
+ xps_item_t *clip_tag = NULL;
+ xps_item_t *fill_tag = NULL;
+ xps_item_t *opacity_mask_tag = NULL;
+
+ char *fill_opacity_att = NULL;
+
+ xps_part_t *part;
+ xps_font_t *font;
+
+ char partname[1024];
+ char *subfont;
+
+ gs_matrix matrix;
+ float font_size = 10.0;
+ int subfontid = 0;
+ int is_sideways = 0;
+ int bidi_level = 0;
+
+ /*
+ * Extract attributes and extended attributes.
+ */
+
+ bidi_level_att = xps_att(root, "BidiLevel");
+ caret_stops_att = xps_att(root, "CaretStops");
+ fill_att = xps_att(root, "Fill");
+ font_size_att = xps_att(root, "FontRenderingEmSize");
+ font_uri_att = xps_att(root, "FontUri");
+ origin_x_att = xps_att(root, "OriginX");
+ origin_y_att = xps_att(root, "OriginY");
+ is_sideways_att = xps_att(root, "IsSideways");
+ indices_att = xps_att(root, "Indices");
+ unicode_att = xps_att(root, "UnicodeString");
+ style_att = xps_att(root, "StyleSimulations");
+ transform_att = xps_att(root, "RenderTransform");
+ clip_att = xps_att(root, "Clip");
+ opacity_att = xps_att(root, "Opacity");
+ opacity_mask_att = xps_att(root, "OpacityMask");
+
+ for (node = xps_down(root); node; node = xps_next(node))
+ {
+ if (!strcmp(xps_tag(node), "Glyphs.RenderTransform"))
+ transform_tag = xps_down(node);
+
+ if (!strcmp(xps_tag(node), "Glyphs.OpacityMask"))
+ opacity_mask_tag = xps_down(node);
+
+ if (!strcmp(xps_tag(node), "Glyphs.Clip"))
+ clip_tag = xps_down(node);
+
+ if (!strcmp(xps_tag(node), "Glyphs.Fill"))
+ fill_tag = xps_down(node);
+ }
+
+ fill_uri = base_uri;
+ opacity_mask_uri = base_uri;
+
+ xps_resolve_resource_reference(ctx, dict, &transform_att, &transform_tag, NULL);
+ xps_resolve_resource_reference(ctx, dict, &clip_att, &clip_tag, NULL);
+ xps_resolve_resource_reference(ctx, dict, &fill_att, &fill_tag, &fill_uri);
+ xps_resolve_resource_reference(ctx, dict, &opacity_mask_att, &opacity_mask_tag, &opacity_mask_uri);
+
+ /*
+ * Check that we have all the necessary information.
+ */
+
+ if (!font_size_att || !font_uri_att || !origin_x_att || !origin_y_att)
+ return gs_throw(-1, "missing attributes in glyphs element");
+
+ if (!indices_att && !unicode_att)
+ return 0; /* nothing to draw */
+
+ if (is_sideways_att)
+ is_sideways = !strcmp(is_sideways_att, "true");
+
+ if (bidi_level_att)
+ bidi_level = atoi(bidi_level_att);
+
+ /*
+ * Find and load the font resource
+ */
+
+ xps_absolute_path(partname, base_uri, font_uri_att, sizeof partname);
+ subfont = strrchr(partname, '#');
+ if (subfont)
+ {
+ subfontid = atoi(subfont + 1);
+ *subfont = 0;
+ }
+
+ font = xps_hash_lookup(ctx->font_table, partname);
+ if (!font)
+ {
+ part = xps_read_part(ctx, partname);
+ if (!part)
+ return gs_throw1(-1, "cannot find font resource part '%s'", partname);
+
+ /* deobfuscate if necessary */
+ if (strstr(part->name, ".odttf"))
+ xps_deobfuscate_font_resource(ctx, part);
+ if (strstr(part->name, ".ODTTF"))
+ xps_deobfuscate_font_resource(ctx, part);
+
+ font = xps_new_font(ctx, part->data, part->size, subfontid);
+ if (!font)
+ return gs_rethrow1(-1, "cannot load font resource '%s'", partname);
+
+ xps_select_best_font_encoding(font);
+
+ xps_hash_insert(ctx, ctx->font_table, part->name, font);
+
+ /* NOTE: we kept part->name in the hashtable and part->data in the font */
+ xps_free(ctx, part);
+ }
+
+ /*
+ * Set up graphics state.
+ */
+
+ gs_gsave(ctx->pgs);
+
+ if (transform_att || transform_tag)
+ {
+ gs_matrix transform;
+
+ if (transform_att)
+ xps_parse_render_transform(ctx, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(ctx, transform_tag, &transform);
+
+ gs_concat(ctx->pgs, &transform);
+ }
+
+ if (clip_att || clip_tag)
+ {
+ if (clip_att)
+ xps_parse_abbreviated_geometry(ctx, clip_att);
+ if (clip_tag)
+ xps_parse_path_geometry(ctx, dict, clip_tag, 0);
+ xps_clip(ctx);
+ }
+
+ font_size = atof(font_size_att);
+
+ gs_setfont(ctx->pgs, font->font);
+ gs_make_scaling(font_size, -font_size, &matrix);
+ if (is_sideways)
+ gs_matrix_rotate(&matrix, 90.0, &matrix);
+
+ gs_setcharmatrix(ctx->pgs, &matrix);
+
+ gs_matrix_multiply(&matrix, &font->font->orig_FontMatrix, &font->font->FontMatrix);
+
+ code = xps_begin_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+ if (code)
+ {
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "cannot create transparency group");
+ }
+
+ /*
+ * If it's a solid color brush fill/stroke do a simple fill
+ */
+
+ if (fill_tag && !strcmp(xps_tag(fill_tag), "SolidColorBrush"))
+ {
+ fill_opacity_att = xps_att(fill_tag, "Opacity");
+ fill_att = xps_att(fill_tag, "Color");
+ fill_tag = NULL;
+ }
+
+ if (fill_att)
+ {
+ float samples[32];
+ gs_color_space *colorspace;
+ xps_parse_color(ctx, base_uri, fill_att, &colorspace, samples);
+ if (fill_opacity_att)
+ samples[0] = atof(fill_opacity_att);
+ xps_set_color(ctx, colorspace, samples);
+ code = xps_parse_glyphs_imp(ctx, font, font_size,
+ atof(origin_x_att), atof(origin_y_att),
+ is_sideways, bidi_level,
+ indices_att, unicode_att, 0);
+ if (code)
+ {
+ xps_end_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "cannot parse glyphs data");
+ }
+ }
+
+ /*
+ * If it's a visual brush or image, use the charpath as a clip mask to paint brush
+ */
+
+ if (fill_tag)
+ {
+ ctx->fill_rule = 1; /* always use non-zero winding rule for char paths */
+ code = xps_parse_glyphs_imp(ctx, font, font_size,
+ atof(origin_x_att), atof(origin_y_att),
+ is_sideways, bidi_level, indices_att, unicode_att, 1);
+ if (code)
+ {
+ xps_end_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "cannot parse glyphs data");
+ }
+
+ code = xps_parse_brush(ctx, fill_uri, dict, fill_tag);
+ if (code)
+ {
+ xps_end_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "cannot parse fill brush");
+ }
+ }
+
+ xps_end_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+
+ gs_grestore(ctx->pgs);
+
+ return 0;
+}
diff --git a/xps/xpsgradient.c b/xps/xpsgradient.c
new file mode 100644
index 00000000..0c6bcf60
--- /dev/null
+++ b/xps/xpsgradient.c
@@ -0,0 +1,978 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* XPS interpreter - gradient support */
+
+#include "ghostxps.h"
+
+#define MAX_STOPS 256
+
+enum { SPREAD_PAD, SPREAD_REPEAT, SPREAD_REFLECT };
+
+/*
+ * Parse a list of GradientStop elements.
+ * Fill the offset and color arrays, and
+ * return the number of stops parsed.
+ */
+
+struct stop
+{
+ float offset;
+ float color[4];
+};
+
+static int cmp_stop(const void *a, const void *b)
+{
+ const struct stop *astop = a;
+ const struct stop *bstop = b;
+ float diff = astop->offset - bstop->offset;
+ if (diff < 0)
+ return -1;
+ if (diff > 0)
+ return 1;
+ return 0;
+}
+
+static inline float lerp(float a, float b, float x)
+{
+ return a + (b - a) * x;
+}
+
+static int
+xps_parse_gradient_stops(xps_context_t *ctx, char *base_uri, xps_item_t *node,
+ struct stop *stops, int maxcount)
+{
+ unsigned short sample_in[8], sample_out[8]; /* XPS allows up to 8 bands */
+ gsicc_rendering_param_t rendering_params;
+ gsicc_link_t *icclink;
+ gs_color_space *colorspace;
+ float sample[8];
+ int before, after;
+ int count;
+ int i, k;
+
+ /* We may have to insert 2 extra stops when postprocessing */
+ maxcount -= 2;
+
+ count = 0;
+ while (node && count < maxcount)
+ {
+ if (!strcmp(xps_tag(node), "GradientStop"))
+ {
+ char *offset = xps_att(node, "Offset");
+ char *color = xps_att(node, "Color");
+ if (offset && color)
+ {
+ stops[count].offset = atof(offset);
+
+ xps_parse_color(ctx, base_uri, color, &colorspace, sample);
+
+ /* Set the rendering parameters */
+ rendering_params.black_point_comp = BP_ON;
+ rendering_params.object_type = GS_PATH_TAG;
+ rendering_params.rendering_intent = gsPERCEPTUAL;
+
+ /* Get link to map from source to sRGB */
+ icclink = gsicc_get_link((gs_imager_state*) ctx->pgs,
+ NULL, colorspace, ctx->srgb,
+ &rendering_params, ctx->memory, false);
+
+ if (icclink != NULL && !icclink->is_identity)
+ {
+ /* Transform the color */
+ int num_colors = gsicc_getsrc_channel_count(colorspace->cmm_icc_profile_data);
+ for (i = 0; i < num_colors; i++)
+ {
+ sample_in[i] = sample[i+1]*65535;
+ }
+ gscms_transform_color(icclink, sample_in, sample_out, 2, NULL);
+
+ stops[count].color[0] = sample[0]; /* Alpha */
+ stops[count].color[1] = (float) sample_out[0] / 65535.0; /* sRGB */
+ stops[count].color[2] = (float) sample_out[1] / 65535.0;
+ stops[count].color[3] = (float) sample_out[2] / 65535.0;
+ }
+ else
+ {
+ stops[count].color[0] = sample[0];
+ stops[count].color[1] = sample[1];
+ stops[count].color[2] = sample[2];
+ stops[count].color[3] = sample[3];
+ }
+
+ count ++;
+ }
+ }
+
+ if (icclink != NULL)
+ gsicc_release_link(icclink);
+ icclink = NULL;
+ node = xps_next(node);
+
+ }
+
+ if (count == 0)
+ {
+ gs_warn("gradient brush has no gradient stops");
+ stops[0].offset = 0;
+ stops[0].color[0] = 1;
+ stops[0].color[1] = 0;
+ stops[0].color[2] = 0;
+ stops[0].color[3] = 0;
+ stops[1].offset = 1;
+ stops[1].color[0] = 1;
+ stops[1].color[1] = 1;
+ stops[1].color[2] = 1;
+ stops[1].color[3] = 1;
+ return 2;
+ }
+
+ if (count == maxcount)
+ gs_warn("gradient brush exceeded maximum number of gradient stops");
+
+ /* Postprocess to make sure the range of offsets is 0.0 to 1.0 */
+
+ qsort(stops, count, sizeof(struct stop), cmp_stop);
+
+ before = -1;
+ after = -1;
+
+ for (i = 0; i < count; i++)
+ {
+ if (stops[i].offset < 0)
+ before = i;
+ if (stops[i].offset > 1)
+ {
+ after = i;
+ break;
+ }
+ }
+
+ /* Remove all stops < 0 except the largest one */
+ if (before > 0)
+ {
+ memmove(stops, stops + before, (count - before) * sizeof(struct stop));
+ count -= before;
+ }
+
+ /* Remove all stops > 1 except the smallest one */
+ if (after >= 0)
+ count = after + 1;
+
+ /* Expand single stop to 0 .. 1 */
+ if (count == 1)
+ {
+ stops[1] = stops[0];
+ stops[0].offset = 0;
+ stops[1].offset = 1;
+ return 2;
+ }
+
+ /* First stop < 0 -- interpolate value to 0 */
+ if (stops[0].offset < 0)
+ {
+ float d = -stops[0].offset / (stops[1].offset - stops[0].offset);
+ stops[0].offset = 0;
+ for (k = 0; k < 4; k++)
+ stops[0].color[k] = lerp(stops[0].color[k], stops[1].color[k], d);
+ }
+
+ /* Last stop > 1 -- interpolate value to 1 */
+ if (stops[count-1].offset > 1)
+ {
+ float d = (1 - stops[count-2].offset) / (stops[count-1].offset - stops[count-2].offset);
+ stops[count-1].offset = 1;
+ for (k = 0; k < 4; k++)
+ stops[count-1].color[k] = lerp(stops[count-2].color[k], stops[count-1].color[k], d);
+ }
+
+ /* First stop > 0 -- insert a duplicate at 0 */
+ if (stops[0].offset > 0)
+ {
+ memmove(stops + 1, stops, count * sizeof(struct stop));
+ stops[0] = stops[1];
+ stops[0].offset = 0;
+ count++;
+ }
+
+ /* Last stop < 1 -- insert a duplicate at 1 */
+ if (stops[count-1].offset < 1)
+ {
+ stops[count] = stops[count-1];
+ stops[count].offset = 1;
+ count++;
+ }
+
+ return count;
+}
+
+static int
+xps_gradient_has_transparent_colors(struct stop *stops, int count)
+{
+ int i;
+ for (i = 0; i < count; i++)
+ if (stops[i].color[0] < 1)
+ return 1;
+ return 0;
+}
+
+/*
+ * Create a Function object to map [0..1] to RGB colors
+ * based on the gradient stop arrays.
+ *
+ * We do this by creating a stitching function that joins
+ * a series of linear functions (one linear function
+ * for each gradient stop-pair).
+ */
+
+static gs_function_t *
+xps_create_gradient_stop_function(xps_context_t *ctx, struct stop *stops, int count, int opacity_only)
+{
+ gs_function_1ItSg_params_t sparams;
+ gs_function_ElIn_params_t lparams;
+ gs_function_t *sfunc;
+ gs_function_t *lfunc;
+
+ float *domain, *range, *c0, *c1, *bounds, *encode;
+ const gs_function_t **functions;
+
+ int code;
+ int k;
+ int i;
+
+ k = count - 1; /* number of intervals / functions */
+
+ domain = xps_alloc(ctx, 2 * sizeof(float));
+ domain[0] = 0.0;
+ domain[1] = 1.0;
+ sparams.m = 1;
+ sparams.Domain = domain;
+
+ range = xps_alloc(ctx, 6 * sizeof(float));
+ range[0] = 0.0;
+ range[1] = 1.0;
+ range[2] = 0.0;
+ range[3] = 1.0;
+ range[4] = 0.0;
+ range[5] = 1.0;
+ sparams.n = 3;
+ sparams.Range = range;
+
+ functions = xps_alloc(ctx, k * sizeof(void*));
+ bounds = xps_alloc(ctx, (k - 1) * sizeof(float));
+ encode = xps_alloc(ctx, (k * 2) * sizeof(float));
+
+ sparams.k = k;
+ sparams.Functions = functions;
+ sparams.Bounds = bounds;
+ sparams.Encode = encode;
+
+ for (i = 0; i < k; i++)
+ {
+ domain = xps_alloc(ctx, 2 * sizeof(float));
+ domain[0] = 0.0;
+ domain[1] = 1.0;
+ lparams.m = 1;
+ lparams.Domain = domain;
+
+ range = xps_alloc(ctx, 6 * sizeof(float));
+ range[0] = 0.0;
+ range[1] = 1.0;
+ range[2] = 0.0;
+ range[3] = 1.0;
+ range[4] = 0.0;
+ range[5] = 1.0;
+ lparams.n = 3;
+ lparams.Range = range;
+
+ c0 = xps_alloc(ctx, 3 * sizeof(float));
+ lparams.C0 = c0;
+
+ c1 = xps_alloc(ctx, 3 * sizeof(float));
+ lparams.C1 = c1;
+
+ if (opacity_only)
+ {
+ c0[0] = stops[i].color[0];
+ c0[1] = stops[i].color[0];
+ c0[2] = stops[i].color[0];
+
+ c1[0] = stops[i+1].color[0];
+ c1[1] = stops[i+1].color[0];
+ c1[2] = stops[i+1].color[0];
+ }
+ else
+ {
+ c0[0] = stops[i].color[1];
+ c0[1] = stops[i].color[2];
+ c0[2] = stops[i].color[3];
+
+ c1[0] = stops[i+1].color[1];
+ c1[1] = stops[i+1].color[2];
+ c1[2] = stops[i+1].color[3];
+ }
+
+ lparams.N = 1;
+
+ code = gs_function_ElIn_init(&lfunc, &lparams, ctx->memory);
+ if (code < 0)
+ {
+ gs_rethrow(code, "gs_function_ElIn_init failed");
+ return NULL;
+ }
+
+ functions[i] = lfunc;
+
+ if (i > 0)
+ bounds[i - 1] = stops[i].offset;
+
+ encode[i * 2 + 0] = 0.0;
+ encode[i * 2 + 1] = 1.0;
+ }
+
+ code = gs_function_1ItSg_init(&sfunc, &sparams, ctx->memory);
+ if (code < 0)
+ {
+ gs_rethrow(code, "gs_function_1ItSg_init failed");
+ return NULL;
+ }
+
+ return sfunc;
+}
+
+/*
+ * Shadings and functions are ghostscript type objects,
+ * and as such rely on the garbage collector for cleanup.
+ * We can't have none of that here, so we have to
+ * write our own destructors.
+ */
+
+static void
+xps_free_gradient_stop_function(xps_context_t *ctx, gs_function_t *func)
+{
+ gs_function_t *lfunc;
+ gs_function_1ItSg_params_t *sparams;
+ gs_function_ElIn_params_t *lparams;
+ int i;
+
+ sparams = (gs_function_1ItSg_params_t*) &func->params;
+ xps_free(ctx, (void*)sparams->Domain);
+ xps_free(ctx, (void*)sparams->Range);
+
+ for (i = 0; i < sparams->k; i++)
+ {
+ lfunc = (gs_function_t*) sparams->Functions[i]; /* discard const */
+ lparams = (gs_function_ElIn_params_t*) &lfunc->params;
+ xps_free(ctx, (void*)lparams->Domain);
+ xps_free(ctx, (void*)lparams->Range);
+ xps_free(ctx, (void*)lparams->C0);
+ xps_free(ctx, (void*)lparams->C1);
+ xps_free(ctx, lfunc);
+ }
+
+ xps_free(ctx, (void*)sparams->Bounds);
+ xps_free(ctx, (void*)sparams->Encode);
+ xps_free(ctx, (void*)sparams->Functions);
+ xps_free(ctx, func);
+}
+
+/*
+ * For radial gradients that have a cone drawing we have to
+ * reverse the direction of the gradient because we draw
+ * the shading in the opposite direction with the
+ * big circle first.
+ */
+static gs_function_t *
+xps_reverse_function(xps_context_t *ctx, gs_function_t *func, float *fary, void *vary)
+{
+ gs_function_1ItSg_params_t sparams;
+ gs_function_t *sfunc;
+ int code;
+
+ /* take from stack allocated arrays that the caller provides */
+ float *domain = fary + 0;
+ float *range = fary + 2;
+ float *encode = fary + 2 + 6;
+ const gs_function_t **functions = vary;
+
+ domain[0] = 0.0;
+ domain[1] = 1.0;
+
+ range[0] = 0.0;
+ range[1] = 1.0;
+ range[2] = 0.0;
+ range[3] = 1.0;
+ range[4] = 0.0;
+ range[5] = 1.0;
+
+ functions[0] = func;
+
+ encode[0] = 1.0;
+ encode[1] = 0.0;
+
+ sparams.m = 1;
+ sparams.Domain = domain;
+ sparams.n = 3;
+ sparams.Range = range;
+ sparams.k = 1;
+ sparams.Functions = functions;
+ sparams.Bounds = NULL;
+ sparams.Encode = encode;
+
+ code = gs_function_1ItSg_init(&sfunc, &sparams, ctx->memory);
+ if (code < 0)
+ {
+ gs_rethrow(code, "gs_function_1ItSg_init failed");
+ return NULL;
+ }
+
+ return sfunc;
+}
+
+/*
+ * Radial gradients map more or less to Radial shadings.
+ * The inner circle is always a point.
+ * The outer circle is actually an ellipse,
+ * mess with the transform to squash the circle into the right aspect.
+ */
+
+static int
+xps_draw_one_radial_gradient(xps_context_t *ctx,
+ gs_function_t *func, int extend,
+ float x0, float y0, float r0,
+ float x1, float y1, float r1)
+{
+ gs_memory_t *mem = ctx->memory;
+ gs_shading_t *shading;
+ gs_shading_R_params_t params;
+ int code;
+
+ gs_shading_R_params_init(&params);
+ {
+ params.ColorSpace = ctx->srgb;
+
+ params.Coords[0] = x0;
+ params.Coords[1] = y0;
+ params.Coords[2] = r0;
+ params.Coords[3] = x1;
+ params.Coords[4] = y1;
+ params.Coords[5] = r1;
+
+ params.Extend[0] = extend;
+ params.Extend[1] = extend;
+
+ params.Function = func;
+ }
+
+ code = gs_shading_R_init(&shading, &params, mem);
+ if (code < 0)
+ return gs_rethrow(code, "gs_shading_R_init failed");
+
+ gs_setsmoothness(ctx->pgs, 0.02);
+
+ code = gs_shfill(ctx->pgs, shading);
+ if (code < 0)
+ {
+ gs_free_object(mem, shading, "gs_shading_R");
+ return gs_rethrow(code, "gs_shfill failed");
+ }
+
+ gs_free_object(mem, shading, "gs_shading_R");
+
+ return 0;
+}
+
+/*
+ * Linear gradients map to Axial shadings.
+ */
+
+static int
+xps_draw_one_linear_gradient(xps_context_t *ctx,
+ gs_function_t *func, int extend,
+ float x0, float y0, float x1, float y1)
+{
+ gs_memory_t *mem = ctx->memory;
+ gs_shading_t *shading;
+ gs_shading_A_params_t params;
+ int code;
+
+ gs_shading_A_params_init(&params);
+ {
+ params.ColorSpace = ctx->srgb;
+
+ params.Coords[0] = x0;
+ params.Coords[1] = y0;
+ params.Coords[2] = x1;
+ params.Coords[3] = y1;
+
+ params.Extend[0] = extend;
+ params.Extend[1] = extend;
+
+ params.Function = func;
+ }
+
+ code = gs_shading_A_init(&shading, &params, mem);
+ if (code < 0)
+ return gs_rethrow(code, "gs_shading_A_init failed");
+
+ gs_setsmoothness(ctx->pgs, 0.02);
+
+ code = gs_shfill(ctx->pgs, shading);
+ if (code < 0)
+ {
+ gs_free_object(mem, shading, "gs_shading_A");
+ return gs_rethrow(code, "gs_shfill failed");
+ }
+
+ gs_free_object(mem, shading, "gs_shading_A");
+
+ return 0;
+}
+
+/*
+ * We need to loop and create many shading objects to account
+ * for the Repeat and Reflect SpreadMethods.
+ * I'm not smart enough to calculate this analytically
+ * so we iterate and check each object until we
+ * reach a reasonable limit for infinite cases.
+ */
+
+static inline float point_inside_circle(float px, float py, float x, float y, float r)
+{
+ float dx = px - x;
+ float dy = py - y;
+ return (dx * dx + dy * dy) <= (r * r);
+}
+
+static int
+xps_draw_radial_gradient(xps_context_t *ctx, xps_item_t *root, int spread, gs_function_t *func)
+{
+ gs_rect bbox;
+ float x0, y0, r0;
+ float x1, y1, r1;
+ float xrad = 1;
+ float yrad = 1;
+ float invscale;
+ float dx, dy;
+ int code;
+ int i;
+ int done;
+
+ char *center_att = xps_att(root, "Center");
+ char *origin_att = xps_att(root, "GradientOrigin");
+ char *radius_x_att = xps_att(root, "RadiusX");
+ char *radius_y_att = xps_att(root, "RadiusY");
+
+ if (origin_att)
+ sscanf(origin_att, "%g,%g", &x0, &y0);
+ if (center_att)
+ sscanf(center_att, "%g,%g", &x1, &y1);
+ if (radius_x_att)
+ xrad = atof(radius_x_att);
+ if (radius_y_att)
+ yrad = atof(radius_y_att);
+
+ /* scale the ctm to make ellipses */
+ gs_gsave(ctx->pgs);
+ gs_scale(ctx->pgs, 1.0, yrad / xrad);
+
+ invscale = xrad / yrad;
+ y0 = y0 * invscale;
+ y1 = y1 * invscale;
+
+ r0 = 0.0;
+ r1 = xrad;
+
+ dx = x1 - x0;
+ dy = y1 - y0;
+
+ xps_bounds_in_user_space(ctx, &bbox);
+
+ if (spread == SPREAD_PAD)
+ {
+ if (!point_inside_circle(x0, y0, x1, y1, r1))
+ {
+ gs_function_t *reverse;
+ float in[1];
+ float out[4];
+ float fary[10];
+ void *vary[1];
+
+ /* PDF shadings with extend doesn't work the same way as XPS
+ * gradients when the radial shading is a cone. In this case
+ * we fill the background ourselves.
+ */
+
+ in[0] = 1.0;
+ out[0] = 1.0;
+ out[1] = 0.0;
+ out[2] = 0.0;
+ out[3] = 0.0;
+ if (ctx->opacity_only)
+ gs_function_evaluate(func, in, out);
+ else
+ gs_function_evaluate(func, in, out + 1);
+
+ xps_set_color(ctx, ctx->srgb, out);
+
+ gs_moveto(ctx->pgs, bbox.p.x, bbox.p.y);
+ gs_lineto(ctx->pgs, bbox.q.x, bbox.p.y);
+ gs_lineto(ctx->pgs, bbox.q.x, bbox.q.y);
+ gs_lineto(ctx->pgs, bbox.p.x, bbox.q.y);
+ gs_closepath(ctx->pgs);
+ gs_fill(ctx->pgs);
+
+ /* We also have to reverse the direction so the bigger circle
+ * comes first or the graphical results do not match. We also
+ * have to reverse the direction of the function to compensate.
+ */
+
+ reverse = xps_reverse_function(ctx, func, fary, vary);
+ if (!reverse)
+ {
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(-1, "could not create the reversed function");
+ }
+
+ code = xps_draw_one_radial_gradient(ctx, reverse, 1, x1, y1, r1, x0, y0, r0);
+ if (code < 0)
+ {
+ xps_free(ctx, reverse);
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "could not draw radial gradient");
+ }
+
+ xps_free(ctx, reverse);
+ }
+ else
+ {
+ code = xps_draw_one_radial_gradient(ctx, func, 1, x0, y0, r0, x1, y1, r1);
+ if (code < 0)
+ {
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "could not draw radial gradient");
+ }
+ }
+ }
+ else
+ {
+ for (i = 0; i < 100; i++)
+ {
+ /* Draw current circle */
+
+ if (!point_inside_circle(x0, y0, x1, y1, r1))
+ dputs("xps: we should reverse gradient here too\n");
+
+ if (spread == SPREAD_REFLECT && (i & 1))
+ code = xps_draw_one_radial_gradient(ctx, func, 0, x1, y1, r1, x0, y0, r0);
+ else
+ code = xps_draw_one_radial_gradient(ctx, func, 0, x0, y0, r0, x1, y1, r1);
+ if (code < 0)
+ {
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "could not draw axial gradient");
+ }
+
+ /* Check if circle encompassed the entire bounding box (break loop if we do) */
+
+ done = 1;
+ if (!point_inside_circle(bbox.p.x, bbox.p.y, x1, y1, r1)) done = 0;
+ if (!point_inside_circle(bbox.p.x, bbox.q.y, x1, y1, r1)) done = 0;
+ if (!point_inside_circle(bbox.q.x, bbox.q.y, x1, y1, r1)) done = 0;
+ if (!point_inside_circle(bbox.q.x, bbox.p.y, x1, y1, r1)) done = 0;
+ if (done)
+ break;
+
+ /* Prepare next circle */
+
+ r0 = r1;
+ r1 += xrad;
+
+ x0 += dx;
+ y0 += dy;
+ x1 += dx;
+ y1 += dy;
+ }
+ }
+
+ gs_grestore(ctx->pgs);
+
+ return 0;
+}
+
+/*
+ * Calculate how many iterations are needed to cover
+ * the bounding box.
+ */
+
+static int
+xps_draw_linear_gradient(xps_context_t *ctx, xps_item_t *root, int spread, gs_function_t *func)
+{
+ gs_rect bbox;
+ float x0, y0, x1, y1;
+ float dx, dy;
+ int code;
+ int i;
+
+ char *start_point_att = xps_att(root, "StartPoint");
+ char *end_point_att = xps_att(root, "EndPoint");
+
+ x0 = 0;
+ y0 = 0;
+ x1 = 0;
+ y1 = 1;
+
+ if (start_point_att)
+ sscanf(start_point_att, "%g,%g", &x0, &y0);
+ if (end_point_att)
+ sscanf(end_point_att, "%g,%g", &x1, &y1);
+
+ dx = x1 - x0;
+ dy = y1 - y0;
+
+ xps_bounds_in_user_space(ctx, &bbox);
+
+ if (spread == SPREAD_PAD)
+ {
+ code = xps_draw_one_linear_gradient(ctx, func, 1, x0, y0, x1, y1);
+ if (code < 0)
+ return gs_rethrow(code, "could not draw axial gradient");
+ }
+ else
+ {
+ float len;
+ float a, b;
+ float dist[4];
+ float d0, d1;
+ int i0, i1;
+
+ len = sqrt(dx * dx + dy * dy);
+ a = dx / len;
+ b = dy / len;
+
+ dist[0] = a * (bbox.p.x - x0) + b * (bbox.p.y - y0);
+ dist[1] = a * (bbox.p.x - x0) + b * (bbox.q.y - y0);
+ dist[2] = a * (bbox.q.x - x0) + b * (bbox.q.y - y0);
+ dist[3] = a * (bbox.q.x - x0) + b * (bbox.p.y - y0);
+
+ d0 = dist[0];
+ d1 = dist[0];
+ for (i = 1; i < 4; i++)
+ {
+ if (dist[i] < d0) d0 = dist[i];
+ if (dist[i] > d1) d1 = dist[i];
+ }
+
+ i0 = floor(d0 / len);
+ i1 = ceil(d1 / len);
+
+ for (i = i0; i < i1; i++)
+ {
+ if (spread == SPREAD_REFLECT && (i & 1))
+ {
+ code = xps_draw_one_linear_gradient(ctx, func, 0,
+ x1 + dx * i, y1 + dy * i,
+ x0 + dx * i, y0 + dy * i);
+ }
+ else
+ {
+ code = xps_draw_one_linear_gradient(ctx, func, 0,
+ x0 + dx * i, y0 + dy * i,
+ x1 + dx * i, y1 + dy * i);
+ }
+ if (code < 0)
+ return gs_rethrow(code, "could not draw axial gradient");
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Parse XML tag and attributes for a gradient brush, create color/opacity
+ * function objects and call gradient drawing primitives.
+ */
+
+static int
+xps_parse_gradient_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root,
+ int (*draw)(xps_context_t *, xps_item_t *, int, gs_function_t *))
+{
+ xps_item_t *node;
+
+ char *opacity_att;
+ char *interpolation_att;
+ char *spread_att;
+ char *mapping_att;
+ char *transform_att;
+
+ xps_item_t *transform_tag = NULL;
+ xps_item_t *stop_tag = NULL;
+
+ struct stop stop_list[MAX_STOPS];
+ int stop_count;
+ gs_matrix transform;
+ int spread_method;
+ int code;
+
+ gs_rect bbox;
+
+ gs_function_t *color_func;
+ gs_function_t *opacity_func;
+ int has_opacity = 0;
+
+ opacity_att = xps_att(root, "Opacity");
+ interpolation_att = xps_att(root, "ColorInterpolationMode");
+ spread_att = xps_att(root, "SpreadMethod");
+ mapping_att = xps_att(root, "MappingMode");
+ transform_att = xps_att(root, "Transform");
+
+ for (node = xps_down(root); node; node = xps_next(node))
+ {
+ if (!strcmp(xps_tag(node), "LinearGradientBrush.Transform"))
+ transform_tag = xps_down(node);
+ if (!strcmp(xps_tag(node), "RadialGradientBrush.Transform"))
+ transform_tag = xps_down(node);
+ if (!strcmp(xps_tag(node), "LinearGradientBrush.GradientStops"))
+ stop_tag = xps_down(node);
+ if (!strcmp(xps_tag(node), "RadialGradientBrush.GradientStops"))
+ stop_tag = xps_down(node);
+ }
+
+ xps_resolve_resource_reference(ctx, dict, &transform_att, &transform_tag, NULL);
+
+ spread_method = SPREAD_PAD;
+ if (spread_att)
+ {
+ if (!strcmp(spread_att, "Pad"))
+ spread_method = SPREAD_PAD;
+ if (!strcmp(spread_att, "Reflect"))
+ spread_method = SPREAD_REFLECT;
+ if (!strcmp(spread_att, "Repeat"))
+ spread_method = SPREAD_REPEAT;
+ }
+
+ gs_make_identity(&transform);
+ if (transform_att)
+ xps_parse_render_transform(ctx, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(ctx, transform_tag, &transform);
+
+ if (!stop_tag)
+ return gs_throw(-1, "missing gradient stops tag");
+
+ stop_count = xps_parse_gradient_stops(ctx, base_uri, stop_tag, stop_list, MAX_STOPS);
+ if (stop_count == 0)
+ return gs_throw(-1, "no gradient stops found");
+
+ color_func = xps_create_gradient_stop_function(ctx, stop_list, stop_count, 0);
+ if (!color_func)
+ return gs_rethrow(-1, "could not create color gradient function");
+
+ opacity_func = xps_create_gradient_stop_function(ctx, stop_list, stop_count, 1);
+ if (!opacity_func)
+ return gs_rethrow(-1, "could not create opacity gradient function");
+
+ has_opacity = xps_gradient_has_transparent_colors(stop_list, stop_count);
+
+ xps_clip(ctx);
+
+ gs_gsave(ctx->pgs);
+ gs_concat(ctx->pgs, &transform);
+
+ xps_bounds_in_user_space(ctx, &bbox);
+
+ code = xps_begin_opacity(ctx, base_uri, dict, opacity_att, NULL);
+ if (code)
+ {
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "cannot create transparency group");
+ }
+
+ if (ctx->opacity_only)
+ {
+ code = draw(ctx, root, spread_method, opacity_func);
+ if (code)
+ {
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "cannot draw gradient opacity");
+ }
+ }
+ else
+ {
+ if (has_opacity)
+ {
+ gs_transparency_mask_params_t params;
+ gs_transparency_group_params_t tgp;
+
+ gs_trans_mask_params_init(&params, TRANSPARENCY_MASK_Luminosity);
+ gs_begin_transparency_mask(ctx->pgs, &params, &bbox, 0);
+ code = draw(ctx, root, spread_method, opacity_func);
+ if (code)
+ {
+ gs_end_transparency_mask(ctx->pgs, TRANSPARENCY_CHANNEL_Opacity);
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "cannot draw gradient opacity");
+ }
+ gs_end_transparency_mask(ctx->pgs, TRANSPARENCY_CHANNEL_Opacity);
+
+ gs_trans_group_params_init(&tgp);
+ gs_begin_transparency_group(ctx->pgs, &tgp, &bbox);
+ code = draw(ctx, root, spread_method, color_func);
+ if (code)
+ {
+ gs_end_transparency_group(ctx->pgs);
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "cannot draw gradient color");
+ }
+ gs_end_transparency_group(ctx->pgs);
+ }
+ else
+ {
+ code = draw(ctx, root, spread_method, color_func);
+ if (code)
+ {
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "cannot draw gradient color");
+ }
+ }
+ }
+
+ xps_end_opacity(ctx, base_uri, dict, opacity_att, NULL);
+
+ gs_grestore(ctx->pgs);
+
+ xps_free_gradient_stop_function(ctx, opacity_func);
+ xps_free_gradient_stop_function(ctx, color_func);
+
+ return 0;
+}
+
+int
+xps_parse_linear_gradient_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root)
+{
+ int code;
+ code = xps_parse_gradient_brush(ctx, base_uri, dict, root, xps_draw_linear_gradient);
+ if (code < 0)
+ return gs_rethrow(code, "cannot parse linear gradient brush");
+ return gs_okay;
+}
+
+int
+xps_parse_radial_gradient_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root)
+{
+ int code;
+ code = xps_parse_gradient_brush(ctx, base_uri, dict, root, xps_draw_radial_gradient);
+ if (code < 0)
+ return gs_rethrow(code, "cannot parse radial gradient brush");
+ return gs_okay;
+}
diff --git a/xps/xpshash.c b/xps/xpshash.c
new file mode 100644
index 00000000..459566c9
--- /dev/null
+++ b/xps/xpshash.c
@@ -0,0 +1,217 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* Linear probe hash table.
+ *
+ * Simple hashtable with open adressing linear probe.
+ * Does not manage memory of key/value pointers.
+ * Does not support deleting entries.
+ */
+
+#include "ghostxps.h"
+
+static const unsigned primes[] =
+{
+ 61, 127, 251, 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521,
+ 131071, 262139, 524287, 1048573, 2097143, 4194301, 8388593, 0
+};
+
+typedef struct xps_hash_entry_s xps_hash_entry_t;
+
+struct xps_hash_entry_s
+{
+ char *key;
+ void *value;
+};
+
+struct xps_hash_table_s
+{
+ void *ctx;
+ unsigned int size;
+ unsigned int load;
+ xps_hash_entry_t *entries;
+};
+
+static inline int
+xps_tolower(int c)
+{
+ if (c >= 'A' && c <= 'Z')
+ return c + 32;
+ return c;
+}
+
+static unsigned int
+xps_hash(char *s)
+{
+ unsigned int h = 0;
+ while (*s)
+ h = xps_tolower(*s++) + (h << 6) + (h << 16) - h;
+ return h;
+}
+
+xps_hash_table_t *
+xps_hash_new(xps_context_t *ctx)
+{
+ xps_hash_table_t *table;
+
+ table = xps_alloc(ctx, sizeof(xps_hash_table_t));
+ if (!table)
+ {
+ gs_throw(-1, "out of memory: hash table struct");
+ return NULL;
+ }
+
+ table->size = primes[0];
+ table->load = 0;
+
+ table->entries = xps_alloc(ctx, sizeof(xps_hash_entry_t) * table->size);
+ if (!table->entries)
+ {
+ xps_free(ctx, table);
+ gs_throw(-1, "out of memory: hash table entries array");
+ return NULL;
+ }
+
+ memset(table->entries, 0, sizeof(xps_hash_entry_t) * table->size);
+
+ return table;
+}
+
+static int
+xps_hash_double(xps_context_t *ctx, xps_hash_table_t *table)
+{
+ xps_hash_entry_t *old_entries;
+ xps_hash_entry_t *new_entries;
+ unsigned int old_size = table->size;
+ unsigned int new_size = table->size * 2;
+ int i;
+
+ for (i = 0; primes[i] != 0; i++)
+ {
+ if (primes[i] > old_size)
+ {
+ new_size = primes[i];
+ break;
+ }
+ }
+
+ old_entries = table->entries;
+ new_entries = xps_alloc(ctx, sizeof(xps_hash_entry_t) * new_size);
+ if (!new_entries)
+ return gs_throw(-1, "out of memory: hash table entries array");
+
+ table->size = new_size;
+ table->entries = new_entries;
+ table->load = 0;
+
+ memset(table->entries, 0, sizeof(xps_hash_entry_t) * table->size);
+
+ for (i = 0; i < old_size; i++)
+ if (old_entries[i].value)
+ xps_hash_insert(ctx, table, old_entries[i].key, old_entries[i].value);
+
+ xps_free(ctx, old_entries);
+
+ return 0;
+}
+
+void
+xps_hash_free(xps_context_t *ctx, xps_hash_table_t *table,
+ void (*free_key)(xps_context_t *ctx, void *),
+ void (*free_value)(xps_context_t *ctx, void *))
+{
+ int i;
+
+ for (i = 0; i < table->size; i++)
+ {
+ if (table->entries[i].key && free_key)
+ free_key(ctx, table->entries[i].key);
+ if (table->entries[i].value && free_value)
+ free_value(ctx, table->entries[i].value);
+ }
+
+ xps_free(ctx, table->entries);
+ xps_free(ctx, table);
+}
+
+void *
+xps_hash_lookup(xps_hash_table_t *table, char *key)
+{
+ xps_hash_entry_t *entries = table->entries;
+ unsigned int size = table->size;
+ unsigned int pos = xps_hash(key) % size;
+
+ while (1)
+ {
+ if (!entries[pos].value)
+ return NULL;
+
+ if (xps_strcasecmp(key, entries[pos].key) == 0)
+ return entries[pos].value;
+
+ pos = (pos + 1) % size;
+ }
+}
+
+int
+xps_hash_insert(xps_context_t *ctx, xps_hash_table_t *table, char *key, void *value)
+{
+ xps_hash_entry_t *entries;
+ unsigned int size, pos;
+
+ /* Grow the table at 80% load */
+ if (table->load > table->size * 8 / 10)
+ {
+ if (xps_hash_double(ctx, table) < 0)
+ return gs_rethrow(-1, "cannot grow hash table");
+ }
+
+ entries = table->entries;
+ size = table->size;
+ pos = xps_hash(key) % size;
+
+ while (1)
+ {
+ if (!entries[pos].value)
+ {
+ entries[pos].key = key;
+ entries[pos].value = value;
+ table->load ++;
+ return 0;
+ }
+
+ if (xps_strcasecmp(key, entries[pos].key) == 0)
+ {
+ return 0;
+ }
+
+ pos = (pos + 1) % size;
+ }
+}
+
+void
+xps_hash_debug(xps_hash_table_t *table)
+{
+ int i;
+
+ printf("hash table load %d / %d\n", table->load, table->size);
+
+ for (i = 0; i < table->size; i++)
+ {
+ if (!table->entries[i].value)
+ printf("table % 4d: empty\n", i);
+ else
+ printf("table % 4d: key=%s value=%p\n", i,
+ table->entries[i].key, table->entries[i].value);
+ }
+}
diff --git a/xps/xpsimage.c b/xps/xpsimage.c
new file mode 100644
index 00000000..7a69ba09
--- /dev/null
+++ b/xps/xpsimage.c
@@ -0,0 +1,470 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* XPS interpreter - image support */
+
+/* TODO: we should be smarter here and do incremental decoding
+ * and rendering instead of uncompressing the whole image to
+ * memory before drawing.
+ */
+
+#include "ghostxps.h"
+
+/*
+ * Un-interleave the alpha channel.
+ */
+
+static void
+xps_isolate_alpha_channel_8(xps_context_t *ctx, xps_image_t *image)
+{
+ int n = image->comps;
+ int y, x, k;
+ byte *sp, *dp, *ap;
+
+ image->alpha = xps_alloc(ctx, image->width * image->height);
+
+ for (y = 0; y < image->height; y++)
+ {
+ sp = image->samples + image->width * n * y;
+ dp = image->samples + image->width * (n - 1) * y;
+ ap = image->alpha + image->width * y;
+ for (x = 0; x < image->width; x++)
+ {
+ for (k = 0; k < n - 1; k++)
+ *dp++ = *sp++;
+ *ap++ = *sp++;
+ }
+ }
+
+ image->hasalpha = 0;
+ image->comps --;
+ image->stride = image->width * image->comps;
+}
+
+static void
+xps_isolate_alpha_channel_16(xps_context_t *ctx, xps_image_t *image)
+{
+ int n = image->comps;
+ int y, x, k;
+ unsigned short *sp, *dp, *ap;
+
+ image->alpha = xps_alloc(ctx, image->width * image->height * 2);
+
+ for (y = 0; y < image->height; y++)
+ {
+ sp = ((unsigned short*)image->samples) + (image->width * n * y);
+ dp = ((unsigned short*)image->samples) + (image->width * (n - 1) * y);
+ ap = ((unsigned short*)image->alpha) + (image->width * y);
+ for (x = 0; x < image->width; x++)
+ {
+ for (k = 0; k < n - 1; k++)
+ *dp++ = *sp++;
+ *ap++ = *sp++;
+ }
+ }
+
+ image->hasalpha = 0;
+ image->comps --;
+ image->stride = image->width * image->comps * 2;
+}
+
+static int
+xps_image_has_alpha(xps_context_t *ctx, xps_part_t *part)
+{
+ byte *buf = part->data;
+ int len = part->size;
+
+ if (len < 8)
+ {
+ gs_warn("unknown image file format");
+ return 0;
+ }
+
+ if (buf[0] == 0xff && buf[1] == 0xd8)
+ return 0; /* JPEG never has an alpha channel */
+ else if (memcmp(buf, "\211PNG\r\n\032\n", 8) == 0)
+ return xps_png_has_alpha(ctx, buf, len);
+ else if (memcmp(buf, "II", 2) == 0 && buf[2] == 0xBC)
+ return xps_jpegxr_has_alpha(ctx, buf, len);
+ else if (memcmp(buf, "MM", 2) == 0)
+ return xps_tiff_has_alpha(ctx, buf, len);
+ else if (memcmp(buf, "II", 2) == 0)
+ return xps_tiff_has_alpha(ctx, buf, len);
+
+ return 0;
+}
+
+static int
+xps_decode_image(xps_context_t *ctx, xps_part_t *part, xps_image_t *image)
+{
+ byte *buf = part->data;
+ int len = part->size;
+ cmm_profile_t *profile;
+ int error;
+
+ if (len < 8)
+ return gs_throw(-1, "unknown image file format");
+
+ memset(image, 0, sizeof(xps_image_t));
+ image->samples = NULL;
+ image->alpha = NULL;
+
+ if (buf[0] == 0xff && buf[1] == 0xd8)
+ {
+ error = xps_decode_jpeg(ctx, buf, len, image);
+ if (error)
+ return gs_rethrow(error, "could not decode jpeg image");
+ }
+ else if (memcmp(buf, "\211PNG\r\n\032\n", 8) == 0)
+ {
+ error = xps_decode_png(ctx, buf, len, image);
+ if (error)
+ return gs_rethrow(error, "could not decode png image");
+ }
+ else if (memcmp(buf, "II", 2) == 0 && buf[2] == 0xBC)
+ {
+ error = xps_decode_jpegxr(ctx, buf, len, image);
+ if (error)
+ return gs_rethrow(error, "could not decode jpeg-xr image");
+ }
+ else if (memcmp(buf, "MM", 2) == 0 || memcmp(buf, "II", 2) == 0)
+ {
+ error = xps_decode_tiff(ctx, buf, len, image);
+ if (error)
+ return gs_rethrow(error, "could not decode tiff image");
+ }
+ else
+ return gs_throw(-1, "unknown image file format");
+
+ // TODO: refcount image->colorspace
+
+ /* See if we need to use the embedded profile. */
+ if (image->profile)
+ {
+ /*
+ See if we can set up to use the embedded profile.
+ Note these profiles are NOT added to the xps color cache.
+ As such, they must be destroyed when the image brush ends.
+ */
+
+ /* Create the profile */
+ profile = gsicc_profile_new(NULL, ctx->memory, NULL, 0);
+
+ /* Set buffer */
+ profile->buffer = image->profile;
+ profile->buffer_size = image->profilesize;
+
+ /* Parse */
+ gsicc_init_profile_info(profile);
+
+ if (profile->profile_handle == NULL)
+ {
+ /* Problem with profile. Just ignore it */
+ gs_warn("ignoring problem with icc profile embedded in an image");
+ gsicc_profile_reference(profile, -1);
+ }
+ else
+ {
+ /* Check the profile is OK for channel data count.
+ * Need to be careful here since alpha is put into comps */
+ if ((image->comps - image->hasalpha) == gsicc_getsrc_channel_count(profile))
+ {
+ /* Create a new colorspace and associate with the profile */
+ // TODO: refcount image->colorspace
+ gs_cspace_build_ICC(&image->colorspace, NULL, ctx->memory);
+ image->colorspace->cmm_icc_profile_data = profile;
+ }
+ else
+ {
+ /* Problem with profile. Just ignore it */
+ gs_warn("ignoring icc profile embedded in an image with wrong number of components");
+ gsicc_profile_reference(profile, -1);
+ }
+ }
+ }
+
+ if (image->hasalpha)
+ {
+ if (image->bits < 8)
+ dprintf1("cannot isolate alpha channel in %d bpc images\n", image->bits);
+ if (image->bits == 8)
+ xps_isolate_alpha_channel_8(ctx, image);
+ if (image->bits == 16)
+ xps_isolate_alpha_channel_16(ctx, image);
+ }
+
+ return gs_okay;
+}
+
+static int
+xps_paint_image_brush_imp(xps_context_t *ctx, xps_image_t *image, int alpha)
+{
+ gs_image_enum *penum;
+ gs_color_space *colorspace;
+ gs_image_t gsimage;
+ int code;
+
+ unsigned int count;
+ unsigned int used;
+ byte *samples;
+
+ if (alpha)
+ {
+ colorspace = ctx->gray;
+ samples = image->alpha;
+ count = (image->width * image->bits + 7) / 8 * image->height;
+ used = 0;
+ }
+ else
+ {
+ colorspace = image->colorspace;
+ samples = image->samples;
+ count = image->stride * image->height;
+ used = 0;
+ }
+
+ memset(&gsimage, 0, sizeof(gsimage));
+ gs_image_t_init(&gsimage, colorspace);
+ gsimage.ColorSpace = colorspace;
+ gsimage.BitsPerComponent = image->bits;
+ gsimage.Width = image->width;
+ gsimage.Height = image->height;
+
+ gsimage.ImageMatrix.xx = image->xres / 96.0;
+ gsimage.ImageMatrix.yy = image->yres / 96.0;
+
+ gsimage.Interpolate = 1;
+
+ penum = gs_image_enum_alloc(ctx->memory, "xps_parse_image_brush (gs_image_enum_alloc)");
+ if (!penum)
+ return gs_throw(-1, "gs_enum_allocate failed");
+
+ if ((code = gs_image_init(penum, &gsimage, false, ctx->pgs)) < 0)
+ return gs_throw(code, "gs_image_init failed");
+
+ if ((code = gs_image_next(penum, samples, count, &used)) < 0)
+ return gs_throw(code, "gs_image_next failed");
+
+ if (count < used)
+ return gs_throw2(-1, "not enough image data (image=%d used=%d)", count, used);
+
+ if (count > used)
+ return gs_throw2(0, "too much image data (image=%d used=%d)", count, used);
+
+ gs_image_cleanup_and_free_enum(penum, ctx->pgs);
+
+ return 0;
+}
+
+static int
+xps_paint_image_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root, void *vimage)
+{
+ xps_image_t *image = vimage;
+ int code;
+
+ if (ctx->opacity_only)
+ {
+ if (image->alpha)
+ {
+ code = xps_paint_image_brush_imp(ctx, image, 1);
+ if (code < 0)
+ return gs_rethrow(code, "cannot draw alpha channel image");
+ }
+ return 0;
+ }
+
+ if (image->alpha)
+ {
+ gs_transparency_mask_params_t params;
+ gs_transparency_group_params_t tgp;
+ gs_rect bbox;
+
+ xps_bounds_in_user_space(ctx, &bbox);
+
+ code = gs_gsave(ctx->pgs);
+ if (code < 0)
+ return gs_rethrow(code, "cannot gsave before transparency group");
+
+ gs_setcolorspace(ctx->pgs, ctx->gray);
+ gs_trans_mask_params_init(&params, TRANSPARENCY_MASK_Luminosity);
+ gs_begin_transparency_mask(ctx->pgs, &params, &bbox, 0);
+ code = xps_paint_image_brush_imp(ctx, image, 1);
+ if (code < 0)
+ {
+ gs_end_transparency_mask(ctx->pgs, TRANSPARENCY_CHANNEL_Opacity);
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "cannot draw alpha channel image");
+ }
+ gs_end_transparency_mask(ctx->pgs, TRANSPARENCY_CHANNEL_Opacity);
+
+ gs_setcolorspace(ctx->pgs, image->colorspace);
+ gs_trans_group_params_init(&tgp);
+ gs_begin_transparency_group(ctx->pgs, &tgp, &bbox);
+ code = xps_paint_image_brush_imp(ctx, image, 0);
+ if (code < 0)
+ {
+ gs_end_transparency_group(ctx->pgs);
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "cannot draw color channel image");
+ }
+ gs_end_transparency_group(ctx->pgs);
+
+ code = gs_grestore(ctx->pgs);
+ if (code < 0)
+ return gs_rethrow(code, "cannot grestore after transparency group");
+ }
+ else
+ {
+ code = xps_paint_image_brush_imp(ctx, image, 0);
+ if (code < 0)
+ return gs_rethrow(code, "cannot draw image");
+ }
+ return 0;
+}
+
+static int
+xps_find_image_brush_source_part(xps_context_t *ctx, char *base_uri, xps_item_t *root,
+ xps_part_t **partp, char **profilep)
+{
+ xps_part_t *part;
+ char *image_source_att;
+ char buf[1024];
+ char partname[1024];
+ char *image_name;
+ char *profile_name;
+ char *p;
+
+ image_source_att = xps_att(root, "ImageSource");
+ if (!image_source_att)
+ return gs_throw(-1, "missing ImageSource attribute");
+
+ /* "{ColorConvertedBitmap /Resources/Image.tiff /Resources/Profile.icc}" */
+ if (strstr(image_source_att, "{ColorConvertedBitmap") == image_source_att)
+ {
+ image_name = NULL;
+ profile_name = NULL;
+
+ xps_strlcpy(buf, image_source_att, sizeof buf);
+ p = strchr(buf, ' ');
+ if (p)
+ {
+ image_name = p + 1;
+ p = strchr(p + 1, ' ');
+ if (p)
+ {
+ *p = 0;
+ profile_name = p + 1;
+ p = strchr(p + 1, '}');
+ if (p)
+ *p = 0;
+ }
+ }
+ }
+ else
+ {
+ image_name = image_source_att;
+ profile_name = NULL;
+ }
+
+ if (!image_name)
+ return gs_throw1(-1, "cannot parse image resource name '%s'", image_source_att);
+
+ xps_absolute_path(partname, base_uri, image_name, sizeof partname);
+ part = xps_read_part(ctx, partname);
+ if (!part)
+ return gs_throw1(-1, "cannot find image resource part '%s'", partname);
+
+ *partp = part;
+ *profilep = xps_strdup(ctx, profile_name);
+
+ return 0;
+}
+
+int
+xps_parse_image_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root)
+{
+ xps_part_t *part;
+ xps_image_t *image;
+ gs_color_space *colorspace;
+ char *profilename;
+ int code;
+
+ code = xps_find_image_brush_source_part(ctx, base_uri, root, &part, &profilename);
+ if (code < 0)
+ return gs_rethrow(code, "cannot find image source");
+
+ image = xps_alloc(ctx, sizeof(xps_image_t));
+ if (!image)
+ return gs_throw(-1, "out of memory: image struct");
+
+ code = xps_decode_image(ctx, part, image);
+ if (code < 0)
+ return gs_rethrow1(code, "cannot decode image '%s'", part->name);
+
+ /* Override any embedded colorspace profiles if the external one matches. */
+ if (profilename)
+ {
+ colorspace = xps_read_icc_colorspace(ctx, base_uri, profilename);
+ if (colorspace && cs_num_components(colorspace) == cs_num_components(image->colorspace))
+ {
+ // TODO: refcount image->colorspace
+ image->colorspace = colorspace;
+ }
+ }
+
+ code = xps_parse_tiling_brush(ctx, base_uri, dict, root, xps_paint_image_brush, image);
+ if (code < 0)
+ return gs_rethrow(-1, "cannot parse tiling brush");
+
+ if (profilename)
+ xps_free(ctx, profilename);
+ xps_free_image(ctx, image);
+ xps_free_part(ctx, part);
+
+ return 0;
+}
+
+int
+xps_image_brush_has_transparency(xps_context_t *ctx, char *base_uri, xps_item_t *root)
+{
+ xps_part_t *imagepart;
+ int code;
+ int has_alpha;
+ char *profilename;
+
+ code = xps_find_image_brush_source_part(ctx, base_uri, root, &imagepart, &profilename);
+ if (code < 0)
+ {
+ gs_catch(code, "cannot find image source");
+ return 0;
+ }
+
+ has_alpha = xps_image_has_alpha(ctx, imagepart);
+
+ xps_free_part(ctx, imagepart);
+
+ return has_alpha;
+}
+
+void
+xps_free_image(xps_context_t *ctx, xps_image_t *image)
+{
+ // TODO: refcount image->colorspace
+ if (image->samples)
+ xps_free(ctx, image->samples);
+ if (image->alpha)
+ xps_free(ctx, image->alpha);
+ if (image->profile)
+ xps_free(ctx, image->profile);
+ xps_free(ctx, image);
+}
diff --git a/xps/xpsjpeg.c b/xps/xpsjpeg.c
new file mode 100644
index 00000000..878fd85a
--- /dev/null
+++ b/xps/xpsjpeg.c
@@ -0,0 +1,143 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* XPS interpreter - JPEG image support */
+
+#include "ghostxps.h"
+
+#include "stream.h"
+#include "strimpl.h"
+#include "gsstate.h"
+#include "jpeglib_.h"
+#include "sdct.h"
+#include "sjpeg.h"
+
+static int
+xps_report_error(stream_state * st, const char *str)
+{
+ (void) gs_throw1(-1, "%s", str);
+ return 0;
+}
+
+int
+xps_decode_jpeg(xps_context_t *ctx, byte *rbuf, int rlen, xps_image_t *image)
+{
+ jpeg_decompress_data jddp;
+ stream_DCT_state state;
+ stream_cursor_read rp;
+ stream_cursor_write wp;
+ int code;
+ int wlen;
+ byte *wbuf;
+ jpeg_saved_marker_ptr curr_marker;
+
+ s_init_state((stream_state*)&state, &s_DCTD_template, ctx->memory);
+ state.report_error = xps_report_error;
+
+ s_DCTD_template.set_defaults((stream_state*)&state);
+
+ state.jpeg_memory = ctx->memory;
+ state.data.decompress = &jddp;
+
+ jddp.template = s_DCTD_template;
+ jddp.memory = ctx->memory;
+ jddp.scanline_buffer = NULL;
+
+ if ((code = gs_jpeg_create_decompress(&state)) < 0)
+ return gs_throw(-1, "cannot gs_jpeg_create_decompress");
+
+ s_DCTD_template.init((stream_state*)&state);
+
+ rp.ptr = rbuf - 1;
+ rp.limit = rbuf + rlen - 1;
+
+ /* read the header only by not having a write buffer */
+ wp.ptr = 0;
+ wp.limit = 0;
+
+ /* Set up to save the ICC marker APP2.
+ * According to the spec we should be getting APP1 APP2 and APP13.
+ * Library gets APP0 and APP14. */
+ jpeg_save_markers(&(jddp.dinfo), 0xe2, 0xFFFF);
+
+ code = s_DCTD_template.process((stream_state*)&state, &rp, &wp, true);
+ if (code != 1)
+ return gs_throw(-1, "premature EOF or error in jpeg");
+
+ /* Check if we had an ICC profile */
+ curr_marker = jddp.dinfo.marker_list;
+ while (curr_marker != NULL)
+ {
+ if (curr_marker->marker == 0xe2)
+ {
+ /* Found ICC profile. Create a buffer and copy over now.
+ * Strip JPEG APP2 14 byte header */
+ image->profilesize = curr_marker->data_length - 14;
+ image->profile = xps_alloc(ctx, image->profilesize);
+ if (image->profile)
+ {
+ /* If we can't create it, just ignore */
+ memcpy(image->profile, &(curr_marker->data[14]), image->profilesize);
+ }
+ break;
+ }
+ curr_marker = curr_marker->next;
+ }
+
+ image->width = jddp.dinfo.output_width;
+ image->height = jddp.dinfo.output_height;
+ image->comps = jddp.dinfo.output_components;
+ image->bits = 8;
+ image->stride = image->width * image->comps;
+
+ if (image->comps == 1)
+ image->colorspace = ctx->gray;
+ if (image->comps == 3)
+ image->colorspace = ctx->srgb;
+ if (image->comps == 4)
+ image->colorspace = ctx->cmyk;
+
+ if (jddp.dinfo.density_unit == 1)
+ {
+ image->xres = jddp.dinfo.X_density;
+ image->yres = jddp.dinfo.Y_density;
+ }
+ else if (jddp.dinfo.density_unit == 2)
+ {
+ image->xres = jddp.dinfo.X_density * 2.54;
+ image->yres = jddp.dinfo.Y_density * 2.54;
+ }
+ else
+ {
+ image->xres = 96;
+ image->yres = 96;
+ }
+
+ wlen = image->stride * image->height;
+ wbuf = xps_alloc(ctx, wlen);
+ if (!wbuf)
+ return gs_throw1(-1, "out of memory allocating samples: %d", wlen);
+
+ image->samples = wbuf;
+
+ wp.ptr = wbuf - 1;
+ wp.limit = wbuf + wlen - 1;
+
+ code = s_DCTD_template.process((stream_state*)&state, &rp, &wp, true);
+ if (code != EOFC)
+ return gs_throw1(-1, "error in jpeg (code = %d)", code);
+
+ gs_jpeg_destroy(&state);
+
+ return gs_okay;
+}
diff --git a/xps/xpsjxr.c b/xps/xpsjxr.c
new file mode 100644
index 00000000..1e9b0e73
--- /dev/null
+++ b/xps/xpsjxr.c
@@ -0,0 +1,259 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* JPEG-XR (formerly HD-Photo (formerly Windows Media Photo)) support */
+
+#include "ghostxps.h"
+
+#ifdef _MSC_VER
+#undef _MSC_VER
+#endif
+
+#include "jpegxr.h"
+
+struct state { xps_context_t *ctx; xps_image_t *output; };
+
+static const char *
+jxr_error_string(int code)
+{
+ switch (code)
+ {
+ case JXR_EC_OK: return "No error";
+ default:
+ case JXR_EC_ERROR: return "Unspecified error";
+ case JXR_EC_BADMAGIC: return "Stream lacks proper magic number";
+ case JXR_EC_FEATURE_NOT_IMPLEMENTED: return "Feature not implemented";
+ case JXR_EC_IO: return "Error reading/writing data";
+ case JXR_EC_BADFORMAT: return "Bad file format";
+ }
+}
+
+#define CLAMP(v, mn, mx) (v < mn ? mn : v > mx ? mx : v)
+
+static inline int
+scale_bits(int depth, int value)
+{
+ union { int iv; float fv; } bd32f;
+
+ switch (depth)
+ {
+ case JXR_BD1WHITE1:
+ return value * 255;
+ case JXR_BD1BLACK1:
+ return value ? 0 : 255;
+ case JXR_BD8:
+ return value;
+ case JXR_BD16:
+ return value >> 8;
+ case JXR_BD16S: /* -4 .. 4 ; 8192 = 1.0 */
+ value = value >> 5;
+ return CLAMP(value, 0, 255);
+ case JXR_BD32S: /* -128 .. 128 ; 16777216 = 1.0 */
+ value = value >> 16;
+ return CLAMP(value, 0, 255);
+ case JXR_BD32F:
+ bd32f.iv = value;
+ value = bd32f.fv * 255;
+ return CLAMP(value, 0, 255);
+#if 0
+ case JXR_BDRESERVED: return value;
+ case JXR_BD16F: return value;
+ case JXR_BD5: return value;
+ case JXR_BD10: return value;
+ case JXR_BD565: return value;
+#endif
+ }
+ return value;
+}
+
+static void
+xps_decode_jpegxr_block(jxr_image_t image, int mx, int my, int *data)
+{
+ struct state *state = jxr_get_user_data(image);
+ xps_context_t *ctx = state->ctx;
+ xps_image_t *output = state->output;
+ int depth;
+ unsigned char *p;
+ int x, y, k;
+
+ if (!output->samples)
+ {
+ output->width = jxr_get_IMAGE_WIDTH(image);
+ output->height = jxr_get_IMAGE_HEIGHT(image);
+ output->comps = jxr_get_IMAGE_CHANNELS(image);
+ output->hasalpha = jxr_get_ALPHACHANNEL_FLAG(image);
+ output->bits = 8;
+ output->stride = output->width * output->comps;
+ output->samples = xps_alloc(ctx, output->stride * output->height);
+
+ switch (output->comps)
+ {
+ default:
+ case 1: output->colorspace = ctx->gray; break;
+ case 3: output->colorspace = ctx->srgb; break;
+ case 4: output->colorspace = ctx->cmyk; break;
+ }
+ }
+
+ depth = jxr_get_OUTPUT_BITDEPTH(image);
+
+ my = my * 16;
+ mx = mx * 16;
+
+ for (y = 0; y < 16; y++)
+ {
+ if (my + y >= output->height)
+ return;
+ p = output->samples + (my + y) * output->stride + mx * output->comps;
+ for (x = 0; x < 16; x++)
+ {
+ if (mx + x >= output->width)
+ data += output->comps;
+ else
+ for (k = 0; k < output->comps; k++)
+ *p++ = scale_bits(depth, *data++);
+ }
+ }
+}
+
+static void
+xps_decode_jpegxr_alpha_block(jxr_image_t image, int mx, int my, int *data)
+{
+ struct state *state = jxr_get_user_data(image);
+ xps_context_t *ctx = state->ctx;
+ xps_image_t *output = state->output;
+ int depth;
+ unsigned char *p;
+ int x, y, k;
+
+ if (!output->alpha)
+ {
+ output->alpha = xps_alloc(ctx, output->width * output->height);
+ }
+
+ depth = jxr_get_OUTPUT_BITDEPTH(image);
+
+ my = my * 16;
+ mx = mx * 16;
+
+ for (y = 0; y < 16; y++)
+ {
+ if (my + y >= output->height)
+ return;
+ p = output->alpha + (my + y) * output->width + mx;
+ for (x = 0; x < 16; x++)
+ {
+ if (mx + x >= output->width)
+ data ++;
+ else
+ *p++ = scale_bits(depth, *data++);
+ }
+ }
+}
+
+int
+xps_decode_jpegxr(xps_context_t *ctx, byte *buf, int len, xps_image_t *output)
+{
+ FILE *file;
+ char name[gp_file_name_sizeof];
+ struct state state;
+ jxr_container_t container;
+ jxr_image_t image;
+ int offset, alpha_offset;
+ int rc;
+
+ memset(output, 0, sizeof(*output));
+
+ file = gp_open_scratch_file(ctx->memory, "jpegxr-scratch-", name, "wb+");
+ if (!file)
+ return gs_throw(gs_error_invalidfileaccess, "cannot open scratch file");
+ rc = fwrite(buf, 1, len, file);
+ if (rc != len)
+ return gs_throw(gs_error_invalidfileaccess, "cannot write to scratch file");
+ fseek(file, 0, SEEK_SET);
+
+ container = jxr_create_container();
+ rc = jxr_read_image_container(container, file);
+ if (rc < 0)
+ return gs_throw1(-1, "jxr_read_image_container: %s", jxr_error_string(rc));
+
+ offset = jxrc_image_offset(container, 0);
+ alpha_offset = jxrc_alpha_offset(container, 0);
+
+ output->xres = jxrc_width_resolution(container, 0);
+ output->yres = jxrc_height_resolution(container, 0);
+
+ image = jxr_create_input();
+ jxr_set_PROFILE_IDC(image, 111);
+ jxr_set_LEVEL_IDC(image, 255);
+ jxr_set_pixel_format(image, jxrc_image_pixelformat(container, 0));
+ jxr_set_container_parameters(image,
+ jxrc_image_pixelformat(container, 0),
+ jxrc_image_width(container, 0),
+ jxrc_image_height(container, 0),
+ jxrc_alpha_offset(container, 0),
+ jxrc_image_band_presence(container, 0),
+ jxrc_alpha_band_presence(container, 0), 0);
+
+ jxr_set_block_output(image, xps_decode_jpegxr_block);
+ state.ctx = ctx;
+ state.output = output;
+ jxr_set_user_data(image, &state);
+
+ fseek(file, offset, SEEK_SET);
+ rc = jxr_read_image_bitstream(image, file);
+ if (rc < 0)
+ return gs_throw1(-1, "jxr_read_image_bitstream: %s", jxr_error_string(rc));
+
+ jxr_destroy(image);
+
+ if (alpha_offset > 0)
+ {
+ image = jxr_create_input();
+ jxr_set_PROFILE_IDC(image, 111);
+ jxr_set_LEVEL_IDC(image, 255);
+ jxr_set_pixel_format(image, jxrc_image_pixelformat(container, 0));
+ jxr_set_container_parameters(image,
+ jxrc_image_pixelformat(container, 0),
+ jxrc_image_width(container, 0),
+ jxrc_image_height(container, 0),
+ jxrc_alpha_offset(container, 0),
+ jxrc_image_band_presence(container, 0),
+ jxrc_alpha_band_presence(container, 0), 0);
+
+ jxr_set_block_output(image, xps_decode_jpegxr_alpha_block);
+ state.ctx = ctx;
+ state.output = output;
+ jxr_set_user_data(image, &state);
+
+ fseek(file, alpha_offset, SEEK_SET);
+ rc = jxr_read_image_bitstream(image, file);
+ if (rc < 0)
+ return gs_throw1(-1, "jxr_read_image_bitstream: %s", jxr_error_string(rc));
+
+ jxr_destroy(image);
+ }
+
+ jxr_destroy_container(container);
+
+ fclose(file);
+ unlink(name);
+
+ return gs_okay;
+}
+
+int
+xps_jpegxr_has_alpha(xps_context_t *ctx, byte *buf, int len)
+{
+ return 1;
+}
diff --git a/xps/xpsmem.c b/xps/xpsmem.c
new file mode 100644
index 00000000..95199f07
--- /dev/null
+++ b/xps/xpsmem.c
@@ -0,0 +1,182 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* XPS interpreter - string manipulation functions */
+
+#include "ghostxps.h"
+
+void *
+xps_realloc_imp(xps_context_t *ctx, void *ptr, int size, const char *func)
+{
+ if (!ptr)
+ return gs_alloc_bytes(ctx->memory, size, func);
+ return gs_resize_object(ctx->memory, ptr, size, func);
+}
+
+static inline int
+xps_tolower(int c)
+{
+ if (c >= 'A' && c <= 'Z')
+ return c + 32;
+ return c;
+}
+
+int
+xps_strcasecmp(char *a, char *b)
+{
+ while (xps_tolower(*a) == xps_tolower(*b))
+ {
+ if (*a++ == 0)
+ return 0;
+ b++;
+ }
+ return xps_tolower(*a) - xps_tolower(*b);
+}
+
+char *
+xps_strdup_imp(xps_context_t *ctx, const char *str, const char *cname)
+{
+ char *cpy = NULL;
+ if (str)
+ cpy = (char*) gs_alloc_bytes(ctx->memory, strlen(str) + 1, cname);
+ if (cpy)
+ strcpy(cpy, str);
+ return cpy;
+}
+
+size_t
+xps_strlcpy(char *dst, const char *src, size_t siz)
+{
+ register char *d = dst;
+ register const char *s = src;
+ register int n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0 && --n != 0) {
+ do {
+ if ((*d++ = *s++) == 0)
+ break;
+ } while (--n != 0);
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+
+ return(s - src - 1); /* count does not include NUL */
+}
+
+size_t
+xps_strlcat(char *dst, const char *src, size_t siz)
+{
+ register char *d = dst;
+ register const char *s = src;
+ register int n = siz;
+ int dlen;
+
+ /* Find the end of dst and adjust bytes left but don't go past end */
+ while (*d != '\0' && n-- != 0)
+ d++;
+ dlen = d - dst;
+ n = siz - dlen;
+
+ if (n == 0)
+ return dlen + strlen(s);
+ while (*s != '\0') {
+ if (n != 1) {
+ *d++ = *s;
+ n--;
+ }
+ s++;
+ }
+ *d = '\0';
+
+ return dlen + (s - src); /* count does not include NUL */
+}
+
+#define SEP(x) ((x)=='/' || (x) == 0)
+
+static char *
+xps_clean_path(char *name)
+{
+ char *p, *q, *dotdot;
+ int rooted;
+
+ rooted = name[0] == '/';
+
+ /*
+ * invariants:
+ * p points at beginning of path element we're considering.
+ * q points just past the last path element we wrote (no slash).
+ * dotdot points just past the point where .. cannot backtrack
+ * any further (no slash).
+ */
+ p = q = dotdot = name + rooted;
+ while (*p)
+ {
+ if(p[0] == '/') /* null element */
+ p++;
+ else if (p[0] == '.' && SEP(p[1]))
+ p += 1; /* don't count the separator in case it is nul */
+ else if (p[0] == '.' && p[1] == '.' && SEP(p[2]))
+ {
+ p += 2;
+ if (q > dotdot) /* can backtrack */
+ {
+ while(--q > dotdot && *q != '/')
+ ;
+ }
+ else if (!rooted) /* /.. is / but ./../ is .. */
+ {
+ if (q != name)
+ *q++ = '/';
+ *q++ = '.';
+ *q++ = '.';
+ dotdot = q;
+ }
+ }
+ else /* real path element */
+ {
+ if (q != name+rooted)
+ *q++ = '/';
+ while ((*q = *p) != '/' && *q != 0)
+ p++, q++;
+ }
+ }
+
+ if (q == name) /* empty string is really "." */
+ *q++ = '.';
+ *q = '\0';
+
+ return name;
+}
+
+void
+xps_absolute_path(char *output, char *base_uri, char *path, int output_size)
+{
+ if (path[0] == '/')
+ {
+ xps_strlcpy(output, path, output_size);
+ }
+ else
+ {
+ xps_strlcpy(output, base_uri, output_size);
+ xps_strlcat(output, "/", output_size);
+ xps_strlcat(output, path, output_size);
+ }
+ xps_clean_path(output);
+}
diff --git a/xps/xpsopacity.c b/xps/xpsopacity.c
new file mode 100644
index 00000000..bd845efb
--- /dev/null
+++ b/xps/xpsopacity.c
@@ -0,0 +1,102 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* XPS interpreter - transparency support */
+
+#include "ghostxps.h"
+
+void
+xps_bounds_in_user_space(xps_context_t *ctx, gs_rect *ubox)
+{
+ gx_clip_path *clip_path;
+ gs_rect dbox;
+ int code;
+
+ code = gx_effective_clip_path(ctx->pgs, &clip_path);
+ if (code < 0)
+ gs_warn("gx_effective_clip_path failed");
+
+ dbox.p.x = fixed2float(clip_path->outer_box.p.x);
+ dbox.p.y = fixed2float(clip_path->outer_box.p.y);
+ dbox.q.x = fixed2float(clip_path->outer_box.q.x);
+ dbox.q.y = fixed2float(clip_path->outer_box.q.y);
+ gs_bbox_transform_inverse(&dbox, &ctm_only(ctx->pgs), ubox);
+}
+
+int
+xps_begin_opacity(xps_context_t *ctx, char *base_uri, xps_resource_t *dict,
+ char *opacity_att, xps_item_t *opacity_mask_tag)
+{
+ gs_transparency_group_params_t tgp;
+ gs_transparency_mask_params_t tmp;
+ gs_rect bbox;
+ float opacity;
+ int save;
+ int code;
+
+ if (!opacity_att && !opacity_mask_tag)
+ return 0;
+
+ opacity = 1.0;
+ if (opacity_att)
+ opacity = atof(opacity_att);
+ gs_setopacityalpha(ctx->pgs, opacity);
+
+ xps_bounds_in_user_space(ctx, &bbox);
+
+ if (opacity_mask_tag)
+ {
+ gs_trans_mask_params_init(&tmp, TRANSPARENCY_MASK_Luminosity);
+ gs_begin_transparency_mask(ctx->pgs, &tmp, &bbox, 0);
+
+ gs_gsave(ctx->pgs);
+
+ /* Need a path to fill/clip for the brush */
+ gs_moveto(ctx->pgs, bbox.p.x, bbox.p.y);
+ gs_lineto(ctx->pgs, bbox.p.x, bbox.q.y);
+ gs_lineto(ctx->pgs, bbox.q.x, bbox.q.y);
+ gs_lineto(ctx->pgs, bbox.q.x, bbox.p.y);
+ gs_closepath(ctx->pgs);
+
+ /* opacity-only mode: use alpha value as gray color to create luminosity mask */
+ save = ctx->opacity_only;
+ ctx->opacity_only = 1;
+
+ code = xps_parse_brush(ctx, base_uri, dict, opacity_mask_tag);
+ if (code)
+ {
+ gs_grestore(ctx->pgs);
+ gs_end_transparency_mask(ctx->pgs, TRANSPARENCY_CHANNEL_Opacity);
+ ctx->opacity_only = save;
+ return gs_rethrow(code, "cannot parse opacity mask brush");
+ }
+
+ gs_grestore(ctx->pgs);
+ gs_end_transparency_mask(ctx->pgs, TRANSPARENCY_CHANNEL_Opacity);
+ ctx->opacity_only = save;
+ }
+
+ gs_trans_group_params_init(&tgp);
+ gs_begin_transparency_group(ctx->pgs, &tgp, &bbox);
+
+ return 0;
+}
+
+void
+xps_end_opacity(xps_context_t *ctx, char *base_uri, xps_resource_t *dict,
+ char *opacity_att, xps_item_t *opacity_mask_tag)
+{
+ if (!opacity_att && !opacity_mask_tag)
+ return;
+ gs_end_transparency_group(ctx->pgs);
+}
diff --git a/xps/xpspage.c b/xps/xpspage.c
new file mode 100644
index 00000000..c6424d7c
--- /dev/null
+++ b/xps/xpspage.c
@@ -0,0 +1,281 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* XPS interpreter - page parsing */
+
+#include "ghostxps.h"
+
+int
+xps_parse_canvas(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root)
+{
+ xps_resource_t *new_dict = NULL;
+ xps_item_t *node;
+ char *opacity_mask_uri;
+ int code;
+
+ char *transform_att;
+ char *clip_att;
+ char *opacity_att;
+ char *opacity_mask_att;
+
+ xps_item_t *transform_tag = NULL;
+ xps_item_t *clip_tag = NULL;
+ xps_item_t *opacity_mask_tag = NULL;
+
+ gs_matrix transform;
+
+ transform_att = xps_att(root, "RenderTransform");
+ clip_att = xps_att(root, "Clip");
+ opacity_att = xps_att(root, "Opacity");
+ opacity_mask_att = xps_att(root, "OpacityMask");
+
+ for (node = xps_down(root); node; node = xps_next(node))
+ {
+ if (!strcmp(xps_tag(node), "Canvas.Resources") && xps_down(node))
+ {
+ code = xps_parse_resource_dictionary(ctx, &new_dict, base_uri, xps_down(node));
+ if (code)
+ return gs_rethrow(code, "cannot load Canvas.Resources");
+ new_dict->parent = dict;
+ dict = new_dict;
+ }
+
+ if (!strcmp(xps_tag(node), "Canvas.RenderTransform"))
+ transform_tag = xps_down(node);
+ if (!strcmp(xps_tag(node), "Canvas.Clip"))
+ clip_tag = xps_down(node);
+ if (!strcmp(xps_tag(node), "Canvas.OpacityMask"))
+ opacity_mask_tag = xps_down(node);
+ }
+
+ opacity_mask_uri = base_uri;
+ xps_resolve_resource_reference(ctx, dict, &transform_att, &transform_tag, NULL);
+ xps_resolve_resource_reference(ctx, dict, &clip_att, &clip_tag, NULL);
+ xps_resolve_resource_reference(ctx, dict, &opacity_mask_att, &opacity_mask_tag, &opacity_mask_uri);
+
+ gs_gsave(ctx->pgs);
+
+ gs_make_identity(&transform);
+ if (transform_att)
+ xps_parse_render_transform(ctx, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(ctx, transform_tag, &transform);
+ gs_concat(ctx->pgs, &transform);
+
+ if (clip_att || clip_tag)
+ {
+ if (clip_att)
+ xps_parse_abbreviated_geometry(ctx, clip_att);
+ if (clip_tag)
+ xps_parse_path_geometry(ctx, dict, clip_tag, 0);
+ xps_clip(ctx);
+ }
+
+ code = xps_begin_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+ if (code)
+ {
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "cannot create transparency group");
+ }
+
+ for (node = xps_down(root); node; node = xps_next(node))
+ {
+ code = xps_parse_element(ctx, base_uri, dict, node);
+ if (code)
+ {
+ xps_end_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "cannot parse child of Canvas");
+ }
+ }
+
+ xps_end_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+
+ gs_grestore(ctx->pgs);
+
+ if (new_dict)
+ xps_free_resource_dictionary(ctx, new_dict);
+
+ return 0;
+}
+
+int
+xps_parse_fixed_page(xps_context_t *ctx, xps_part_t *part)
+{
+ xps_item_t *root, *node;
+ xps_resource_t *dict;
+ char *width_att;
+ char *height_att;
+ int has_transparency;
+ char base_uri[1024];
+ char *s;
+ int code;
+
+ if_debug1('|', "doc: parsing page %s\n", part->name);
+
+ xps_strlcpy(base_uri, part->name, sizeof base_uri);
+ s = strrchr(base_uri, '/');
+ if (s)
+ s[1] = 0;
+
+ root = xps_parse_xml(ctx, part->data, part->size);
+ if (!root)
+ return gs_rethrow(-1, "cannot parse xml");
+
+ if (strcmp(xps_tag(root), "FixedPage"))
+ return gs_throw1(-1, "expected FixedPage element (found %s)", xps_tag(root));
+
+ width_att = xps_att(root, "Width");
+ height_att = xps_att(root, "Height");
+
+ if (!width_att)
+ return gs_throw(-1, "FixedPage missing required attribute: Width");
+ if (!height_att)
+ return gs_throw(-1, "FixedPage missing required attribute: Height");
+
+ dict = NULL;
+
+ /* Setup new page */
+ {
+ gs_memory_t *mem = ctx->memory;
+ gs_state *pgs = ctx->pgs;
+ gx_device *dev = gs_currentdevice(pgs);
+ gs_param_float_array fa;
+ float fv[2];
+ gs_c_param_list list;
+
+ gs_c_param_list_write(&list, mem);
+
+ fv[0] = atoi(width_att) / 96.0 * 72.0;
+ fv[1] = atoi(height_att) / 96.0 * 72.0;
+ fa.persistent = false;
+ fa.data = fv;
+ fa.size = 2;
+
+ code = param_write_float_array((gs_param_list *)&list, ".MediaSize", &fa);
+ if ( code >= 0 )
+ {
+ gs_c_param_list_read(&list);
+ code = gs_putdeviceparams(dev, (gs_param_list *)&list);
+ }
+ gs_c_param_list_release(&list);
+
+ /* nb this is for the demo it is wrong and should be removed */
+ gs_initgraphics(pgs);
+
+ /* 96 dpi default - and put the origin at the top of the page */
+
+ gs_initmatrix(pgs);
+
+ code = gs_scale(pgs, 72.0/96.0, -72.0/96.0);
+ if (code < 0)
+ return gs_rethrow(code, "cannot set page transform");
+
+ code = gs_translate(pgs, 0.0, -atoi(height_att));
+ if (code < 0)
+ return gs_rethrow(code, "cannot set page transform");
+
+ code = gs_erasepage(pgs);
+ if (code < 0)
+ return gs_rethrow(code, "cannot clear page");
+ }
+
+ /* Pre-parse looking for transparency */
+
+ has_transparency = 0;
+
+ for (node = xps_down(root); node; node = xps_next(node))
+ {
+ if (!strcmp(xps_tag(node), "FixedPage.Resources") && xps_down(node))
+ if (xps_resource_dictionary_has_transparency(ctx, base_uri, xps_down(node)))
+ has_transparency = 1;
+ if (xps_element_has_transparency(ctx, base_uri, node))
+ has_transparency = 1;
+ }
+
+ /* save the state with the original device before we push */
+ gs_gsave(ctx->pgs);
+
+ if (ctx->use_transparency && has_transparency)
+ {
+ code = gs_push_pdf14trans_device(ctx->pgs);
+ if (code < 0)
+ {
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "cannot install transparency device");
+ }
+ }
+
+ /* Initialize the default profiles in the ctx to what is in the manager */
+ ctx->gray->cmm_icc_profile_data = ctx->pgs->icc_manager->default_gray;
+ ctx->srgb->cmm_icc_profile_data = ctx->pgs->icc_manager->default_rgb;
+ /* scrgb really needs to be a bit different.
+ * Unless we are handling nonlinearity before conversion from float. ToDo. */
+ ctx->scrgb->cmm_icc_profile_data = ctx->pgs->icc_manager->default_rgb;
+ ctx->cmyk->cmm_icc_profile_data = ctx->pgs->icc_manager->default_cmyk;
+
+ /* Draw contents */
+
+ for (node = xps_down(root); node; node = xps_next(node))
+ {
+ if (!strcmp(xps_tag(node), "FixedPage.Resources") && xps_down(node))
+ {
+ code = xps_parse_resource_dictionary(ctx, &dict, base_uri, xps_down(node));
+ if (code)
+ {
+ gs_pop_pdf14trans_device(ctx->pgs);
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "cannot load FixedPage.Resources");
+ }
+ }
+ code = xps_parse_element(ctx, base_uri, dict, node);
+ if (code)
+ {
+ gs_pop_pdf14trans_device(ctx->pgs);
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "cannot parse child of FixedPage");
+ }
+ }
+
+ if (ctx->use_transparency && has_transparency)
+ {
+ code = gs_pop_pdf14trans_device(ctx->pgs);
+ if (code < 0)
+ {
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "cannot uninstall transparency device");
+ }
+ }
+
+ /* Flush page */
+ {
+ code = xps_show_page(ctx, 1, true); /* copies, flush */
+ if (code < 0)
+ {
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "cannot flush page");
+ }
+ }
+
+ /* restore the original device, discarding the pdf14 compositor */
+ gs_grestore(ctx->pgs);
+
+ if (dict)
+ {
+ xps_free_resource_dictionary(ctx, dict);
+ }
+
+ xps_free_item(ctx, root);
+
+ return 0;
+}
diff --git a/xps/xpspath.c b/xps/xpspath.c
new file mode 100644
index 00000000..89e9716a
--- /dev/null
+++ b/xps/xpspath.c
@@ -0,0 +1,1036 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* XPS interpreter - path (vector drawing) support */
+
+#include "ghostxps.h"
+
+void
+xps_clip(xps_context_t *ctx)
+{
+ if (ctx->fill_rule == 0)
+ gs_eoclip(ctx->pgs);
+ else
+ gs_clip(ctx->pgs);
+ gs_newpath(ctx->pgs);
+}
+
+void
+xps_fill(xps_context_t *ctx)
+{
+ if (gs_currentopacityalpha(ctx->pgs) < 0.001)
+ gs_newpath(ctx->pgs);
+ else if (ctx->fill_rule == 0) {
+ if (gs_eofill(ctx->pgs) == gs_error_Remap_Color)
+ xps_high_level_pattern(ctx);
+ gs_eofill(ctx->pgs);
+ }
+ else {
+ if (gs_fill(ctx->pgs) == gs_error_Remap_Color)
+ xps_high_level_pattern(ctx);
+ gs_fill(ctx->pgs);
+ }
+}
+
+/* Draw an arc segment transformed by the matrix, we approximate with straight
+ * line segments. We cannot use the gs_arc function because they only draw
+ * circular arcs, we need to transform the line to make them elliptical but
+ * without transforming the line width.
+ */
+static inline void
+xps_draw_arc_segment(xps_context_t *ctx, gs_matrix *mtx, float th0, float th1, int iscw)
+{
+ float t, d;
+ gs_point p;
+
+ while (th1 < th0)
+ th1 += M_PI * 2.0;
+
+ d = 1 * (M_PI / 180.0); /* 1-degree precision */
+
+ if (iscw)
+ {
+ gs_point_transform(cos(th0), sin(th0), mtx, &p);
+ gs_lineto(ctx->pgs, p.x, p.y);
+ for (t = th0; t < th1; t += d)
+ {
+ gs_point_transform(cos(t), sin(t), mtx, &p);
+ gs_lineto(ctx->pgs, p.x, p.y);
+ }
+ gs_point_transform(cos(th1), sin(th1), mtx, &p);
+ gs_lineto(ctx->pgs, p.x, p.y);
+ }
+ else
+ {
+ th0 += M_PI * 2;
+ gs_point_transform(cos(th0), sin(th0), mtx, &p);
+ gs_lineto(ctx->pgs, p.x, p.y);
+ for (t = th0; t > th1; t -= d)
+ {
+ gs_point_transform(cos(t), sin(t), mtx, &p);
+ gs_lineto(ctx->pgs, p.x, p.y);
+ }
+ gs_point_transform(cos(th1), sin(th1), mtx, &p);
+ gs_lineto(ctx->pgs, p.x, p.y);
+ }
+}
+
+/* Given two vectors find the angle between them. */
+static inline double
+angle_between(const gs_point u, const gs_point v)
+{
+ double det = u.x * v.y - u.y * v.x;
+ double sign = (det < 0 ? -1.0 : 1.0);
+ double magu = u.x * u.x + u.y * u.y;
+ double magv = v.x * v.x + v.y * v.y;
+ double udotv = u.x * v.x + u.y * v.y;
+ double t = udotv / (magu * magv);
+ /* guard against rounding errors when near |1| (where acos will return NaN) */
+ if (t < -1.0) t = -1.0;
+ if (t > 1.0) t = 1.0;
+ return sign * acos(t);
+}
+
+static void
+xps_draw_arc(xps_context_t *ctx,
+ float size_x, float size_y, float rotation_angle,
+ int is_large_arc, int is_clockwise,
+ float point_x, float point_y)
+{
+ gs_matrix rotmat, revmat;
+ gs_matrix mtx;
+ gs_point pt;
+ double rx, ry;
+ double x1, y1, x2, y2;
+ double x1t, y1t;
+ double cxt, cyt, cx, cy;
+ double t1, t2, t3;
+ double sign;
+ double th1, dth;
+
+ gs_currentpoint(ctx->pgs, &pt);
+ x1 = pt.x;
+ y1 = pt.y;
+ x2 = point_x;
+ y2 = point_y;
+ rx = size_x;
+ ry = size_y;
+
+ if (is_clockwise != is_large_arc)
+ sign = 1;
+ else
+ sign = -1;
+
+ gs_make_rotation(rotation_angle, &rotmat);
+ gs_make_rotation(-rotation_angle, &revmat);
+
+ /* http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes */
+ /* Conversion from endpoint to center parameterization */
+
+ /* F.6.6.1 -- ensure radii are positive and non-zero */
+ rx = fabsf(rx);
+ ry = fabsf(ry);
+ if (rx < 0.001 || ry < 0.001)
+ {
+ gs_lineto(ctx->pgs, x2, y2);
+ return;
+ }
+
+ /* F.6.5.1 */
+ gs_distance_transform((x1 - x2) / 2.0, (y1 - y2) / 2.0, &revmat, &pt);
+ x1t = pt.x;
+ y1t = pt.y;
+
+ /* F.6.6.2 -- ensure radii are large enough */
+ t1 = (x1t * x1t) / (rx * rx) + (y1t * y1t) / (ry * ry);
+ if (t1 > 1.0)
+ {
+ rx = rx * sqrtf(t1);
+ ry = ry * sqrtf(t1);
+ }
+
+ /* F.6.5.2 */
+ t1 = (rx * rx * ry * ry) - (rx * rx * y1t * y1t) - (ry * ry * x1t * x1t);
+ t2 = (rx * rx * y1t * y1t) + (ry * ry * x1t * x1t);
+ t3 = t1 / t2;
+ /* guard against rounding errors; sqrt of negative numbers is bad for your health */
+ if (t3 < 0.0) t3 = 0.0;
+ t3 = sqrtf(t3);
+
+ cxt = sign * t3 * (rx * y1t) / ry;
+ cyt = sign * t3 * -(ry * x1t) / rx;
+
+ /* F.6.5.3 */
+ gs_distance_transform(cxt, cyt, &rotmat, &pt);
+ cx = pt.x + (x1 + x2) / 2;
+ cy = pt.y + (y1 + y2) / 2;
+
+ /* F.6.5.4 */
+ {
+ gs_point coord1, coord2, coord3, coord4;
+ coord1.x = 1;
+ coord1.y = 0;
+ coord2.x = (x1t - cxt) / rx;
+ coord2.y = (y1t - cyt) / ry;
+ coord3.x = (x1t - cxt) / rx;
+ coord3.y = (y1t - cyt) / ry;
+ coord4.x = (-x1t - cxt) / rx;
+ coord4.y = (-y1t - cyt) / ry;
+ th1 = angle_between(coord1, coord2);
+ dth = angle_between(coord3, coord4);
+ if (dth < 0 && !is_clockwise)
+ dth += (degrees_to_radians * 360);
+ if (dth > 0 && is_clockwise)
+ dth -= (degrees_to_radians * 360);
+ }
+
+ gs_make_identity(&mtx);
+ gs_matrix_translate(&mtx, cx, cy, &mtx);
+ gs_matrix_rotate(&mtx, rotation_angle, &mtx);
+ gs_matrix_scale(&mtx, rx, ry, &mtx);
+ xps_draw_arc_segment(ctx, &mtx, th1, th1 + dth, is_clockwise);
+
+ gs_lineto(ctx->pgs, point_x, point_y);
+}
+
+/*
+ * Parse an abbreviated geometry string, and call
+ * ghostscript moveto/lineto/curveto functions to
+ * build up a path.
+ */
+
+void
+xps_parse_abbreviated_geometry(xps_context_t *ctx, char *geom)
+{
+ char **args;
+ char **pargs;
+ char *s = geom;
+ gs_point pt;
+ int i, n;
+ int cmd, old;
+ float x1, y1, x2, y2, x3, y3;
+ float smooth_x, smooth_y; /* saved cubic bezier control point for smooth curves */
+ int reset_smooth;
+
+ args = xps_alloc(ctx, sizeof(char*) * (strlen(geom) + 1));
+ pargs = args;
+
+ //dprintf1("new path (%.70s)\n", geom);
+ gs_newpath(ctx->pgs);
+
+ while (*s)
+ {
+ if ((*s >= 'A' && *s <= 'Z') || (*s >= 'a' && *s <= 'z'))
+ {
+ *pargs++ = s++;
+ }
+ else if ((*s >= '0' && *s <= '9') || *s == '.' || *s == '+' || *s == '-' || *s == 'e' || *s == 'E')
+ {
+ *pargs++ = s;
+ while ((*s >= '0' && *s <= '9') || *s == '.' || *s == '+' || *s == '-' || *s == 'e' || *s == 'E')
+ s ++;
+ }
+ else
+ {
+ s++;
+ }
+ }
+
+ pargs[0] = s;
+ pargs[1] = 0;
+
+ n = pargs - args;
+ i = 0;
+
+ old = 0;
+
+ reset_smooth = 1;
+ smooth_x = 0.0;
+ smooth_y = 0.0;
+
+ while (i < n)
+ {
+ cmd = args[i][0];
+ if (cmd == '+' || cmd == '.' || cmd == '-' || (cmd >= '0' && cmd <= '9'))
+ cmd = old; /* it's a number, repeat old command */
+ else
+ i ++;
+
+ if (reset_smooth)
+ {
+ smooth_x = 0.0;
+ smooth_y = 0.0;
+ }
+
+ reset_smooth = 1;
+
+ switch (cmd)
+ {
+ case 'F':
+ ctx->fill_rule = atoi(args[i]);
+ i ++;
+ break;
+
+ case 'M':
+ gs_moveto(ctx->pgs, atof(args[i]), atof(args[i+1]));
+ //dprintf2("moveto %g %g\n", atof(args[i]), atof(args[i+1]));
+ i += 2;
+ break;
+ case 'm':
+ gs_rmoveto(ctx->pgs, atof(args[i]), atof(args[i+1]));
+ //dprintf2("rmoveto %g %g\n", atof(args[i]), atof(args[i+1]));
+ i += 2;
+ break;
+
+ case 'L':
+ gs_lineto(ctx->pgs, atof(args[i]), atof(args[i+1]));
+ //dprintf2("lineto %g %g\n", atof(args[i]), atof(args[i+1]));
+ i += 2;
+ break;
+ case 'l':
+ gs_rlineto(ctx->pgs, atof(args[i]), atof(args[i+1]));
+ //dprintf2("rlineto %g %g\n", atof(args[i]), atof(args[i+1]));
+ i += 2;
+ break;
+
+ case 'H':
+ gs_currentpoint(ctx->pgs, &pt);
+ gs_lineto(ctx->pgs, atof(args[i]), pt.y);
+ //dprintf1("hlineto %g\n", atof(args[i]));
+ i += 1;
+ break;
+ case 'h':
+ gs_rlineto(ctx->pgs, atof(args[i]), 0.0);
+ //dprintf1("rhlineto %g\n", atof(args[i]));
+ i += 1;
+ break;
+
+ case 'V':
+ gs_currentpoint(ctx->pgs, &pt);
+ gs_lineto(ctx->pgs, pt.x, atof(args[i]));
+ //dprintf1("vlineto %g\n", atof(args[i]));
+ i += 1;
+ break;
+ case 'v':
+ gs_rlineto(ctx->pgs, 0.0, atof(args[i]));
+ //dprintf1("rvlineto %g\n", atof(args[i]));
+ i += 1;
+ break;
+
+ case 'C':
+ x1 = atof(args[i+0]);
+ y1 = atof(args[i+1]);
+ x2 = atof(args[i+2]);
+ y2 = atof(args[i+3]);
+ x3 = atof(args[i+4]);
+ y3 = atof(args[i+5]);
+ gs_curveto(ctx->pgs, x1, y1, x2, y2, x3, y3);
+ i += 6;
+ reset_smooth = 0;
+ smooth_x = x3 - x2;
+ smooth_y = y3 - y2;
+ break;
+
+ case 'c':
+ gs_currentpoint(ctx->pgs, &pt);
+ x1 = atof(args[i+0]) + pt.x;
+ y1 = atof(args[i+1]) + pt.y;
+ x2 = atof(args[i+2]) + pt.x;
+ y2 = atof(args[i+3]) + pt.y;
+ x3 = atof(args[i+4]) + pt.x;
+ y3 = atof(args[i+5]) + pt.y;
+ gs_curveto(ctx->pgs, x1, y1, x2, y2, x3, y3);
+ i += 6;
+ reset_smooth = 0;
+ smooth_x = x3 - x2;
+ smooth_y = y3 - y2;
+ break;
+
+ case 'S':
+ gs_currentpoint(ctx->pgs, &pt);
+ x1 = atof(args[i+0]);
+ y1 = atof(args[i+1]);
+ x2 = atof(args[i+2]);
+ y2 = atof(args[i+3]);
+ //dprintf2("smooth %g %g\n", smooth_x, smooth_y);
+ gs_curveto(ctx->pgs, pt.x + smooth_x, pt.y + smooth_y, x1, y1, x2, y2);
+ i += 4;
+ reset_smooth = 0;
+ smooth_x = x2 - x1;
+ smooth_y = y2 - y1;
+ break;
+
+ case 's':
+ gs_currentpoint(ctx->pgs, &pt);
+ x1 = atof(args[i+0]) + pt.x;
+ y1 = atof(args[i+1]) + pt.y;
+ x2 = atof(args[i+2]) + pt.x;
+ y2 = atof(args[i+3]) + pt.y;
+ //dprintf2("smooth %g %g\n", smooth_x, smooth_y);
+ gs_curveto(ctx->pgs, pt.x + smooth_x, pt.y + smooth_y, x1, y1, x2, y2);
+ i += 4;
+ reset_smooth = 0;
+ smooth_x = x2 - x1;
+ smooth_y = y2 - y1;
+ break;
+
+ case 'Q':
+ gs_currentpoint(ctx->pgs, &pt);
+ x1 = atof(args[i+0]);
+ y1 = atof(args[i+1]);
+ x2 = atof(args[i+2]);
+ y2 = atof(args[i+3]);
+ //dprintf4("conicto %g %g %g %g\n", x1, y1, x2, y2);
+ gs_curveto(ctx->pgs,
+ (pt.x + 2 * x1) / 3, (pt.y + 2 * y1) / 3,
+ (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
+ x2, y2);
+ i += 4;
+ break;
+ case 'q':
+ gs_currentpoint(ctx->pgs, &pt);
+ x1 = atof(args[i+0]) + pt.x;
+ y1 = atof(args[i+1]) + pt.y;
+ x2 = atof(args[i+2]) + pt.x;
+ y2 = atof(args[i+3]) + pt.y;
+ //dprintf4("conicto %g %g %g %g\n", x1, y1, x2, y2);
+ gs_curveto(ctx->pgs,
+ (pt.x + 2 * x1) / 3, (pt.y + 2 * y1) / 3,
+ (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
+ x2, y2);
+ i += 4;
+ break;
+
+ case 'A':
+ xps_draw_arc(ctx,
+ atof(args[i+0]), atof(args[i+1]), atof(args[i+2]),
+ atoi(args[i+3]), atoi(args[i+4]),
+ atof(args[i+5]), atof(args[i+6]));
+ i += 7;
+ break;
+ case 'a':
+ gs_currentpoint(ctx->pgs, &pt);
+ xps_draw_arc(ctx,
+ atof(args[i+0]), atof(args[i+1]), atof(args[i+2]),
+ atoi(args[i+3]), atoi(args[i+4]),
+ atof(args[i+5]) + pt.x, atof(args[i+6]) + pt.y);
+ i += 7;
+ break;
+
+ case 'Z':
+ case 'z':
+ gs_closepath(ctx->pgs);
+ //dputs("closepath\n");
+ break;
+
+ default:
+ /* eek */
+ break;
+ }
+
+ old = cmd;
+ }
+
+ xps_free(ctx, args);
+}
+
+static void
+xps_parse_arc_segment(xps_context_t *ctx, xps_item_t *root, int stroking, int *skipped_stroke)
+{
+ /* ArcSegment pretty much follows the SVG algorithm for converting an
+ * arc in endpoint representation to an arc in centerpoint
+ * representation. Once in centerpoint it can be given to the
+ * graphics library in the form of a postscript arc. */
+
+ float rotation_angle;
+ int is_large_arc, is_clockwise;
+ float point_x, point_y;
+ float size_x, size_y;
+ int is_stroked;
+
+ char *point_att = xps_att(root, "Point");
+ char *size_att = xps_att(root, "Size");
+ char *rotation_angle_att = xps_att(root, "RotationAngle");
+ char *is_large_arc_att = xps_att(root, "IsLargeArc");
+ char *sweep_direction_att = xps_att(root, "SweepDirection");
+ char *is_stroked_att = xps_att(root, "IsStroked");
+
+ if (!point_att || !size_att || !rotation_angle_att || !is_large_arc_att || !sweep_direction_att)
+ {
+ gs_warn("ArcSegment element is missing attributes");
+ return;
+ }
+
+ is_stroked = 1;
+ if (is_stroked_att && !strcmp(is_stroked_att, "false"))
+ is_stroked = 0;
+ if (!is_stroked)
+ *skipped_stroke = 1;
+
+ sscanf(point_att, "%g,%g", &point_x, &point_y);
+ sscanf(size_att, "%g,%g", &size_x, &size_y);
+ rotation_angle = atof(rotation_angle_att);
+ is_large_arc = !strcmp(is_large_arc_att, "true");
+ is_clockwise = !strcmp(sweep_direction_att, "Clockwise");
+
+ if (stroking && !is_stroked)
+ {
+ gs_moveto(ctx->pgs, point_x, point_y);
+ return;
+ }
+
+ xps_draw_arc(ctx, size_x, size_y, rotation_angle, is_large_arc, is_clockwise, point_x, point_y);
+}
+
+static void
+xps_parse_poly_quadratic_bezier_segment(xps_context_t *ctx, xps_item_t *root, int stroking, int *skipped_stroke)
+{
+ char *points_att = xps_att(root, "Points");
+ char *is_stroked_att = xps_att(root, "IsStroked");
+ float x[2], y[2];
+ int is_stroked;
+ gs_point pt;
+ char *s;
+ int n;
+
+ if (!points_att)
+ {
+ gs_warn("PolyQuadraticBezierSegment element has no points");
+ return;
+ }
+
+ is_stroked = 1;
+ if (is_stroked_att && !strcmp(is_stroked_att, "false"))
+ is_stroked = 0;
+ if (!is_stroked)
+ *skipped_stroke = 1;
+
+ s = points_att;
+ n = 0;
+ while (*s != 0)
+ {
+ while (*s == ' ') s++;
+ sscanf(s, "%g,%g", &x[n], &y[n]);
+ while (*s != ' ' && *s != 0) s++;
+ n ++;
+ if (n == 2)
+ {
+ if (stroking && !is_stroked)
+ {
+ gs_moveto(ctx->pgs, x[1], y[1]);
+ }
+ else
+ {
+ gs_currentpoint(ctx->pgs, &pt);
+ gs_curveto(ctx->pgs,
+ (pt.x + 2 * x[0]) / 3, (pt.y + 2 * y[0]) / 3,
+ (x[1] + 2 * x[0]) / 3, (y[1] + 2 * y[0]) / 3,
+ x[1], y[1]);
+ }
+ n = 0;
+ }
+ }
+}
+
+static void
+xps_parse_poly_bezier_segment(xps_context_t *ctx, xps_item_t *root, int stroking, int *skipped_stroke)
+{
+ char *points_att = xps_att(root, "Points");
+ char *is_stroked_att = xps_att(root, "IsStroked");
+ float x[3], y[3];
+ int is_stroked;
+ char *s;
+ int n;
+
+ if (!points_att)
+ {
+ gs_warn("PolyBezierSegment element has no points");
+ return;
+ }
+
+ is_stroked = 1;
+ if (is_stroked_att && !strcmp(is_stroked_att, "false"))
+ is_stroked = 0;
+ if (!is_stroked)
+ *skipped_stroke = 1;
+
+ s = points_att;
+ n = 0;
+ while (*s != 0)
+ {
+ while (*s == ' ') s++;
+ sscanf(s, "%g,%g", &x[n], &y[n]);
+ while (*s != ' ' && *s != 0) s++;
+ n ++;
+ if (n == 3)
+ {
+ if (stroking && !is_stroked)
+ gs_moveto(ctx->pgs, x[2], y[2]);
+ else
+ gs_curveto(ctx->pgs, x[0], y[0], x[1], y[1], x[2], y[2]);
+ n = 0;
+ }
+ }
+}
+
+static void
+xps_parse_poly_line_segment(xps_context_t *ctx, xps_item_t *root, int stroking, int *skipped_stroke)
+{
+ char *points_att = xps_att(root, "Points");
+ char *is_stroked_att = xps_att(root, "IsStroked");
+ int is_stroked;
+ float x, y;
+ char *s;
+
+ if (!points_att)
+ {
+ gs_warn("PolyLineSegment element has no points");
+ return;
+ }
+
+ is_stroked = 1;
+ if (is_stroked_att && !strcmp(is_stroked_att, "false"))
+ is_stroked = 0;
+ if (!is_stroked)
+ *skipped_stroke = 1;
+
+ s = points_att;
+ while (*s != 0)
+ {
+ while (*s == ' ') s++;
+ sscanf(s, "%g,%g", &x, &y);
+ if (stroking && !is_stroked)
+ gs_moveto(ctx->pgs, x, y);
+ else
+ gs_lineto(ctx->pgs, x, y);
+ while (*s != ' ' && *s != 0) s++;
+ }
+}
+
+static void
+xps_parse_path_figure(xps_context_t *ctx, xps_item_t *root, int stroking)
+{
+ xps_item_t *node;
+
+ char *is_closed_att;
+ char *start_point_att;
+ char *is_filled_att;
+
+ int is_closed = 0;
+ int is_filled = 1;
+ float start_x = 0.0;
+ float start_y = 0.0;
+
+ int skipped_stroke = 0;
+
+ is_closed_att = xps_att(root, "IsClosed");
+ start_point_att = xps_att(root, "StartPoint");
+ is_filled_att = xps_att(root, "IsFilled");
+
+ if (is_closed_att)
+ is_closed = !strcmp(is_closed_att, "true");
+ if (is_filled_att)
+ is_filled = !strcmp(is_filled_att, "true");
+ if (start_point_att)
+ sscanf(start_point_att, "%g,%g", &start_x, &start_y);
+
+ if (!stroking && !is_filled) /* not filled, when filling */
+ return;
+
+ gs_moveto(ctx->pgs, start_x, start_y);
+
+ for (node = xps_down(root); node; node = xps_next(node))
+ {
+ if (!strcmp(xps_tag(node), "ArcSegment"))
+ xps_parse_arc_segment(ctx, node, stroking, &skipped_stroke);
+ if (!strcmp(xps_tag(node), "PolyBezierSegment"))
+ xps_parse_poly_bezier_segment(ctx, node, stroking, &skipped_stroke);
+ if (!strcmp(xps_tag(node), "PolyLineSegment"))
+ xps_parse_poly_line_segment(ctx, node, stroking, &skipped_stroke);
+ if (!strcmp(xps_tag(node), "PolyQuadraticBezierSegment"))
+ xps_parse_poly_quadratic_bezier_segment(ctx, node, stroking, &skipped_stroke);
+ }
+
+ if (is_closed)
+ {
+ if (stroking && skipped_stroke)
+ gs_lineto(ctx->pgs, start_x, start_y); /* we've skipped using gs_moveto... */
+ else
+ gs_closepath(ctx->pgs); /* no skipped segments, safe to closepath properly */
+ }
+}
+
+void
+xps_parse_path_geometry(xps_context_t *ctx, xps_resource_t *dict, xps_item_t *root, int stroking)
+{
+ xps_item_t *node;
+
+ char *figures_att;
+ char *fill_rule_att;
+ char *transform_att;
+
+ xps_item_t *transform_tag = NULL;
+ xps_item_t *figures_tag = NULL; /* only used by resource */
+
+ gs_matrix transform;
+ gs_matrix saved_transform;
+
+ gs_newpath(ctx->pgs);
+
+ figures_att = xps_att(root, "Figures");
+ fill_rule_att = xps_att(root, "FillRule");
+ transform_att = xps_att(root, "Transform");
+
+ for (node = xps_down(root); node; node = xps_next(node))
+ {
+ if (!strcmp(xps_tag(node), "PathGeometry.Transform"))
+ transform_tag = xps_down(node);
+ }
+
+ xps_resolve_resource_reference(ctx, dict, &transform_att, &transform_tag, NULL);
+ xps_resolve_resource_reference(ctx, dict, &figures_att, &figures_tag, NULL);
+
+ if (fill_rule_att)
+ {
+ if (!strcmp(fill_rule_att, "NonZero"))
+ ctx->fill_rule = 1;
+ if (!strcmp(fill_rule_att, "EvenOdd"))
+ ctx->fill_rule = 0;
+ }
+
+ gs_make_identity(&transform);
+ if (transform_att || transform_tag)
+ {
+ if (transform_att)
+ xps_parse_render_transform(ctx, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(ctx, transform_tag, &transform);
+ }
+
+ gs_currentmatrix(ctx->pgs, &saved_transform);
+ gs_concat(ctx->pgs, &transform);
+
+ if (figures_att)
+ {
+ xps_parse_abbreviated_geometry(ctx, figures_att);
+ }
+
+ if (figures_tag)
+ {
+ xps_parse_path_figure(ctx, figures_tag, stroking);
+ }
+
+ for (node = xps_down(root); node; node = xps_next(node))
+ {
+ if (!strcmp(xps_tag(node), "PathFigure"))
+ xps_parse_path_figure(ctx, node, stroking);
+ }
+
+ gs_setmatrix(ctx->pgs, &saved_transform);
+}
+
+static int
+xps_parse_line_cap(char *attr)
+{
+ if (attr)
+ {
+ if (!strcmp(attr, "Flat")) return gs_cap_butt;
+ if (!strcmp(attr, "Square")) return gs_cap_square;
+ if (!strcmp(attr, "Round")) return gs_cap_round;
+ if (!strcmp(attr, "Triangle")) return gs_cap_triangle;
+ }
+ return gs_cap_butt;
+}
+
+/*
+ * Parse an XPS <Path> element, and call relevant ghostscript
+ * functions for drawing and/or clipping the child elements.
+ */
+
+int
+xps_parse_path(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root)
+{
+ xps_item_t *node;
+ int code;
+
+ char *fill_uri;
+ char *stroke_uri;
+ char *opacity_mask_uri;
+
+ char *transform_att;
+ char *clip_att;
+ char *data_att;
+ char *fill_att;
+ char *stroke_att;
+ char *opacity_att;
+ char *opacity_mask_att;
+
+ xps_item_t *transform_tag = NULL;
+ xps_item_t *clip_tag = NULL;
+ xps_item_t *data_tag = NULL;
+ xps_item_t *fill_tag = NULL;
+ xps_item_t *stroke_tag = NULL;
+ xps_item_t *opacity_mask_tag = NULL;
+
+ char *fill_opacity_att = NULL;
+ char *stroke_opacity_att = NULL;
+
+ char *stroke_dash_array_att;
+ char *stroke_dash_cap_att;
+ char *stroke_dash_offset_att;
+ char *stroke_end_line_cap_att;
+ char *stroke_start_line_cap_att;
+ char *stroke_line_join_att;
+ char *stroke_miter_limit_att;
+ char *stroke_thickness_att;
+
+ gs_line_join linejoin;
+ float linewidth;
+ float miterlimit;
+ float samples[32];
+ gs_color_space *colorspace;
+
+ gs_gsave(ctx->pgs);
+
+ ctx->fill_rule = 0;
+
+ /*
+ * Extract attributes and extended attributes.
+ */
+
+ transform_att = xps_att(root, "RenderTransform");
+ clip_att = xps_att(root, "Clip");
+ data_att = xps_att(root, "Data");
+ fill_att = xps_att(root, "Fill");
+ stroke_att = xps_att(root, "Stroke");
+ opacity_att = xps_att(root, "Opacity");
+ opacity_mask_att = xps_att(root, "OpacityMask");
+
+ stroke_dash_array_att = xps_att(root, "StrokeDashArray");
+ stroke_dash_cap_att = xps_att(root, "StrokeDashCap");
+ stroke_dash_offset_att = xps_att(root, "StrokeDashOffset");
+ stroke_end_line_cap_att = xps_att(root, "StrokeEndLineCap");
+ stroke_start_line_cap_att = xps_att(root, "StrokeStartLineCap");
+ stroke_line_join_att = xps_att(root, "StrokeLineJoin");
+ stroke_miter_limit_att = xps_att(root, "StrokeMiterLimit");
+ stroke_thickness_att = xps_att(root, "StrokeThickness");
+
+ for (node = xps_down(root); node; node = xps_next(node))
+ {
+ if (!strcmp(xps_tag(node), "Path.RenderTransform"))
+ transform_tag = xps_down(node);
+
+ if (!strcmp(xps_tag(node), "Path.OpacityMask"))
+ opacity_mask_tag = xps_down(node);
+
+ if (!strcmp(xps_tag(node), "Path.Clip"))
+ clip_tag = xps_down(node);
+
+ if (!strcmp(xps_tag(node), "Path.Fill"))
+ fill_tag = xps_down(node);
+
+ if (!strcmp(xps_tag(node), "Path.Stroke"))
+ stroke_tag = xps_down(node);
+
+ if (!strcmp(xps_tag(node), "Path.Data"))
+ data_tag = xps_down(node);
+ }
+
+ fill_uri = base_uri;
+ stroke_uri = base_uri;
+ opacity_mask_uri = base_uri;
+
+ xps_resolve_resource_reference(ctx, dict, &data_att, &data_tag, NULL);
+ xps_resolve_resource_reference(ctx, dict, &clip_att, &clip_tag, NULL);
+ xps_resolve_resource_reference(ctx, dict, &transform_att, &transform_tag, NULL);
+ xps_resolve_resource_reference(ctx, dict, &fill_att, &fill_tag, &fill_uri);
+ xps_resolve_resource_reference(ctx, dict, &stroke_att, &stroke_tag, &stroke_uri);
+ xps_resolve_resource_reference(ctx, dict, &opacity_mask_att, &opacity_mask_tag, &opacity_mask_uri);
+
+ /*
+ * Act on the information we have gathered:
+ */
+
+ if (fill_tag && !strcmp(xps_tag(fill_tag), "SolidColorBrush"))
+ {
+ fill_opacity_att = xps_att(fill_tag, "Opacity");
+ fill_att = xps_att(fill_tag, "Color");
+ fill_tag = NULL;
+ }
+
+ if (stroke_tag && !strcmp(xps_tag(stroke_tag), "SolidColorBrush"))
+ {
+ stroke_opacity_att = xps_att(stroke_tag, "Opacity");
+ stroke_att = xps_att(stroke_tag, "Color");
+ stroke_tag = NULL;
+ }
+
+ gs_setlinestartcap(ctx->pgs, xps_parse_line_cap(stroke_start_line_cap_att));
+ gs_setlineendcap(ctx->pgs, xps_parse_line_cap(stroke_end_line_cap_att));
+ gs_setlinedashcap(ctx->pgs, xps_parse_line_cap(stroke_dash_cap_att));
+
+ linejoin = gs_join_miter;
+ if (stroke_line_join_att)
+ {
+ if (!strcmp(stroke_line_join_att, "Miter")) linejoin = gs_join_miter;
+ if (!strcmp(stroke_line_join_att, "Bevel")) linejoin = gs_join_bevel;
+ if (!strcmp(stroke_line_join_att, "Round")) linejoin = gs_join_round;
+ }
+ gs_setlinejoin(ctx->pgs, linejoin);
+
+ miterlimit = 10.0;
+ if (stroke_miter_limit_att)
+ miterlimit = atof(stroke_miter_limit_att);
+ gs_setmiterlimit(ctx->pgs, miterlimit);
+
+ linewidth = 1.0;
+ if (stroke_thickness_att)
+ linewidth = atof(stroke_thickness_att);
+ gs_setlinewidth(ctx->pgs, linewidth);
+
+ if (stroke_dash_array_att)
+ {
+ char *s = stroke_dash_array_att;
+ float dash_array[100];
+ float dash_offset = 0.0;
+ int dash_count = 0;
+
+ if (stroke_dash_offset_att)
+ dash_offset = atof(stroke_dash_offset_att) * linewidth;
+
+ while (*s)
+ {
+ while (*s == ' ')
+ s++;
+ dash_array[dash_count++] = atof(s) * linewidth;
+ while (*s && *s != ' ')
+ s++;
+ }
+
+ gs_setdash(ctx->pgs, dash_array, dash_count, dash_offset);
+ }
+ else
+ {
+ gs_setdash(ctx->pgs, NULL, 0, 0.0);
+ }
+
+ if (transform_att || transform_tag)
+ {
+ gs_matrix transform;
+
+ if (transform_att)
+ xps_parse_render_transform(ctx, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(ctx, transform_tag, &transform);
+
+ gs_concat(ctx->pgs, &transform);
+ }
+
+ if (clip_att || clip_tag)
+ {
+ if (clip_att)
+ xps_parse_abbreviated_geometry(ctx, clip_att);
+ if (clip_tag)
+ xps_parse_path_geometry(ctx, dict, clip_tag, 0);
+ xps_clip(ctx);
+ }
+
+#if 0 // XXX
+ if (opacity_att || opacity_mask_tag)
+ {
+ /* clip the bounds with the actual path */
+ if (data_att)
+ xps_parse_abbreviated_geometry(ctx, data_att);
+ if (data_tag)
+ xps_parse_path_geometry(ctx, dict, data_tag, 0);
+ xps_update_bounds(ctx, &saved_bounds_opacity);
+ gs_newpath(ctx->pgs);
+ }
+#endif
+
+ code = xps_begin_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+ if (code)
+ {
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "cannot create transparency group");
+ }
+
+ if (fill_att)
+ {
+ xps_parse_color(ctx, base_uri, fill_att, &colorspace, samples);
+ if (fill_opacity_att)
+ samples[0] = atof(fill_opacity_att);
+ xps_set_color(ctx, colorspace, samples);
+
+ if (data_att)
+ xps_parse_abbreviated_geometry(ctx, data_att);
+ if (data_tag)
+ xps_parse_path_geometry(ctx, dict, data_tag, 0);
+
+ xps_fill(ctx);
+ }
+
+ if (fill_tag)
+ {
+ if (data_att)
+ xps_parse_abbreviated_geometry(ctx, data_att);
+ if (data_tag)
+ xps_parse_path_geometry(ctx, dict, data_tag, 0);
+
+ code = xps_parse_brush(ctx, fill_uri, dict, fill_tag);
+ if (code < 0)
+ {
+ xps_end_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "cannot parse fill brush");
+ }
+ }
+
+ if (stroke_att)
+ {
+ xps_parse_color(ctx, base_uri, stroke_att, &colorspace, samples);
+ if (stroke_opacity_att)
+ samples[0] = atof(stroke_opacity_att);
+ xps_set_color(ctx, colorspace, samples);
+
+ if (data_att)
+ xps_parse_abbreviated_geometry(ctx, data_att);
+ if (data_tag)
+ xps_parse_path_geometry(ctx, dict, data_tag, 1);
+
+ gs_stroke(ctx->pgs);
+ }
+
+ if (stroke_tag)
+ {
+ if (data_att)
+ xps_parse_abbreviated_geometry(ctx, data_att);
+ if (data_tag)
+ xps_parse_path_geometry(ctx, dict, data_tag, 1);
+
+ ctx->fill_rule = 1; /* over-ride fill rule when converting outline to stroked */
+ gs_strokepath2(ctx->pgs);
+
+ code = xps_parse_brush(ctx, stroke_uri, dict, stroke_tag);
+ if (code < 0)
+ {
+ xps_end_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "cannot parse stroke brush");
+ }
+ }
+
+ xps_end_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+ gs_grestore(ctx->pgs);
+ return 0;
+}
diff --git a/xps/xpspng.c b/xps/xpspng.c
new file mode 100644
index 00000000..d88461cc
--- /dev/null
+++ b/xps/xpspng.c
@@ -0,0 +1,293 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* XPS interpreter - PNG image support */
+
+#include "ghostxps.h"
+
+#include "stream.h"
+#include "strimpl.h"
+#include "gsstate.h"
+
+/* silence a warning where #if SHARE_LIBPNG is used when it's undefined */
+#ifndef SHARE_LIBPNG
+#define SHARE_LIBPNG 0
+#endif
+#include "png_.h"
+
+/*
+ * PNG using libpng directly (no gs wrappers)
+ */
+
+struct xps_png_io_s
+{
+ byte *ptr;
+ byte *lim;
+};
+
+static void
+xps_png_read(png_structp png, png_bytep data, png_size_t length)
+{
+ struct xps_png_io_s *io = png_get_io_ptr(png);
+ if (io->ptr + length > io->lim)
+ png_error(png, "Read Error");
+ memcpy(data, io->ptr, length);
+ io->ptr += length;
+}
+
+static png_voidp
+xps_png_malloc(png_structp png, png_size_t size)
+{
+ gs_memory_t *mem = png_get_mem_ptr(png);
+ return gs_alloc_bytes(mem, size, "libpng");
+}
+
+static void
+xps_png_free(png_structp png, png_voidp ptr)
+{
+ gs_memory_t *mem = png_get_mem_ptr(png);
+ gs_free_object(mem, ptr, "libpng");
+}
+
+/* This only determines if we have an alpha value */
+int
+xps_png_has_alpha(xps_context_t *ctx, byte *rbuf, int rlen)
+{
+ png_structp png;
+ png_infop info;
+ struct xps_png_io_s io;
+ int has_alpha;
+
+ /*
+ * Set up PNG structs and input source
+ */
+
+ io.ptr = rbuf;
+ io.lim = rbuf + rlen;
+
+ png = png_create_read_struct_2(PNG_LIBPNG_VER_STRING,
+ NULL, NULL, NULL,
+ ctx->memory, xps_png_malloc, xps_png_free);
+ if (!png) {
+ gs_warn("png_create_read_struct");
+ return 0;
+ }
+
+ info = png_create_info_struct(png);
+ if (!info) {
+ gs_warn("png_create_info_struct");
+ return 0;
+ }
+
+ png_set_read_fn(png, &io, xps_png_read);
+ png_set_crc_action(png, PNG_CRC_WARN_USE, PNG_CRC_WARN_USE);
+
+ /*
+ * Jump to here on errors.
+ */
+
+ if (setjmp(png_jmpbuf(png)))
+ {
+ png_destroy_read_struct(&png, &info, NULL);
+ gs_warn("png reading failed");
+ return 0;
+ }
+
+ /*
+ * Read PNG header
+ */
+
+ png_read_info(png, info);
+
+ switch (png_get_color_type(png, info))
+ {
+ case PNG_COLOR_TYPE_PALETTE:
+ case PNG_COLOR_TYPE_GRAY:
+ case PNG_COLOR_TYPE_RGB:
+ has_alpha = 0;
+ break;
+
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ has_alpha = 1;
+ break;
+
+ default:
+ gs_warn("cannot handle this png color type");
+ has_alpha = 0;
+ break;
+ }
+
+ /*
+ * Clean up memory.
+ */
+
+ png_destroy_read_struct(&png, &info, NULL);
+
+ return has_alpha;
+}
+
+int
+xps_decode_png(xps_context_t *ctx, byte *rbuf, int rlen, xps_image_t *image)
+{
+ png_structp png;
+ png_infop info;
+ struct xps_png_io_s io;
+ int npasses;
+ int pass;
+ int y;
+
+ /*
+ * Set up PNG structs and input source
+ */
+
+ io.ptr = rbuf;
+ io.lim = rbuf + rlen;
+
+ png = png_create_read_struct_2(PNG_LIBPNG_VER_STRING,
+ NULL, NULL, NULL,
+ ctx->memory, xps_png_malloc, xps_png_free);
+ if (!png)
+ return gs_throw(-1, "png_create_read_struct");
+
+ info = png_create_info_struct(png);
+ if (!info)
+ return gs_throw(-1, "png_create_info_struct");
+
+ png_set_read_fn(png, &io, xps_png_read);
+ png_set_crc_action(png, PNG_CRC_WARN_USE, PNG_CRC_WARN_USE);
+
+ /*
+ * Jump to here on errors.
+ */
+
+ if (setjmp(png_jmpbuf(png)))
+ {
+ png_destroy_read_struct(&png, &info, NULL);
+ return gs_throw(-1, "png reading failed");
+ }
+
+ /*
+ * Read PNG header
+ */
+
+ png_read_info(png, info);
+
+ if (png_get_interlace_type(png, info) == PNG_INTERLACE_ADAM7)
+ {
+ npasses = png_set_interlace_handling(png);
+ }
+ else
+ {
+ npasses = 1;
+ }
+
+ if (png_get_color_type(png, info) == PNG_COLOR_TYPE_PALETTE)
+ {
+ png_set_palette_to_rgb(png);
+ }
+
+ if (png_get_valid(png, info, PNG_INFO_tRNS))
+ {
+ /* this will also expand the depth to 8-bits */
+ png_set_tRNS_to_alpha(png);
+ }
+
+ png_read_update_info(png, info);
+
+ image->width = png_get_image_width(png, info);
+ image->height = png_get_image_height(png, info);
+ image->comps = png_get_channels(png, info);
+ image->bits = png_get_bit_depth(png, info);
+
+ /* See if we have an icc profile */
+ if (info->iccp_profile != NULL)
+ {
+ image->profilesize = info->iccp_proflen;
+ image->profile = xps_alloc(ctx, info->iccp_proflen);
+ if (image->profile)
+ {
+ /* If we can't create it, just ignore */
+ memcpy(image->profile, info->iccp_profile, info->iccp_proflen);
+ }
+ }
+
+ switch (png_get_color_type(png, info))
+ {
+ case PNG_COLOR_TYPE_GRAY:
+ image->colorspace = ctx->gray;
+ image->hasalpha = 0;
+ break;
+
+ case PNG_COLOR_TYPE_RGB:
+ image->colorspace = ctx->srgb;
+ image->hasalpha = 0;
+ break;
+
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
+ image->colorspace = ctx->gray;
+ image->hasalpha = 1;
+ break;
+
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ image->colorspace = ctx->srgb;
+ image->hasalpha = 1;
+ break;
+
+ default:
+ return gs_throw(-1, "cannot handle this png color type");
+ }
+
+ /*
+ * Extract DPI, default to 96 dpi
+ */
+
+ image->xres = 96;
+ image->yres = 96;
+
+ if (info->valid & PNG_INFO_pHYs)
+ {
+ png_uint_32 xres, yres;
+ int unit;
+ png_get_pHYs(png, info, &xres, &yres, &unit);
+ if (unit == PNG_RESOLUTION_METER)
+ {
+ image->xres = xres * 0.0254 + 0.5;
+ image->yres = yres * 0.0254 + 0.5;
+ }
+ }
+
+ /*
+ * Read rows, filling transformed output into image buffer.
+ */
+
+ image->stride = (image->width * image->comps * image->bits + 7) / 8;
+
+ image->samples = xps_alloc(ctx, image->stride * image->height);
+
+ for (pass = 0; pass < npasses; pass++)
+ {
+ for (y = 0; y < image->height; y++)
+ {
+ png_read_row(png, image->samples + (y * image->stride), NULL);
+ }
+ }
+
+ /*
+ * Clean up memory.
+ */
+
+ png_destroy_read_struct(&png, &info, NULL);
+
+ return gs_okay;
+}
diff --git a/xps/xpsresource.c b/xps/xpsresource.c
new file mode 100644
index 00000000..a21d6bed
--- /dev/null
+++ b/xps/xpsresource.c
@@ -0,0 +1,204 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* XPS interpreter - resource functions */
+
+#include "ghostxps.h"
+
+static xps_item_t *
+xps_find_resource(xps_context_t *ctx, xps_resource_t *dict, char *name, char **urip)
+{
+ xps_resource_t *head, *node;
+ for (head = dict; head; head = head->parent)
+ {
+ for (node = head; node; node = node->next)
+ {
+ if (!strcmp(node->name, name))
+ {
+ if (urip && head->base_uri)
+ *urip = head->base_uri;
+ return node->data;
+ }
+ }
+ }
+ return NULL;
+}
+
+static xps_item_t *
+xps_parse_resource_reference(xps_context_t *ctx, xps_resource_t *dict, char *att, char **urip)
+{
+ char name[1024];
+ char *s;
+
+ if (strstr(att, "{StaticResource ") != att)
+ return NULL;
+
+ xps_strlcpy(name, att + 16, sizeof name);
+ s = strrchr(name, '}');
+ if (s)
+ *s = 0;
+
+ return xps_find_resource(ctx, dict, name, urip);
+}
+
+void
+xps_resolve_resource_reference(xps_context_t *ctx, xps_resource_t *dict,
+ char **attp, xps_item_t **tagp, char **urip)
+{
+ if (*attp)
+ {
+ xps_item_t *rsrc = xps_parse_resource_reference(ctx, dict, *attp, urip);
+ if (rsrc)
+ {
+ *attp = NULL;
+ *tagp = rsrc;
+ }
+ }
+}
+
+static int
+xps_parse_remote_resource_dictionary(xps_context_t *ctx, xps_resource_t **dictp, char *base_uri, char *source_att)
+{
+ char part_name[1024];
+ char part_uri[1024];
+ xps_resource_t *dict;
+ xps_part_t *part;
+ xps_item_t *xml;
+ char *s;
+ int code;
+
+ /* External resource dictionaries MUST NOT reference other resource dictionaries */
+ xps_absolute_path(part_name, base_uri, source_att, sizeof part_name);
+ part = xps_read_part(ctx, part_name);
+ if (!part)
+ {
+ return gs_throw1(-1, "cannot find remote resource part '%s'", part_name);
+ }
+
+ xml = xps_parse_xml(ctx, part->data, part->size);
+ if (!xml)
+ {
+ xps_free_part(ctx, part);
+ return gs_rethrow(-1, "cannot parse xml");
+ }
+
+ if (strcmp(xps_tag(xml), "ResourceDictionary"))
+ {
+ xps_free_item(ctx, xml);
+ xps_free_part(ctx, part);
+ return gs_throw1(-1, "expected ResourceDictionary element (found %s)", xps_tag(xml));
+ }
+
+ xps_strlcpy(part_uri, part_name, sizeof part_uri);
+ s = strrchr(part_uri, '/');
+ if (s)
+ s[1] = 0;
+
+ code = xps_parse_resource_dictionary(ctx, &dict, part_uri, xml);
+ if (code)
+ {
+ xps_free_item(ctx, xml);
+ xps_free_part(ctx, part);
+ return gs_rethrow1(code, "cannot parse remote resource dictionary: %s", part_uri);
+ }
+
+ dict->base_xml = xml; /* pass on ownership */
+
+ xps_free_part(ctx, part);
+
+ *dictp = dict;
+ return gs_okay;
+}
+
+int
+xps_parse_resource_dictionary(xps_context_t *ctx, xps_resource_t **dictp, char *base_uri, xps_item_t *root)
+{
+ xps_resource_t *head;
+ xps_resource_t *entry;
+ xps_item_t *node;
+ char *source;
+ char *key;
+ int code;
+
+ source = xps_att(root, "Source");
+ if (source)
+ {
+ code = xps_parse_remote_resource_dictionary(ctx, dictp, base_uri, source);
+ if (code)
+ return gs_rethrow(code, "cannot parse remote resource dictionary");
+ return gs_okay;
+ }
+
+ head = NULL;
+
+ for (node = xps_down(root); node; node = xps_next(node))
+ {
+ /* Usually "x:Key"; we have already processed and stripped namespace */
+ key = xps_att(node, "Key");
+ if (key)
+ {
+ entry = xps_alloc(ctx, sizeof(xps_resource_t));
+ if (!entry)
+ return gs_throw(-1, "cannot allocate resource entry");
+ entry->name = key;
+ entry->base_uri = NULL;
+ entry->base_xml = NULL;
+ entry->data = node;
+ entry->next = head;
+ entry->parent = NULL;
+ head = entry;
+ }
+ }
+
+ if (head)
+ {
+ head->base_uri = xps_strdup(ctx, base_uri);
+ }
+
+ *dictp = head;
+ return gs_okay;
+}
+
+void
+xps_free_resource_dictionary(xps_context_t *ctx, xps_resource_t *dict)
+{
+ xps_resource_t *next;
+ while (dict)
+ {
+ next = dict->next;
+ if (dict->base_xml)
+ xps_free_item(ctx, dict->base_xml);
+ if (dict->base_uri)
+ xps_free(ctx, dict->base_uri);
+ xps_free(ctx, dict);
+ dict = next;
+ }
+}
+
+void
+xps_debug_resource_dictionary(xps_resource_t *dict)
+{
+ while (dict)
+ {
+ if (dict->base_uri)
+ dprintf1("URI = '%s'\n", dict->base_uri);
+ dprintf2("KEY = '%s' VAL = %p\n", dict->name, dict->data);
+ if (dict->parent)
+ {
+ dputs("PARENT = {\n");
+ xps_debug_resource_dictionary(dict->parent);
+ dputs("}\n");
+ }
+ dict = dict->next;
+ }
+}
diff --git a/xps/xpstiff.c b/xps/xpstiff.c
new file mode 100644
index 00000000..3e2627ef
--- /dev/null
+++ b/xps/xpstiff.c
@@ -0,0 +1,1091 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* XPS interpreter - TIFF image support */
+
+#include "ghostxps.h"
+
+#include "stream.h"
+#include "strimpl.h"
+#include "gsstate.h"
+#include "jpeglib_.h"
+#include "sdct.h"
+#include "sjpeg.h"
+#include "srlx.h"
+#include "slzwx.h"
+#include "szlibx.h"
+#include "scfx.h"
+#include "memory_.h"
+
+/*
+ * TIFF image loader. Should be enough to support TIFF files in XPS.
+ * Baseline TIFF 6.0 plus CMYK, LZW, Flate and JPEG support.
+ * Limited bit depths (1,2,4,8).
+ * Limited planar configurations (1=chunky).
+ * No tiles (easy fix if necessary).
+ * TODO: RGBPal images
+ */
+
+typedef struct xps_tiff_s xps_tiff_t;
+
+struct xps_tiff_s
+{
+ /* "file" */
+ byte *bp, *rp, *ep;
+
+ /* byte order */
+ unsigned order;
+
+ /* where we can find the strips of image data */
+ unsigned rowsperstrip;
+ unsigned *stripoffsets;
+ unsigned *stripbytecounts;
+
+ /* colormap */
+ unsigned *colormap;
+
+ /* assorted tags */
+ unsigned subfiletype;
+ unsigned photometric;
+ unsigned compression;
+ unsigned imagewidth;
+ unsigned imagelength;
+ unsigned samplesperpixel;
+ unsigned bitspersample;
+ unsigned planar;
+ unsigned extrasamples;
+ unsigned xresolution;
+ unsigned yresolution;
+ unsigned resolutionunit;
+ unsigned fillorder;
+ unsigned g3opts;
+ unsigned g4opts;
+ unsigned predictor;
+
+ unsigned ycbcrsubsamp[2];
+
+ byte *jpegtables; /* point into "file" buffer */
+ unsigned jpegtableslen;
+
+ byte *profile;
+ int profilesize;
+};
+
+enum
+{
+ TII = 0x4949, /* 'II' */
+ TMM = 0x4d4d, /* 'MM' */
+ TBYTE = 1,
+ TASCII = 2,
+ TSHORT = 3,
+ TLONG = 4,
+ TRATIONAL = 5
+};
+
+#define NewSubfileType 254
+#define ImageWidth 256
+#define ImageLength 257
+#define BitsPerSample 258
+#define Compression 259
+#define PhotometricInterpretation 262
+#define FillOrder 266
+#define StripOffsets 273
+#define SamplesPerPixel 277
+#define RowsPerStrip 278
+#define StripByteCounts 279
+#define XResolution 282
+#define YResolution 283
+#define PlanarConfiguration 284
+#define T4Options 292
+#define T6Options 293
+#define ResolutionUnit 296
+#define Predictor 317
+#define ColorMap 320
+#define TileWidth 322
+#define TileLength 323
+#define TileOffsets 324
+#define TileByteCounts 325
+#define ExtraSamples 338
+#define JPEGTables 347
+#define YCbCrSubSampling 520
+#define ICCProfile 34675
+
+static const byte bitrev[256] =
+{
+ 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
+ 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
+ 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
+ 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
+ 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
+ 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+ 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
+ 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
+ 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
+ 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
+ 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
+ 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+ 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
+ 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
+ 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
+ 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
+ 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
+ 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+ 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
+ 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
+ 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
+ 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
+ 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
+ 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+ 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
+ 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
+ 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
+ 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
+ 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
+ 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+ 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
+ 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
+};
+
+static int
+xps_report_error(stream_state * st, const char *str)
+{
+ (void) gs_throw1(-1, "%s", str);
+ return 0;
+}
+
+static inline int
+readbyte(xps_tiff_t *tiff)
+{
+ if (tiff->rp < tiff->ep)
+ return *tiff->rp++;
+ return EOF;
+}
+
+static inline unsigned
+readshort(xps_tiff_t *tiff)
+{
+ unsigned a = readbyte(tiff);
+ unsigned b = readbyte(tiff);
+ if (tiff->order == TII)
+ return (b << 8) | a;
+ return (a << 8) | b;
+}
+
+static inline unsigned
+readlong(xps_tiff_t *tiff)
+{
+ unsigned a = readbyte(tiff);
+ unsigned b = readbyte(tiff);
+ unsigned c = readbyte(tiff);
+ unsigned d = readbyte(tiff);
+ if (tiff->order == TII)
+ return (d << 24) | (c << 16) | (b << 8) | a;
+ return (a << 24) | (b << 16) | (c << 8) | d;
+}
+
+static int
+xps_decode_tiff_uncompressed(xps_context_t *ctx, xps_tiff_t *tiff, byte *rp, byte *rl, byte *wp, byte *wl)
+{
+ memcpy(wp, rp, wl - wp);
+ return gs_okay;
+}
+
+static int
+xps_decode_tiff_packbits(xps_context_t *ctx, xps_tiff_t *tiff, byte *rp, byte *rl, byte *wp, byte *wl)
+{
+ stream_RLD_state state;
+ stream_cursor_read scr;
+ stream_cursor_write scw;
+ int code;
+
+ s_init_state((stream_state*)&state, &s_RLD_template, ctx->memory);
+ state.report_error = xps_report_error;
+
+ s_RLD_template.set_defaults((stream_state*)&state);
+ s_RLD_template.init((stream_state*)&state);
+
+ scr.ptr = rp - 1;
+ scr.limit = rl - 1;
+ scw.ptr = wp - 1;
+ scw.limit = wl - 1;
+
+ code = s_RLD_template.process((stream_state*)&state, &scr, &scw, true);
+ if (code == ERRC)
+ return gs_throw1(-1, "error in packbits data (code = %d)", code);
+
+ return gs_okay;
+}
+
+static int
+xps_decode_tiff_lzw(xps_context_t *ctx, xps_tiff_t *tiff, byte *rp, byte *rl, byte *wp, byte *wl)
+{
+ stream_LZW_state state;
+ stream_cursor_read scr;
+ stream_cursor_write scw;
+ int code;
+
+ s_init_state((stream_state*)&state, &s_LZWD_template, ctx->memory);
+ state.report_error = xps_report_error;
+
+ s_LZWD_template.set_defaults((stream_state*)&state);
+
+ /* old-style TIFF 5.0 reversed bit order, late change */
+ if (rp[0] == 0 && rp[1] & 0x01)
+ {
+ state.EarlyChange = 0;
+ state.FirstBitLowOrder = 1;
+ }
+
+ /* new-style TIFF 6.0 normal bit order, early change */
+ else
+ {
+ state.EarlyChange = 1;
+ state.FirstBitLowOrder = 0;
+ }
+
+ s_LZWD_template.init((stream_state*)&state);
+
+ scr.ptr = rp - 1;
+ scr.limit = rl - 1;
+ scw.ptr = wp - 1;
+ scw.limit = wl - 1;
+
+ code = s_LZWD_template.process((stream_state*)&state, &scr, &scw, true);
+ if (code == ERRC)
+ {
+ s_LZWD_template.release((stream_state*)&state);
+ return gs_throw1(-1, "error in lzw data (code = %d)", code);
+ }
+
+ s_LZWD_template.release((stream_state*)&state);
+
+ return gs_okay;
+}
+
+static int
+xps_decode_tiff_flate(xps_context_t *ctx, xps_tiff_t *tiff, byte *rp, byte *rl, byte *wp, byte *wl)
+{
+ stream_zlib_state state;
+ stream_cursor_read scr;
+ stream_cursor_write scw;
+ int code;
+
+ s_init_state((stream_state*)&state, &s_zlibD_template, ctx->memory);
+ state.report_error = xps_report_error;
+
+ s_zlibD_template.set_defaults((stream_state*)&state);
+
+ s_zlibD_template.init((stream_state*)&state);
+
+ scr.ptr = rp - 1;
+ scr.limit = rl - 1;
+ scw.ptr = wp - 1;
+ scw.limit = wl - 1;
+
+ code = s_zlibD_template.process((stream_state*)&state, &scr, &scw, true);
+ if (code == ERRC)
+ {
+ s_zlibD_template.release((stream_state*)&state);
+ return gs_throw1(-1, "error in flate data (code = %d)", code);
+ }
+
+ s_zlibD_template.release((stream_state*)&state);
+ return gs_okay;
+}
+
+static int
+xps_decode_tiff_fax(xps_context_t *ctx, xps_tiff_t *tiff, int comp, byte *rp, byte *rl, byte *wp, byte *wl)
+{
+ stream_CFD_state state;
+ stream_cursor_read scr;
+ stream_cursor_write scw;
+ int code;
+
+ s_init_state((stream_state*)&state, &s_CFD_template, ctx->memory);
+ state.report_error = xps_report_error;
+
+ s_CFD_template.set_defaults((stream_state*)&state);
+
+ state.EndOfLine = false;
+ state.EndOfBlock = false;
+ state.Columns = tiff->imagewidth;
+ state.Rows = tiff->imagelength;
+ state.BlackIs1 = tiff->photometric == 0;
+
+ state.K = 0;
+ if (comp == 4)
+ state.K = -1;
+ if (comp == 2)
+ state.EncodedByteAlign = true;
+
+ s_CFD_template.init((stream_state*)&state);
+
+ scr.ptr = rp - 1;
+ scr.limit = rl - 1;
+ scw.ptr = wp - 1;
+ scw.limit = wl - 1;
+
+ code = s_CFD_template.process((stream_state*)&state, &scr, &scw, true);
+ if (code == ERRC)
+ {
+ s_CFD_template.release((stream_state*)&state);
+ return gs_throw1(-1, "error in fax data (code = %d)", code);
+ }
+
+ s_CFD_template.release((stream_state*)&state);
+ return gs_okay;
+}
+
+/*
+ * We need more find control over JPEG decoding parameters than
+ * the s_DCTD_template filter will give us. So we abuse the
+ * filter, and take control after the filter setup (which sets up
+ * the memory manager and error handling) and call the gs_jpeg
+ * wrappers directly for doing the actual decoding.
+ */
+
+static int
+xps_decode_tiff_jpeg(xps_context_t *ctx, xps_tiff_t *tiff, byte *rp, byte *rl, byte *wp, byte *wl)
+{
+ stream_DCT_state state; /* used by gs_jpeg_* wrappers */
+ jpeg_decompress_data jddp;
+ struct jpeg_source_mgr *srcmgr;
+ JSAMPROW scanlines[1];
+ int stride;
+ int code;
+
+ /*
+ * Set up the JPEG and DCT filter voodoo.
+ */
+
+ s_init_state((stream_state*)&state, &s_DCTD_template, ctx->memory);
+ state.report_error = xps_report_error;
+ s_DCTD_template.set_defaults((stream_state*)&state);
+
+ state.jpeg_memory = ctx->memory;
+ state.data.decompress = &jddp;
+
+ jddp.template = s_DCTD_template;
+ jddp.memory = ctx->memory;
+ jddp.scanline_buffer = NULL;
+
+ if ((code = gs_jpeg_create_decompress(&state)) < 0)
+ return gs_throw(-1, "error in gs_jpeg_create_decompress");
+
+ s_DCTD_template.init((stream_state*)&state);
+
+ srcmgr = jddp.dinfo.src;
+
+ /*
+ * Read the abbreviated table file.
+ */
+
+ if (tiff->jpegtables)
+ {
+ srcmgr->next_input_byte = tiff->jpegtables;
+ srcmgr->bytes_in_buffer = tiff->jpegtableslen;
+
+ code = gs_jpeg_read_header(&state, FALSE);
+ if (code != JPEG_HEADER_TABLES_ONLY)
+ return gs_throw(-1, "error in jpeg table data");
+ }
+
+ /*
+ * Read the image jpeg header.
+ */
+
+ srcmgr->next_input_byte = rp;
+ srcmgr->bytes_in_buffer = rl - rp;
+
+ if ((code = gs_jpeg_read_header(&state, TRUE)) < 0)
+ return gs_throw(-1, "error in jpeg_read_header");
+
+ /* when TIFF says RGB and libjpeg says YCbCr, libjpeg is wrong */
+ if (tiff->photometric == 2 && jddp.dinfo.jpeg_color_space == JCS_YCbCr)
+ {
+ jddp.dinfo.jpeg_color_space = JCS_RGB;
+ }
+
+ /*
+ * Decode the strip image data.
+ */
+
+ if ((code = gs_jpeg_start_decompress(&state)) < 0)
+ return gs_throw(-1, "error in jpeg_start_decompress");
+
+ stride = jddp.dinfo.output_width * jddp.dinfo.output_components;
+
+ while (wp + stride <= wl && jddp.dinfo.output_scanline < jddp.dinfo.output_height)
+ {
+ scanlines[0] = wp;
+ code = gs_jpeg_read_scanlines(&state, scanlines, 1);
+ if (code < 0)
+ return gs_throw(01, "error in jpeg_read_scanlines");
+ wp += stride;
+ }
+
+ /*
+ * Clean up.
+ */
+
+ if ((code = gs_jpeg_finish_decompress(&state)) < 0)
+ return gs_throw(-1, "error in jpeg_finish_decompress");
+
+ gs_jpeg_destroy(&state);
+
+ return gs_okay;
+}
+
+static inline int
+getcomp(byte *line, int x, int bpc)
+{
+ switch (bpc)
+ {
+ case 1: return line[x / 8] >> (7 - (x % 8)) & 0x01;
+ case 2: return line[x / 4] >> ((3 - (x % 4)) * 2) & 0x03;
+ case 4: return line[x / 2] >> ((1 - (x % 2)) * 4) & 0x0f;
+ case 8: return line[x];
+ case 16: return ((line[x * 2 + 0]) << 8) | (line[x * 2 + 1]);
+ }
+ return 0;
+}
+
+static inline void
+putcomp(byte *line, int x, int bpc, int value)
+{
+ int maxval = (1 << bpc) - 1;
+
+ // clear bits first
+ switch (bpc)
+ {
+ case 1: line[x / 8] &= ~(maxval << (7 - (x % 8))); break;
+ case 2: line[x / 4] &= ~(maxval << ((3 - (x % 4)) * 2)); break;
+ case 4: line[x / 2] &= ~(maxval << ((1 - (x % 2)) * 4)); break;
+ }
+
+ switch (bpc)
+ {
+ case 1: line[x / 8] |= value << (7 - (x % 8)); break;
+ case 2: line[x / 4] |= value << ((3 - (x % 4)) * 2); break;
+ case 4: line[x / 2] |= value << ((1 - (x % 2)) * 4); break;
+ case 8: line[x] = value; break;
+ case 16: line[x * 2 + 0] = value >> 8; line[x * 2 + 1] = value & 0xFF; break;
+ }
+}
+
+static void
+xps_unpredict_tiff(byte *line, int width, int comps, int bits)
+{
+ byte left[32];
+ int i, k, v;
+
+ for (k = 0; k < comps; k++)
+ left[k] = 0;
+
+ for (i = 0; i < width; i++)
+ {
+ for (k = 0; k < comps; k++)
+ {
+ v = getcomp(line, i * comps + k, bits);
+ v = v + left[k];
+ v = v % (1 << bits);
+ putcomp(line, i * comps + k, bits, v);
+ left[k] = v;
+ }
+ }
+}
+
+static void
+xps_invert_tiff(byte *line, int width, int comps, int bits, int alpha)
+{
+ int i, k, v;
+ int m = (1 << bits) - 1;
+
+ for (i = 0; i < width; i++)
+ {
+ for (k = 0; k < comps; k++)
+ {
+ v = getcomp(line, i * comps + k, bits);
+ if (!alpha || k < comps - 1)
+ v = m - v;
+ putcomp(line, i * comps + k, bits, v);
+ }
+ }
+}
+
+static int
+xps_expand_colormap(xps_context_t *ctx, xps_tiff_t *tiff, xps_image_t *image)
+{
+ int maxval = 1 << image->bits;
+ byte *samples;
+ byte *src, *dst;
+ int stride;
+ int x, y;
+
+ /* colormap has first all red, then all green, then all blue values */
+ /* colormap values are 0..65535, bits is 4 or 8 */
+ /* image can be with or without extrasamples: comps is 1 or 2 */
+
+ if (image->comps != 1 && image->comps != 2)
+ return gs_throw(-1, "invalid number of samples for RGBPal");
+
+ if (image->bits != 4 && image->bits != 8)
+ return gs_throw(-1, "invalid number of bits for RGBPal");
+
+ stride = image->width * (image->comps + 2);
+
+ samples = xps_alloc(ctx, stride * image->height);
+ if (!samples)
+ return gs_throw(-1, "out of memory: samples");
+
+ for (y = 0; y < image->height; y++)
+ {
+ src = image->samples + (image->stride * y);
+ dst = samples + (stride * y);
+
+ for (x = 0; x < image->width; x++)
+ {
+ if (tiff->extrasamples)
+ {
+ int c = getcomp(src, x * 2, image->bits);
+ int a = getcomp(src, x * 2 + 1, image->bits);
+ *dst++ = tiff->colormap[c + 0] >> 8;
+ *dst++ = tiff->colormap[c + maxval] >> 8;
+ *dst++ = tiff->colormap[c + maxval * 2] >> 8;
+ *dst++ = a << (8 - image->bits);
+ }
+ else
+ {
+ int c = getcomp(src, x, image->bits);
+ *dst++ = tiff->colormap[c + 0] >> 8;
+ *dst++ = tiff->colormap[c + maxval] >> 8;
+ *dst++ = tiff->colormap[c + maxval * 2] >> 8;
+ }
+ }
+ }
+
+ image->bits = 8;
+ image->stride = stride;
+ image->samples = samples;
+
+ return gs_okay;
+}
+
+static int
+xps_decode_tiff_strips(xps_context_t *ctx, xps_tiff_t *tiff, xps_image_t *image)
+{
+ int error;
+
+ /* switch on compression to create a filter */
+ /* feed each strip to the filter */
+ /* read out the data and pack the samples into an xps_image */
+
+ /* type 32773 / packbits -- nothing special (same row-padding as PDF) */
+ /* type 2 / ccitt rle -- no EOL, no RTC, rows are byte-aligned */
+ /* type 3 and 4 / g3 and g4 -- each strip starts new section */
+ /* type 5 / lzw -- each strip is handled separately */
+
+ byte *wp;
+ unsigned row;
+ unsigned strip;
+ unsigned i;
+
+ if (!tiff->rowsperstrip || !tiff->stripoffsets || !tiff->rowsperstrip)
+ return gs_throw(-1, "no image data in tiff; maybe it is tiled");
+
+ if (tiff->planar != 1)
+ return gs_throw(-1, "image data is not in chunky format");
+
+ image->width = tiff->imagewidth;
+ image->height = tiff->imagelength;
+ image->comps = tiff->samplesperpixel;
+ image->bits = tiff->bitspersample;
+ image->stride = (image->width * image->comps * image->bits + 7) / 8;
+
+ switch (tiff->photometric)
+ {
+ case 0: /* WhiteIsZero -- inverted */
+ image->colorspace = ctx->gray;
+ break;
+ case 1: /* BlackIsZero */
+ image->colorspace = ctx->gray;
+ break;
+ case 2: /* RGB */
+ image->colorspace = ctx->srgb;
+ break;
+ case 3: /* RGBPal */
+ image->colorspace = ctx->srgb;
+ break;
+ case 5: /* CMYK */
+ image->colorspace = ctx->cmyk;
+ break;
+ case 6: /* YCbCr */
+ /* it's probably a jpeg ... we let jpeg convert to rgb */
+ image->colorspace = ctx->srgb;
+ break;
+ default:
+ return gs_throw1(-1, "unknown photometric: %d", tiff->photometric);
+ }
+
+ switch (tiff->resolutionunit)
+ {
+ case 2:
+ image->xres = tiff->xresolution;
+ image->yres = tiff->yresolution;
+ break;
+ case 3:
+ image->xres = tiff->xresolution * 2.54 + 0.5;
+ image->yres = tiff->yresolution * 2.54 + 0.5;
+
+ break;
+ default:
+ image->xres = 96;
+ image->yres = 96;
+ break;
+ }
+
+ /* Note xres and yres could be 0 even if unit was set. If so default to 96dpi */
+ if (image->xres == 0 || image->yres == 0)
+ {
+ image->xres = 96;
+ image->yres = 96;
+ }
+
+ image->samples = xps_alloc(ctx, image->stride * image->height);
+ if (!image->samples)
+ return gs_throw(-1, "could not allocate image samples");
+
+ memset(image->samples, 0x55, image->stride * image->height);
+
+ wp = image->samples;
+
+ strip = 0;
+ for (row = 0; row < tiff->imagelength; row += tiff->rowsperstrip)
+ {
+ unsigned offset = tiff->stripoffsets[strip];
+ unsigned rlen = tiff->stripbytecounts[strip];
+ unsigned wlen = image->stride * tiff->rowsperstrip;
+ byte *rp = tiff->bp + offset;
+
+ if (wp + wlen > image->samples + image->stride * image->height)
+ wlen = image->samples + image->stride * image->height - wp;
+
+ if (rp + rlen > tiff->ep)
+ return gs_throw(-1, "strip extends beyond the end of the file");
+
+ /* the bits are in un-natural order */
+ if (tiff->fillorder == 2)
+ for (i = 0; i < rlen; i++)
+ rp[i] = bitrev[rp[i]];
+
+ switch (tiff->compression)
+ {
+ case 1:
+ error = xps_decode_tiff_uncompressed(ctx, tiff, rp, rp + rlen, wp, wp + wlen);
+ break;
+ case 2:
+ error = xps_decode_tiff_fax(ctx, tiff, 2, rp, rp + rlen, wp, wp + wlen);
+ break;
+ case 3:
+ error = xps_decode_tiff_fax(ctx, tiff, 3, rp, rp + rlen, wp, wp + wlen);
+ break;
+ case 4:
+ error = xps_decode_tiff_fax(ctx, tiff, 4, rp, rp + rlen, wp, wp + wlen);
+ break;
+ case 5:
+ error = xps_decode_tiff_lzw(ctx, tiff, rp, rp + rlen, wp, wp + wlen);
+ break;
+ case 6:
+ error = gs_throw(-1, "deprecated JPEG in TIFF compression not supported");
+ break;
+ case 7:
+ error = xps_decode_tiff_jpeg(ctx, tiff, rp, rp + rlen, wp, wp + wlen);
+ break;
+ case 8:
+ error = xps_decode_tiff_flate(ctx, tiff, rp, rp + rlen, wp, wp + wlen);
+ break;
+ case 32773:
+ error = xps_decode_tiff_packbits(ctx, tiff, rp, rp + rlen, wp, wp + wlen);
+ break;
+ default:
+ error = gs_throw1(-1, "unknown TIFF compression: %d", tiff->compression);
+ }
+
+ if (error)
+ return gs_rethrow1(error, "could not decode strip %d", row / tiff->rowsperstrip);
+
+ /* scramble the bits back into original order */
+ if (tiff->fillorder == 2)
+ for (i = 0; i < rlen; i++)
+ rp[i] = bitrev[rp[i]];
+
+ wp += image->stride * tiff->rowsperstrip;
+ strip ++;
+ }
+
+ /* Predictor (only for LZW and Flate) */
+ if ((tiff->compression == 5 || tiff->compression == 8) && tiff->predictor == 2)
+ {
+ byte *p = image->samples;
+ for (i = 0; i < image->height; i++)
+ {
+ xps_unpredict_tiff(p, image->width, tiff->samplesperpixel, image->bits);
+ p += image->stride;
+ }
+ }
+
+ /* RGBPal */
+ if (tiff->photometric == 3 && tiff->colormap)
+ {
+ error = xps_expand_colormap(ctx, tiff, image);
+ if (error)
+ return gs_rethrow(error, "could not expand colormap");
+ }
+
+ /* WhiteIsZero .. invert */
+ if (tiff->photometric == 0)
+ {
+ byte *p = image->samples;
+ for (i = 0; i < image->height; i++)
+ {
+ xps_invert_tiff(p, image->width, image->comps, image->bits, tiff->extrasamples);
+ p += image->stride;
+ }
+ }
+
+ /* Premultiplied transparency */
+ if (tiff->extrasamples == 1)
+ {
+ image->hasalpha = 1;
+ }
+
+ /* Non-pre-multiplied transparency */
+ if (tiff->extrasamples == 2)
+ {
+ image->hasalpha = 1;
+ }
+
+ return gs_okay;
+}
+
+static void
+xps_read_tiff_bytes(unsigned char *p, xps_tiff_t *tiff, unsigned ofs, unsigned n)
+{
+ tiff->rp = tiff->bp + ofs;
+ if (tiff->rp > tiff->ep)
+ tiff->rp = tiff->bp;
+
+ while (n--)
+ {
+ *p++ = readbyte(tiff);
+ }
+}
+
+static void
+xps_read_tiff_tag_value(unsigned *p, xps_tiff_t *tiff, unsigned type, unsigned ofs, unsigned n)
+{
+ tiff->rp = tiff->bp + ofs;
+ if (tiff->rp > tiff->ep)
+ tiff->rp = tiff->bp;
+
+ while (n--)
+ {
+ switch (type)
+ {
+ case TRATIONAL:
+ *p = readlong(tiff);
+ *p = *p / readlong(tiff);
+ p ++;
+ break;
+ case TBYTE: *p++ = readbyte(tiff); break;
+ case TSHORT: *p++ = readshort(tiff); break;
+ case TLONG: *p++ = readlong(tiff); break;
+ default: *p++ = 0; break;
+ }
+ }
+}
+
+static int
+xps_read_tiff_tag(xps_context_t *ctx, xps_tiff_t *tiff, unsigned offset)
+{
+ unsigned tag;
+ unsigned type;
+ unsigned count;
+ unsigned value;
+
+ tiff->rp = tiff->bp + offset;
+
+ tag = readshort(tiff);
+ type = readshort(tiff);
+ count = readlong(tiff);
+
+ if ((type == TBYTE && count <= 4) ||
+ (type == TSHORT && count <= 2) ||
+ (type == TLONG && count <= 1))
+ value = tiff->rp - tiff->bp;
+ else
+ value = readlong(tiff);
+
+ switch (tag)
+ {
+ case NewSubfileType:
+ xps_read_tiff_tag_value(&tiff->subfiletype, tiff, type, value, 1);
+ break;
+ case ImageWidth:
+ xps_read_tiff_tag_value(&tiff->imagewidth, tiff, type, value, 1);
+ break;
+ case ImageLength:
+ xps_read_tiff_tag_value(&tiff->imagelength, tiff, type, value, 1);
+ break;
+ case BitsPerSample:
+ xps_read_tiff_tag_value(&tiff->bitspersample, tiff, type, value, 1);
+ break;
+ case Compression:
+ xps_read_tiff_tag_value(&tiff->compression, tiff, type, value, 1);
+ break;
+ case PhotometricInterpretation:
+ xps_read_tiff_tag_value(&tiff->photometric, tiff, type, value, 1);
+ break;
+ case FillOrder:
+ xps_read_tiff_tag_value(&tiff->fillorder, tiff, type, value, 1);
+ break;
+ case SamplesPerPixel:
+ xps_read_tiff_tag_value(&tiff->samplesperpixel, tiff, type, value, 1);
+ break;
+ case RowsPerStrip:
+ xps_read_tiff_tag_value(&tiff->rowsperstrip, tiff, type, value, 1);
+ break;
+ case XResolution:
+ xps_read_tiff_tag_value(&tiff->xresolution, tiff, type, value, 1);
+ break;
+ case YResolution:
+ xps_read_tiff_tag_value(&tiff->yresolution, tiff, type, value, 1);
+ break;
+ case PlanarConfiguration:
+ xps_read_tiff_tag_value(&tiff->planar, tiff, type, value, 1);
+ break;
+ case T4Options:
+ xps_read_tiff_tag_value(&tiff->g3opts, tiff, type, value, 1);
+ break;
+ case T6Options:
+ xps_read_tiff_tag_value(&tiff->g4opts, tiff, type, value, 1);
+ break;
+ case Predictor:
+ xps_read_tiff_tag_value(&tiff->predictor, tiff, type, value, 1);
+ break;
+ case ResolutionUnit:
+ xps_read_tiff_tag_value(&tiff->resolutionunit, tiff, type, value, 1);
+ break;
+ case YCbCrSubSampling:
+ xps_read_tiff_tag_value(tiff->ycbcrsubsamp, tiff, type, value, 2);
+ break;
+ case ExtraSamples:
+ xps_read_tiff_tag_value(&tiff->extrasamples, tiff, type, value, 1);
+ break;
+ case ICCProfile:
+ tiff->profile = xps_alloc(ctx, count);
+ if (!tiff->profile)
+ return gs_throw(-1, "could not allocate embedded icc profile");
+ /* ICC profile data type is set to UNDEFINED.
+ * TBYTE reading not correct in xps_read_tiff_tag_value */
+ xps_read_tiff_bytes(tiff->profile, tiff, value, count);
+ tiff->profilesize = count;
+ break;
+
+ case JPEGTables:
+ tiff->jpegtables = tiff->bp + value;
+ tiff->jpegtableslen = count;
+ break;
+
+ case StripOffsets:
+ tiff->stripoffsets = (unsigned*) xps_alloc(ctx, count * sizeof(unsigned));
+ if (!tiff->stripoffsets)
+ return gs_throw(-1, "could not allocate strip offsets");
+ xps_read_tiff_tag_value(tiff->stripoffsets, tiff, type, value, count);
+ break;
+
+ case StripByteCounts:
+ tiff->stripbytecounts = (unsigned*) xps_alloc(ctx, count * sizeof(unsigned));
+ if (!tiff->stripbytecounts)
+ return gs_throw(-1, "could not allocate strip byte counts");
+ xps_read_tiff_tag_value(tiff->stripbytecounts, tiff, type, value, count);
+ break;
+
+ case ColorMap:
+ tiff->colormap = (unsigned*) xps_alloc(ctx, count * sizeof(unsigned));
+ if (!tiff->colormap)
+ return gs_throw(-1, "could not allocate color map");
+ xps_read_tiff_tag_value(tiff->colormap, tiff, type, value, count);
+ break;
+
+ case TileWidth:
+ case TileLength:
+ case TileOffsets:
+ case TileByteCounts:
+ return gs_throw(-1, "tiled tiffs not supported");
+
+ default:
+ /* printf("unknown tag: %d t=%d n=%d\n", tag, type, count); */
+ break;
+ }
+
+ return gs_okay;
+}
+
+static void
+xps_swap_byte_order(byte *buf, int n)
+{
+ int i, t;
+ for (i = 0; i < n; i++)
+ {
+ t = buf[i * 2 + 0];
+ buf[i * 2 + 0] = buf[i * 2 + 1];
+ buf[i * 2 + 1] = t;
+ }
+}
+
+static int
+xps_decode_tiff_header(xps_context_t *ctx, xps_tiff_t *tiff, byte *buf, int len)
+{
+ unsigned version;
+ unsigned offset;
+ unsigned count;
+ unsigned i;
+ int error;
+
+ memset(tiff, 0, sizeof(xps_tiff_t));
+
+ tiff->bp = buf;
+ tiff->rp = buf;
+ tiff->ep = buf + len;
+
+ /* tag defaults, where applicable */
+ tiff->bitspersample = 1;
+ tiff->compression = 1;
+ tiff->samplesperpixel = 1;
+ tiff->resolutionunit = 2;
+ tiff->rowsperstrip = 0xFFFFFFFF;
+ tiff->fillorder = 1;
+ tiff->planar = 1;
+ tiff->subfiletype = 0;
+ tiff->predictor = 1;
+ tiff->ycbcrsubsamp[0] = 2;
+ tiff->ycbcrsubsamp[1] = 2;
+
+ /*
+ * Read IFH
+ */
+
+ /* get byte order marker */
+ tiff->order = TII;
+ tiff->order = readshort(tiff);
+ if (tiff->order != TII && tiff->order != TMM)
+ return gs_throw(-1, "not a TIFF file, wrong magic marker");
+
+ /* check version */
+ version = readshort(tiff);
+ if (version != 42)
+ return gs_throw(-1, "not a TIFF file, wrong version marker");
+
+ /* get offset of IFD */
+ offset = readlong(tiff);
+
+ /*
+ * Read IFD
+ */
+
+ tiff->rp = tiff->bp + offset;
+
+ count = readshort(tiff);
+
+ offset += 2;
+ for (i = 0; i < count; i++)
+ {
+ error = xps_read_tiff_tag(ctx, tiff, offset);
+ if (error)
+ return gs_rethrow(error, "could not read TIFF header tag");
+ offset += 12;
+ }
+
+ return gs_okay;
+}
+
+int
+xps_decode_tiff(xps_context_t *ctx, byte *buf, int len, xps_image_t *image)
+{
+ int error;
+ xps_tiff_t tiffst;
+ xps_tiff_t *tiff = &tiffst;
+
+ error = xps_decode_tiff_header(ctx, tiff, buf, len);
+ if (error)
+ return gs_rethrow(error, "cannot decode tiff header");
+
+ /*
+ * Decode the image strips
+ */
+
+ if (tiff->rowsperstrip > tiff->imagelength)
+ tiff->rowsperstrip = tiff->imagelength;
+
+ error = xps_decode_tiff_strips(ctx, tiff, image);
+ if (error)
+ return gs_rethrow(error, "could not decode image data");
+
+ /*
+ * Byte swap 16-bit images to big endian if necessary.
+ */
+ if (image->bits == 16)
+ {
+ if (tiff->order == TII)
+ xps_swap_byte_order(image->samples, image->width * image->height * image->comps);
+ }
+
+ /*
+ * Save ICC profile data
+ */
+ image->profile = tiff->profile;
+ image->profilesize = tiff->profilesize;
+
+ /*
+ * Clean up scratch memory
+ */
+
+ if (tiff->colormap) xps_free(ctx, tiff->colormap);
+ if (tiff->stripoffsets) xps_free(ctx, tiff->stripoffsets);
+ if (tiff->stripbytecounts) xps_free(ctx, tiff->stripbytecounts);
+
+ return gs_okay;
+}
+
+int
+xps_tiff_has_alpha(xps_context_t *ctx, byte *buf, int len)
+{
+ int error;
+ xps_tiff_t tiffst;
+ xps_tiff_t *tiff = &tiffst;
+
+ error = xps_decode_tiff_header(ctx, tiff, buf, len);
+ if (error)
+ {
+ gs_catch(error, "cannot decode tiff header");
+ return 0;
+ }
+
+ if (tiff->profile) xps_free(ctx, tiff->profile);
+ if (tiff->colormap) xps_free(ctx, tiff->colormap);
+ if (tiff->stripoffsets) xps_free(ctx, tiff->stripoffsets);
+ if (tiff->stripbytecounts) xps_free(ctx, tiff->stripbytecounts);
+
+ return tiff->extrasamples == 2 || tiff->extrasamples == 1;
+}
diff --git a/xps/xpstile.c b/xps/xpstile.c
new file mode 100644
index 00000000..d7118b52
--- /dev/null
+++ b/xps/xpstile.c
@@ -0,0 +1,399 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* XPS interpreter - tiles for pattern rendering */
+
+#include "ghostxps.h"
+
+/*
+ * Parse a tiling brush (visual and image brushes at this time) common
+ * properties. Use the callback to draw the individual tiles.
+ */
+
+enum { TILE_NONE, TILE_TILE, TILE_FLIP_X, TILE_FLIP_Y, TILE_FLIP_X_Y };
+
+struct tile_closure_s
+{
+ xps_context_t *ctx;
+ char *base_uri;
+ xps_resource_t *dict;
+ xps_item_t *tag;
+ gs_rect viewbox;
+ int tile_mode;
+ void *user;
+ int (*func)(xps_context_t*, char*, xps_resource_t*, xps_item_t*, void*);
+};
+
+static int
+xps_paint_tiling_brush_clipped(struct tile_closure_s *c)
+{
+ xps_context_t *ctx = c->ctx;
+ int code;
+
+ gs_moveto(ctx->pgs, c->viewbox.p.x, c->viewbox.p.y);
+ gs_lineto(ctx->pgs, c->viewbox.p.x, c->viewbox.q.y);
+ gs_lineto(ctx->pgs, c->viewbox.q.x, c->viewbox.q.y);
+ gs_lineto(ctx->pgs, c->viewbox.q.x, c->viewbox.p.y);
+ gs_closepath(ctx->pgs);
+ gs_clip(ctx->pgs);
+ gs_newpath(ctx->pgs);
+
+ code = c->func(c->ctx, c->base_uri, c->dict, c->tag, c->user);
+ if (code < 0)
+ return gs_rethrow(code, "cannot draw clipped tile");
+
+ return 0;
+}
+
+static int
+xps_paint_tiling_brush(const gs_client_color *pcc, gs_state *pgs)
+{
+ const gs_client_pattern *ppat = gs_getpattern(pcc);
+ struct tile_closure_s *c = ppat->client_data;
+ xps_context_t *ctx = c->ctx;
+ gs_state *saved_pgs;
+ int code;
+
+ saved_pgs = ctx->pgs;
+ ctx->pgs = pgs;
+
+ gs_gsave(ctx->pgs);
+ code = xps_paint_tiling_brush_clipped(c);
+ if (code)
+ goto cleanup;
+ gs_grestore(ctx->pgs);
+
+ if (c->tile_mode == TILE_FLIP_X || c->tile_mode == TILE_FLIP_X_Y)
+ {
+ gs_gsave(ctx->pgs);
+ gs_translate(ctx->pgs, c->viewbox.q.x * 2, 0.0);
+ gs_scale(ctx->pgs, -1.0, 1.0);
+ code = xps_paint_tiling_brush_clipped(c);
+ if (code)
+ goto cleanup;
+ gs_grestore(ctx->pgs);
+ }
+
+ if (c->tile_mode == TILE_FLIP_Y || c->tile_mode == TILE_FLIP_X_Y)
+ {
+ gs_gsave(ctx->pgs);
+ gs_translate(ctx->pgs, 0.0, c->viewbox.q.y * 2);
+ gs_scale(ctx->pgs, 1.0, -1.0);
+ code = xps_paint_tiling_brush_clipped(c);
+ if (code)
+ goto cleanup;
+ gs_grestore(ctx->pgs);
+ }
+
+ if (c->tile_mode == TILE_FLIP_X_Y)
+ {
+ gs_gsave(ctx->pgs);
+ gs_translate(ctx->pgs, c->viewbox.q.x * 2, c->viewbox.q.y * 2);
+ gs_scale(ctx->pgs, -1.0, -1.0);
+ code = xps_paint_tiling_brush_clipped(c);
+ if (code)
+ goto cleanup;
+ gs_grestore(ctx->pgs);
+ }
+
+ ctx->pgs = saved_pgs;
+
+ return 0;
+
+cleanup:
+ gs_grestore(ctx->pgs);
+ ctx->pgs = saved_pgs;
+ return gs_rethrow(code, "cannot draw tile");
+}
+
+int
+xps_high_level_pattern(xps_context_t *ctx)
+{
+ gs_matrix m;
+ gs_rect bbox;
+ gs_fixed_rect clip_box;
+ int code;
+ gx_device_color *pdc = gs_currentdevicecolor_inline(ctx->pgs);
+ const gs_client_pattern *ppat = gs_getpattern(&pdc->ccolor);
+ gs_pattern1_instance_t *pinst =
+ (gs_pattern1_instance_t *)gs_currentcolor(ctx->pgs)->pattern;
+
+ code = gx_pattern_cache_add_dummy_entry((gs_imager_state *)ctx->pgs,
+ pinst, ctx->pgs->device->color_info.depth);
+ if (code < 0)
+ return code;
+
+ code = gs_gsave(ctx->pgs);
+ if (code < 0)
+ return code;
+
+ dev_proc(ctx->pgs->device, get_initial_matrix)(ctx->pgs->device, &m);
+ gs_setmatrix(ctx->pgs, &m);
+ code = gs_bbox_transform(&ppat->BBox, &ctm_only(ctx->pgs), &bbox);
+ if (code < 0) {
+ gs_grestore(ctx->pgs);
+ return code;
+ }
+ clip_box.p.x = float2fixed(bbox.p.x);
+ clip_box.p.y = float2fixed(bbox.p.y);
+ clip_box.q.x = float2fixed(bbox.q.x);
+ clip_box.q.y = float2fixed(bbox.q.y);
+ code = gx_clip_to_rectangle(ctx->pgs, &clip_box);
+ if (code < 0) {
+ gs_grestore(ctx->pgs);
+ return code;
+ }
+ code = dev_proc(ctx->pgs->device, pattern_manage)(ctx->pgs->device, pinst->id, pinst,
+ pattern_manage__start_accum);
+ if (code < 0) {
+ gs_grestore(ctx->pgs);
+ return code;
+ }
+
+ code = xps_paint_tiling_brush(&pdc->ccolor, ctx->pgs);
+ if (code) {
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "high level pattern brush function failed");
+ }
+
+ code = gs_grestore(ctx->pgs);
+ if (code < 0)
+ return code;
+
+ code = dev_proc(ctx->pgs->device, pattern_manage)(ctx->pgs->device, gx_no_bitmap_id, NULL,
+ pattern_manage__finish_accum);
+
+ return code;
+}
+
+static int
+xps_remap_pattern(const gs_client_color *pcc, gs_state *pgs)
+{
+ const gs_client_pattern *ppat = gs_getpattern(pcc);
+ struct tile_closure_s *c = ppat->client_data;
+ xps_context_t *ctx = c->ctx;
+ int code;
+
+ /* pgs->device is the newly created pattern accumulator, but we want to test the device
+ * that is 'behind' that, the actual output device, so we use the one from
+ * the saved XPS graphics state.
+ */
+ code = dev_proc(ctx->pgs->device, pattern_manage)(ctx->pgs->device, ppat->uid.id, ppat,
+ pattern_manage__can_accum);
+
+ if (code == 1) {
+ /* Device handles high-level patterns, so return 'remap'.
+ * This closes the internal accumulator device, as we no longer need
+ * it, and the error trickles back up to the PDL client. The client
+ * must then take action to start the device's accumulator, draw the
+ * pattern, close the device's accumulator and generate a cache entry.
+ */
+ return gs_error_Remap_Color;
+ } else {
+ code = xps_paint_tiling_brush(pcc, pgs);
+ if (code)
+ return gs_rethrow(code, "remap pattern brush function failed");
+ return 0;
+ }
+}
+
+int
+xps_parse_tiling_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root,
+ int (*func)(xps_context_t*, char*, xps_resource_t*, xps_item_t*, void*), void *user)
+{
+ xps_item_t *node;
+ int code;
+
+ char *opacity_att;
+ char *transform_att;
+ char *viewbox_att;
+ char *viewport_att;
+ char *tile_mode_att;
+ char *viewbox_units_att;
+ char *viewport_units_att;
+
+ xps_item_t *transform_tag = NULL;
+
+ gs_matrix transform;
+ gs_rect viewbox;
+ gs_rect viewport;
+ float scalex, scaley;
+ int tile_mode;
+
+ opacity_att = xps_att(root, "Opacity");
+ transform_att = xps_att(root, "Transform");
+ viewbox_att = xps_att(root, "Viewbox");
+ viewport_att = xps_att(root, "Viewport");
+ tile_mode_att = xps_att(root, "TileMode");
+ viewbox_units_att = xps_att(root, "ViewboxUnits");
+ viewport_units_att = xps_att(root, "ViewportUnits");
+
+ for (node = xps_down(root); node; node = xps_next(node))
+ {
+ if (!strcmp(xps_tag(node), "ImageBrush.Transform"))
+ transform_tag = xps_down(node);
+ if (!strcmp(xps_tag(node), "VisualBrush.Transform"))
+ transform_tag = xps_down(node);
+ }
+
+ xps_resolve_resource_reference(ctx, dict, &transform_att, &transform_tag, NULL);
+
+ gs_make_identity(&transform);
+ if (transform_att)
+ xps_parse_render_transform(ctx, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(ctx, transform_tag, &transform);
+
+ viewbox.p.x = 0.0; viewbox.p.y = 0.0;
+ viewbox.q.x = 1.0; viewbox.q.y = 1.0;
+ if (viewbox_att)
+ xps_parse_rectangle(ctx, viewbox_att, &viewbox);
+
+ viewport.p.x = 0.0; viewport.p.y = 0.0;
+ viewport.q.x = 1.0; viewport.q.y = 1.0;
+ if (viewport_att)
+ xps_parse_rectangle(ctx, viewport_att, &viewport);
+
+ /* some sanity checks on the viewport/viewbox size */
+ if (fabs(viewport.q.x - viewport.p.x) < 0.01) return 0;
+ if (fabs(viewport.q.y - viewport.p.y) < 0.01) return 0;
+ if (fabs(viewbox.q.x - viewbox.p.x) < 0.01) return 0;
+ if (fabs(viewbox.q.y - viewbox.p.y) < 0.01) return 0;
+
+ scalex = (viewport.q.x - viewport.p.x) / (viewbox.q.x - viewbox.p.x);
+ scaley = (viewport.q.y - viewport.p.y) / (viewbox.q.y - viewbox.p.y);
+
+ tile_mode = TILE_NONE;
+ if (tile_mode_att)
+ {
+ if (!strcmp(tile_mode_att, "None"))
+ tile_mode = TILE_NONE;
+ if (!strcmp(tile_mode_att, "Tile"))
+ tile_mode = TILE_TILE;
+ if (!strcmp(tile_mode_att, "FlipX"))
+ tile_mode = TILE_FLIP_X;
+ if (!strcmp(tile_mode_att, "FlipY"))
+ tile_mode = TILE_FLIP_Y;
+ if (!strcmp(tile_mode_att, "FlipXY"))
+ tile_mode = TILE_FLIP_X_Y;
+ }
+
+ gs_gsave(ctx->pgs);
+
+ code = xps_begin_opacity(ctx, base_uri, dict, opacity_att, NULL);
+ if (code)
+ {
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "cannot create transparency group");
+ }
+
+ /* TODO(tor): check viewport and tiling to see if we can set it to TILE_NONE */
+
+ if (tile_mode != TILE_NONE)
+ {
+ struct tile_closure_s closure;
+
+ gs_client_pattern gspat;
+ gs_client_color gscolor;
+ gs_color_space *cs;
+
+ closure.ctx = ctx;
+ closure.base_uri = base_uri;
+ closure.dict = dict;
+ closure.tag = root;
+ closure.tile_mode = tile_mode;
+ closure.user = user;
+ closure.func = func;
+
+ closure.viewbox.p.x = viewbox.p.x;
+ closure.viewbox.p.y = viewbox.p.y;
+ closure.viewbox.q.x = viewbox.q.x;
+ closure.viewbox.q.y = viewbox.q.y;
+
+ gs_pattern1_init(&gspat);
+ uid_set_UniqueID(&gspat.uid, gs_next_ids(ctx->memory, 1));
+ gspat.PaintType = 1;
+ gspat.TilingType = 1;
+ gspat.PaintProc = xps_remap_pattern;
+ gspat.client_data = &closure;
+
+ gspat.XStep = viewbox.q.x - viewbox.p.x;
+ gspat.YStep = viewbox.q.y - viewbox.p.y;
+ gspat.BBox.p.x = viewbox.p.x;
+ gspat.BBox.p.y = viewbox.p.y;
+ gspat.BBox.q.x = viewbox.q.x;
+ gspat.BBox.q.y = viewbox.q.y;
+
+ if (tile_mode == TILE_FLIP_X || tile_mode == TILE_FLIP_X_Y)
+ {
+ gspat.BBox.q.x += gspat.XStep;
+ gspat.XStep *= 2;
+ }
+
+ if (tile_mode == TILE_FLIP_Y || tile_mode == TILE_FLIP_X_Y)
+ {
+ gspat.BBox.q.y += gspat.YStep;
+ gspat.YStep *= 2;
+ }
+
+ gs_matrix_translate(&transform, viewport.p.x, viewport.p.y, &transform);
+ gs_matrix_scale(&transform, scalex, scaley, &transform);
+ gs_matrix_translate(&transform, -viewbox.p.x, -viewbox.p.y, &transform);
+
+ cs = ctx->srgb;
+ gs_setcolorspace(ctx->pgs, cs);
+ gs_makepattern(&gscolor, &gspat, &transform, ctx->pgs, NULL);
+ gs_setpattern(ctx->pgs, &gscolor);
+
+ xps_fill(ctx);
+
+ /* gs_makepattern increments the pattern count stored in the color
+ * structure. We will discard the color struct (its on the stack)
+ * so we need to decrement the reference before we throw away
+ * the structure.
+ */
+ gs_pattern_reference(&gscolor, -1);
+ }
+ else
+ {
+ xps_clip(ctx);
+
+ gs_concat(ctx->pgs, &transform);
+
+ gs_translate(ctx->pgs, viewport.p.x, viewport.p.y);
+ gs_scale(ctx->pgs, scalex, scaley);
+ gs_translate(ctx->pgs, -viewbox.p.x, -viewbox.p.y);
+
+ gs_moveto(ctx->pgs, viewbox.p.x, viewbox.p.y);
+ gs_lineto(ctx->pgs, viewbox.p.x, viewbox.q.y);
+ gs_lineto(ctx->pgs, viewbox.q.x, viewbox.q.y);
+ gs_lineto(ctx->pgs, viewbox.q.x, viewbox.p.y);
+ gs_closepath(ctx->pgs);
+ gs_clip(ctx->pgs);
+ gs_newpath(ctx->pgs);
+
+ code = func(ctx, base_uri, dict, root, user);
+ if (code < 0)
+ {
+ xps_end_opacity(ctx, base_uri, dict, opacity_att, NULL);
+ gs_grestore(ctx->pgs);
+ return gs_rethrow(code, "cannot draw tile");
+ }
+ }
+
+ xps_end_opacity(ctx, base_uri, dict, opacity_att, NULL);
+
+ gs_grestore(ctx->pgs);
+
+ return 0;
+}
diff --git a/xps/xpstop.c b/xps/xpstop.c
new file mode 100644
index 00000000..13149da6
--- /dev/null
+++ b/xps/xpstop.c
@@ -0,0 +1,576 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* Top-level API implementation of XML Paper Specification */
+
+/* Language wrapper implementation (see pltop.h) */
+
+#include "ghostxps.h"
+
+#include "pltop.h"
+#include "plparse.h" /* for e_ExitLanguage */
+
+#include "gxdevice.h" /* so we can include gxht.h below */
+#include "gxht.h" /* gsht1.h is incomplete, we need storage size of gs_halftone */
+#include "gsht1.h"
+
+int xps_zip_trace = 0;
+int xps_doc_trace = 0;
+
+static int xps_install_halftone(xps_context_t *ctx, gx_device *pdevice);
+
+#define XPS_PARSER_MIN_INPUT_SIZE (8192 * 4)
+
+/*
+ * The XPS interpeter is identical to pl_interp_t.
+ * The XPS interpreter instance is derived from pl_interp_instance_t.
+ */
+
+typedef struct xps_interp_instance_s xps_interp_instance_t;
+
+struct xps_interp_instance_s
+{
+ pl_interp_instance_t pl; /* common part: must be first */
+
+ pl_page_action_t pre_page_action; /* action before page out */
+ void *pre_page_closure; /* closure to call pre_page_action with */
+ pl_page_action_t post_page_action; /* action before page out */
+ void *post_page_closure; /* closure to call post_page_action with */
+
+ xps_context_t *ctx;
+ FILE *scratch_file;
+ char scratch_name[gp_file_name_sizeof];
+};
+
+/* version and build date are not currently used */
+#define XPS_VERSION NULL
+#define XPS_BUILD_DATE NULL
+
+static const pl_interp_characteristics_t *
+xps_imp_characteristics(const pl_interp_implementation_t *pimpl)
+{
+ static pl_interp_characteristics_t xps_characteristics =
+ {
+ "XPS",
+ "PK", /* string to recognize XPS files */
+ "Artifex",
+ XPS_VERSION,
+ XPS_BUILD_DATE,
+ XPS_PARSER_MIN_INPUT_SIZE, /* Minimum input size */
+ };
+ return &xps_characteristics;
+}
+
+static int
+xps_imp_allocate_interp(pl_interp_t **ppinterp,
+ const pl_interp_implementation_t *pimpl,
+ gs_memory_t *pmem)
+{
+ static pl_interp_t interp; /* there's only one interpreter */
+ *ppinterp = &interp;
+ return 0;
+}
+
+/* Do per-instance interpreter allocation/init. No device is set yet */
+static int
+xps_imp_allocate_interp_instance(pl_interp_instance_t **ppinstance,
+ pl_interp_t *pinterp,
+ gs_memory_t *pmem)
+{
+ xps_interp_instance_t *instance;
+ xps_context_t *ctx;
+ gs_state *pgs;
+ int code;
+
+ instance = (xps_interp_instance_t *) gs_alloc_bytes(pmem,
+ sizeof(xps_interp_instance_t), "xps_imp_allocate_interp_instance");
+
+ ctx = (xps_context_t *) gs_alloc_bytes(pmem,
+ sizeof(xps_context_t), "xps_imp_allocate_interp_instance");
+
+ pgs = gs_state_alloc(pmem);
+#ifdef ICCBRANCH
+ gsicc_init_iccmanager(pgs);
+#endif
+ memset(ctx, 0, sizeof(xps_context_t));
+
+ if (!instance || !ctx || !pgs)
+ {
+ if (instance)
+ gs_free_object(pmem, instance, "xps_imp_allocate_interp_instance");
+ if (ctx)
+ gs_free_object(pmem, ctx, "xps_imp_allocate_interp_instance");
+ if (pgs)
+ gs_state_free(pgs);
+ return gs_error_VMerror;
+ }
+
+ ctx->instance = instance;
+ ctx->memory = pmem;
+ ctx->pgs = pgs;
+ /* Declare PDL client support for high level patterns, for the benefit
+ * of pdfwrite and other high-level devices
+ */
+ ctx->pgs->have_pattern_streams = true;
+ ctx->fontdir = NULL;
+ ctx->file = NULL;
+ ctx->zip_count = 0;
+ ctx->zip_table = NULL;
+
+ /* Gray, RGB and CMYK profiles set when color spaces installed in graphics lib */
+ ctx->gray = gs_cspace_new_DeviceGray(ctx->memory);
+ ctx->cmyk = gs_cspace_new_DeviceCMYK(ctx->memory);
+ ctx->srgb = gs_cspace_new_DeviceRGB(ctx->memory);
+ ctx->scrgb = gs_cspace_new_DeviceRGB(ctx->memory); /* This needs a different profile */
+
+ instance->pre_page_action = 0;
+ instance->pre_page_closure = 0;
+ instance->post_page_action = 0;
+ instance->post_page_closure = 0;
+
+ instance->ctx = ctx;
+ instance->scratch_file = NULL;
+ instance->scratch_name[0] = 0;
+
+ ctx->fontdir = gs_font_dir_alloc(ctx->memory);
+ gs_setaligntopixels(ctx->fontdir, 1); /* no subpixels */
+ gs_setgridfittt(ctx->fontdir, 1); /* see gx_ttf_outline in gxttfn.c for values */
+
+ *ppinstance = (pl_interp_instance_t *)instance;
+
+ return 0;
+}
+
+/* Set a client language into an interperter instance */
+static int
+xps_imp_set_client_instance(pl_interp_instance_t *pinstance,
+ pl_interp_instance_t *pclient,
+ pl_interp_instance_clients_t which_client)
+{
+ /* ignore */
+ return 0;
+}
+
+static int
+xps_imp_set_pre_page_action(pl_interp_instance_t *pinstance,
+ pl_page_action_t action, void *closure)
+{
+ xps_interp_instance_t *instance = (xps_interp_instance_t *)pinstance;
+ instance->pre_page_action = action;
+ instance->pre_page_closure = closure;
+ return 0;
+}
+
+static int
+xps_imp_set_post_page_action(pl_interp_instance_t *pinstance,
+ pl_page_action_t action, void *closure)
+{
+ xps_interp_instance_t *instance = (xps_interp_instance_t *)pinstance;
+ instance->post_page_action = action;
+ instance->post_page_closure = closure;
+ return 0;
+}
+
+static int
+xps_imp_set_device(pl_interp_instance_t *pinstance, gx_device *pdevice)
+{
+ xps_interp_instance_t *instance = (xps_interp_instance_t *)pinstance;
+ xps_context_t *ctx = instance->ctx;
+ int code;
+
+ gs_opendevice(pdevice);
+
+#ifdef ICCBRANCH
+ code = gsicc_init_device_profile(ctx->pgs, pdevice);
+ if (code < 0)
+ return code;
+#endif
+
+ code = gs_setdevice_no_erase(ctx->pgs, pdevice);
+ if (code < 0)
+ goto cleanup_setdevice;
+
+ gs_setaccuratecurves(ctx->pgs, true); /* NB not sure */
+ gs_setfilladjust(ctx->pgs, 0, 0);
+
+ /* gsave and grestore (among other places) assume that */
+ /* there are at least 2 gstates on the graphics stack. */
+ /* Ensure that now. */
+ code = gs_gsave(ctx->pgs);
+ if (code < 0)
+ goto cleanup_gsave;
+
+ code = gs_erasepage(ctx->pgs);
+ if (code < 0)
+ goto cleanup_erase;
+
+ code = xps_install_halftone(ctx, pdevice);
+ if (code < 0)
+ goto cleanup_halftone;
+
+ return 0;
+
+cleanup_halftone:
+cleanup_erase:
+ /* undo gsave */
+ gs_grestore_only(ctx->pgs); /* destroys gs_save stack */
+
+cleanup_gsave:
+ /* undo setdevice */
+ gs_nulldevice(ctx->pgs);
+
+cleanup_setdevice:
+ /* nothing to undo */
+ return code;
+}
+
+static int
+xps_imp_get_device_memory(pl_interp_instance_t *pinstance, gs_memory_t **ppmem)
+{
+ /* huh? we do nothing here */
+ return 0;
+}
+
+/* Parse an entire random access file */
+static int
+xps_imp_process_file(pl_interp_instance_t *pinstance, char *filename)
+{
+ xps_interp_instance_t *instance = (xps_interp_instance_t *)pinstance;
+ xps_context_t *ctx = instance->ctx;
+ int code;
+
+ code = xps_process_file(ctx, filename);
+ if (code)
+ gs_catch1(code, "cannot process xps file '%s'", filename);
+
+ return code;
+}
+
+/* Parse a cursor-full of data */
+static int
+xps_imp_process(pl_interp_instance_t *pinstance, stream_cursor_read *cursor)
+{
+ xps_interp_instance_t *instance = (xps_interp_instance_t *)pinstance;
+ xps_context_t *ctx = instance->ctx;
+ int avail, n;
+
+ if (!instance->scratch_file)
+ {
+ instance->scratch_file = gp_open_scratch_file(ctx->memory,
+ "ghostxps-scratch-", instance->scratch_name, "wb");
+ if (!instance->scratch_file)
+ {
+ gs_catch(gs_error_invalidfileaccess, "cannot open scratch file");
+ return e_ExitLanguage;
+ }
+ if_debug1('|', "xps: open scratch file '%s'\n", instance->scratch_name);
+ }
+
+ avail = cursor->limit - cursor->ptr;
+ n = fwrite(cursor->ptr + 1, 1, avail, instance->scratch_file);
+ if (n != avail)
+ {
+ gs_catch(gs_error_invalidfileaccess, "cannot write to scratch file");
+ return e_ExitLanguage;
+ }
+ cursor->ptr = cursor->limit;
+
+ return 0;
+}
+
+/* Skip to end of job.
+ * Return 1 if done, 0 ok but EOJ not found, else negative error code.
+ */
+static int
+xps_imp_flush_to_eoj(pl_interp_instance_t *pinstance, stream_cursor_read *pcursor)
+{
+ /* assume XPS cannot be pjl embedded */
+ pcursor->ptr = pcursor->limit;
+ return 0;
+}
+
+/* Parser action for end-of-file */
+static int
+xps_imp_process_eof(pl_interp_instance_t *pinstance)
+{
+ xps_interp_instance_t *instance = (xps_interp_instance_t *)pinstance;
+ xps_context_t *ctx = instance->ctx;
+ int code;
+
+ if (instance->scratch_file)
+ {
+ if_debug0('|', "xps: executing scratch file\n");
+ fclose(instance->scratch_file);
+ instance->scratch_file = NULL;
+ code = xps_process_file(ctx, instance->scratch_name);
+ unlink(instance->scratch_name);
+ if (code < 0)
+ {
+ gs_catch(code, "cannot process XPS file");
+ return e_ExitLanguage;
+ }
+ }
+
+ return 0;
+}
+
+/* Report any errors after running a job */
+static int
+xps_imp_report_errors(pl_interp_instance_t *pinstance,
+ int code, /* prev termination status */
+ long file_position, /* file position of error, -1 if unknown */
+ bool force_to_cout /* force errors to cout */
+ )
+{
+ return 0;
+}
+
+/* Prepare interp instance for the next "job" */
+static int
+xps_imp_init_job(pl_interp_instance_t *pinstance)
+{
+ xps_interp_instance_t *instance = (xps_interp_instance_t *)pinstance;
+ xps_context_t *ctx = instance->ctx;
+
+ if (gs_debug_c('|'))
+ xps_zip_trace = 1;
+ if (gs_debug_c('|'))
+ xps_doc_trace = 1;
+
+ ctx->font_table = xps_hash_new(ctx);
+ ctx->colorspace_table = xps_hash_new(ctx);
+
+ ctx->start_part = NULL;
+
+ ctx->use_transparency = 1;
+ if (getenv("XPS_DISABLE_TRANSPARENCY"))
+ ctx->use_transparency = 0;
+
+ ctx->opacity_only = 0;
+ ctx->fill_rule = 0;
+
+ return 0;
+}
+
+static void xps_free_key_func(xps_context_t *ctx, void *ptr)
+{
+ xps_free(ctx, ptr);
+}
+
+static void xps_free_font_func(xps_context_t *ctx, void *ptr)
+{
+ xps_free_font(ctx, ptr);
+}
+
+/* Wrap up interp instance after a "job" */
+static int
+xps_imp_dnit_job(pl_interp_instance_t *pinstance)
+{
+ xps_interp_instance_t *instance = (xps_interp_instance_t *)pinstance;
+ xps_context_t *ctx = instance->ctx;
+ int i;
+
+ if (gs_debug_c('|'))
+ xps_debug_fixdocseq(ctx);
+
+ for (i = 0; i < ctx->zip_count; i++)
+ xps_free(ctx, ctx->zip_table[i].name);
+ xps_free(ctx, ctx->zip_table);
+
+ /* TODO: free resources too */
+ xps_hash_free(ctx, ctx->font_table, xps_free_key_func, xps_free_font_func);
+ xps_hash_free(ctx, ctx->colorspace_table, xps_free_key_func, NULL);
+
+ xps_free_fixed_pages(ctx);
+ xps_free_fixed_documents(ctx);
+
+ return 0;
+}
+
+/* Remove a device from an interperter instance */
+static int
+xps_imp_remove_device(pl_interp_instance_t *pinstance)
+{
+ xps_interp_instance_t *instance = (xps_interp_instance_t *)pinstance;
+ xps_context_t *ctx = instance->ctx;
+
+ int code = 0; /* first error status encountered */
+ int error;
+
+ /* return to original gstate */
+ gs_grestore_only(ctx->pgs); /* destroys gs_save stack */
+
+ /* Deselect device */
+ /* NB */
+ error = gs_nulldevice(ctx->pgs);
+ if (code >= 0)
+ code = error;
+
+ return code;
+}
+
+/* Deallocate a interpreter instance */
+static int
+xps_imp_deallocate_interp_instance(pl_interp_instance_t *pinstance)
+{
+ xps_interp_instance_t *instance = (xps_interp_instance_t *)pinstance;
+ xps_context_t *ctx = instance->ctx;
+ gs_memory_t *mem = ctx->memory;
+
+ /* language clients don't free the font cache machinery */
+
+ // free gstate?
+ gs_free_object(mem, ctx, "xps_imp_deallocate_interp_instance");
+ gs_free_object(mem, instance, "xps_imp_deallocate_interp_instance");
+
+ return 0;
+}
+
+/* Do static deinit of XPS interpreter */
+static int
+xps_imp_deallocate_interp(pl_interp_t *pinterp)
+{
+ /* nothing to do */
+ return 0;
+}
+
+/* Parser implementation descriptor */
+const pl_interp_implementation_t xps_implementation =
+{
+ xps_imp_characteristics,
+ xps_imp_allocate_interp,
+ xps_imp_allocate_interp_instance,
+ xps_imp_set_client_instance,
+ xps_imp_set_pre_page_action,
+ xps_imp_set_post_page_action,
+ xps_imp_set_device,
+ xps_imp_init_job,
+ xps_imp_process_file,
+ xps_imp_process,
+ xps_imp_flush_to_eoj,
+ xps_imp_process_eof,
+ xps_imp_report_errors,
+ xps_imp_dnit_job,
+ xps_imp_remove_device,
+ xps_imp_deallocate_interp_instance,
+ xps_imp_deallocate_interp,
+ xps_imp_get_device_memory,
+};
+
+/*
+ * End-of-page function called by XPS parser.
+ */
+int
+xps_show_page(xps_context_t *ctx, int num_copies, int flush)
+{
+ pl_interp_instance_t *pinstance = ctx->instance;
+ xps_interp_instance_t *instance = ctx->instance;
+
+ int code = 0;
+
+ /* do pre-page action */
+ if (instance->pre_page_action)
+ {
+ code = instance->pre_page_action(pinstance, instance->pre_page_closure);
+ if (code < 0)
+ return code;
+ if (code != 0)
+ return 0; /* code > 0 means abort w/no error */
+ }
+
+ /* output the page */
+ code = gs_output_page(ctx->pgs, num_copies, flush);
+ if (code < 0)
+ return code;
+
+ /* do post-page action */
+ if (instance->post_page_action)
+ {
+ code = instance->post_page_action(pinstance, instance->post_page_closure);
+ if (code < 0)
+ return code;
+ }
+
+ return 0;
+}
+
+/*
+ * We need to install a halftone ourselves, this is not
+ * done automatically.
+ */
+
+static float
+identity_transfer(floatp tint, const gx_transfer_map *ignore_map)
+{
+ return tint;
+}
+
+/* The following is a 45 degree spot screen with the spots enumerated
+ * in a defined order. */
+static byte order16x16[256] = {
+ 38, 11, 14, 32, 165, 105, 90, 171, 38, 12, 14, 33, 161, 101, 88, 167,
+ 30, 6, 0, 16, 61, 225, 231, 125, 30, 6, 1, 17, 63, 222, 227, 122,
+ 27, 3, 8, 19, 71, 242, 205, 110, 28, 4, 9, 20, 74, 246, 208, 106,
+ 35, 24, 22, 40, 182, 46, 56, 144, 36, 25, 22, 41, 186, 48, 58, 148,
+ 152, 91, 81, 174, 39, 12, 15, 34, 156, 95, 84, 178, 40, 13, 16, 34,
+ 69, 212, 235, 129, 31, 7, 2, 18, 66, 216, 239, 133, 32, 8, 2, 18,
+ 79, 254, 203, 114, 28, 4, 10, 20, 76, 250, 199, 118, 29, 5, 10, 21,
+ 193, 44, 54, 142, 36, 26, 23, 42, 189, 43, 52, 139, 37, 26, 24, 42,
+ 39, 12, 15, 33, 159, 99, 87, 169, 38, 11, 14, 33, 163, 103, 89, 172,
+ 31, 7, 1, 17, 65, 220, 229, 123, 30, 6, 1, 17, 62, 223, 233, 127,
+ 28, 4, 9, 20, 75, 248, 210, 108, 27, 3, 9, 19, 72, 244, 206, 112,
+ 36, 25, 23, 41, 188, 49, 60, 150, 35, 25, 22, 41, 184, 47, 57, 146,
+ 157, 97, 85, 180, 40, 13, 16, 35, 154, 93, 83, 176, 39, 13, 15, 34,
+ 67, 218, 240, 135, 32, 8, 3, 19, 70, 214, 237, 131, 31, 7, 2, 18,
+ 78, 252, 197, 120, 29, 5, 11, 21, 80, 255, 201, 116, 29, 5, 10, 21,
+ 191, 43, 51, 137, 37, 27, 24, 43, 195, 44, 53, 140, 37, 26, 23, 42
+};
+
+#define source_phase_x 4
+#define source_phase_y 0
+
+static int
+xps_install_halftone(xps_context_t *ctx, gx_device *pdevice)
+{
+ gs_halftone ht;
+ gs_string thresh;
+ int code;
+
+ int width = 16;
+ int height = 16;
+ thresh.data = order16x16;
+ thresh.size = width * height;
+
+ if (gx_device_must_halftone(pdevice))
+ {
+ ht.type = ht_type_threshold;
+ ht.params.threshold.width = width;
+ ht.params.threshold.height = height;
+ ht.params.threshold.thresholds.data = thresh.data;
+ ht.params.threshold.thresholds.size = thresh.size;
+ ht.params.threshold.transfer = 0;
+ ht.params.threshold.transfer_closure.proc = 0;
+
+ gs_settransfer(ctx->pgs, identity_transfer);
+
+ code = gs_sethalftone(ctx->pgs, &ht);
+ if (code < 0)
+ return gs_throw(code, "could not install halftone");
+
+ code = gs_sethalftonephase(ctx->pgs, 0, 0);
+ if (code < 0)
+ return gs_throw(code, "could not set halftone phase");
+ }
+
+ return 0;
+}
diff --git a/xps/xpsutf.c b/xps/xpsutf.c
new file mode 100644
index 00000000..3e1c05c9
--- /dev/null
+++ b/xps/xpsutf.c
@@ -0,0 +1,69 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* XPS interpreter - unicode text functions */
+
+#include "ghostxps.h"
+
+/*
+ * http://tools.ietf.org/html/rfc3629
+ */
+
+int
+xps_utf8_to_ucs(int *p, const char *ss, int n)
+{
+ unsigned char *s = (unsigned char *)ss;
+
+ if (s == NULL)
+ goto bad;
+
+ if ((s[0] & 0x80) == 0)
+ {
+ *p = (s[0] & 0x7f);
+ return 1;
+ }
+
+ if ((s[0] & 0xe0) == 0xc0)
+ {
+ if (n < 2 || s[1] < 0x80)
+ goto bad;
+ *p = (s[0] & 0x1f) << 6;
+ *p |= (s[1] & 0x3f);
+ return 2;
+ }
+
+ if ((s[0] & 0xf0) == 0xe0)
+ {
+ if (n < 3 || s[1] < 0x80 || s[2] < 0x80)
+ goto bad;
+ *p = (s[0] & 0x0f) << 12;
+ *p |= (s[1] & 0x3f) << 6;
+ *p |= (s[2] & 0x3f);
+ return 3;
+ }
+
+ if ((s[0] & 0xf8) == 0xf0)
+ {
+ if (n < 4 || s[1] < 0x80 || s[2] < 0x80 || s[3] < 0x80)
+ goto bad;
+ *p = (s[0] & 0x07) << 18;
+ *p |= (s[1] & 0x3f) << 12;
+ *p |= (s[2] & 0x3f) << 6;
+ *p |= (s[3] & 0x3f);
+ return 4;
+ }
+
+bad:
+ *p = 0x80;
+ return 1;
+}
diff --git a/xps/xpsvisual.c b/xps/xpsvisual.c
new file mode 100644
index 00000000..f23ef991
--- /dev/null
+++ b/xps/xpsvisual.c
@@ -0,0 +1,62 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* XPS interpreter - visual brush functions */
+
+#include "ghostxps.h"
+
+enum { TILE_NONE, TILE_TILE, TILE_FLIP_X, TILE_FLIP_Y, TILE_FLIP_X_Y };
+
+struct userdata
+{
+ xps_context_t *ctx;
+ xps_resource_t *dict;
+ xps_item_t *visual_tag;
+};
+
+static int
+xps_paint_visual_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root, void *visual_tag)
+{
+ return xps_parse_element(ctx, base_uri, dict, (xps_item_t *)visual_tag);
+}
+
+int
+xps_parse_visual_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root)
+{
+ xps_item_t *node;
+ int code;
+
+ char *visual_uri;
+ char *visual_att;
+ xps_item_t *visual_tag = NULL;
+
+ visual_att = xps_att(root, "Visual");
+
+ for (node = xps_down(root); node; node = xps_next(node))
+ {
+ if (!strcmp(xps_tag(node), "VisualBrush.Visual"))
+ visual_tag = xps_down(node);
+ }
+
+ visual_uri = base_uri;
+ xps_resolve_resource_reference(ctx, dict, &visual_att, &visual_tag, &visual_uri);
+
+ if (visual_tag)
+ {
+ code = xps_parse_tiling_brush(ctx, visual_uri, dict, root, xps_paint_visual_brush, visual_tag);
+ if (code)
+ return gs_rethrow(code, "cannot parse tiling brush");
+ }
+
+ return 0;
+}
diff --git a/xps/xpsxml.c b/xps/xpsxml.c
new file mode 100644
index 00000000..ce93bcfe
--- /dev/null
+++ b/xps/xpsxml.c
@@ -0,0 +1,353 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* Simple XML document object model on top of Expat. */
+
+#include "ghostxps.h"
+
+#include <expat.h>
+
+#define XMLBUFLEN 4096
+
+#define NS_XPS "http://schemas.microsoft.com/xps/2005/06"
+#define NS_MC "http://schemas.openxmlformats.org/markup-compatibility/2006"
+
+typedef struct xps_parser_s xps_parser_t;
+
+struct xps_parser_s
+{
+ xps_context_t *ctx;
+ xps_item_t *root;
+ xps_item_t *head;
+ char *error;
+ int compat;
+ char *base; /* base of relative URIs */
+};
+
+struct xps_item_s
+{
+ char *name;
+ char **atts;
+ xps_item_t *up;
+ xps_item_t *down;
+ xps_item_t *next;
+};
+
+static char *
+skip_namespace(char *s)
+{
+ char *p = strchr(s, ' ');
+ if (p)
+ return p + 1;
+ return s;
+}
+
+static void
+on_open_tag(void *zp, char *ns_name, char **atts)
+{
+ xps_parser_t *parser = zp;
+ xps_context_t *ctx = parser->ctx;
+ xps_item_t *item;
+ xps_item_t *tail;
+ int namelen;
+ int attslen;
+ int textlen;
+ char *name, *p;
+ int i;
+
+ if (parser->error)
+ return;
+
+ /* check namespace */
+
+ name = NULL;
+
+ p = strstr(ns_name, NS_XPS);
+ if (p == ns_name)
+ {
+ name = strchr(ns_name, ' ') + 1;
+ }
+
+ p = strstr(ns_name, NS_MC);
+ if (p == ns_name)
+ {
+ name = strchr(ns_name, ' ') + 1;
+ parser->compat = 1;
+ }
+
+ if (!name)
+ {
+ dprintf1("unknown namespace: %s\n", ns_name);
+ name = ns_name;
+ }
+
+ /* count size to alloc */
+
+ namelen = strlen(name) + 1; /* zero terminated */
+ attslen = sizeof(char*); /* with space for sentinel */
+ textlen = 0;
+ for (i = 0; atts[i]; i++)
+ {
+ attslen += sizeof(char*);
+ if ((i & 1) == 0)
+ textlen += strlen(skip_namespace(atts[i])) + 1;
+ else
+ textlen += strlen(atts[i]) + 1;
+ }
+
+ item = xps_alloc(ctx, sizeof(xps_item_t) + attslen + namelen + textlen);
+ if (!item)
+ {
+ parser->error = "out of memory";
+ }
+
+ /* copy strings to new memory */
+
+ item->atts = (char**) (((char*)item) + sizeof(xps_item_t));
+ item->name = ((char*)item) + sizeof(xps_item_t) + attslen;
+ p = ((char*)item) + sizeof(xps_item_t) + attslen + namelen;
+
+ strcpy(item->name, name);
+ for (i = 0; atts[i]; i++)
+ {
+ item->atts[i] = p;
+ if ((i & 1) == 0)
+ strcpy(item->atts[i], skip_namespace(atts[i]));
+ else
+ strcpy(item->atts[i], atts[i]);
+ p += strlen(p) + 1;
+ }
+
+ item->atts[i] = 0;
+
+ /* link item into tree */
+
+ item->up = parser->head;
+ item->down = NULL;
+ item->next = NULL;
+
+ if (!parser->head)
+ {
+ parser->root = item;
+ parser->head = item;
+ return;
+ }
+
+ if (!parser->head->down)
+ {
+ parser->head->down = item;
+ parser->head = item;
+ return;
+ }
+
+ tail = parser->head->down;
+ while (tail->next)
+ tail = tail->next;
+ tail->next = item;
+ parser->head = item;
+}
+
+static void
+on_close_tag(void *zp, char *name)
+{
+ xps_parser_t *parser = zp;
+
+ if (parser->error)
+ return;
+
+ if (parser->head)
+ parser->head = parser->head->up;
+}
+
+static inline int
+is_xml_space(int c)
+{
+ return c == ' ' || c == '\t' || c == '\r' || c == '\n';
+}
+
+static void
+on_text(void *zp, char *buf, int len)
+{
+ xps_parser_t *parser = zp;
+ xps_context_t *ctx = parser->ctx;
+ char *atts[3];
+ int i;
+
+ if (parser->error)
+ return;
+
+ for (i = 0; i < len; i++)
+ {
+ if (!is_xml_space(buf[i]))
+ {
+ char *tmp = xps_alloc(ctx, len + 1);
+ if (!tmp)
+ {
+ parser->error = "out of memory";
+ return;
+ }
+
+ atts[0] = "";
+ atts[1] = tmp;
+ atts[2] = NULL;
+
+ memcpy(tmp, buf, len);
+ tmp[len] = 0;
+ on_open_tag(zp, "", atts);
+ on_close_tag(zp, "");
+ xps_free(ctx, tmp);
+ return;
+ }
+ }
+}
+
+static xps_item_t *
+xps_process_compatibility(xps_context_t *ctx, xps_item_t *root)
+{
+ gs_warn("XPS document uses markup compatibility tags");
+ return root;
+}
+
+xps_item_t *
+xps_parse_xml(xps_context_t *ctx, byte *buf, int len)
+{
+ xps_parser_t parser;
+ XML_Parser xp;
+ int code;
+
+ parser.ctx = ctx;
+ parser.root = NULL;
+ parser.head = NULL;
+ parser.error = NULL;
+ parser.compat = 0;
+
+ xp = XML_ParserCreateNS(NULL, ' ');
+ if (!xp)
+ {
+ gs_throw(-1, "xml error: could not create expat parser");
+ return NULL;
+ }
+
+ XML_SetUserData(xp, &parser);
+ XML_SetParamEntityParsing(xp, XML_PARAM_ENTITY_PARSING_NEVER);
+ XML_SetStartElementHandler(xp, (XML_StartElementHandler)on_open_tag);
+ XML_SetEndElementHandler(xp, (XML_EndElementHandler)on_close_tag);
+ XML_SetCharacterDataHandler(xp, (XML_CharacterDataHandler)on_text);
+
+ code = XML_Parse(xp, (char*)buf, len, 1);
+ if (code == 0)
+ {
+ if (parser.root)
+ xps_free_item(ctx, parser.root);
+ XML_ParserFree(xp);
+ gs_throw1(-1, "xml error: %s", XML_ErrorString(XML_GetErrorCode(xp)));
+ return NULL;
+ }
+
+ XML_ParserFree(xp);
+
+ if (parser.compat)
+ xps_process_compatibility(ctx, parser.root);
+
+ return parser.root;
+}
+
+xps_item_t *
+xps_next(xps_item_t *item)
+{
+ return item->next;
+}
+
+xps_item_t *
+xps_down(xps_item_t *item)
+{
+ return item->down;
+}
+
+char *
+xps_tag(xps_item_t *item)
+{
+ return item->name;
+}
+
+char *
+xps_att(xps_item_t *item, const char *att)
+{
+ int i;
+ for (i = 0; item->atts[i]; i += 2)
+ if (!strcmp(item->atts[i], att))
+ return item->atts[i + 1];
+ return NULL;
+}
+
+void
+xps_free_item(xps_context_t *ctx, xps_item_t *item)
+{
+ xps_item_t *next;
+ while (item)
+ {
+ next = item->next;
+ if (item->down)
+ xps_free_item(ctx, item->down);
+ xps_free(ctx, item);
+ item = next;
+ }
+}
+
+static void indent(int n)
+{
+ while (n--)
+ printf(" ");
+}
+
+static void
+xps_debug_item_imp(xps_item_t *item, int level, int loop)
+{
+ int i;
+
+ while (item)
+ {
+ indent(level);
+
+ if (strlen(item->name) == 0)
+ printf("%s\n", item->atts[1]);
+ else
+ {
+ printf("<%s", item->name);
+
+ for (i = 0; item->atts[i]; i += 2)
+ printf(" %s=\"%s\"", item->atts[i], item->atts[i+1]);
+
+ if (item->down)
+ {
+ printf(">\n");
+ xps_debug_item_imp(item->down, level + 1, 1);
+ indent(level);
+ printf("</%s>\n", item->name);
+ }
+ else
+ printf(" />\n");
+ }
+
+ item = item->next;
+
+ if (!loop)
+ return;
+ }
+}
+
+void
+xps_debug_item(xps_item_t *item, int level)
+{
+ xps_debug_item_imp(item, level, 0);
+}
diff --git a/xps/xpszip.c b/xps/xpszip.c
new file mode 100644
index 00000000..1aac7349
--- /dev/null
+++ b/xps/xpszip.c
@@ -0,0 +1,568 @@
+/* Copyright (C) 2006-2010 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* XPS interpreter - zip container parsing */
+
+#include "ghostxps.h"
+
+static int isfile(char *path)
+{
+ FILE *file = fopen(path, "rb");
+ if (file)
+ {
+ fclose(file);
+ return 1;
+ }
+ return 0;
+}
+
+static inline int getshort(FILE *file)
+{
+ int a = getc(file);
+ int b = getc(file);
+ return a | (b << 8);
+}
+
+static inline int getlong(FILE *file)
+{
+ int a = getc(file);
+ int b = getc(file);
+ int c = getc(file);
+ int d = getc(file);
+ return a | (b << 8) | (c << 16) | (d << 24);
+}
+
+static void *
+xps_zip_alloc_items(xps_context_t *ctx, int items, int size)
+{
+ return xps_alloc(ctx, items * size);
+}
+
+static void
+xps_zip_free(xps_context_t *ctx, void *ptr)
+{
+ xps_free(ctx, ptr);
+}
+
+static int
+xps_compare_entries(const void *a0, const void *b0)
+{
+ xps_entry_t *a = (xps_entry_t*) a0;
+ xps_entry_t *b = (xps_entry_t*) b0;
+ return xps_strcasecmp(a->name, b->name);
+}
+
+static xps_entry_t *
+xps_find_zip_entry(xps_context_t *ctx, char *name)
+{
+ int l = 0;
+ int r = ctx->zip_count - 1;
+ while (l <= r)
+ {
+ int m = (l + r) >> 1;
+ int c = xps_strcasecmp(name, ctx->zip_table[m].name);
+ if (c < 0)
+ r = m - 1;
+ else if (c > 0)
+ l = m + 1;
+ else
+ return &ctx->zip_table[m];
+ }
+ return NULL;
+}
+
+/*
+ * Inflate the data in a zip entry.
+ */
+
+static int
+xps_read_zip_entry(xps_context_t *ctx, xps_entry_t *ent, unsigned char *outbuf)
+{
+ z_stream stream;
+ unsigned char *inbuf;
+ int sig;
+ int version, general, method;
+ int namelength, extralength;
+ int code;
+
+ if_debug1('|', "zip: inflating entry '%s'\n", ent->name);
+
+ fseek(ctx->file, ent->offset, 0);
+
+ sig = getlong(ctx->file);
+ if (sig != ZIP_LOCAL_FILE_SIG)
+ return gs_throw1(-1, "wrong zip local file signature (0x%x)", sig);
+
+ version = getshort(ctx->file);
+ general = getshort(ctx->file);
+ method = getshort(ctx->file);
+ (void) getshort(ctx->file); /* file time */
+ (void) getshort(ctx->file); /* file date */
+ (void) getlong(ctx->file); /* crc-32 */
+ (void) getlong(ctx->file); /* csize */
+ (void) getlong(ctx->file); /* usize */
+ namelength = getshort(ctx->file);
+ extralength = getshort(ctx->file);
+
+ fseek(ctx->file, namelength + extralength, 1);
+
+ if (method == 0)
+ {
+ fread(outbuf, 1, ent->usize, ctx->file);
+ }
+ else if (method == 8)
+ {
+ inbuf = xps_alloc(ctx, ent->csize);
+
+ fread(inbuf, 1, ent->csize, ctx->file);
+
+ memset(&stream, 0, sizeof(z_stream));
+ stream.zalloc = (alloc_func) xps_zip_alloc_items;
+ stream.zfree = (free_func) xps_zip_free;
+ stream.opaque = ctx;
+ stream.next_in = inbuf;
+ stream.avail_in = ent->csize;
+ stream.next_out = outbuf;
+ stream.avail_out = ent->usize;
+
+ code = inflateInit2(&stream, -15);
+ if (code != Z_OK)
+ return gs_throw1(-1, "zlib inflateInit2 error: %s", stream.msg);
+ code = inflate(&stream, Z_FINISH);
+ if (code != Z_STREAM_END)
+ {
+ inflateEnd(&stream);
+ return gs_throw1(-1, "zlib inflate error: %s", stream.msg);
+ }
+ code = inflateEnd(&stream);
+ if (code != Z_OK)
+ return gs_throw1(-1, "zlib inflateEnd error: %s", stream.msg);
+
+ xps_free(ctx, inbuf);
+ }
+ else
+ {
+ return gs_throw1(-1, "unknown compression method (%d)", method);
+ }
+
+ return gs_okay;
+}
+
+/*
+ * Read the central directory in a zip file.
+ */
+
+static int
+xps_read_zip_dir(xps_context_t *ctx, int start_offset)
+{
+ int sig;
+ int offset, count;
+ int namesize, metasize, commentsize;
+ int i;
+
+ fseek(ctx->file, start_offset, 0);
+
+ sig = getlong(ctx->file);
+ if (sig != ZIP_END_OF_CENTRAL_DIRECTORY_SIG)
+ return gs_throw1(-1, "wrong zip end of central directory signature (0x%x)", sig);
+
+ (void) getshort(ctx->file); /* this disk */
+ (void) getshort(ctx->file); /* start disk */
+ (void) getshort(ctx->file); /* entries in this disk */
+ count = getshort(ctx->file); /* entries in central directory disk */
+ (void) getlong(ctx->file); /* size of central directory */
+ offset = getlong(ctx->file); /* offset to central directory */
+
+ ctx->zip_count = count;
+ ctx->zip_table = xps_alloc(ctx, sizeof(xps_entry_t) * count);
+ if (!ctx->zip_table)
+ return gs_throw(-1, "cannot allocate zip entry table");
+
+ memset(ctx->zip_table, 0, sizeof(xps_entry_t) * count);
+
+ fseek(ctx->file, offset, 0);
+
+ for (i = 0; i < count; i++)
+ {
+ sig = getlong(ctx->file);
+ if (sig != ZIP_CENTRAL_DIRECTORY_SIG)
+ return gs_throw1(-1, "wrong zip central directory signature (0x%x)", sig);
+
+ (void) getshort(ctx->file); /* version made by */
+ (void) getshort(ctx->file); /* version to extract */
+ (void) getshort(ctx->file); /* general */
+ (void) getshort(ctx->file); /* method */
+ (void) getshort(ctx->file); /* last mod file time */
+ (void) getshort(ctx->file); /* last mod file date */
+ (void) getlong(ctx->file); /* crc-32 */
+ ctx->zip_table[i].csize = getlong(ctx->file);
+ ctx->zip_table[i].usize = getlong(ctx->file);
+ namesize = getshort(ctx->file);
+ metasize = getshort(ctx->file);
+ commentsize = getshort(ctx->file);
+ (void) getshort(ctx->file); /* disk number start */
+ (void) getshort(ctx->file); /* int file atts */
+ (void) getlong(ctx->file); /* ext file atts */
+ ctx->zip_table[i].offset = getlong(ctx->file);
+
+ ctx->zip_table[i].name = xps_alloc(ctx, namesize + 1);
+ if (!ctx->zip_table[i].name)
+ return gs_throw(-1, "cannot allocate zip entry name");
+
+ fread(ctx->zip_table[i].name, 1, namesize, ctx->file);
+ ctx->zip_table[i].name[namesize] = 0;
+
+ fseek(ctx->file, metasize, 1);
+ fseek(ctx->file, commentsize, 1);
+ }
+
+ qsort(ctx->zip_table, count, sizeof(xps_entry_t), xps_compare_entries);
+
+ for (i = 0; i < ctx->zip_count; i++)
+ {
+ if_debug3('|', "zip entry '%s' csize=%d usize=%d\n",
+ ctx->zip_table[i].name,
+ ctx->zip_table[i].csize,
+ ctx->zip_table[i].usize);
+ }
+
+ return gs_okay;
+}
+
+static int
+xps_find_and_read_zip_dir(xps_context_t *ctx)
+{
+ int filesize, back, maxback;
+ int i, n;
+ char buf[512];
+
+ fseek(ctx->file, 0, SEEK_END);
+ filesize = ftell(ctx->file);
+
+ maxback = MIN(filesize, 0xFFFF + sizeof buf);
+ back = MIN(maxback, sizeof buf);
+
+ while (back < maxback)
+ {
+ fseek(ctx->file, filesize - back, 0);
+
+ n = fread(buf, 1, sizeof buf, ctx->file);
+ if (n < 0)
+ return gs_throw(-1, "cannot read end of central directory");
+
+ for (i = n - 4; i > 0; i--)
+ if (!memcmp(buf + i, "PK\5\6", 4))
+ return xps_read_zip_dir(ctx, filesize - back + i);
+
+ back += sizeof buf - 4;
+ }
+
+ return gs_throw(-1, "cannot find end of central directory");
+}
+
+/*
+ * Read and interleave split parts from a ZIP file.
+ */
+
+static xps_part_t *
+xps_read_zip_part(xps_context_t *ctx, char *partname)
+{
+ char buf[2048];
+ xps_entry_t *ent;
+ xps_part_t *part;
+ int count, size, offset, i;
+ char *name;
+
+ name = partname;
+ if (name[0] == '/')
+ name ++;
+
+ /* All in one piece */
+ ent = xps_find_zip_entry(ctx, name);
+ if (ent)
+ {
+ part = xps_new_part(ctx, partname, ent->usize);
+ xps_read_zip_entry(ctx, ent, part->data);
+ return part;
+ }
+
+ /* Count the number of pieces and their total size */
+ count = 0;
+ size = 0;
+ while (1)
+ {
+ sprintf(buf, "%s/[%d].piece", name, count);
+ ent = xps_find_zip_entry(ctx, buf);
+ if (!ent)
+ {
+ sprintf(buf, "%s/[%d].last.piece", name, count);
+ ent = xps_find_zip_entry(ctx, buf);
+ }
+ if (!ent)
+ break;
+ count ++;
+ size += ent->usize;
+ }
+
+ /* Inflate the pieces */
+ if (count)
+ {
+ part = xps_new_part(ctx, partname, size);
+ offset = 0;
+ for (i = 0; i < count; i++)
+ {
+ if (i < count - 1)
+ sprintf(buf, "%s/[%d].piece", name, i);
+ else
+ sprintf(buf, "%s/[%d].last.piece", name, i);
+ ent = xps_find_zip_entry(ctx, buf);
+ xps_read_zip_entry(ctx, ent, part->data + offset);
+ offset += ent->usize;
+ }
+ return part;
+ }
+
+ return NULL;
+}
+
+/*
+ * Read and interleave split parts from files in the directory.
+ */
+
+static xps_part_t *
+xps_read_dir_part(xps_context_t *ctx, char *name)
+{
+ char buf[2048];
+ xps_part_t *part;
+ FILE *file;
+ int count, size, offset, i, n;
+
+ xps_strlcpy(buf, ctx->directory, sizeof buf);
+ xps_strlcat(buf, name, sizeof buf);
+
+ /* All in one piece */
+ file = fopen(buf, "rb");
+ if (file)
+ {
+ fseek(file, 0, SEEK_END);
+ size = ftell(file);
+ fseek(file, 0, SEEK_SET);
+ part = xps_new_part(ctx, name, size);
+ fread(part->data, 1, size, file);
+ fclose(file);
+ return part;
+ }
+
+ /* Count the number of pieces and their total size */
+ count = 0;
+ size = 0;
+ while (1)
+ {
+ sprintf(buf, "%s%s/[%d].piece", ctx->directory, name, count);
+ file = fopen(buf, "rb");
+ if (!file)
+ {
+ sprintf(buf, "%s%s/[%d].last.piece", ctx->directory, name, count);
+ file = fopen(buf, "rb");
+ }
+ if (!file)
+ break;
+ count ++;
+ fseek(file, 0, SEEK_END);
+ size += ftell(file);
+ fclose(file);
+ }
+
+ /* Inflate the pieces */
+ if (count)
+ {
+ part = xps_new_part(ctx, name, size);
+ offset = 0;
+ for (i = 0; i < count; i++)
+ {
+ if (i < count - 1)
+ sprintf(buf, "%s%s/[%d].piece", ctx->directory, name, i);
+ else
+ sprintf(buf, "%s%s/[%d].last.piece", ctx->directory, name, i);
+ file = fopen(buf, "rb");
+ n = fread(part->data + offset, 1, size - offset, file);
+ offset += n;
+ fclose(file);
+ }
+ return part;
+ }
+
+ return NULL;
+}
+
+xps_part_t *
+xps_read_part(xps_context_t *ctx, char *partname)
+{
+ if (ctx->directory)
+ return xps_read_dir_part(ctx, partname);
+ return xps_read_zip_part(ctx, partname);
+}
+
+/*
+ * Read and process the XPS document.
+ */
+
+static int
+xps_read_and_process_metadata_part(xps_context_t *ctx, char *name)
+{
+ xps_part_t *part;
+ int code;
+
+ part = xps_read_part(ctx, name);
+ if (!part)
+ return gs_rethrow1(-1, "cannot read zip part '%s'", name);
+
+ code = xps_parse_metadata(ctx, part);
+ if (code)
+ return gs_rethrow1(code, "cannot process metadata part '%s'", name);
+
+ xps_free_part(ctx, part);
+
+ return gs_okay;
+}
+
+static int
+xps_read_and_process_page_part(xps_context_t *ctx, char *name)
+{
+ xps_part_t *part;
+ int code;
+
+ part = xps_read_part(ctx, name);
+ if (!part)
+ return gs_rethrow1(-1, "cannot read zip part '%s'", name);
+
+ code = xps_parse_fixed_page(ctx, part);
+ if (code)
+ return gs_rethrow1(code, "cannot parse fixed page part '%s'", name);
+
+ xps_free_part(ctx, part);
+
+ return gs_okay;
+}
+
+/*
+ * Called by xpstop.c
+ */
+
+int
+xps_process_file(xps_context_t *ctx, char *filename)
+{
+ char buf[2048];
+ xps_document_t *doc;
+ xps_page_t *page;
+ int code;
+ char *p;
+
+ ctx->file = fopen(filename, "rb");
+ if (!ctx->file)
+ return gs_throw1(-1, "cannot open file: '%s'", filename);
+
+ if (strstr(filename, ".fpage"))
+ {
+ xps_part_t *part;
+ int size;
+
+ if_debug0('|', "zip: single page mode\n");
+ xps_strlcpy(buf, filename, sizeof buf);
+ while (1)
+ {
+ p = strrchr(buf, '/');
+ if (!p)
+ p = strrchr(buf, '\\');
+ if (!p)
+ break;
+ xps_strlcpy(p, "/_rels/.rels", buf + sizeof buf - p);
+ if_debug1('|', "zip: testing if '%s' exists\n", buf);
+ if (isfile(buf))
+ {
+ *p = 0;
+ ctx->directory = xps_strdup(ctx, buf);
+ if_debug1('|', "zip: using '%s' as root directory\n", ctx->directory);
+ break;
+ }
+ *p = 0;
+ }
+ if (!ctx->directory)
+ {
+ if_debug0('|', "zip: no /_rels/.rels found; assuming absolute paths\n");
+ ctx->directory = xps_strdup(ctx, "");
+ }
+
+ fseek(ctx->file, 0, SEEK_END);
+ size = ftell(ctx->file);
+ fseek(ctx->file, 0, SEEK_SET);
+ part = xps_new_part(ctx, filename, size);
+ fread(part->data, 1, size, ctx->file);
+
+ code = xps_parse_fixed_page(ctx, part);
+ if (code)
+ return gs_rethrow1(code, "cannot parse fixed page part '%s'", part->name);
+
+ xps_free_part(ctx, part);
+ return gs_okay;
+ }
+
+ if (strstr(filename, "/_rels/.rels") || strstr(filename, "\\_rels\\.rels"))
+ {
+ xps_strlcpy(buf, filename, sizeof buf);
+ p = strstr(buf, "/_rels/.rels");
+ if (!p)
+ p = strstr(buf, "\\_rels\\.rels");
+ *p = 0;
+ ctx->directory = xps_strdup(ctx, buf);
+ if_debug1('|', "zip: using '%s' as root directory\n", ctx->directory);
+ }
+ else
+ {
+ code = xps_find_and_read_zip_dir(ctx);
+ if (code < 0)
+ return gs_rethrow(code, "cannot read zip central directory");
+ }
+
+ code = xps_read_and_process_metadata_part(ctx, "/_rels/.rels");
+ if (code)
+ return gs_rethrow(code, "cannot process root relationship part");
+
+ if (!ctx->start_part)
+ return gs_throw(-1, "cannot find fixed document sequence start part");
+
+ code = xps_read_and_process_metadata_part(ctx, ctx->start_part);
+ if (code)
+ return gs_rethrow(code, "cannot process FixedDocumentSequence part");
+
+ for (doc = ctx->first_fixdoc; doc; doc = doc->next)
+ {
+ code = xps_read_and_process_metadata_part(ctx, doc->name);
+ if (code)
+ return gs_rethrow(code, "cannot process FixedDocument part");
+ }
+
+ for (page = ctx->first_page; page; page = page->next)
+ {
+ code = xps_read_and_process_page_part(ctx, page->name);
+ if (code)
+ return gs_rethrow(code, "cannot process FixedPage part");
+ }
+
+ if (ctx->directory)
+ xps_free(ctx, ctx->directory);
+ if (ctx->file)
+ fclose(ctx->file);
+
+ return gs_okay;
+}