diff options
Diffstat (limited to 'xps')
-rw-r--r-- | xps/muxps.h | 247 | ||||
-rw-r--r-- | xps/xps_common.c | 282 | ||||
-rw-r--r-- | xps/xps_doc.c | 339 | ||||
-rw-r--r-- | xps/xps_glyphs.c | 559 | ||||
-rw-r--r-- | xps/xps_gradient.c | 458 | ||||
-rw-r--r-- | xps/xps_hash.c | 190 | ||||
-rw-r--r-- | xps/xps_image.c | 128 | ||||
-rw-r--r-- | xps/xps_jpeg.c | 131 | ||||
-rw-r--r-- | xps/xps_path.c | 990 | ||||
-rw-r--r-- | xps/xps_png.c | 574 | ||||
-rw-r--r-- | xps/xps_resource.c | 187 | ||||
-rw-r--r-- | xps/xps_tiff.c | 853 | ||||
-rw-r--r-- | xps/xps_tile.c | 347 | ||||
-rw-r--r-- | xps/xps_util.c | 94 | ||||
-rw-r--r-- | xps/xps_xml.c | 387 | ||||
-rw-r--r-- | xps/xps_zip.c | 472 |
16 files changed, 6238 insertions, 0 deletions
diff --git a/xps/muxps.h b/xps/muxps.h new file mode 100644 index 00000000..8a29d674 --- /dev/null +++ b/xps/muxps.h @@ -0,0 +1,247 @@ +#ifndef _MUXPS_H_ +#define _MUXPS_H_ + +#ifndef _FITZ_H_ +#error "fitz.h must be included before muxps.h" +#endif + +typedef unsigned char byte; + +/* + * XPS and ZIP constants. + */ + +typedef struct xps_context_s xps_context; + +#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. + */ + +int xps_strcasecmp(char *a, char *b); +void xps_absolute_path(char *output, char *base_uri, char *path, int output_size); + +/* + * Generic hashtable. + */ + +typedef struct xps_hash_table_s xps_hash_table; + +xps_hash_table *xps_hash_new(void); +void *xps_hash_lookup(xps_hash_table *table, char *key); +int xps_hash_insert(xps_hash_table *table, char *key, void *value); +void xps_hash_free(xps_hash_table *table, + void (*free_key)(void *), + void (*free_value)(void *)); +void xps_hash_debug(xps_hash_table *table); + +/* + * XML document model + */ + +typedef struct element xml_element; + +xml_element *xml_parse_document(byte *buf, int len); +xml_element *xml_next(xml_element *item); +xml_element *xml_down(xml_element *item); +char *xml_tag(xml_element *item); +char *xml_att(xml_element *item, const char *att); +void xml_free_element(xml_element *item); +void xml_print_element(xml_element *item, int level); + +/* + * Container parts. + */ + +typedef struct xps_part_s xps_part; + +struct xps_part_s +{ + char *name; + int size; + int cap; + byte *data; +}; + +xps_part *xps_new_part(xps_context *ctx, char *name, int size); +xps_part *xps_read_part(xps_context *ctx, char *partname); +void xps_free_part(xps_context *ctx, xps_part *part); + +/* + * Document structure. + */ + +typedef struct xps_document_s xps_document; +typedef struct xps_page_s xps_page; + +struct xps_document_s +{ + char *name; + xps_document *next; +}; + +struct xps_page_s +{ + char *name; + int width; + int height; + xml_element *root; + xps_page *next; +}; + +int xps_read_page_list(xps_context *ctx); +void xps_debug_page_list(xps_context *ctx); +void xps_free_page_list(xps_context *ctx); + +int xps_count_pages(xps_context *ctx); +xps_page *xps_load_page(xps_context *ctx, int number); +void xps_free_page(xps_context *ctx, xps_page *page); + +/* + * Images. + */ + +int xps_decode_jpeg(fz_pixmap **imagep, byte *rbuf, int rlen); +int xps_decode_png(fz_pixmap **imagep, byte *rbuf, int rlen); +int xps_decode_tiff(fz_pixmap **imagep, byte *rbuf, int rlen); + +/* + * Fonts. + */ + +typedef struct xps_glyph_metrics_s xps_glyph_metrics; + +struct xps_glyph_metrics_s +{ + float hadv, vadv, vorg; +}; + +int xps_count_font_encodings(fz_font *font); +void xps_identify_font_encoding(fz_font *font, int idx, int *pid, int *eid); +void xps_select_font_encoding(fz_font *font, int idx); +int xps_encode_font_char(fz_font *font, int key); + +void xps_measure_font_glyph(xps_context *ctx, fz_font *font, int gid, xps_glyph_metrics *mtx); + +void xps_debug_path(xps_context *ctx); + +/* + * Colorspaces and colors. + */ + +void xps_parse_color(xps_context *ctx, char *base_uri, char *hexstring, fz_colorspace **csp, float *samples); +void xps_set_color(xps_context *ctx, fz_colorspace *colorspace, float *samples); + +/* + * Resource dictionaries. + */ + +typedef struct xps_resource_s xps_resource; + +struct xps_resource_s +{ + char *name; + char *base_uri; /* only used in the head nodes */ + xml_element *base_xml; /* only used in the head nodes, to free the xml document */ + xml_element *data; + xps_resource *next; + xps_resource *parent; /* up to the previous dict in the stack */ +}; + +int xps_parse_resource_dictionary(xps_context *ctx, xps_resource **dictp, char *base_uri, xml_element *root); +void xps_free_resource_dictionary(xps_context *ctx, xps_resource *dict); +void xps_resolve_resource_reference(xps_context *ctx, xps_resource *dict, char **attp, xml_element **tagp, char **urip); + +void xps_debug_resource_dictionary(xps_resource *dict); + +/* + * Fixed page/graphics parsing. + */ + +void xps_parse_fixed_page(xps_context *ctx, fz_matrix ctm, xps_page *page); +void xps_parse_canvas(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, xml_element *node); +void xps_parse_path(xps_context *ctx, fz_matrix ctm, char *base_uri, xps_resource *dict, xml_element *node); +void xps_parse_glyphs(xps_context *ctx, fz_matrix ctm, char *base_uri, xps_resource *dict, xml_element *node); +void xps_parse_solid_color_brush(xps_context *ctx, fz_matrix ctm, char *base_uri, xps_resource *dict, xml_element *node); +void xps_parse_image_brush(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, xml_element *node); +void xps_parse_visual_brush(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, xml_element *node); +void xps_parse_linear_gradient_brush(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, xml_element *node); +void xps_parse_radial_gradient_brush(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, xml_element *node); + +void xps_parse_tiling_brush(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, xml_element *root, void(*func)(xps_context*, fz_matrix, fz_rect, char*, xps_resource*, xml_element*, void*), void *user); + +void xps_parse_matrix_transform(xps_context *ctx, xml_element *root, fz_matrix *matrix); +void xps_parse_render_transform(xps_context *ctx, char *text, fz_matrix *matrix); +void xps_parse_rectangle(xps_context *ctx, char *text, fz_rect *rect); + +void xps_begin_opacity(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, char *opacity_att, xml_element *opacity_mask_tag); +void xps_end_opacity(xps_context *ctx, char *base_uri, xps_resource *dict, char *opacity_att, xml_element *opacity_mask_tag); + +void xps_parse_brush(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, xml_element *node); +void xps_parse_element(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, xml_element *node); + +void xps_clip(xps_context *ctx, fz_matrix ctm, xps_resource *dict, char *clip_att, xml_element *clip_tag); + +/* + * The interpreter context. + */ + +typedef struct xps_entry_s xps_entry; + +struct xps_entry_s +{ + char *name; + int offset; + int csize; + int usize; +}; + +struct xps_context_s +{ + char *directory; + FILE *file; + int zip_count; + xps_entry *zip_table; + + char *start_part; /* fixed document sequence */ + xps_document *first_fixdoc; /* first fixed document */ + xps_document *last_fixdoc; /* last fixed document */ + xps_page *first_page; /* first page of document */ + xps_page *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 *font_table; + xps_hash_table *colorspace_table; + + /* Opacity attribute stack */ + float opacity[64]; + int opacity_top; + + /* Current color */ + fz_colorspace *colorspace; + float color[8]; + float alpha; + + /* Current device */ + fz_device *dev; +}; + +xps_context *xps_new_context(void); +int xps_open_file(xps_context *ctx, char *filename); +int xps_free_context(xps_context *ctx); + +#endif diff --git a/xps/xps_common.c b/xps/xps_common.c new file mode 100644 index 00000000..65f06e60 --- /dev/null +++ b/xps/xps_common.c @@ -0,0 +1,282 @@ +#include "fitz.h" +#include "muxps.h" + +static inline int unhex(int a) +{ + if (a >= 'A' && a <= 'F') return a - 'A' + 0xA; + if (a >= 'a' && a <= 'f') return a - 'a' + 0xA; + if (a >= '0' && a <= '9') return a - '0'; + return 0; +} + +void +xps_parse_brush(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, xml_element *node) +{ + /* SolidColorBrushes are handled in a special case and will never show up here */ + if (!strcmp(xml_tag(node), "ImageBrush")) + xps_parse_image_brush(ctx, ctm, area, base_uri, dict, node); + else if (!strcmp(xml_tag(node), "VisualBrush")) + xps_parse_visual_brush(ctx, ctm, area, base_uri, dict, node); + else if (!strcmp(xml_tag(node), "LinearGradientBrush")) + xps_parse_linear_gradient_brush(ctx, ctm, area, base_uri, dict, node); + else if (!strcmp(xml_tag(node), "RadialGradientBrush")) + xps_parse_radial_gradient_brush(ctx, ctm, area, base_uri, dict, node); + else + fz_warn("unknown brush tag: %s", xml_tag(node)); +} + +void +xps_parse_element(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, xml_element *node) +{ + if (!strcmp(xml_tag(node), "Path")) + xps_parse_path(ctx, ctm, base_uri, dict, node); + if (!strcmp(xml_tag(node), "Glyphs")) + xps_parse_glyphs(ctx, ctm, base_uri, dict, node); + if (!strcmp(xml_tag(node), "Canvas")) + xps_parse_canvas(ctx, ctm, area, base_uri, dict, node); + /* skip unknown tags (like Foo.Resources and similar) */ +} + +void +xps_begin_opacity(xps_context *ctx, fz_matrix ctm, fz_rect area, + char *base_uri, xps_resource *dict, + char *opacity_att, xml_element *opacity_mask_tag) +{ + float opacity; + + if (!opacity_att && !opacity_mask_tag) + return; + + opacity = 1.0; + if (opacity_att) + opacity = atof(opacity_att); + + if (opacity_mask_tag && !strcmp(xml_tag(opacity_mask_tag), "SolidColorBrush")) + { + char *scb_opacity_att = xml_att(opacity_mask_tag, "Opacity"); + char *scb_color_att = xml_att(opacity_mask_tag, "Color"); + if (scb_opacity_att) + opacity = opacity * atof(scb_opacity_att); + if (scb_color_att) + { + fz_colorspace *colorspace; + float samples[32]; + xps_parse_color(ctx, base_uri, scb_color_att, &colorspace, samples); + opacity = opacity * samples[0]; + } + opacity_mask_tag = NULL; + } + + if (ctx->opacity_top + 1 < nelem(ctx->opacity)) + { + ctx->opacity[ctx->opacity_top + 1] = ctx->opacity[ctx->opacity_top] * opacity; + ctx->opacity_top++; + } + + if (opacity_mask_tag) + { + ctx->dev->beginmask(ctx->dev->user, area, 0, NULL, NULL); + xps_parse_brush(ctx, ctm, area, base_uri, dict, opacity_mask_tag); + ctx->dev->endmask(ctx->dev->user); + } +} + +void +xps_end_opacity(xps_context *ctx, char *base_uri, xps_resource *dict, + char *opacity_att, xml_element *opacity_mask_tag) +{ + if (!opacity_att && !opacity_mask_tag) + return; + + if (ctx->opacity_top > 0) + ctx->opacity_top--; + + if (opacity_mask_tag) + { + if (strcmp(xml_tag(opacity_mask_tag), "SolidColorBrush")) + ctx->dev->popclip(ctx->dev->user); + } +} + +void +xps_parse_render_transform(xps_context *ctx, char *transform, fz_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->a = args[0]; matrix->b = args[1]; + matrix->c = args[2]; matrix->d = args[3]; + matrix->e = args[4]; matrix->f = args[5]; +} + +void +xps_parse_matrix_transform(xps_context *ctx, xml_element *root, fz_matrix *matrix) +{ + char *transform; + + *matrix = fz_identity; + + if (!strcmp(xml_tag(root), "MatrixTransform")) + { + transform = xml_att(root, "Matrix"); + if (transform) + xps_parse_render_transform(ctx, transform, matrix); + } +} + +void +xps_parse_rectangle(xps_context *ctx, char *text, fz_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->x0 = args[0]; + rect->y0 = args[1]; + rect->x1 = args[0] + args[2]; + rect->y1 = args[1] + args[3]; +} + +static int count_commas(char *s) +{ + int n = 0; + while (*s) + { + if (*s == ',') + n ++; + s ++; + } + return n; +} + +void +xps_parse_color(xps_context *ctx, char *base_uri, char *string, + fz_colorspace **csp, float *samples) +{ + char *p; + int i, n; + char buf[1024]; + char *profile; + + *csp = fz_devicergb; + + 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] == '#') + { + 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) + { + fz_warn("cannot find icc profile uri in '%s'", string); + return; + } + + *profile++ = 0; + p = strchr(profile, ' '); + if (!p) + { + fz_warn("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; + } + + /* TODO: load ICC profile */ + switch (n) + { + case 2: *csp = fz_devicegray; break; + case 4: *csp = fz_devicergb; break; + case 5: *csp = fz_devicecmyk; break; + default: *csp = fz_devicegray; break; + } + } +} + +void +xps_set_color(xps_context *ctx, fz_colorspace *colorspace, float *samples) +{ + int i; + ctx->colorspace = colorspace; + for (i = 0; i < colorspace->n; i++) + ctx->color[i] = samples[i + 1]; + ctx->alpha = samples[0] * ctx->opacity[ctx->opacity_top]; +} diff --git a/xps/xps_doc.c b/xps/xps_doc.c new file mode 100644 index 00000000..c9a4eec5 --- /dev/null +++ b/xps/xps_doc.c @@ -0,0 +1,339 @@ +#include "fitz.h" +#include "muxps.h" + +/* + * The FixedDocumentSequence and FixedDocument parts determine + * which parts correspond to actual pages, and the page order. + */ + +void +xps_debug_page_list(xps_context *ctx) +{ + xps_document *fixdoc = ctx->first_fixdoc; + xps_page *page = ctx->first_page; + + if (ctx->start_part) + printf("start part %s\n", ctx->start_part); + + while (fixdoc) + { + printf("fixdoc %s\n", fixdoc->name); + fixdoc = fixdoc->next; + } + + while (page) + { + printf("page %s w=%d h=%d\n", page->name, page->width, page->height); + page = page->next; + } +} + +static void +xps_add_fixed_document(xps_context *ctx, char *name) +{ + xps_document *fixdoc; + + /* Check for duplicates first */ + for (fixdoc = ctx->first_fixdoc; fixdoc; fixdoc = fixdoc->next) + if (!strcmp(fixdoc->name, name)) + return; + + fixdoc = fz_malloc(sizeof(xps_document)); + fixdoc->name = fz_strdup(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; + } +} + +static void +xps_add_fixed_page(xps_context *ctx, char *name, int width, int height) +{ + xps_page *page; + + /* Check for duplicates first */ + for (page = ctx->first_page; page; page = page->next) + if (!strcmp(page->name, name)) + return; + + page = fz_malloc(sizeof(xps_page)); + page->name = fz_strdup(name); + page->width = width; + page->height = height; + page->root = NULL; + 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; + } +} + +static void +xps_free_fixed_pages(xps_context *ctx) +{ + xps_page *node = ctx->first_page; + while (node) + { + xps_page *next = node->next; + fz_free(node->name); + fz_free(node); + node = next; + } + ctx->first_page = NULL; + ctx->last_page = NULL; +} + +static void +xps_free_fixed_documents(xps_context *ctx) +{ + xps_document *node = ctx->first_fixdoc; + while (node) + { + xps_document *next = node->next; + fz_free(node->name); + fz_free(node); + node = next; + } + ctx->first_fixdoc = NULL; + ctx->last_fixdoc = NULL; +} + +void +xps_free_page_list(xps_context *ctx) +{ + xps_free_fixed_documents(ctx); + xps_free_fixed_pages(ctx); +} + +/* + * Parse the fixed document sequence structure and _rels/.rels to find the start part. + */ + +static void +xps_parse_metadata_imp(xps_context *ctx, xml_element *item) +{ + while (item) + { + xps_parse_metadata_imp(ctx, xml_down(item)); + + if (!strcmp(xml_tag(item), "Relationship")) + { + char *target = xml_att(item, "Target"); + char *type = xml_att(item, "Type"); + if (target && type) + { + char tgtbuf[1024]; + xps_absolute_path(tgtbuf, ctx->base_uri, target, sizeof tgtbuf); + if (!strcmp(type, REL_START_PART)) + ctx->start_part = fz_strdup(tgtbuf); + } + } + + if (!strcmp(xml_tag(item), "DocumentReference")) + { + char *source = xml_att(item, "Source"); + if (source) + { + char srcbuf[1024]; + xps_absolute_path(srcbuf, ctx->base_uri, source, sizeof srcbuf); + xps_add_fixed_document(ctx, srcbuf); + } + } + + if (!strcmp(xml_tag(item), "PageContent")) + { + char *source = xml_att(item, "Source"); + char *width_att = xml_att(item, "Width"); + char *height_att = xml_att(item, "Height"); + int width = width_att ? atoi(width_att) : 0; + int height = height_att ? atoi(height_att) : 0; + if (source) + { + char srcbuf[1024]; + xps_absolute_path(srcbuf, ctx->base_uri, source, sizeof srcbuf); + xps_add_fixed_page(ctx, srcbuf, width, height); + } + } + + item = xml_next(item); + } +} + +static int +xps_parse_metadata(xps_context *ctx, xps_part *part) +{ + xml_element *root; + char buf[1024]; + char *s; + + /* Save directory name part */ + fz_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; + + root = xml_parse_document(part->data, part->size); + if (!root) + return fz_rethrow(-1, "cannot parse metadata part '%s'", part->name); + + xps_parse_metadata_imp(ctx, root); + + xml_free_element(root); + + ctx->base_uri = NULL; + ctx->part_uri = NULL; + + return fz_okay; +} + +static int +xps_read_and_process_metadata_part(xps_context *ctx, char *name) +{ + xps_part *part; + int code; + + part = xps_read_part(ctx, name); + if (!part) + return fz_rethrow(-1, "cannot read zip part '%s'", name); + + code = xps_parse_metadata(ctx, part); + if (code) + return fz_rethrow(code, "cannot process metadata part '%s'", name); + + xps_free_part(ctx, part); + + return fz_okay; +} + +int +xps_read_page_list(xps_context *ctx) +{ + xps_document *doc; + int code; + + code = xps_read_and_process_metadata_part(ctx, "/_rels/.rels"); + if (code) + return fz_rethrow(code, "cannot process root relationship part"); + + if (!ctx->start_part) + return fz_throw("cannot find fixed document sequence start part"); + + code = xps_read_and_process_metadata_part(ctx, ctx->start_part); + if (code) + return fz_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 fz_rethrow(code, "cannot process FixedDocument part"); + } + + return fz_okay; +} + +int +xps_count_pages(xps_context *ctx) +{ + xps_page *page; + int n = 0; + for (page = ctx->first_page; page; page = page->next) + n ++; + return n; +} + +static int +xps_load_fixed_page(xps_context *ctx, xps_page *page) +{ + xps_part *part; + xml_element *root; + char *width_att; + char *height_att; + + part = xps_read_part(ctx, page->name); + if (!part) + return fz_rethrow(-1, "cannot read zip part '%s'", page->name); + + root = xml_parse_document(part->data, part->size); + if (!root) + return fz_rethrow(-1, "cannot parse xml part '%s'", page->name); + + xps_free_part(ctx, part); + + if (strcmp(xml_tag(root), "FixedPage")) + return fz_throw("expected FixedPage element (found %s)", xml_tag(root)); + + width_att = xml_att(root, "Width"); + if (!width_att) + return fz_throw("FixedPage missing required attribute: Width"); + + height_att = xml_att(root, "Height"); + if (!height_att) + return fz_throw("FixedPage missing required attribute: Height"); + + page->width = atoi(width_att); + page->height = atoi(height_att); + page->root = root; + + return 0; +} + +xps_page * +xps_load_page(xps_context *ctx, int number) +{ + xps_page *page; + int code; + int n = 0; + + for (page = ctx->first_page; page; page = page->next) + { + if (n == number) + { + if (!page->root) + { + code = xps_load_fixed_page(ctx, page); + if (code) { + fz_rethrow(code, "cannot load page %d", number + 1); + return NULL; + } + } + return page; + } + n ++; + } + return nil; +} + +void +xps_free_page(xps_context *ctx, xps_page *page) +{ + if (page->root) + xml_free_element(page->root); + page->root = NULL; +} diff --git a/xps/xps_glyphs.c b/xps/xps_glyphs.c new file mode 100644 index 00000000..9d0d9fca --- /dev/null +++ b/xps/xps_glyphs.c @@ -0,0 +1,559 @@ +#include "fitz.h" +#include "muxps.h" + +#include <ft2build.h> +#include FT_FREETYPE_H +#include FT_ADVANCES_H + +static inline int ishex(int a) +{ + return (a >= 'A' && a <= 'F') || (a >= 'a' && a <= 'f') || (a >= '0' && a <= '9'); +} + +static inline int unhex(int a) +{ + if (a >= 'A' && a <= 'F') return a - 'A' + 0xA; + if (a >= 'a' && a <= 'f') return a - 'a' + 0xA; + if (a >= '0' && a <= '9') return a - '0'; + return 0; +} + +int +xps_count_font_encodings(fz_font *font) +{ + FT_Face face = font->ftface; + return face->num_charmaps; +} + +void +xps_identify_font_encoding(fz_font *font, int idx, int *pid, int *eid) +{ + FT_Face face = font->ftface; + *pid = face->charmaps[idx]->platform_id; + *eid = face->charmaps[idx]->encoding_id; +} + +void +xps_select_font_encoding(fz_font *font, int idx) +{ + FT_Face face = font->ftface; + FT_Set_Charmap(face, face->charmaps[idx]); +} + +int +xps_encode_font_char(fz_font *font, int code) +{ + FT_Face face = font->ftface; + int gid = FT_Get_Char_Index(face, code); + if (gid == 0 && face->charmap->platform_id == 3 && face->charmap->encoding_id == 0) + gid = FT_Get_Char_Index(face, 0xF000 | code); + return gid; +} + +void +xps_measure_font_glyph(xps_context *ctx, fz_font *font, int gid, xps_glyph_metrics *mtx) +{ + int mask = FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING | FT_LOAD_IGNORE_TRANSFORM; + FT_Face face = font->ftface; + FT_Fixed hadv, vadv; + + FT_Set_Char_Size(face, 64, 64, 72, 72); + FT_Get_Advance(face, gid, mask, &hadv); + FT_Get_Advance(face, gid, mask | FT_LOAD_VERTICAL_LAYOUT, &vadv); + + mtx->hadv = hadv / 65536.0f; + mtx->vadv = vadv / 65536.0f; + mtx->vorg = face->ascender / (float) face->units_per_EM; +} + +/* + * 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 *ctx, xps_part *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 (ishex(*p)) + buf[i++] = *p; + } + buf[i] = 0; + + if (i != 32) + { + fz_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(fz_font *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; + } + } + } + + fz_warn("cannot find a suitable cmap"); +} + +/* + * 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 fz_text * +xps_parse_glyphs_imp(xps_context *ctx, fz_matrix ctm, fz_font *font, float size, + float originx, float originy, int is_sideways, int bidi_level, + char *indices, char *unicode) +{ + xps_glyph_metrics mtx; + fz_text *text; + fz_matrix tm; + float e, f; + float x = originx; + float y = originy; + char *us = unicode; + char *is = indices; + int un = 0; + + if (!unicode && !indices) + fz_warn("glyphs element with neither characters nor indices"); + + if (us) + { + if (us[0] == '{' && us[1] == '}') + us = us + 2; + un = strlen(us); + } + + if (is_sideways) + tm = fz_concat(fz_scale(-size, size), fz_rotate(90)); + else + tm = fz_scale(size, -size); + + text = fz_newtext(font, tm, is_sideways); + + while ((us && un > 0) || (is && *is)) + { + int char_code = '?'; + 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; + + /* TODO: add code chars with cluster mappings for proper text extraction */ + + while (code_count--) + { + if (us && un > 0) + { + int t = chartorune(&char_code, us); + us += t; un -= t; + } + } + + while (glyph_count--) + { + int glyph_index = -1; + float u_offset = 0.0; + float v_offset = 0.0; + float advance; + + if (is && *is) + is = xps_parse_glyph_index(is, &glyph_index); + + 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 (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 (is_sideways) + { + e = x + u_offset + (mtx.vorg * size); + f = y - v_offset + (mtx.hadv * 0.5 * size); + } + else + { + e = x + u_offset; + f = y - v_offset; + } + + fz_addtext(text, glyph_index, char_code, e, f); + + x += advance * 0.01 * size; + } + } + + return text; +} + +void +xps_parse_glyphs(xps_context *ctx, fz_matrix ctm, + char *base_uri, xps_resource *dict, xml_element *root) +{ + xml_element *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; + + xml_element *transform_tag = NULL; + xml_element *clip_tag = NULL; + xml_element *fill_tag = NULL; + xml_element *opacity_mask_tag = NULL; + + char *fill_opacity_att = NULL; + + xps_part *part; + fz_font *font; + + char partname[1024]; + char *subfont; + + float font_size = 10.0; + int subfontid = 0; + int is_sideways = 0; + int bidi_level = 0; + + fz_text *text; + fz_rect area; + + /* + * Extract attributes and extended attributes. + */ + + bidi_level_att = xml_att(root, "BidiLevel"); + caret_stops_att = xml_att(root, "CaretStops"); + fill_att = xml_att(root, "Fill"); + font_size_att = xml_att(root, "FontRenderingEmSize"); + font_uri_att = xml_att(root, "FontUri"); + origin_x_att = xml_att(root, "OriginX"); + origin_y_att = xml_att(root, "OriginY"); + is_sideways_att = xml_att(root, "IsSideways"); + indices_att = xml_att(root, "Indices"); + unicode_att = xml_att(root, "UnicodeString"); + style_att = xml_att(root, "StyleSimulations"); + transform_att = xml_att(root, "RenderTransform"); + clip_att = xml_att(root, "Clip"); + opacity_att = xml_att(root, "Opacity"); + opacity_mask_att = xml_att(root, "OpacityMask"); + + for (node = xml_down(root); node; node = xml_next(node)) + { + if (!strcmp(xml_tag(node), "Glyphs.RenderTransform")) + transform_tag = xml_down(node); + if (!strcmp(xml_tag(node), "Glyphs.OpacityMask")) + opacity_mask_tag = xml_down(node); + if (!strcmp(xml_tag(node), "Glyphs.Clip")) + clip_tag = xml_down(node); + if (!strcmp(xml_tag(node), "Glyphs.Fill")) + fill_tag = xml_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) { + fz_warn("missing attributes in glyphs element"); + return; + } + + if (!indices_att && !unicode_att) + return; /* 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) { + fz_warn("cannot find font resource part '%s'", partname); + return; + } + + /* 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); + + code = fz_newfontfrombuffer(&font, part->data, part->size, subfontid); + if (code) { + fz_catch(code, "cannot load font resource '%s'", partname); + xps_free_part(ctx, part); + return; + } + + xps_select_best_font_encoding(font); + + xps_hash_insert(ctx->font_table, part->name, font); + + /* NOTE: we kept part->name in the hashtable and part->data in the font */ + fz_free(part); + } + + /* + * Set up graphics state. + */ + + if (transform_att || transform_tag) + { + fz_matrix transform; + if (transform_att) + xps_parse_render_transform(ctx, transform_att, &transform); + if (transform_tag) + xps_parse_matrix_transform(ctx, transform_tag, &transform); + ctm = fz_concat(transform, ctm); + } + + if (clip_att || clip_tag) + xps_clip(ctx, ctm, dict, clip_att, clip_tag); + + font_size = atof(font_size_att); + + text = xps_parse_glyphs_imp(ctx, ctm, font, font_size, + atof(origin_x_att), atof(origin_y_att), + is_sideways, bidi_level, indices_att, unicode_att); + + area = fz_boundtext(text, ctm); + + xps_begin_opacity(ctx, ctm, area, opacity_mask_uri, dict, opacity_att, opacity_mask_tag); + + /* + * If it's a solid color brush fill/stroke do a simple fill + */ + + if (fill_tag && !strcmp(xml_tag(fill_tag), "SolidColorBrush")) + { + fill_opacity_att = xml_att(fill_tag, "Opacity"); + fill_att = xml_att(fill_tag, "Color"); + fill_tag = NULL; + } + + if (fill_att) + { + float samples[32]; + fz_colorspace *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); + + ctx->dev->filltext(ctx->dev->user, text, ctm, + ctx->colorspace, ctx->color, ctx->alpha); + } + + /* + * If it's a visual brush or image, use the charpath as a clip mask to paint brush + */ + + if (fill_tag) + { + ctx->dev->cliptext(ctx->dev->user, text, ctm, 0); + xps_parse_brush(ctx, ctm, area, fill_uri, dict, fill_tag); + ctx->dev->popclip(ctx->dev->user); + } + + xps_end_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag); + + fz_freetext(text); + + if (clip_att || clip_tag) + ctx->dev->popclip(ctx->dev->user); +} diff --git a/xps/xps_gradient.c b/xps/xps_gradient.c new file mode 100644 index 00000000..b23098bd --- /dev/null +++ b/xps/xps_gradient.c @@ -0,0 +1,458 @@ +#include "fitz.h" +#include "muxps.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 r, g, b, a; +}; + +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 *ctx, char *base_uri, xml_element *node, + struct stop *stops, int maxcount) +{ + fz_colorspace *colorspace; + float sample[8]; + float rgb[3]; + int before, after; + int count; + int i; + + /* We may have to insert 2 extra stops when postprocessing */ + maxcount -= 2; + + count = 0; + while (node && count < maxcount) + { + if (!strcmp(xml_tag(node), "GradientStop")) + { + char *offset = xml_att(node, "Offset"); + char *color = xml_att(node, "Color"); + if (offset && color) + { + stops[count].offset = atof(offset); + + xps_parse_color(ctx, base_uri, color, &colorspace, sample); + + fz_convertcolor(colorspace, sample + 1, fz_devicergb, rgb); + + stops[count].r = rgb[0]; + stops[count].g = rgb[1]; + stops[count].b = rgb[2]; + stops[count].a = sample[0]; + + count ++; + } + } + node = xml_next(node); + } + + if (count == 0) + { + fz_warn("gradient brush has no gradient stops"); + stops[0].offset = 0; + stops[0].r = 0; + stops[0].g = 0; + stops[0].b = 0; + stops[0].a = 1; + stops[1].offset = 1; + stops[1].r = 1; + stops[1].g = 1; + stops[1].b = 1; + stops[1].a = 1; + return 2; + } + + if (count == maxcount) + fz_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; + stops[0].r = lerp(stops[0].r, stops[1].r, d); + stops[0].g = lerp(stops[0].g, stops[1].g, d); + stops[0].b = lerp(stops[0].b, stops[1].b, d); + stops[0].a = lerp(stops[0].a, stops[1].a, 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; + stops[0].r = lerp(stops[count-2].r, stops[count-1].r, d); + stops[0].g = lerp(stops[count-2].g, stops[count-1].g, d); + stops[0].b = lerp(stops[count-2].b, stops[count-1].b, d); + stops[0].a = lerp(stops[count-2].a, stops[count-1].a, 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 void +xps_sample_gradient_stops(fz_shade *shade, struct stop *stops, int count) +{ + float offset, d; + int i, k; + + k = 0; + for (i = 0; i < 256; i++) + { + offset = i / 255.0f; + while (k + 1 < count && offset > stops[k+1].offset) + k++; + + d = (offset - stops[k].offset) / (stops[k+1].offset - stops[k].offset); + + shade->function[i][0] = lerp(stops[k].r, stops[k+1].r, d); + shade->function[i][1] = lerp(stops[k].g, stops[k+1].g, d); + shade->function[i][2] = lerp(stops[k].b, stops[k+1].b, d); + shade->function[i][3] = lerp(stops[k].a, stops[k+1].a, d); + } +} + +/* + * 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 void +xps_draw_one_radial_gradient(xps_context *ctx, fz_matrix ctm, + struct stop *stops, int count, + int extend, + float x0, float y0, float r0, + float x1, float y1, float r1) +{ + fz_shade *shade; + + /* TODO: this (and the stuff in pdf_shade) should move to res_shade.c */ + shade = fz_malloc(sizeof(fz_shade)); + shade->refs = 1; + shade->colorspace = fz_devicergb; + shade->bbox = fz_infiniterect; + shade->matrix = fz_identity; + shade->usebackground = 0; + shade->usefunction = 1; + shade->type = FZ_RADIAL; + shade->extend[0] = extend; + shade->extend[1] = extend; + + xps_sample_gradient_stops(shade, stops, count); + + shade->meshlen = 6; + shade->meshcap = 6; + shade->mesh = fz_calloc(shade->meshcap, sizeof(float)); + shade->mesh[0] = x0; + shade->mesh[1] = y0; + shade->mesh[2] = r0; + shade->mesh[3] = x1; + shade->mesh[4] = y1; + shade->mesh[5] = r1; + + ctx->dev->fillshade(ctx->dev->user, shade, ctm, 1); + + fz_dropshade(shade); +} + +/* + * Linear gradients map to Axial shadings. + */ + +static void +xps_draw_one_linear_gradient(xps_context *ctx, fz_matrix ctm, + struct stop *stops, int count, + int extend, + float x0, float y0, float x1, float y1) +{ + fz_shade *shade; + + /* TODO: this (and the stuff in pdf_shade) should move to res_shade.c */ + shade = fz_malloc(sizeof(fz_shade)); + shade->refs = 1; + shade->colorspace = fz_devicergb; + shade->bbox = fz_infiniterect; + shade->matrix = fz_identity; + shade->usebackground = 0; + shade->usefunction = 1; + shade->type = FZ_LINEAR; + shade->extend[0] = extend; + shade->extend[1] = extend; + + xps_sample_gradient_stops(shade, stops, count); + + shade->meshlen = 6; + shade->meshcap = 6; + shade->mesh = fz_calloc(shade->meshcap, sizeof(float)); + shade->mesh[0] = x0; + shade->mesh[1] = y0; + shade->mesh[2] = 0; + shade->mesh[3] = x1; + shade->mesh[4] = y1; + shade->mesh[5] = 0; + + ctx->dev->fillshade(ctx->dev->user, shade, ctm, 1); + + fz_dropshade(shade); +} + +/* + * 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 void +xps_draw_radial_gradient(xps_context *ctx, fz_matrix ctm, + struct stop *stops, int count, + xml_element *root, int spread) +{ + float x0, y0, r0; + float x1, y1, r1; + float xrad = 1; + float yrad = 1; + float invscale; + + char *center_att = xml_att(root, "Center"); + char *origin_att = xml_att(root, "GradientOrigin"); + char *radius_x_att = xml_att(root, "RadiusX"); + char *radius_y_att = xml_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 */ + ctm = fz_concat(fz_scale(1.0, yrad / xrad), ctm); + + invscale = xrad / yrad; + y0 = y0 * invscale; + y1 = y1 * invscale; + + r0 = 0.0; + r1 = xrad; + + xps_draw_one_radial_gradient(ctx, ctm, stops, count, 1, x0, y0, r0, x1, y1, r1); +} + +/* + * Calculate how many iterations are needed to cover + * the bounding box. + */ + +static void +xps_draw_linear_gradient(xps_context *ctx, fz_matrix ctm, + struct stop *stops, int count, + xml_element *root, int spread) +{ + float x0, y0, x1, y1; + + char *start_point_att = xml_att(root, "StartPoint"); + char *end_point_att = xml_att(root, "EndPoint"); + + x0 = y0 = 0; + x1 = 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); + + xps_draw_one_linear_gradient(ctx, ctm, stops, count, 1, x0, y0, x1, y1); +} + +/* + * Parse XML tag and attributes for a gradient brush, create color/opacity + * function objects and call gradient drawing primitives. + */ + +static void +xps_parse_gradient_brush(xps_context *ctx, fz_matrix ctm, fz_rect area, + char *base_uri, xps_resource *dict, xml_element *root, + void (*draw)(xps_context *, fz_matrix, struct stop *, int, xml_element *, int)) +{ + xml_element *node; + + char *opacity_att; + char *interpolation_att; + char *spread_att; + char *mapping_att; + char *transform_att; + + xml_element *transform_tag = NULL; + xml_element *stop_tag = NULL; + + struct stop stop_list[MAX_STOPS]; + int stop_count; + fz_matrix transform; + int spread_method; + + opacity_att = xml_att(root, "Opacity"); + interpolation_att = xml_att(root, "ColorInterpolationMode"); + spread_att = xml_att(root, "SpreadMethod"); + mapping_att = xml_att(root, "MappingMode"); + transform_att = xml_att(root, "Transform"); + + for (node = xml_down(root); node; node = xml_next(node)) + { + if (!strcmp(xml_tag(node), "LinearGradientBrush.Transform")) + transform_tag = xml_down(node); + if (!strcmp(xml_tag(node), "RadialGradientBrush.Transform")) + transform_tag = xml_down(node); + if (!strcmp(xml_tag(node), "LinearGradientBrush.GradientStops")) + stop_tag = xml_down(node); + if (!strcmp(xml_tag(node), "RadialGradientBrush.GradientStops")) + stop_tag = xml_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; + } + + transform = fz_identity; + if (transform_att) + xps_parse_render_transform(ctx, transform_att, &transform); + if (transform_tag) + xps_parse_matrix_transform(ctx, transform_tag, &transform); + ctm = fz_concat(transform, ctm); + + if (!stop_tag) { + fz_warn("missing gradient stops tag"); + return; + } + + stop_count = xps_parse_gradient_stops(ctx, base_uri, stop_tag, stop_list, MAX_STOPS); + if (stop_count == 0) + { + fz_warn("no gradient stops found"); + return; + } + + xps_begin_opacity(ctx, ctm, area, base_uri, dict, opacity_att, NULL); + + draw(ctx, ctm, stop_list, stop_count, root, spread_method); + + xps_end_opacity(ctx, base_uri, dict, opacity_att, NULL); +} + +void +xps_parse_linear_gradient_brush(xps_context *ctx, fz_matrix ctm, fz_rect area, + char *base_uri, xps_resource *dict, xml_element *root) +{ + xps_parse_gradient_brush(ctx, ctm, area, base_uri, dict, root, xps_draw_linear_gradient); +} + +void +xps_parse_radial_gradient_brush(xps_context *ctx, fz_matrix ctm, fz_rect area, + char *base_uri, xps_resource *dict, xml_element *root) +{ + xps_parse_gradient_brush(ctx, ctm, area, base_uri, dict, root, xps_draw_radial_gradient); +} diff --git a/xps/xps_hash.c b/xps/xps_hash.c new file mode 100644 index 00000000..f2a7d48a --- /dev/null +++ b/xps/xps_hash.c @@ -0,0 +1,190 @@ +/* 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 "fitz.h" +#include "muxps.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; + +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 *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 * +xps_hash_new(void) +{ + xps_hash_table *table; + + table = fz_malloc(sizeof(xps_hash_table)); + table->size = primes[0]; + table->load = 0; + + table->entries = fz_calloc(table->size, sizeof(xps_hash_entry)); + memset(table->entries, 0, table->size * sizeof(xps_hash_entry)); + + return table; +} + +static int +xps_hash_double(xps_hash_table *table) +{ + xps_hash_entry *old_entries; + xps_hash_entry *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 = fz_calloc(new_size, sizeof(xps_hash_entry)); + + table->size = new_size; + table->entries = new_entries; + table->load = 0; + + memset(table->entries, 0, table->size * sizeof(xps_hash_entry)); + + for (i = 0; i < old_size; i++) + if (old_entries[i].value) + xps_hash_insert(table, old_entries[i].key, old_entries[i].value); + + fz_free(old_entries); + + return 0; +} + +void +xps_hash_free(xps_hash_table *table, + void (*free_key)(void *), + void (*free_value)(void *)) +{ + int i; + + for (i = 0; i < table->size; i++) + { + if (table->entries[i].key && free_key) + free_key(table->entries[i].key); + if (table->entries[i].value && free_value) + free_value(table->entries[i].value); + } + + fz_free(table->entries); + fz_free(table); +} + +void * +xps_hash_lookup(xps_hash_table *table, char *key) +{ + xps_hash_entry *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_hash_table *table, char *key, void *value) +{ + xps_hash_entry *entries; + unsigned int size, pos; + + /* Grow the table at 80% load */ + if (table->load > table->size * 8 / 10) + { + if (xps_hash_double(table) < 0) + return fz_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 *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/xps_image.c b/xps/xps_image.c new file mode 100644 index 00000000..f4b75a1b --- /dev/null +++ b/xps/xps_image.c @@ -0,0 +1,128 @@ +#include "fitz.h" +#include "muxps.h" + +static int +xps_decode_image(fz_pixmap **imagep, byte *buf, int len) +{ + int error; + + if (len < 8) + return fz_throw("unknown image file format"); + + if (buf[0] == 0xff && buf[1] == 0xd8) + { + error = xps_decode_jpeg(imagep, buf, len); + if (error) + return fz_rethrow(error, "cannot decode jpeg image"); + } + else if (memcmp(buf, "\211PNG\r\n\032\n", 8) == 0) + { + error = xps_decode_png(imagep, buf, len); + if (error) + return fz_rethrow(error, "cannot decode png image"); + } + else if (memcmp(buf, "II", 2) == 0 && buf[2] == 0xBC) + { + return fz_throw("JPEG-XR codec is not available"); + } + else if (memcmp(buf, "MM", 2) == 0 || memcmp(buf, "II", 2) == 0) + { + error = xps_decode_tiff(imagep, buf, len); + if (error) + return fz_rethrow(error, "cannot decode TIFF image"); + } + else + return fz_throw("unknown image file format"); + + return fz_okay; +} + +static void +xps_paint_image_brush(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, + xml_element *root, void *vimage) +{ + fz_pixmap *pixmap = vimage; + float xs = pixmap->w * 96.0 / pixmap->xres; + float ys = pixmap->h * 96.0 / pixmap->yres; + fz_matrix im = fz_scale(xs, -ys); + im.f = ys; + ctm = fz_concat(im, ctm); + ctx->dev->fillimage(ctx->dev->user, pixmap, ctm, ctx->opacity[ctx->opacity_top]); +} + +static xps_part * +xps_find_image_brush_source_part(xps_context *ctx, char *base_uri, xml_element *root) +{ + char *image_source_att; + char buf[1024]; + char partname[1024]; + char *image_name; + char *profile_name; + char *p; + + image_source_att = xml_att(root, "ImageSource"); + if (!image_source_att) + return NULL; + + /* "{ColorConvertedBitmap /Resources/Image.tiff /Resources/Profile.icc}" */ + if (strstr(image_source_att, "{ColorConvertedBitmap") == image_source_att) + { + image_name = NULL; + profile_name = NULL; + + fz_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 NULL; + + xps_absolute_path(partname, base_uri, image_name, sizeof partname); + + return xps_read_part(ctx, partname); +} + +void +xps_parse_image_brush(xps_context *ctx, fz_matrix ctm, fz_rect area, + char *base_uri, xps_resource *dict, xml_element *root) +{ + xps_part *part; + fz_pixmap *image; + int code; + + part = xps_find_image_brush_source_part(ctx, base_uri, root); + if (!part) { + fz_warn("cannot find image source"); + return; + } + + code = xps_decode_image(&image, part->data, part->size); + if (code < 0) { + xps_free_part(ctx, part); + fz_catch(-1, "cannot decode image resource"); + return; + } + + xps_parse_tiling_brush(ctx, ctm, area, base_uri, dict, root, xps_paint_image_brush, image); + + fz_droppixmap(image); + xps_free_part(ctx, part); +} diff --git a/xps/xps_jpeg.c b/xps/xps_jpeg.c new file mode 100644 index 00000000..a8ea40f5 --- /dev/null +++ b/xps/xps_jpeg.c @@ -0,0 +1,131 @@ +#include "fitz.h" +#include "muxps.h" + +#include <jpeglib.h> +#include <setjmp.h> + +struct jpeg_error_mgr_jmp +{ + struct jpeg_error_mgr super; + jmp_buf env; + char msg[JMSG_LENGTH_MAX]; +}; + +static void error_exit(j_common_ptr cinfo) +{ + struct jpeg_error_mgr_jmp *err = (struct jpeg_error_mgr_jmp *)cinfo->err; + cinfo->err->format_message(cinfo, err->msg); + longjmp(err->env, 1); +} + +static void init_source(j_decompress_ptr cinfo) +{ + /* nothing to do */ +} + +static void term_source(j_decompress_ptr cinfo) +{ + /* nothing to do */ +} + +static boolean fill_input_buffer(j_decompress_ptr cinfo) +{ + static unsigned char eoi[2] = { 0xFF, JPEG_EOI }; + struct jpeg_source_mgr *src = cinfo->src; + src->next_input_byte = eoi; + src->bytes_in_buffer = 2; + return 1; +} + +static void skip_input_data(j_decompress_ptr cinfo, long num_bytes) +{ + struct jpeg_source_mgr *src = cinfo->src; + if (num_bytes > 0) + { + src->next_input_byte += num_bytes; + src->bytes_in_buffer -= num_bytes; + } +} + +int +xps_decode_jpeg(fz_pixmap **imagep, byte *rbuf, int rlen) +{ + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr_jmp err; + struct jpeg_source_mgr src; + unsigned char *row[1], *sp, *dp; + fz_colorspace *colorspace; + int x, k; + + fz_pixmap *image = NULL; + + if (setjmp(err.env)) + { + if (image) + fz_droppixmap(image); + return fz_throw("jpeg error: %s", err.msg); + } + + cinfo.err = jpeg_std_error(&err.super); + err.super.error_exit = error_exit; + + jpeg_create_decompress(&cinfo); + + cinfo.src = &src; + src.init_source = init_source; + src.fill_input_buffer = fill_input_buffer; + src.skip_input_data = skip_input_data; + src.resync_to_restart = jpeg_resync_to_restart; + src.term_source = term_source; + src.next_input_byte = rbuf; + src.bytes_in_buffer = rlen; + + jpeg_read_header(&cinfo, 1); + + jpeg_start_decompress(&cinfo); + + if (cinfo.output_components == 1) + colorspace = fz_devicegray; + else if (cinfo.output_components == 3) + colorspace = fz_devicergb; + else if (cinfo.output_components == 4) + colorspace = fz_devicecmyk; + else + return fz_throw("bad number of components in jpeg: %d", cinfo.output_components); + + image = fz_newpixmap(colorspace, 0, 0, cinfo.output_width, cinfo.output_height); + + if (cinfo.density_unit == 1) + { + image->xres = cinfo.X_density; + image->yres = cinfo.Y_density; + } + else if (cinfo.density_unit == 2) + { + image->xres = cinfo.X_density * 2.54; + image->yres = cinfo.Y_density * 2.54; + } + + fz_clearpixmap(image); + + row[0] = fz_malloc(cinfo.output_components * cinfo.output_width); + dp = image->samples; + while (cinfo.output_scanline < cinfo.output_height) + { + jpeg_read_scanlines(&cinfo, row, 1); + sp = row[0]; + for (x = 0; x < cinfo.output_width; x++) + { + for (k = 0; k < cinfo.output_components; k++) + *dp++ = *sp++; + *dp++ = 255; + } + } + fz_free(row[0]); + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + + *imagep = image; + return fz_okay; +} diff --git a/xps/xps_path.c b/xps/xps_path.c new file mode 100644 index 00000000..28b34894 --- /dev/null +++ b/xps/xps_path.c @@ -0,0 +1,990 @@ +#include "fitz.h" +#include "muxps.h" + +static fz_point +fz_currentpoint(fz_path *path) +{ + fz_point c, m; + int i; + + c.x = c.y = m.x = m.y = 0; + i = 0; + + while (i < path->len) + { + switch (path->els[i++].k) + { + case FZ_MOVETO: + m.x = c.x = path->els[i++].v; + m.y = c.y = path->els[i++].v; + break; + case FZ_LINETO: + c.x = path->els[i++].v; + c.y = path->els[i++].v; + break; + case FZ_CURVETO: + i += 4; + c.x = path->els[i++].v; + c.y = path->els[i++].v; + break; + case FZ_CLOSEPATH: + c = m; + } + } + + return c; +} + +/* Draw an arc segment transformed by the matrix, we approximate with straight + * line segments. We cannot use the fz_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(fz_path *path, fz_matrix mtx, float th0, float th1, int iscw) +{ + float t, d; + fz_point p; + + while (th1 < th0) + th1 += M_PI * 2.0; + + d = 1 * (M_PI / 180.0); /* 1-degree precision */ + + if (iscw) + { + p.x = cos(th0); + p.y = sin(th0); + p = fz_transformpoint(mtx, p); + fz_lineto(path, p.x, p.y); + for (t = th0; t < th1; t += d) + { + p.x = cos(t); + p.y = sin(t); + p = fz_transformpoint(mtx, p); + fz_lineto(path, p.x, p.y); + } + p.x = cos(th1); + p.y = sin(th1); + p = fz_transformpoint(mtx, p); + fz_lineto(path, p.x, p.y); + } + else + { + th0 += M_PI * 2; + p.x = cos(th0); + p.y = sin(th0); + p = fz_transformpoint(mtx, p); + fz_lineto(path, p.x, p.y); + for (t = th0; t > th1; t -= d) + { + p.x = cos(t); + p.y = sin(t); + p = fz_transformpoint(mtx, p); + fz_lineto(path, p.x, p.y); + } + p.x = cos(th1); + p.y = sin(th1); + p = fz_transformpoint(mtx, p); + fz_lineto(path, p.x, p.y); + } +} + +/* Given two vectors find the angle between them. */ +static inline double +angle_between(const fz_point u, const fz_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(fz_path *path, + float size_x, float size_y, float rotation_angle, + int is_large_arc, int is_clockwise, + float point_x, float point_y) +{ + fz_matrix rotmat, revmat; + fz_matrix mtx; + fz_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; + + pt = fz_currentpoint(path); + 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; + + rotmat = fz_rotate(rotation_angle); + revmat = fz_rotate(-rotation_angle); + + /* 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) + { + fz_lineto(path, x2, y2); + return; + } + + /* F.6.5.1 */ + pt.x = (x1 - x2) / 2; + pt.y = (y1 - y2) / 2; + pt = fz_transformvector(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 */ + pt.x = cxt; + pt.y = cyt; + pt = fz_transformvector(rotmat, pt); + cx = pt.x + (x1 + x2) / 2; + cy = pt.y + (y1 + y2) / 2; + + /* F.6.5.4 */ + { + fz_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 += ((M_PI / 180.0) * 360); + if (dth > 0 && is_clockwise) + dth -= ((M_PI / 180.0) * 360); + } + + mtx = fz_identity; + mtx = fz_concat(fz_translate(cx, cy), mtx); + mtx = fz_concat(fz_rotate(rotation_angle), mtx); + mtx = fz_concat(fz_scale(rx, ry), mtx); + xps_draw_arc_segment(path, mtx, th1, th1 + dth, is_clockwise); + + fz_lineto(path, point_x, point_y); +} + +/* + * Parse an abbreviated geometry string, and call + * ghostscript moveto/lineto/curveto functions to + * build up a path. + */ + +static fz_path * +xps_parse_abbreviated_geometry(xps_context *ctx, char *geom, int *fill_rule) +{ + fz_path *path; + char **args; + char **pargs; + char *s = geom; + fz_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; + + path = fz_newpath(); + + args = fz_calloc(strlen(geom) + 1, sizeof(char*)); + pargs = args; + + 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': + *fill_rule = atoi(args[i]); + i ++; + break; + + case 'M': + fz_moveto(path, atof(args[i]), atof(args[i+1])); + i += 2; + break; + case 'm': + pt = fz_currentpoint(path); + fz_moveto(path, pt.x + atof(args[i]), pt.y + atof(args[i+1])); + i += 2; + break; + + case 'L': + fz_lineto(path, atof(args[i]), atof(args[i+1])); + i += 2; + break; + case 'l': + pt = fz_currentpoint(path); + fz_lineto(path, pt.x + atof(args[i]), pt.y + atof(args[i+1])); + i += 2; + break; + + case 'H': + pt = fz_currentpoint(path); + fz_lineto(path, atof(args[i]), pt.y); + i += 1; + break; + case 'h': + pt = fz_currentpoint(path); + fz_lineto(path, pt.x + atof(args[i]), pt.y); + i += 1; + break; + + case 'V': + pt = fz_currentpoint(path); + fz_lineto(path, pt.x, atof(args[i])); + i += 1; + break; + case 'v': + pt = fz_currentpoint(path); + fz_lineto(path, pt.x, pt.y + 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]); + fz_curveto(path, x1, y1, x2, y2, x3, y3); + i += 6; + reset_smooth = 0; + smooth_x = x3 - x2; + smooth_y = y3 - y2; + break; + + case 'c': + pt = fz_currentpoint(path); + 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; + fz_curveto(path, x1, y1, x2, y2, x3, y3); + i += 6; + reset_smooth = 0; + smooth_x = x3 - x2; + smooth_y = y3 - y2; + break; + + case 'S': + pt = fz_currentpoint(path); + x1 = atof(args[i+0]); + y1 = atof(args[i+1]); + x2 = atof(args[i+2]); + y2 = atof(args[i+3]); + fz_curveto(path, 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': + pt = fz_currentpoint(path); + 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; + fz_curveto(path, 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': + pt = fz_currentpoint(path); + x1 = atof(args[i+0]); + y1 = atof(args[i+1]); + x2 = atof(args[i+2]); + y2 = atof(args[i+3]); + fz_curveto(path, + (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': + pt = fz_currentpoint(path); + 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; + fz_curveto(path, + (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(path, + 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': + pt = fz_currentpoint(path); + xps_draw_arc(path, + 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': + fz_closepath(path); + break; + + default: + /* eek */ + break; + } + + old = cmd; + } + + fz_free(args); + return path; +} + +static void +xps_parse_arc_segment(fz_path *path, xml_element *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 = xml_att(root, "Point"); + char *size_att = xml_att(root, "Size"); + char *rotation_angle_att = xml_att(root, "RotationAngle"); + char *is_large_arc_att = xml_att(root, "IsLargeArc"); + char *sweep_direction_att = xml_att(root, "SweepDirection"); + char *is_stroked_att = xml_att(root, "IsStroked"); + + if (!point_att || !size_att || !rotation_angle_att || !is_large_arc_att || !sweep_direction_att) + { + fz_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) + { + fz_moveto(path, point_x, point_y); + return; + } + + xps_draw_arc(path, size_x, size_y, rotation_angle, is_large_arc, is_clockwise, point_x, point_y); +} + +static void +xps_parse_poly_quadratic_bezier_segment(fz_path *path, xml_element *root, int stroking, int *skipped_stroke) +{ + char *points_att = xml_att(root, "Points"); + char *is_stroked_att = xml_att(root, "IsStroked"); + float x[2], y[2]; + int is_stroked; + fz_point pt; + char *s; + int n; + + if (!points_att) + { + fz_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) + { + fz_moveto(path, x[1], y[1]); + } + else + { + pt = fz_currentpoint(path); + fz_curveto(path, + (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(fz_path *path, xml_element *root, int stroking, int *skipped_stroke) +{ + char *points_att = xml_att(root, "Points"); + char *is_stroked_att = xml_att(root, "IsStroked"); + float x[3], y[3]; + int is_stroked; + char *s; + int n; + + if (!points_att) + { + fz_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) + fz_moveto(path, x[2], y[2]); + else + fz_curveto(path, x[0], y[0], x[1], y[1], x[2], y[2]); + n = 0; + } + } +} + +static void +xps_parse_poly_line_segment(fz_path *path, xml_element *root, int stroking, int *skipped_stroke) +{ + char *points_att = xml_att(root, "Points"); + char *is_stroked_att = xml_att(root, "IsStroked"); + int is_stroked; + float x, y; + char *s; + + if (!points_att) + { + fz_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) + fz_moveto(path, x, y); + else + fz_lineto(path, x, y); + while (*s != ' ' && *s != 0) s++; + } +} + +static void +xps_parse_path_figure(fz_path *path, xml_element *root, int stroking) +{ + xml_element *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 = xml_att(root, "IsClosed"); + start_point_att = xml_att(root, "StartPoint"); + is_filled_att = xml_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; + + fz_moveto(path, start_x, start_y); + + for (node = xml_down(root); node; node = xml_next(node)) + { + if (!strcmp(xml_tag(node), "ArcSegment")) + xps_parse_arc_segment(path, node, stroking, &skipped_stroke); + if (!strcmp(xml_tag(node), "PolyBezierSegment")) + xps_parse_poly_bezier_segment(path, node, stroking, &skipped_stroke); + if (!strcmp(xml_tag(node), "PolyLineSegment")) + xps_parse_poly_line_segment(path, node, stroking, &skipped_stroke); + if (!strcmp(xml_tag(node), "PolyQuadraticBezierSegment")) + xps_parse_poly_quadratic_bezier_segment(path, node, stroking, &skipped_stroke); + } + + if (is_closed) + { + if (stroking && skipped_stroke) + fz_lineto(path, start_x, start_y); /* we've skipped using fz_moveto... */ + else + fz_closepath(path); /* no skipped segments, safe to closepath properly */ + } +} + +fz_path * +xps_parse_path_geometry(xps_context *ctx, xps_resource *dict, xml_element *root, int stroking, int *fill_rule) +{ + xml_element *node; + + char *figures_att; + char *fill_rule_att; + char *transform_att; + + xml_element *transform_tag = NULL; + xml_element *figures_tag = NULL; /* only used by resource */ + + fz_matrix transform; + fz_path *path; + + figures_att = xml_att(root, "Figures"); + fill_rule_att = xml_att(root, "FillRule"); + transform_att = xml_att(root, "Transform"); + + for (node = xml_down(root); node; node = xml_next(node)) + { + if (!strcmp(xml_tag(node), "PathGeometry.Transform")) + transform_tag = xml_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")) + *fill_rule = 1; + if (!strcmp(fill_rule_att, "EvenOdd")) + *fill_rule = 0; + } + + transform = fz_identity; + if (transform_att) + xps_parse_render_transform(ctx, transform_att, &transform); + if (transform_tag) + xps_parse_matrix_transform(ctx, transform_tag, &transform); + + if (figures_att) + path = xps_parse_abbreviated_geometry(ctx, figures_att, fill_rule); + else + path = fz_newpath(); + + if (figures_tag) + xps_parse_path_figure(path, figures_tag, stroking); + + for (node = xml_down(root); node; node = xml_next(node)) + { + if (!strcmp(xml_tag(node), "PathFigure")) + xps_parse_path_figure(path, node, stroking); + } + + if (transform_att || transform_tag) + fz_transformpath(path, transform); + + return path; +} + +static int +xps_parse_line_cap(char *attr) +{ + if (attr) + { + if (!strcmp(attr, "Flat")) return 0; + if (!strcmp(attr, "Round")) return 1; + if (!strcmp(attr, "Square")) return 2; + if (!strcmp(attr, "Triangle")) return 3; /* FIXME add triangle caps */ + } + return 0; +} + +void +xps_clip(xps_context *ctx, fz_matrix ctm, xps_resource *dict, char *clip_att, xml_element *clip_tag) +{ + fz_path *path; + int fill_rule = 0; + + if (clip_att) + path = xps_parse_abbreviated_geometry(ctx, clip_att, &fill_rule); + else if (clip_tag) + path = xps_parse_path_geometry(ctx, dict, clip_tag, 0, &fill_rule); + else + path = fz_newpath(); + ctx->dev->clippath(ctx->dev->user, path, fill_rule == 0, ctm); + fz_freepath(path); +} + +/* + * Parse an XPS <Path> element, and call relevant ghostscript + * functions for drawing and/or clipping the child elements. + */ + +void +xps_parse_path(xps_context *ctx, fz_matrix ctm, char *base_uri, xps_resource *dict, xml_element *root) +{ + xml_element *node; + + 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; + + xml_element *transform_tag = NULL; + xml_element *clip_tag = NULL; + xml_element *data_tag = NULL; + xml_element *fill_tag = NULL; + xml_element *stroke_tag = NULL; + xml_element *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; + + fz_strokestate stroke; + fz_matrix transform; + float samples[32]; + fz_colorspace *colorspace; + fz_path *path; + fz_rect area; + int fill_rule; + + /* + * Extract attributes and extended attributes. + */ + + transform_att = xml_att(root, "RenderTransform"); + clip_att = xml_att(root, "Clip"); + data_att = xml_att(root, "Data"); + fill_att = xml_att(root, "Fill"); + stroke_att = xml_att(root, "Stroke"); + opacity_att = xml_att(root, "Opacity"); + opacity_mask_att = xml_att(root, "OpacityMask"); + + stroke_dash_array_att = xml_att(root, "StrokeDashArray"); + stroke_dash_cap_att = xml_att(root, "StrokeDashCap"); + stroke_dash_offset_att = xml_att(root, "StrokeDashOffset"); + stroke_end_line_cap_att = xml_att(root, "StrokeEndLineCap"); + stroke_start_line_cap_att = xml_att(root, "StrokeStartLineCap"); + stroke_line_join_att = xml_att(root, "StrokeLineJoin"); + stroke_miter_limit_att = xml_att(root, "StrokeMiterLimit"); + stroke_thickness_att = xml_att(root, "StrokeThickness"); + + for (node = xml_down(root); node; node = xml_next(node)) + { + if (!strcmp(xml_tag(node), "Path.RenderTransform")) + transform_tag = xml_down(node); + if (!strcmp(xml_tag(node), "Path.OpacityMask")) + opacity_mask_tag = xml_down(node); + if (!strcmp(xml_tag(node), "Path.Clip")) + clip_tag = xml_down(node); + if (!strcmp(xml_tag(node), "Path.Fill")) + fill_tag = xml_down(node); + if (!strcmp(xml_tag(node), "Path.Stroke")) + stroke_tag = xml_down(node); + if (!strcmp(xml_tag(node), "Path.Data")) + data_tag = xml_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 (!data_att && !data_tag) + return; + + if (fill_tag && !strcmp(xml_tag(fill_tag), "SolidColorBrush")) + { + fill_opacity_att = xml_att(fill_tag, "Opacity"); + fill_att = xml_att(fill_tag, "Color"); + fill_tag = NULL; + } + + if (stroke_tag && !strcmp(xml_tag(stroke_tag), "SolidColorBrush")) + { + stroke_opacity_att = xml_att(stroke_tag, "Opacity"); + stroke_att = xml_att(stroke_tag, "Color"); + stroke_tag = NULL; + } + + stroke.linecap = xps_parse_line_cap(stroke_start_line_cap_att); +// fz_setlineendcap(ctx->pgs, xps_parse_line_cap(stroke_end_line_cap_att)); +// fz_setlinedashcap(ctx->pgs, xps_parse_line_cap(stroke_dash_cap_att)); + + stroke.linejoin = 0; + if (stroke_line_join_att) + { + if (!strcmp(stroke_line_join_att, "Miter")) stroke.linejoin = 0; + if (!strcmp(stroke_line_join_att, "Round")) stroke.linejoin = 1; + if (!strcmp(stroke_line_join_att, "Bevel")) stroke.linejoin = 2; + } + + stroke.miterlimit = 10.0; + if (stroke_miter_limit_att) + stroke.miterlimit = atof(stroke_miter_limit_att); + + stroke.linewidth = 1.0; + if (stroke_thickness_att) + stroke.linewidth = atof(stroke_thickness_att); + + stroke.dashphase = 0; + stroke.dashlen = 0; + if (stroke_dash_array_att) + { + char *s = stroke_dash_array_att; + + if (stroke_dash_offset_att) + stroke.dashphase = atof(stroke_dash_offset_att) * stroke.linewidth; + + while (*s && stroke.dashlen < nelem(stroke.dashlist)) + { + while (*s == ' ') + s++; + stroke.dashlist[stroke.dashlen++] = atof(s) * stroke.linewidth; + while (*s && *s != ' ') + s++; + } + } + + transform = fz_identity; + if (transform_att) + xps_parse_render_transform(ctx, transform_att, &transform); + if (transform_tag) + xps_parse_matrix_transform(ctx, transform_tag, &transform); + ctm = fz_concat(transform, ctm); + + if (clip_att || clip_tag) + xps_clip(ctx, ctm, dict, clip_att, clip_tag); + + fill_rule = 0; + if (data_att) + path = xps_parse_abbreviated_geometry(ctx, data_att, &fill_rule); + else if (data_tag) + path = xps_parse_path_geometry(ctx, dict, data_tag, 0, &fill_rule); + + if (stroke_att || stroke_tag) + area = fz_boundpath(path, &stroke, ctm); + else + area = fz_boundpath(path, NULL, ctm); + + xps_begin_opacity(ctx, ctm, area, opacity_mask_uri, dict, opacity_att, opacity_mask_tag); + + 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); + + ctx->dev->fillpath(ctx->dev->user, path, fill_rule == 0, ctm, + ctx->colorspace, ctx->color, ctx->alpha); + } + + if (fill_tag) + { + area = fz_boundpath(path, NULL, ctm); + + ctx->dev->clippath(ctx->dev->user, path, fill_rule == 0, ctm); + xps_parse_brush(ctx, ctm, area, fill_uri, dict, fill_tag); + ctx->dev->popclip(ctx->dev->user); + } + + 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); + + ctx->dev->strokepath(ctx->dev->user, path, &stroke, ctm, + ctx->colorspace, ctx->color, ctx->alpha); + } + + if (stroke_tag) + { + ctx->dev->clipstrokepath(ctx->dev->user, path, &stroke, ctm); + xps_parse_brush(ctx, ctm, area, stroke_uri, dict, stroke_tag); + ctx->dev->popclip(ctx->dev->user); + } + + xps_end_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag); + + fz_freepath(path); + path = NULL; + + if (clip_att || clip_tag) + ctx->dev->popclip(ctx->dev->user); +} diff --git a/xps/xps_png.c b/xps/xps_png.c new file mode 100644 index 00000000..829bde90 --- /dev/null +++ b/xps/xps_png.c @@ -0,0 +1,574 @@ +#include "fitz.h" +#include "muxps.h" + +#include <zlib.h> + +struct info +{ + int width, height, depth, n; + int interlace, indexed; + int size; + unsigned char *samples; + unsigned char palette[256*4]; + int transparency; + int trns[3]; + int xres, yres; +}; + +static inline int getint(unsigned char *p) +{ + return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3]; +} + +static inline int +getcomp(unsigned char *line, int x, int bpc) +{ + switch (bpc) + { + case 1: return (line[x >> 3] >> ( 7 - (x & 7) ) ) & 1; + case 2: return (line[x >> 2] >> ( ( 3 - (x & 3) ) << 1 ) ) & 3; + case 4: return (line[x >> 1] >> ( ( 1 - (x & 1) ) << 2 ) ) & 15; + case 8: return line[x]; + case 16: return line[x << 1] << 8 | line[(x << 1) + 1]; + } + return 0; +} + +static inline void +putcomp(unsigned char *line, int x, int bpc, int value) +{ + int maxval = (1 << bpc) - 1; + + switch (bpc) + { + case 1: line[x >> 3] &= ~(maxval << (7 - (x & 7))); break; + case 2: line[x >> 2] &= ~(maxval << ((3 - (x & 3)) << 1)); break; + case 4: line[x >> 1] &= ~(maxval << ((1 - (x & 1)) << 2)); break; + } + + switch (bpc) + { + case 1: line[x >> 3] |= value << (7 - (x & 7)); break; + case 2: line[x >> 2] |= value << ((3 - (x & 3)) << 1); break; + case 4: line[x >> 1] |= value << ((1 - (x & 1)) << 2); break; + case 8: line[x] = value; break; + case 16: line[x << 1] = value >> 8; line[(x << 1) + 1] = value & 0xFF; break; + } +} + +static const unsigned char png_signature[8] = +{ + 137, 80, 78, 71, 13, 10, 26, 10 +}; + +static void *zalloc(void *opaque, unsigned int items, unsigned int size) +{ + return fz_calloc(items, size); +} + +static void zfree(void *opaque, void *address) +{ + fz_free(address); +} + +static inline int paeth(int a, int b, int c) +{ + /* The definitions of ac and bc are correct, not a typo. */ + int ac = b - c, bc = a - c, abcc = ac + bc; + int pa = (ac < 0 ? -ac : ac); + int pb = (bc < 0 ? -bc : bc); + int pc = (abcc < 0 ? -abcc : abcc); + return pa <= pb && pa <= pc ? a : pb <= pc ? b : c; +} + +static void +png_predict(unsigned char *samples, int width, int height, int n, int depth) +{ + int stride = (width * n * depth + 7) / 8; + int bpp = (n * depth + 7) / 8; + int i, row; + + for (row = 0; row < height; row ++) + { + unsigned char *src = samples + (stride + 1) * row; + unsigned char *dst = samples + stride * row; + + unsigned char *a = dst; + unsigned char *b = dst - stride; + unsigned char *c = dst - stride; + + switch (*src++) + { + default: + case 0: /* None */ + for (i = 0; i < stride; i++) + *dst++ = *src++; + break; + + case 1: /* Sub */ + for (i = 0; i < bpp; i++) + *dst++ = *src++; + for (i = bpp; i < stride; i++) + *dst++ = *src++ + *a++; + break; + + case 2: /* Up */ + if (row == 0) + for (i = 0; i < stride; i++) + *dst++ = *src++; + else + for (i = 0; i < stride; i++) + *dst++ = *src++ + *b++; + break; + + case 3: /* Average */ + if (row == 0) + { + for (i = 0; i < bpp; i++) + *dst++ = *src++; + for (i = bpp; i < stride; i++) + *dst++ = *src++ + (*a++ >> 1); + } + else + { + for (i = 0; i < bpp; i++) + *dst++ = *src++ + (*b++ >> 1); + for (i = bpp; i < stride; i++) + *dst++ = *src++ + ((*b++ + *a++) >> 1); + } + break; + + case 4: /* Paeth */ + if (row == 0) + { + for (i = 0; i < bpp; i++) + *dst++ = *src++ + paeth(0, 0, 0); + for (i = bpp; i < stride; i++) + *dst++ = *src++ + paeth(*a++, 0, 0); + } + else + { + for (i = 0; i < bpp; i++) + *dst++ = *src++ + paeth(0, *b++, 0); + for (i = bpp; i < stride; i++) + *dst++ = *src++ + paeth(*a++, *b++, *c++); + } + break; + } + } +} + +static const int adam7_ix[7] = { 0, 4, 0, 2, 0, 1, 0 }; +static const int adam7_dx[7] = { 8, 8, 4, 4, 2, 2, 1 }; +static const int adam7_iy[7] = { 0, 0, 4, 0, 2, 0, 1 }; +static const int adam7_dy[7] = { 8, 8, 8, 4, 4, 2, 2 }; + +static void +png_deinterlace_passes(struct info *info, int *w, int *h, int *ofs) +{ + int p, bpp = info->depth * info->n; + ofs[0] = 0; + for (p = 0; p < 7; p++) + { + w[p] = (info->width + adam7_dx[p] - adam7_ix[p] - 1) / adam7_dx[p]; + h[p] = (info->height + adam7_dy[p] - adam7_iy[p] - 1) / adam7_dy[p]; + if (w[p] == 0) h[p] = 0; + if (h[p] == 0) w[p] = 0; + if (w[p] && h[p]) + ofs[p + 1] = ofs[p] + h[p] * (1 + (w[p] * bpp + 7) / 8); + else + ofs[p + 1] = ofs[p]; + } +} + +static void +png_deinterlace(struct info *info, int *passw, int *passh, int *passofs) +{ + int n = info->n; + int depth = info->depth; + int stride = (info->width * n * depth + 7) / 8; + unsigned char *output; + int p, x, y, k; + + output = fz_calloc(info->height, stride); + + for (p = 0; p < 7; p++) + { + unsigned char *sp = info->samples + passofs[p]; + int w = passw[p]; + int h = passh[p]; + + png_predict(sp, w, h, n, depth); + for (y = 0; y < h; y++) + { + for (x = 0; x < w; x++) + { + int outx = x * adam7_dx[p] + adam7_ix[p]; + int outy = y * adam7_dy[p] + adam7_iy[p]; + unsigned char *dp = output + outy * stride; + for (k = 0; k < n; k++) + { + int v = getcomp(sp, x * n + k, depth); + putcomp(dp, outx * n + k, depth, v); + } + } + sp += (w * depth * n + 7) / 8; + } + } + + fz_free(info->samples); + info->samples = output; +} + +static int +png_read_ihdr(struct info *info, unsigned char *p, int size) +{ + int color, compression, filter; + + if (size != 13) + return fz_throw("IHDR chunk is the wrong size"); + + info->width = getint(p + 0); + info->height = getint(p + 4); + info->depth = p[8]; + + color = p[9]; + compression = p[10]; + filter = p[11]; + info->interlace = p[12]; + + if (info->width <= 0) + return fz_throw("image width must be > 0"); + if (info->height <= 0) + return fz_throw("image height must be > 0"); + + if (info->depth != 1 && info->depth != 2 && info->depth != 4 && + info->depth != 8 && info->depth != 16) + return fz_throw("image bit depth must be one of 1, 2, 4, 8, 16"); + if (color == 2 && info->depth < 8) + return fz_throw("illegal bit depth for truecolor"); + if (color == 3 && info->depth > 8) + return fz_throw("illegal bit depth for indexed"); + if (color == 4 && info->depth < 8) + return fz_throw("illegal bit depth for grayscale with alpha"); + if (color == 6 && info->depth < 8) + return fz_throw("illegal bit depth for truecolor with alpha"); + + info->indexed = 0; + if (color == 0) /* gray */ + info->n = 1; + else if (color == 2) /* rgb */ + info->n = 3; + else if (color == 4) /* gray alpha */ + info->n = 2; + else if (color == 6) /* rgb alpha */ + info->n = 4; + else if (color == 3) /* indexed */ + { + info->indexed = 1; + info->n = 1; + } + else + return fz_throw("unknown color type"); + + if (compression != 0) + return fz_throw("unknown compression method"); + if (filter != 0) + return fz_throw("unknown filter method"); + if (info->interlace != 0 && info->interlace != 1) + return fz_throw("interlace method not supported"); + + return fz_okay; +} + +static int +png_read_plte(struct info *info, unsigned char *p, int size) +{ + int n = size / 3; + int i; + + if (n > 256 || n > (1 << info->depth)) + return fz_throw("too many samples in palette"); + + for (i = 0; i < n; i++) + { + info->palette[i * 4] = p[i * 3]; + info->palette[i * 4 + 1] = p[i * 3 + 1]; + info->palette[i * 4 + 2] = p[i * 3 + 2]; + } + + return fz_okay; +} + +static int +png_read_trns(struct info *info, unsigned char *p, int size) +{ + int i; + + info->transparency = 1; + + if (info->indexed) + { + if (size > 256 || size > (1 << info->depth)) + return fz_throw("too many samples in transparency table"); + for (i = 0; i < size; i++) + info->palette[i * 4 + 3] = p[i]; + } + else + { + if (size != info->n * 2) + return fz_throw("tRNS chunk is the wrong size"); + for (i = 0; i < info->n; i++) + info->trns[i] = (p[i * 2] << 8 | p[i * 2 + 1]) & ((1 << info->depth) - 1); + } + + return fz_okay; +} + +static int +png_read_idat(struct info *info, unsigned char *p, int size, z_stream *stm) +{ + int code; + + stm->next_in = p; + stm->avail_in = size; + + code = inflate(stm, Z_SYNC_FLUSH); + if (code != Z_OK && code != Z_STREAM_END) + return fz_throw("zlib error: %s", stm->msg); + if (stm->avail_in != 0) + { + if (stm->avail_out == 0) + return fz_throw("ran out of output before input"); + return fz_throw("inflate did not consume buffer (%d remaining)", stm->avail_in); + } + + return fz_okay; +} + +static int +png_read_phys(struct info *info, unsigned char *p, int size) +{ + if (size != 9) + return fz_throw("pHYs chunk is the wrong size"); + if (p[8] == 1) + { + info->xres = getint(p) * 254 / 10000; + info->yres = getint(p + 4) * 254 / 10000; + } + return fz_okay; +} + +static int +png_read_image(struct info *info, unsigned char *p, int total) +{ + int passw[7], passh[7], passofs[8]; + int code, size; + z_stream stm; + + memset(info, 0, sizeof (struct info)); + memset(info->palette, 255, sizeof(info->palette)); + info->xres = 96; + info->yres = 96; + + /* Read signature */ + + if (total < 8 + 12 || memcmp(p, png_signature, 8)) + return fz_throw("not a png image (wrong signature)"); + + p += 8; + total -= 8; + + /* Read IHDR chunk (must come first) */ + + size = getint(p); + + if (size + 12 > total) + return fz_throw("premature end of data in png image"); + + if (!memcmp(p + 4, "IHDR", 4)) + { + code = png_read_ihdr(info, p + 8, size); + if (code) + return fz_rethrow(code, "cannot read png header"); + } + else + return fz_throw("png file must start with IHDR chunk"); + + p += size + 12; + total -= size + 12; + + /* Prepare output buffer */ + + if (!info->interlace) + { + info->size = info->height * (1 + (info->width * info->n * info->depth + 7) / 8); + } + else + { + png_deinterlace_passes(info, passw, passh, passofs); + info->size = passofs[7]; + } + + info->samples = fz_malloc(info->size); + + stm.zalloc = zalloc; + stm.zfree = zfree; + stm.opaque = NULL; + + stm.next_out = info->samples; + stm.avail_out = info->size; + + code = inflateInit(&stm); + if (code != Z_OK) + return fz_throw("zlib error: %s", stm.msg); + + /* Read remaining chunks until IEND */ + + while (total > 8) + { + size = getint(p); + + if (size + 12 > total) + return fz_throw("premature end of data in png image"); + + if (!memcmp(p + 4, "PLTE", 4)) + { + code = png_read_plte(info, p + 8, size); + if (code) + return fz_rethrow(code, "cannot read png palette"); + } + + if (!memcmp(p + 4, "tRNS", 4)) + { + code = png_read_trns(info, p + 8, size); + if (code) + return fz_rethrow(code, "cannot read png transparency"); + } + + if (!memcmp(p + 4, "pHYs", 4)) + { + code = png_read_phys(info, p + 8, size); + if (code) + return fz_rethrow(code, "cannot read png resolution"); + } + + if (!memcmp(p + 4, "IDAT", 4)) + { + code = png_read_idat(info, p + 8, size, &stm); + if (code) + return fz_rethrow(code, "cannot read png image data"); + } + + if (!memcmp(p + 4, "IEND", 4)) + break; + + p += size + 12; + total -= size + 12; + } + + code = inflateEnd(&stm); + if (code != Z_OK) + return fz_throw("zlib error: %s", stm.msg); + + /* Apply prediction filter and deinterlacing */ + + if (!info->interlace) + png_predict(info->samples, info->width, info->height, info->n, info->depth); + else + png_deinterlace(info, passw, passh, passofs); + + return fz_okay; +} + +static fz_pixmap * +png_expand_palette(struct info *info, fz_pixmap *src) +{ + fz_pixmap *dst = fz_newpixmap(fz_devicergb, 0, 0, src->w, src->h); + unsigned char *sp = src->samples; + unsigned char *dp = dst->samples; + int x, y; + + dst->xres = src->xres; + dst->yres = src->yres; + + for (y = 0; y < info->height; y++) + { + for (x = 0; x < info->width; x++) + { + int v = *sp << 2; + *dp++ = info->palette[v]; + *dp++ = info->palette[v + 1]; + *dp++ = info->palette[v + 2]; + *dp++ = info->palette[v + 3]; + sp += 2; + } + } + + fz_droppixmap(src); + return dst; +} + +static void +png_mask_transparency(struct info *info, fz_pixmap *dst) +{ + int stride = (info->width * info->n * info->depth + 7) / 8; + int depth = info->depth; + int n = info->n; + int x, y, k, t; + + for (y = 0; y < info->height; y++) + { + unsigned char *sp = info->samples + y * stride; + unsigned char *dp = dst->samples + y * dst->w * dst->n; + for (x = 0; x < info->width; x++) + { + t = 1; + for (k = 0; k < n; k++) + if (getcomp(sp, x * n + k, depth) != info->trns[k]) + t = 0; + if (t) + dp[x * dst->n + dst->n - 1] = 0; + } + } +} + +int +xps_decode_png(fz_pixmap **imagep, byte *p, int total) +{ + fz_pixmap *image; + fz_colorspace *colorspace; + struct info png; + int code; + int stride; + + code = png_read_image(&png, p, total); + if (code) + return fz_rethrow(code, "cannot read png image"); + + if (png.n == 3 || png.n == 4) + colorspace = fz_devicergb; + else + colorspace = fz_devicegray; + + stride = (png.width * png.n * png.depth + 7) / 8; + + image = fz_newpixmap(colorspace, 0, 0, png.width, png.height); + image->xres = png.xres; + image->yres = png.yres; + + fz_unpacktile(image, png.samples, png.n, png.depth, stride, png.indexed); + + if (png.indexed) + image = png_expand_palette(&png, image); + else if (png.transparency) + png_mask_transparency(&png, image); + + if (png.transparency || png.n == 2 || png.n == 4) + fz_premultiplypixmap(image); + + fz_free(png.samples); + + *imagep = image; + return fz_okay; +} diff --git a/xps/xps_resource.c b/xps/xps_resource.c new file mode 100644 index 00000000..7cbfe91f --- /dev/null +++ b/xps/xps_resource.c @@ -0,0 +1,187 @@ +#include "fitz.h" +#include "muxps.h" + +static xml_element * +xps_find_resource(xps_context *ctx, xps_resource *dict, char *name, char **urip) +{ + xps_resource *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 xml_element * +xps_parse_resource_reference(xps_context *ctx, xps_resource *dict, char *att, char **urip) +{ + char name[1024]; + char *s; + + if (strstr(att, "{StaticResource ") != att) + return NULL; + + fz_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 *ctx, xps_resource *dict, + char **attp, xml_element **tagp, char **urip) +{ + if (*attp) + { + xml_element *rsrc = xps_parse_resource_reference(ctx, dict, *attp, urip); + if (rsrc) + { + *attp = NULL; + *tagp = rsrc; + } + } +} + +static int +xps_parse_remote_resource_dictionary(xps_context *ctx, xps_resource **dictp, char *base_uri, char *source_att) +{ + char part_name[1024]; + char part_uri[1024]; + xps_resource *dict; + xps_part *part; + xml_element *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 fz_throw("cannot find remote resource part '%s'", part_name); + } + + xml = xml_parse_document(part->data, part->size); + if (!xml) + { + xps_free_part(ctx, part); + return fz_rethrow(-1, "cannot parse xml"); + } + + if (strcmp(xml_tag(xml), "ResourceDictionary")) + { + xml_free_element(xml); + xps_free_part(ctx, part); + return fz_throw("expected ResourceDictionary element (found %s)", xml_tag(xml)); + } + + fz_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) + { + xml_free_element(xml); + xps_free_part(ctx, part); + return fz_rethrow(code, "cannot parse remote resource dictionary: %s", part_uri); + } + + dict->base_xml = xml; /* pass on ownership */ + + xps_free_part(ctx, part); + + *dictp = dict; + return fz_okay; +} + +int +xps_parse_resource_dictionary(xps_context *ctx, xps_resource **dictp, char *base_uri, xml_element *root) +{ + xps_resource *head; + xps_resource *entry; + xml_element *node; + char *source; + char *key; + int code; + + source = xml_att(root, "Source"); + if (source) + { + code = xps_parse_remote_resource_dictionary(ctx, dictp, base_uri, source); + if (code) + return fz_rethrow(code, "cannot parse remote resource dictionary"); + return fz_okay; + } + + head = NULL; + + for (node = xml_down(root); node; node = xml_next(node)) + { + key = xml_att(node, "x:Key"); + if (key) + { + entry = fz_malloc(sizeof(xps_resource)); + 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 = fz_strdup(base_uri); + } + + *dictp = head; + return fz_okay; +} + +void +xps_free_resource_dictionary(xps_context *ctx, xps_resource *dict) +{ + xps_resource *next; + while (dict) + { + next = dict->next; + if (dict->base_xml) + xml_free_element(dict->base_xml); + if (dict->base_uri) + fz_free(dict->base_uri); + fz_free(dict); + dict = next; + } +} + +void +xps_debug_resource_dictionary(xps_resource *dict) +{ + while (dict) + { + if (dict->base_uri) + printf("URI = '%s'\n", dict->base_uri); + printf("KEY = '%s' VAL = %p\n", dict->name, dict->data); + if (dict->parent) + { + printf("PARENT = {\n"); + xps_debug_resource_dictionary(dict->parent); + printf("}\n"); + } + dict = dict->next; + } +} diff --git a/xps/xps_tiff.c b/xps/xps_tiff.c new file mode 100644 index 00000000..d70d5f87 --- /dev/null +++ b/xps/xps_tiff.c @@ -0,0 +1,853 @@ +#include "fitz.h" +#include "muxps.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 + */ + +struct tiff +{ + /* "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; + + /* decoded data */ + fz_colorspace *colorspace; + byte *samples; + int stride; +}; + +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_decode_tiff_uncompressed(struct tiff *tiff, fz_stream *stm, byte *wp, int wlen) +{ + int n = fz_read(stm, wp, wlen); + fz_close(stm); + if (n < 0) + return fz_rethrow(n, "cannot read uncompressed strip"); + return fz_okay; +} + +static int +xps_decode_tiff_packbits(struct tiff *tiff, fz_stream *chain, byte *wp, int wlen) +{ + fz_stream *stm = fz_openrld(chain); + int n = fz_read(stm, wp, wlen); + fz_close(stm); + if (n < 0) + return fz_rethrow(n, "cannot read packbits strip"); + return fz_okay; +} + +static int +xps_decode_tiff_lzw(struct tiff *tiff, fz_stream *chain, byte *wp, int wlen) +{ + fz_stream *stm = fz_openlzwd(chain, NULL); + int n = fz_read(stm, wp, wlen); + fz_close(stm); + if (n < 0) + return fz_rethrow(n, "cannot read lzw strip"); + return fz_okay; +} +static int +xps_decode_tiff_flate(struct tiff *tiff, fz_stream *chain, byte *wp, int wlen) +{ + fz_stream *stm = fz_openflated(chain); + int n = fz_read(stm, wp, wlen); + fz_close(stm); + if (n < 0) + return fz_rethrow(n, "cannot read flate strip"); + return fz_okay; +} + +static int +xps_decode_tiff_fax(struct tiff *tiff, int comp, fz_stream *chain, byte *wp, int wlen) +{ + fz_stream *stm; + fz_obj *params; + fz_obj *columns, *rows, *blackis1, *k, *encodedbytealign; + int n; + + columns = fz_newint(tiff->imagewidth); + rows = fz_newint(tiff->imagelength); + blackis1 = fz_newbool(tiff->photometric == 0); + k = fz_newint(comp == 4 ? -1 : 0); + encodedbytealign = fz_newbool(comp == 2); + + params = fz_newdict(5); + fz_dictputs(params, "Columns", columns); + fz_dictputs(params, "Rows", rows); + fz_dictputs(params, "BlackIs1", blackis1); + fz_dictputs(params, "K", k); + fz_dictputs(params, "EncodedByteAlign", encodedbytealign); + + fz_dropobj(columns); + fz_dropobj(rows); + fz_dropobj(blackis1); + fz_dropobj(k); + fz_dropobj(encodedbytealign); + + stm = fz_openfaxd(chain, params); + n = fz_read(stm, wp, wlen); + fz_close(stm); + fz_dropobj(params); + + if (n < 0) + return fz_rethrow(n, "cannot read fax strip"); + return fz_okay; +} + +static int +xps_decode_tiff_jpeg(struct tiff *tiff, fz_stream *chain, byte *wp, int wlen) +{ + fz_stream *stm = fz_opendctd(chain, NULL); + int n = fz_read(stm, wp, wlen); + fz_close(stm); + if (n < 0) + return fz_rethrow(n, "cannot read jpeg strip"); + return fz_okay; +} + +static inline int +getcomp(byte *line, int x, int bpc) +{ + switch (bpc) + { + case 1: return (line[x >> 3] >> ( 7 - (x & 7) ) ) & 1; + case 2: return (line[x >> 2] >> ( ( 3 - (x & 3) ) << 1 ) ) & 3; + case 4: return (line[x >> 1] >> ( ( 1 - (x & 1) ) << 2 ) ) & 15; + case 8: return line[x]; + case 16: return line[x << 1] << 8 | line[(x << 1) + 1]; + } + return 0; +} + +static inline void +putcomp(byte *line, int x, int bpc, int value) +{ + int maxval = (1 << bpc) - 1; + + switch (bpc) + { + case 1: line[x >> 3] &= ~(maxval << (7 - (x & 7))); break; + case 2: line[x >> 2] &= ~(maxval << ((3 - (x & 3)) << 1)); break; + case 4: line[x >> 1] &= ~(maxval << ((1 - (x & 1)) << 2)); break; + } + + switch (bpc) + { + case 1: line[x >> 3] |= value << (7 - (x & 7)); break; + case 2: line[x >> 2] |= value << ((3 - (x & 3)) << 1); break; + case 4: line[x >> 1] |= value << ((1 - (x & 1)) << 2); break; + case 8: line[x] = value; break; + case 16: line[x << 1] = value >> 8; line[(x << 1) + 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_tiff_colormap(struct tiff *tiff) +{ + int maxval = 1 << tiff->bitspersample; + 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 (tiff->samplesperpixel != 1 && tiff->samplesperpixel != 2) + return fz_throw("invalid number of samples for RGBPal"); + + if (tiff->bitspersample != 4 && tiff->bitspersample != 8) + return fz_throw("invalid number of bits for RGBPal"); + + stride = tiff->imagewidth * (tiff->samplesperpixel + 2); + + samples = fz_malloc(stride * tiff->imagelength); + + for (y = 0; y < tiff->imagelength; y++) + { + src = tiff->samples + (tiff->stride * y); + dst = samples + (stride * y); + + for (x = 0; x < tiff->imagewidth; x++) + { + if (tiff->extrasamples) + { + int c = getcomp(src, x * 2, tiff->bitspersample); + int a = getcomp(src, x * 2 + 1, tiff->bitspersample); + *dst++ = tiff->colormap[c + 0] >> 8; + *dst++ = tiff->colormap[c + maxval] >> 8; + *dst++ = tiff->colormap[c + maxval * 2] >> 8; + *dst++ = a << (8 - tiff->bitspersample); + } + else + { + int c = getcomp(src, x, tiff->bitspersample); + *dst++ = tiff->colormap[c + 0] >> 8; + *dst++ = tiff->colormap[c + maxval] >> 8; + *dst++ = tiff->colormap[c + maxval * 2] >> 8; + } + } + } + + tiff->samplesperpixel += 2; + tiff->bitspersample = 8; + tiff->stride = stride; + tiff->samples = samples; + return fz_okay; +} + +static int +xps_decode_tiff_strips(struct tiff *tiff) +{ + fz_buffer buf; + fz_stream *stm; + 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 fz_throw("no image data in tiff; maybe it is tiled"); + + if (tiff->planar != 1) + return fz_throw("image data is not in chunky format"); + + tiff->stride = (tiff->imagewidth * tiff->samplesperpixel * tiff->bitspersample + 7) / 8; + + switch (tiff->photometric) + { + case 0: /* WhiteIsZero -- inverted */ + tiff->colorspace = fz_devicegray; + break; + case 1: /* BlackIsZero */ + tiff->colorspace = fz_devicegray; + break; + case 2: /* RGB */ + tiff->colorspace = fz_devicergb; + break; + case 3: /* RGBPal */ + tiff->colorspace = fz_devicergb; + break; + case 5: /* CMYK */ + tiff->colorspace = fz_devicecmyk; + break; + case 6: /* YCbCr */ + /* it's probably a jpeg ... we let jpeg convert to rgb */ + tiff->colorspace = fz_devicergb; + break; + default: + return fz_throw("unknown photometric: %d", tiff->photometric); + } + + switch (tiff->resolutionunit) + { + case 2: + /* no unit conversion needed */ + break; + case 3: + tiff->xresolution *= 2.54; + tiff->yresolution *= 2.54; + break; + default: + tiff->xresolution = 96; + tiff->yresolution = 96; + break; + } + + /* Note xres and yres could be 0 even if unit was set. If so default to 96dpi. */ + if (tiff->xresolution == 0 || tiff->yresolution == 0) + { + tiff->xresolution = 96; + tiff->yresolution = 96; + } + + tiff->samples = fz_calloc(tiff->imagelength, tiff->stride); + memset(tiff->samples, 0x55, tiff->imagelength * tiff->stride); + wp = tiff->samples; + + strip = 0; + for (row = 0; row < tiff->imagelength; row += tiff->rowsperstrip) + { + unsigned offset = tiff->stripoffsets[strip]; + unsigned rlen = tiff->stripbytecounts[strip]; + unsigned wlen = tiff->stride * tiff->rowsperstrip; + byte *rp = tiff->bp + offset; + + if (wp + wlen > tiff->samples + tiff->stride * tiff->imagelength) + wlen = tiff->samples + tiff->stride * tiff->imagelength - wp; + + if (rp + rlen > tiff->ep) + return fz_throw("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]]; + + /* create a fz_buffer on the stack */ + buf.refs = 2; + buf.data = rp; + buf.len = rlen; + buf.cap = rlen; + + /* the strip decoders will close this */ + stm = fz_openbuffer(&buf); + + switch (tiff->compression) + { + case 1: + error = xps_decode_tiff_uncompressed(tiff, stm, wp, wlen); + break; + case 2: + error = xps_decode_tiff_fax(tiff, 2, stm, wp, wlen); + break; + case 3: + error = xps_decode_tiff_fax(tiff, 3, stm, wp, wlen); + break; + case 4: + error = xps_decode_tiff_fax(tiff, 4, stm, wp, wlen); + break; + case 5: + error = xps_decode_tiff_lzw(tiff, stm, wp, wlen); + break; + case 6: + error = fz_throw("deprecated JPEG in TIFF compression not supported"); + break; + case 7: + error = xps_decode_tiff_jpeg(tiff, stm, wp, wlen); + break; + case 8: + error = xps_decode_tiff_flate(tiff, stm, wp, wlen); + break; + case 32773: + error = xps_decode_tiff_packbits(tiff, stm, wp, wlen); + break; + default: + error = fz_throw("unknown TIFF compression: %d", tiff->compression); + } + + if (error) + return fz_rethrow(error, "cannot 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 += tiff->stride * tiff->rowsperstrip; + strip ++; + } + + /* Predictor (only for LZW and Flate) */ + if ((tiff->compression == 5 || tiff->compression == 8) && tiff->predictor == 2) + { + byte *p = tiff->samples; + for (i = 0; i < tiff->imagelength; i++) + { + xps_unpredict_tiff(p, tiff->imagewidth, tiff->samplesperpixel, tiff->bitspersample); + p += tiff->stride; + } + } + + /* RGBPal */ + if (tiff->photometric == 3 && tiff->colormap) + { + error = xps_expand_tiff_colormap(tiff); + if (error) + return fz_rethrow(error, "cannot expand colormap"); + } + + /* WhiteIsZero .. invert */ + if (tiff->photometric == 0) + { + byte *p = tiff->samples; + for (i = 0; i < tiff->imagelength; i++) + { + xps_invert_tiff(p, tiff->imagewidth, tiff->samplesperpixel, tiff->bitspersample, tiff->extrasamples); + p += tiff->stride; + } + } + + return fz_okay; +} + +static inline int readbyte(struct tiff *tiff) +{ + if (tiff->rp < tiff->ep) + return *tiff->rp++; + return EOF; +} + +static inline unsigned readshort(struct tiff *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(struct tiff *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 void +xps_read_tiff_bytes(unsigned char *p, struct tiff *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, struct tiff *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(struct tiff *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 = fz_malloc(count); + /* 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: + fz_warn("jpeg tables in tiff not implemented"); + tiff->jpegtables = tiff->bp + value; + tiff->jpegtableslen = count; + break; + + case StripOffsets: + tiff->stripoffsets = fz_calloc(count, sizeof(unsigned)); + xps_read_tiff_tag_value(tiff->stripoffsets, tiff, type, value, count); + break; + + case StripByteCounts: + tiff->stripbytecounts = fz_calloc(count, sizeof(unsigned)); + xps_read_tiff_tag_value(tiff->stripbytecounts, tiff, type, value, count); + break; + + case ColorMap: + tiff->colormap = fz_calloc(count, sizeof(unsigned)); + xps_read_tiff_tag_value(tiff->colormap, tiff, type, value, count); + break; + + case TileWidth: + case TileLength: + case TileOffsets: + case TileByteCounts: + return fz_throw("tiled tiffs not supported"); + + default: + /* printf("unknown tag: %d t=%d n=%d\n", tag, type, count); */ + break; + } + + return fz_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(struct tiff *tiff, byte *buf, int len) +{ + unsigned version; + unsigned offset; + unsigned count; + unsigned i; + int error; + + memset(tiff, 0, sizeof(struct tiff)); + + 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 fz_throw("not a TIFF file, wrong magic marker"); + + /* check version */ + version = readshort(tiff); + if (version != 42) + return fz_throw("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(tiff, offset); + if (error) + return fz_rethrow(error, "cannot read TIFF header tag"); + offset += 12; + } + + return fz_okay; +} + +int +xps_decode_tiff(fz_pixmap **imagep, byte *buf, int len) +{ + int error; + fz_pixmap *image; + struct tiff tiff; + + error = xps_decode_tiff_header(&tiff, buf, len); + if (error) + return fz_rethrow(error, "cannot decode tiff header"); + + /* Decode the image strips */ + + if (tiff.rowsperstrip > tiff.imagelength) + tiff.rowsperstrip = tiff.imagelength; + + error = xps_decode_tiff_strips(&tiff); + if (error) + return fz_rethrow(error, "cannot decode image data"); + + /* Byte swap 16-bit images to big endian if necessary */ + if (tiff.bitspersample == 16) + { + if (tiff.order == TII) + xps_swap_byte_order(tiff.samples, tiff.imagewidth * tiff.imagelength * tiff.samplesperpixel); + } + + /* Expand into fz_pixmap struct */ + + image = fz_newpixmap(tiff.colorspace, 0, 0, tiff.imagewidth, tiff.imagelength); + image->xres = tiff.xresolution; + image->yres = tiff.yresolution; + + fz_unpacktile(image, tiff.samples, tiff.samplesperpixel, tiff.bitspersample, tiff.stride, 0); + + /* We should only do this on non-pre-multiplied images, but files in the wild are bad */ + if (tiff.extrasamples /* == 2 */) + fz_premultiplypixmap(image); + + /* Clean up scratch memory */ + + if (tiff.colormap) fz_free(tiff.colormap); + if (tiff.stripoffsets) fz_free(tiff.stripoffsets); + if (tiff.stripbytecounts) fz_free(tiff.stripbytecounts); + if (tiff.samples) fz_free(tiff.samples); + + *imagep = image; + return fz_okay; +} diff --git a/xps/xps_tile.c b/xps/xps_tile.c new file mode 100644 index 00000000..59dd181b --- /dev/null +++ b/xps/xps_tile.c @@ -0,0 +1,347 @@ +#include "fitz.h" +#include "muxps.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 closure +{ + char *base_uri; + xps_resource *dict; + xml_element *root; + void *user; + void (*func)(xps_context*, fz_matrix, fz_rect, char*, xps_resource*, xml_element*, void*); +}; + +static void +xps_paint_tiling_brush_clipped(xps_context *ctx, fz_matrix ctm, fz_rect viewbox, struct closure *c) +{ + fz_path *path = fz_newpath(); + fz_moveto(path, viewbox.x0, viewbox.y0); + fz_lineto(path, viewbox.x0, viewbox.y1); + fz_lineto(path, viewbox.x1, viewbox.y1); + fz_lineto(path, viewbox.x1, viewbox.y0); + fz_closepath(path); + + ctx->dev->clippath(ctx->dev->user, path, 0, ctm); + + c->func(ctx, ctm, viewbox, c->base_uri, c->dict, c->root, c->user); + + ctx->dev->popclip(ctx->dev->user); +} + +static void +xps_paint_tiling_brush(xps_context *ctx, fz_matrix ctm, fz_rect viewbox, int tile_mode, struct closure *c) +{ + fz_matrix ttm; + + xps_paint_tiling_brush_clipped(ctx, ctm, viewbox, c); + + if (tile_mode == TILE_FLIP_X || tile_mode == TILE_FLIP_X_Y) + { + ttm = fz_concat(fz_translate(viewbox.x1 * 2, 0), ctm); + ttm = fz_concat(fz_scale(-1, 1), ttm); + xps_paint_tiling_brush_clipped(ctx, ttm, viewbox, c); + } + + if (tile_mode == TILE_FLIP_Y || tile_mode == TILE_FLIP_X_Y) + { + ttm = fz_concat(fz_translate(0, viewbox.y1 * 2), ctm); + ttm = fz_concat(fz_scale(1, -1), ttm); + xps_paint_tiling_brush_clipped(ctx, ttm, viewbox, c); + } + + if (tile_mode == TILE_FLIP_X_Y) + { + ttm = fz_concat(fz_translate(viewbox.x1 * 2, viewbox.y1 * 2), ctm); + ttm = fz_concat(fz_scale(-1, -1), ttm); + xps_paint_tiling_brush_clipped(ctx, ttm, viewbox, c); + } +} + +void +xps_parse_tiling_brush(xps_context *ctx, fz_matrix ctm, fz_rect area, + char *base_uri, xps_resource *dict, xml_element *root, + void (*func)(xps_context*, fz_matrix, fz_rect, char*, xps_resource*, xml_element*, void*), void *user) +{ + xml_element *node; + struct closure c; + + char *opacity_att; + char *transform_att; + char *viewbox_att; + char *viewport_att; + char *tile_mode_att; + char *viewbox_units_att; + char *viewport_units_att; + + xml_element *transform_tag = NULL; + + fz_matrix transform; + fz_rect viewbox; + fz_rect viewport; + float xstep, ystep; + float xscale, yscale; + int tile_mode; + + opacity_att = xml_att(root, "Opacity"); + transform_att = xml_att(root, "Transform"); + viewbox_att = xml_att(root, "Viewbox"); + viewport_att = xml_att(root, "Viewport"); + tile_mode_att = xml_att(root, "TileMode"); + viewbox_units_att = xml_att(root, "ViewboxUnits"); + viewport_units_att = xml_att(root, "ViewportUnits"); + + c.base_uri = base_uri; + c.dict = dict; + c.root = root; + c.user = user; + c.func = func; + + for (node = xml_down(root); node; node = xml_next(node)) + { + if (!strcmp(xml_tag(node), "ImageBrush.Transform")) + transform_tag = xml_down(node); + if (!strcmp(xml_tag(node), "VisualBrush.Transform")) + transform_tag = xml_down(node); + } + + xps_resolve_resource_reference(ctx, dict, &transform_att, &transform_tag, NULL); + + transform = fz_identity; + if (transform_att) + xps_parse_render_transform(ctx, transform_att, &transform); + if (transform_tag) + xps_parse_matrix_transform(ctx, transform_tag, &transform); + ctm = fz_concat(transform, ctm); + + viewbox = fz_unitrect; + if (viewbox_att) + xps_parse_rectangle(ctx, viewbox_att, &viewbox); + + viewport = fz_unitrect; + if (viewport_att) + xps_parse_rectangle(ctx, viewport_att, &viewport); + + /* some sanity checks on the viewport/viewbox size */ + if (fabs(viewport.x1 - viewport.x0) < 0.01) return; + if (fabs(viewport.y1 - viewport.y0) < 0.01) return; + if (fabs(viewbox.x1 - viewbox.x0) < 0.01) return; + if (fabs(viewbox.y1 - viewbox.y0) < 0.01) return; + + xstep = viewbox.x1 - viewbox.x0; + ystep = viewbox.y1 - viewbox.y0; + + xscale = (viewport.x1 - viewport.x0) / xstep; + yscale = (viewport.y1 - viewport.y0) / ystep; + + 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; + } + + if (tile_mode == TILE_FLIP_X || tile_mode == TILE_FLIP_X_Y) + xstep *= 2; + if (tile_mode == TILE_FLIP_Y || tile_mode == TILE_FLIP_X_Y) + ystep *= 2; + + xps_begin_opacity(ctx, ctm, area, base_uri, dict, opacity_att, NULL); + + ctm = fz_concat(fz_translate(viewport.x0, viewport.y0), ctm); + ctm = fz_concat(fz_scale(xscale, yscale), ctm); + ctm = fz_concat(fz_translate(-viewbox.x0, -viewbox.y0), ctm); + + if (tile_mode != TILE_NONE) + { + fz_matrix invctm = fz_invertmatrix(ctm); + fz_rect bbox = fz_transformrect(invctm, area); + int x0 = floorf(bbox.x0 / xstep); + int y0 = floorf(bbox.y0 / ystep); + int x1 = ceilf(bbox.x1 / xstep); + int y1 = ceilf(bbox.y1 / ystep); + int x, y; + + for (y = y0; y < y1; y++) + { + for (x = x0; x < x1; x++) + { + fz_matrix ttm = fz_concat(fz_translate(xstep * x, ystep * y), ctm); + xps_paint_tiling_brush(ctx, ttm, viewbox, tile_mode, &c); + } + } + } + else + { + xps_paint_tiling_brush(ctx, ctm, viewbox, tile_mode, &c); + } + + xps_end_opacity(ctx, base_uri, dict, opacity_att, NULL); +} + +static void +xps_paint_visual_brush(xps_context *ctx, fz_matrix ctm, fz_rect area, + char *base_uri, xps_resource *dict, xml_element *root, void *visual_tag) +{ + xps_parse_element(ctx, ctm, area, base_uri, dict, (xml_element *)visual_tag); +} + +void +xps_parse_visual_brush(xps_context *ctx, fz_matrix ctm, fz_rect area, + char *base_uri, xps_resource *dict, xml_element *root) +{ + xml_element *node; + + char *visual_uri; + char *visual_att; + xml_element *visual_tag = NULL; + + visual_att = xml_att(root, "Visual"); + + for (node = xml_down(root); node; node = xml_next(node)) + { + if (!strcmp(xml_tag(node), "VisualBrush.Visual")) + visual_tag = xml_down(node); + } + + visual_uri = base_uri; + xps_resolve_resource_reference(ctx, dict, &visual_att, &visual_tag, &visual_uri); + + if (visual_tag) + { + xps_parse_tiling_brush(ctx, ctm, area, + visual_uri, dict, root, xps_paint_visual_brush, visual_tag); + } +} + +void +xps_parse_canvas(xps_context *ctx, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, xml_element *root) +{ + xps_resource *new_dict = NULL; + xml_element *node; + char *opacity_mask_uri; + int code; + + char *transform_att; + char *clip_att; + char *opacity_att; + char *opacity_mask_att; + + xml_element *transform_tag = NULL; + xml_element *clip_tag = NULL; + xml_element *opacity_mask_tag = NULL; + + fz_matrix transform; + + transform_att = xml_att(root, "RenderTransform"); + clip_att = xml_att(root, "Clip"); + opacity_att = xml_att(root, "Opacity"); + opacity_mask_att = xml_att(root, "OpacityMask"); + + for (node = xml_down(root); node; node = xml_next(node)) + { + if (!strcmp(xml_tag(node), "Canvas.Resources") && xml_down(node)) + { + code = xps_parse_resource_dictionary(ctx, &new_dict, base_uri, xml_down(node)); + if (code) + fz_catch(code, "cannot load Canvas.Resources"); + else + { + new_dict->parent = dict; + dict = new_dict; + } + } + + if (!strcmp(xml_tag(node), "Canvas.RenderTransform")) + transform_tag = xml_down(node); + if (!strcmp(xml_tag(node), "Canvas.Clip")) + clip_tag = xml_down(node); + if (!strcmp(xml_tag(node), "Canvas.OpacityMask")) + opacity_mask_tag = xml_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); + + transform = fz_identity; + if (transform_att) + xps_parse_render_transform(ctx, transform_att, &transform); + if (transform_tag) + xps_parse_matrix_transform(ctx, transform_tag, &transform); + ctm = fz_concat(transform, ctm); + + if (clip_att || clip_tag) + xps_clip(ctx, ctm, dict, clip_att, clip_tag); + + xps_begin_opacity(ctx, ctm, area, opacity_mask_uri, dict, opacity_att, opacity_mask_tag); + + for (node = xml_down(root); node; node = xml_next(node)) + { + xps_parse_element(ctx, ctm, area, base_uri, dict, node); + } + + xps_end_opacity(ctx, opacity_mask_uri, dict, opacity_att, opacity_mask_tag); + + if (clip_att || clip_tag) + ctx->dev->popclip(ctx->dev->user); + + if (new_dict) + xps_free_resource_dictionary(ctx, new_dict); +} + +void +xps_parse_fixed_page(xps_context *ctx, fz_matrix ctm, xps_page *page) +{ + xml_element *node; + xps_resource *dict; + char base_uri[1024]; + fz_rect area; + char *s; + int code; + + fz_strlcpy(base_uri, page->name, sizeof base_uri); + s = strrchr(base_uri, '/'); + if (s) + s[1] = 0; + + dict = NULL; + + ctx->opacity_top = 0; + ctx->opacity[0] = 1; + + if (!page->root) + return; + + area = fz_transformrect(fz_scale(page->width, page->height), fz_unitrect); + + for (node = xml_down(page->root); node; node = xml_next(node)) + { + if (!strcmp(xml_tag(node), "FixedPage.Resources") && xml_down(node)) + { + code = xps_parse_resource_dictionary(ctx, &dict, base_uri, xml_down(node)); + if (code) + fz_catch(code, "cannot load FixedPage.Resources"); + } + xps_parse_element(ctx, ctm, area, base_uri, dict, node); + } + + if (dict) + { + xps_free_resource_dictionary(ctx, dict); + } +} diff --git a/xps/xps_util.c b/xps/xps_util.c new file mode 100644 index 00000000..76175d68 --- /dev/null +++ b/xps/xps_util.c @@ -0,0 +1,94 @@ +#include "fitz.h" +#include "muxps.h" + +static inline int tolower(int c) +{ + if (c >= 'A' && c <= 'Z') + return c + 32; + return c; +} + +int +xps_strcasecmp(char *a, char *b) +{ + while (tolower(*a) == tolower(*b)) + { + if (*a++ == 0) + return 0; + b++; + } + return tolower(*a) - tolower(*b); +} + +#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] == '/') + { + fz_strlcpy(output, path, output_size); + } + else + { + fz_strlcpy(output, base_uri, output_size); + fz_strlcat(output, "/", output_size); + fz_strlcat(output, path, output_size); + } + xps_clean_path(output); +} diff --git a/xps/xps_xml.c b/xps/xps_xml.c new file mode 100644 index 00000000..8448ecef --- /dev/null +++ b/xps/xps_xml.c @@ -0,0 +1,387 @@ +#include "fitz.h" +#include "muxps.h" + +struct attribute +{ + char name[40]; + char *value; + struct attribute *next; +}; + +struct element +{ + char name[40]; + struct attribute *atts; + struct element *up, *down, *next; +}; + +struct parser +{ + struct element *head; +}; + +static inline void indent(int n) +{ + while (n--) putchar(' '); +} + +void xml_print_element(struct element *item, int level) +{ + while (item) { + struct attribute *att; + indent(level); + printf("<%s", item->name); + for (att = item->atts; att; att = att->next) + printf(" %s=\"%s\"", att->name, att->value); + if (item->down) { + printf(">\n"); + xml_print_element(item->down, level + 1); + indent(level); + printf("</%s>\n", item->name); + } + else { + printf("/>\n"); + } + item = item->next; + } +} + +struct element *xml_next(struct element *item) +{ + return item->next; +} + +struct element *xml_down(struct element *item) +{ + return item->down; +} + +char *xml_tag(struct element *item) +{ + return item->name; +} + +char *xml_att(struct element *item, const char *name) +{ + struct attribute *att; + for (att = item->atts; att; att = att->next) + if (!strcmp(att->name, name)) + return att->value; + return NULL; +} + +static void xml_free_attribute(struct attribute *att) +{ + while (att) { + struct attribute *next = att->next; + if (att->value) + fz_free(att->value); + fz_free(att); + att = next; + } +} + +void xml_free_element(struct element *item) +{ + while (item) { + struct element *next = item->next; + if (item->atts) + xml_free_attribute(item->atts); + if (item->down) + xml_free_element(item->down); + fz_free(item); + item = next; + } +} + +static int xml_parse_entity(int *c, char *a) +{ + char *b; + if (a[1] == '#') { + if (a[2] == 'x') + *c = strtol(a + 3, &b, 16); + else + *c = strtol(a + 2, &b, 10); + if (*b == ';') + return b - a + 1; + } + else if (a[1] == 'l' && a[2] == 't' && a[3] == ';') { + *c = '<'; + return 4; + } + else if (a[1] == 'g' && a[2] == 't' && a[3] == ';') { + *c = '>'; + return 4; + } + else if (a[1] == 'a' && a[2] == 'm' && a[3] == 'p' && a[4] == ';') { + *c = '&'; + return 5; + } + else if (a[1] == 'a' && a[2] == 'p' && a[3] == 'o' && a[4] == 's' && a[5] == ';') { + *c = '\''; + return 6; + } + else if (a[1] == 'q' && a[2] == 'u' && a[3] == 'o' && a[4] == 't' && a[5] == ';') { + *c = '"'; + return 6; + } + *c = *a++; + return 1; +} + +static void xml_emit_open_tag(struct parser *parser, char *a, char *b) +{ + struct element *head, *tail; + + head = fz_malloc(sizeof(struct element)); + if (b - a > sizeof(head->name)) + b = a + sizeof(head->name); + memcpy(head->name, a, b - a); + head->name[b - a] = 0; + + head->atts = NULL; + head->up = parser->head; + head->down = NULL; + head->next = NULL; + + if (!parser->head->down) { + parser->head->down = head; + } + else { + tail = parser->head->down; + while (tail->next) + tail = tail->next; + tail->next = head; + } + + parser->head = head; +} + +static void xml_emit_att_name(struct parser *parser, char *a, char *b) +{ + struct element *head = parser->head; + struct attribute *att; + + att = fz_malloc(sizeof(struct attribute)); + if (b - a > sizeof(att->name)) + b = a + sizeof(att->name); + memcpy(att->name, a, b - a); + att->name[b - a] = 0; + att->value = NULL; + att->next = head->atts; + head->atts = att; +} + +static void xml_emit_att_value(struct parser *parser, char *a, char *b) +{ + struct element *head = parser->head; + struct attribute *att = head->atts; + char *s; + int c; + + /* entities are all longer than UTFmax so runetochar is safe */ + s = att->value = fz_malloc(b - a + 1); + while (a < b) { + if (*a == '&') { + a += xml_parse_entity(&c, a); + s += runetochar(s, &c); + } + else { + *s++ = *a++; + } + } + *s = 0; +} + +static void xml_emit_close_tag(struct parser *parser) +{ + if (parser->head->up) + parser->head = parser->head->up; +} + +static inline int isname(int c) +{ + return c == '.' || c == '-' || c == '_' || c == ':' || + (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z'); +} + +static inline int iswhite(int c) +{ + return c == ' ' || c == '\r' || c == '\n' || c == '\t'; +} + +static char *xml_parse_document_imp(struct parser *x, char *p) +{ + char *mark; + int quote; + +parse_text: + mark = p; + while (*p && *p != '<') ++p; + if (*p == '<') { ++p; goto parse_element; } + return NULL; + +parse_element: + if (*p == '/') { ++p; goto parse_closing_element; } + if (*p == '!') { ++p; goto parse_comment; } + if (*p == '?') { ++p; goto parse_processing_instruction; } + while (iswhite(*p)) ++p; + if (isname(*p)) + goto parse_element_name; + return "syntax error in element"; + +parse_comment: + if (*p == '[') goto parse_cdata; + if (*p++ != '-') return "syntax error in comment (<! not followed by --)"; + if (*p++ != '-') return "syntax error in comment (<!- not followed by -)"; + mark = p; + while (*p) { + if (p[0] == '-' && p[1] == '-' && p[2] == '>') { + p += 3; + goto parse_text; + } + ++p; + } + return "end of data in comment"; + +parse_cdata: + if (p[1] != 'C' || p[2] != 'D' || p[3] != 'A' || p[4] != 'T' || p[5] != 'A' || p[6] != '[') + return "syntax error in CDATA section"; + p += 7; + mark = p; + while (*p) { + if (p[0] == ']' && p[1] == ']' && p[2] == '>') { + p += 3; + goto parse_text; + } + ++p; + } + return "end of data in CDATA section"; + +parse_processing_instruction: + while (*p) { + if (p[0] == '?' && p[1] == '>') { + p += 2; + goto parse_text; + } + ++p; + } + return "end of data in processing instruction"; + +parse_closing_element: + while (iswhite(*p)) ++p; + mark = p; + while (isname(*p)) ++p; + while (iswhite(*p)) ++p; + if (*p != '>') + return "syntax error in closing element"; + xml_emit_close_tag(x); + ++p; + goto parse_text; + +parse_element_name: + mark = p; + while (isname(*p)) ++p; + xml_emit_open_tag(x, mark, p); + if (*p == '>') { ++p; goto parse_text; } + if (p[0] == '/' && p[1] == '>') { + xml_emit_close_tag(x); + p += 2; + goto parse_text; + } + if (iswhite(*p)) + goto parse_attributes; + return "syntax error after element name"; + +parse_attributes: + while (iswhite(*p)) ++p; + if (isname(*p)) + goto parse_attribute_name; + if (*p == '>') { ++p; goto parse_text; } + if (p[0] == '/' && p[1] == '>') { + xml_emit_close_tag(x); + p += 2; + goto parse_text; + } + return "syntax error in attributes"; + +parse_attribute_name: + mark = p; + while (isname(*p)) ++p; + xml_emit_att_name(x, mark, p); + while (iswhite(*p)) ++p; + if (*p == '=') { ++p; goto parse_attribute_value; } + return "syntax error after attribute name"; + +parse_attribute_value: + while (iswhite(*p)) ++p; + quote = *p++; + if (quote != '"' && quote != '\'') + return "missing quote character"; + mark = p; + while (*p && *p != quote) ++p; + if (*p == quote) { + xml_emit_att_value(x, mark, p++); + goto parse_attributes; + } + return "end of data in attribute value"; +} + +static char *convert_to_utf8(unsigned char *s, int n) +{ + unsigned char *e = s + n; + char *dst, *d; + int c; + + if (s[0] == 0xFE && s[1] == 0xFF) { + dst = d = fz_malloc(n * 2); + while (s + 1 < e) { + c = s[0] << 8 | s[1]; + d += runetochar(d, &c); + s += 2; + } + *d = 0; + return dst; + } + + if (s[0] == 0xFF && s[1] == 0xFE) { + dst = d = fz_malloc(n * 2); + while (s + 1 < e) { + c = s[0] | s[1] << 8; + d += runetochar(d, &c); + s += 2; + } + *d = 0; + return dst; + } + + return (char*)s; +} + +struct element * +xml_parse_document(unsigned char *s, int n) +{ + struct parser parser; + struct element root; + char *p, *error; + + /* s is already null-terminated (see xps_new_part) */ + + memset(&root, 0, sizeof(root)); + parser.head = &root; + + p = convert_to_utf8(s, n); + + error = xml_parse_document_imp(&parser, p); + if (error) { + fz_throw(error); + return NULL; + } + + if (p != (char*)s) + fz_free(p); + + return root.down; +} diff --git a/xps/xps_zip.c b/xps/xps_zip.c new file mode 100644 index 00000000..d353ec11 --- /dev/null +++ b/xps/xps_zip.c @@ -0,0 +1,472 @@ +#include "fitz.h" +#include "muxps.h" + +#include <zlib.h> + +xps_part * +xps_new_part(xps_context *ctx, char *name, int size) +{ + xps_part *part; + + part = fz_malloc(sizeof(xps_part)); + part->name = fz_strdup(name); + part->size = size; + part->data = fz_malloc(size + 1); + part->data[size] = 0; /* null-terminate for xml parser */ + + return part; +} + +void +xps_free_part(xps_context *ctx, xps_part *part) +{ + fz_free(part->name); + fz_free(part->data); + fz_free(part); +} + +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 *ctx, int items, int size) +{ + return fz_calloc(items, size); +} + +static void +xps_zip_free(xps_context *ctx, void *ptr) +{ + fz_free(ptr); +} + +static int +xps_compare_entries(const void *a0, const void *b0) +{ + xps_entry *a = (xps_entry*) a0; + xps_entry *b = (xps_entry*) b0; + return xps_strcasecmp(a->name, b->name); +} + +static xps_entry * +xps_find_zip_entry(xps_context *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; +} + +static int +xps_read_zip_entry(xps_context *ctx, xps_entry *ent, unsigned char *outbuf) +{ + z_stream stream; + unsigned char *inbuf; + int sig; + int version, general, method; + int namelength, extralength; + int code; + + fseek(ctx->file, ent->offset, 0); + + sig = getlong(ctx->file); + if (sig != ZIP_LOCAL_FILE_SIG) + return fz_throw("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 = fz_malloc(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 fz_throw("zlib inflateInit2 error: %s", stream.msg); + code = inflate(&stream, Z_FINISH); + if (code != Z_STREAM_END) + { + inflateEnd(&stream); + return fz_throw("zlib inflate error: %s", stream.msg); + } + code = inflateEnd(&stream); + if (code != Z_OK) + return fz_throw("zlib inflateEnd error: %s", stream.msg); + + fz_free(inbuf); + } + else + { + return fz_throw("unknown compression method (%d)", method); + } + + return fz_okay; +} + +/* + * Read the central directory in a zip file. + */ + +static int +xps_read_zip_dir(xps_context *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 fz_throw("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 = fz_calloc(count, sizeof(xps_entry)); + memset(ctx->zip_table, 0, sizeof(xps_entry) * count); + + fseek(ctx->file, offset, 0); + + for (i = 0; i < count; i++) + { + sig = getlong(ctx->file); + if (sig != ZIP_CENTRAL_DIRECTORY_SIG) + return fz_throw("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 = fz_malloc(namesize + 1); + 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), xps_compare_entries); + + return fz_okay; +} + +static int +xps_find_and_read_zip_dir(xps_context *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 fz_throw("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 fz_throw("cannot find end of central directory"); +} + +/* + * Read and interleave split parts from a ZIP file. + */ +static xps_part * +xps_read_zip_part(xps_context *ctx, char *partname) +{ + char buf[2048]; + xps_entry *ent; + xps_part *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 * +xps_read_dir_part(xps_context *ctx, char *name) +{ + char buf[2048]; + xps_part *part; + FILE *file; + int count, size, offset, i, n; + + fz_strlcpy(buf, ctx->directory, sizeof buf); + fz_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 * +xps_read_part(xps_context *ctx, char *partname) +{ + if (ctx->directory) + return xps_read_dir_part(ctx, partname); + return xps_read_zip_part(ctx, partname); +} + +int +xps_open_file(xps_context *ctx, char *filename) +{ + char buf[2048]; + int code; + char *p; + + ctx->file = fopen(filename, "rb"); + if (!ctx->file) + return fz_throw("cannot open file: '%s'", filename); + + if (strstr(filename, "/_rels/.rels") || strstr(filename, "\\_rels\\.rels")) + { + fz_strlcpy(buf, filename, sizeof buf); + p = strstr(buf, "/_rels/.rels"); + if (!p) + p = strstr(buf, "\\_rels\\.rels"); + *p = 0; + ctx->directory = fz_strdup(buf); + } + else + { + code = xps_find_and_read_zip_dir(ctx); + if (code < 0) + return fz_rethrow(code, "cannot read zip central directory"); + } + + code = xps_read_page_list(ctx); + if (code) + return fz_rethrow(code, "cannot read page list"); + + return fz_okay; +} + +xps_context * +xps_new_context(void) +{ + xps_context *ctx; + + ctx = fz_malloc(sizeof(xps_context)); + + memset(ctx, 0, sizeof(xps_context)); + + ctx->font_table = xps_hash_new(); + ctx->colorspace_table = xps_hash_new(); + + ctx->start_part = NULL; + + return ctx; +} + +static void xps_free_key_func(void *ptr) +{ + fz_free(ptr); +} + +static void xps_free_font_func(void *ptr) +{ + fz_dropfont(ptr); +} + +int +xps_free_context(xps_context *ctx) +{ + int i; + + if (ctx->file) + fclose(ctx->file); + + for (i = 0; i < ctx->zip_count; i++) + fz_free(ctx->zip_table[i].name); + fz_free(ctx->zip_table); + + xps_hash_free(ctx->font_table, xps_free_key_func, xps_free_font_func); + xps_hash_free(ctx->colorspace_table, xps_free_key_func, NULL); + + xps_free_page_list(ctx); + + return 0; +} |