diff options
-rw-r--r-- | xps/ghostxps.h | 412 | ||||
-rw-r--r-- | xps/xpsanalyze.c | 321 | ||||
-rw-r--r-- | xps/xpscolor.c | 248 | ||||
-rw-r--r-- | xps/xpscommon.c | 115 | ||||
-rw-r--r-- | xps/xpscrc.c | 95 | ||||
-rw-r--r-- | xps/xpsdoc.c | 277 | ||||
-rw-r--r-- | xps/xpsfont.c | 533 | ||||
-rw-r--r-- | xps/xpsglyphs.c | 675 | ||||
-rw-r--r-- | xps/xpsgradient.c | 978 | ||||
-rw-r--r-- | xps/xpshash.c | 217 | ||||
-rw-r--r-- | xps/xpsimage.c | 470 | ||||
-rw-r--r-- | xps/xpsjpeg.c | 143 | ||||
-rw-r--r-- | xps/xpsjxr.c | 259 | ||||
-rw-r--r-- | xps/xpsmem.c | 182 | ||||
-rw-r--r-- | xps/xpsopacity.c | 102 | ||||
-rw-r--r-- | xps/xpspage.c | 281 | ||||
-rw-r--r-- | xps/xpspath.c | 1036 | ||||
-rw-r--r-- | xps/xpspng.c | 293 | ||||
-rw-r--r-- | xps/xpsresource.c | 204 | ||||
-rw-r--r-- | xps/xpstiff.c | 1091 | ||||
-rw-r--r-- | xps/xpstile.c | 399 | ||||
-rw-r--r-- | xps/xpstop.c | 576 | ||||
-rw-r--r-- | xps/xpsutf.c | 69 | ||||
-rw-r--r-- | xps/xpsvisual.c | 62 | ||||
-rw-r--r-- | xps/xpsxml.c | 353 | ||||
-rw-r--r-- | xps/xpszip.c | 568 |
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, ¶ms, 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(¶ms); + { + 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, ¶ms, 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(¶ms); + { + 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, ¶ms, 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(¶ms, TRANSPARENCY_MASK_Luminosity); + gs_begin_transparency_mask(ctx->pgs, ¶ms, &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(¶ms, TRANSPARENCY_MASK_Luminosity); + gs_begin_transparency_mask(ctx->pgs, ¶ms, &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; +} |