summaryrefslogtreecommitdiff
path: root/source/xps
diff options
context:
space:
mode:
authorTor Andersson <tor.andersson@artifex.com>2013-06-19 15:29:44 +0200
committerTor Andersson <tor.andersson@artifex.com>2013-06-20 16:45:35 +0200
commit0a927854a10e1e6b9770a81e2e1d9f3093631757 (patch)
tree3d65d820d9fdba2d0d394d99c36290c851b78ca0 /source/xps
parent1ae8f19179c5f0f8c6352b3c7855465325d5449a (diff)
downloadmupdf-0a927854a10e1e6b9770a81e2e1d9f3093631757.tar.xz
Rearrange source files.
Diffstat (limited to 'source/xps')
-rw-r--r--source/xps/xps-common.c311
-rw-r--r--source/xps/xps-doc.c534
-rw-r--r--source/xps/xps-glyphs.c623
-rw-r--r--source/xps/xps-gradient.c525
-rw-r--r--source/xps/xps-image.c128
-rw-r--r--source/xps/xps-outline.c152
-rw-r--r--source/xps/xps-path.c1053
-rw-r--r--source/xps/xps-resource.c172
-rw-r--r--source/xps/xps-tile.c390
-rw-r--r--source/xps/xps-util.c165
-rw-r--r--source/xps/xps-zip.c697
11 files changed, 4750 insertions, 0 deletions
diff --git a/source/xps/xps-common.c b/source/xps/xps-common.c
new file mode 100644
index 00000000..ec16d879
--- /dev/null
+++ b/source/xps/xps-common.c
@@ -0,0 +1,311 @@
+#include "mupdf/xps.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;
+}
+
+fz_xml *
+xps_lookup_alternate_content(fz_xml *node)
+{
+ for (node = fz_xml_down(node); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "mc:Choice") && fz_xml_att(node, "Requires"))
+ {
+ char list[64];
+ char *next = list, *item;
+ fz_strlcpy(list, fz_xml_att(node, "Requires"), sizeof(list));
+ while ((item = fz_strsep(&next, " \t\r\n")) && (!*item || !strcmp(item, "xps")));
+ if (!item)
+ return fz_xml_down(node);
+ }
+ else if (!strcmp(fz_xml_tag(node), "mc:Fallback"))
+ return fz_xml_down(node);
+ }
+ return NULL;
+}
+
+void
+xps_parse_brush(xps_document *doc, const fz_matrix *ctm, const fz_rect *area, char *base_uri, xps_resource *dict, fz_xml *node)
+{
+ if (doc->cookie && doc->cookie->abort)
+ return;
+ /* SolidColorBrushes are handled in a special case and will never show up here */
+ if (!strcmp(fz_xml_tag(node), "ImageBrush"))
+ xps_parse_image_brush(doc, ctm, area, base_uri, dict, node);
+ else if (!strcmp(fz_xml_tag(node), "VisualBrush"))
+ xps_parse_visual_brush(doc, ctm, area, base_uri, dict, node);
+ else if (!strcmp(fz_xml_tag(node), "LinearGradientBrush"))
+ xps_parse_linear_gradient_brush(doc, ctm, area, base_uri, dict, node);
+ else if (!strcmp(fz_xml_tag(node), "RadialGradientBrush"))
+ xps_parse_radial_gradient_brush(doc, ctm, area, base_uri, dict, node);
+ else
+ fz_warn(doc->ctx, "unknown brush tag: %s", fz_xml_tag(node));
+}
+
+void
+xps_parse_element(xps_document *doc, const fz_matrix *ctm, const fz_rect *area, char *base_uri, xps_resource *dict, fz_xml *node)
+{
+ if (doc->cookie && doc->cookie->abort)
+ return;
+ if (!strcmp(fz_xml_tag(node), "Path"))
+ xps_parse_path(doc, ctm, base_uri, dict, node);
+ if (!strcmp(fz_xml_tag(node), "Glyphs"))
+ xps_parse_glyphs(doc, ctm, base_uri, dict, node);
+ if (!strcmp(fz_xml_tag(node), "Canvas"))
+ xps_parse_canvas(doc, ctm, area, base_uri, dict, node);
+ if (!strcmp(fz_xml_tag(node), "mc:AlternateContent"))
+ {
+ node = xps_lookup_alternate_content(node);
+ if (node)
+ xps_parse_element(doc, ctm, area, base_uri, dict, node);
+ }
+ /* skip unknown tags (like Foo.Resources and similar) */
+}
+
+void
+xps_begin_opacity(xps_document *doc, const fz_matrix *ctm, const fz_rect *area,
+ char *base_uri, xps_resource *dict,
+ char *opacity_att, fz_xml *opacity_mask_tag)
+{
+ float opacity;
+
+ if (!opacity_att && !opacity_mask_tag)
+ return;
+
+ opacity = 1;
+ if (opacity_att)
+ opacity = fz_atof(opacity_att);
+
+ if (opacity_mask_tag && !strcmp(fz_xml_tag(opacity_mask_tag), "SolidColorBrush"))
+ {
+ char *scb_opacity_att = fz_xml_att(opacity_mask_tag, "Opacity");
+ char *scb_color_att = fz_xml_att(opacity_mask_tag, "Color");
+ if (scb_opacity_att)
+ opacity = opacity * fz_atof(scb_opacity_att);
+ if (scb_color_att)
+ {
+ fz_colorspace *colorspace;
+ float samples[32];
+ xps_parse_color(doc, base_uri, scb_color_att, &colorspace, samples);
+ opacity = opacity * samples[0];
+ }
+ opacity_mask_tag = NULL;
+ }
+
+ if (doc->opacity_top + 1 < nelem(doc->opacity))
+ {
+ doc->opacity[doc->opacity_top + 1] = doc->opacity[doc->opacity_top] * opacity;
+ doc->opacity_top++;
+ }
+
+ if (opacity_mask_tag)
+ {
+ fz_begin_mask(doc->dev, area, 0, NULL, NULL);
+ xps_parse_brush(doc, ctm, area, base_uri, dict, opacity_mask_tag);
+ fz_end_mask(doc->dev);
+ }
+}
+
+void
+xps_end_opacity(xps_document *doc, char *base_uri, xps_resource *dict,
+ char *opacity_att, fz_xml *opacity_mask_tag)
+{
+ if (!opacity_att && !opacity_mask_tag)
+ return;
+
+ if (doc->opacity_top > 0)
+ doc->opacity_top--;
+
+ if (opacity_mask_tag)
+ {
+ if (strcmp(fz_xml_tag(opacity_mask_tag), "SolidColorBrush"))
+ fz_pop_clip(doc->dev);
+ }
+}
+
+void
+xps_parse_render_transform(xps_document *doc, char *transform, fz_matrix *matrix)
+{
+ float args[6];
+ char *s = transform;
+ int i;
+
+ args[0] = 1; args[1] = 0;
+ args[2] = 0; args[3] = 1;
+ args[4] = 0; args[5] = 0;
+
+ for (i = 0; i < 6 && *s; i++)
+ {
+ args[i] = fz_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_document *doc, fz_xml *root, fz_matrix *matrix)
+{
+ char *transform;
+
+ *matrix = fz_identity;
+
+ if (!strcmp(fz_xml_tag(root), "MatrixTransform"))
+ {
+ transform = fz_xml_att(root, "Matrix");
+ if (transform)
+ xps_parse_render_transform(doc, transform, matrix);
+ }
+}
+
+void
+xps_parse_rectangle(xps_document *doc, char *text, fz_rect *rect)
+{
+ float args[4];
+ char *s = text;
+ int i;
+
+ args[0] = 0; args[1] = 0;
+ args[2] = 1; args[3] = 1;
+
+ for (i = 0; i < 4 && *s; i++)
+ {
+ args[i] = fz_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_document *doc, char *base_uri, char *string,
+ fz_colorspace **csp, float *samples)
+{
+ char *p;
+ int i, n;
+ char buf[1024];
+ char *profile;
+
+ *csp = fz_device_rgb(doc->ctx);
+
+ samples[0] = 1;
+ samples[1] = 0;
+ samples[2] = 0;
+ samples[3] = 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;
+ 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;
+ samples[1] /= 255;
+ samples[2] /= 255;
+ samples[3] /= 255;
+ }
+
+ 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 */
+ fz_strlcpy(buf, string, sizeof buf);
+
+ profile = strchr(buf, ' ');
+ if (!profile)
+ {
+ fz_warn(doc->ctx, "cannot find icc profile uri in '%s'", string);
+ return;
+ }
+
+ *profile++ = 0;
+ p = strchr(profile, ' ');
+ if (!p)
+ {
+ fz_warn(doc->ctx, "cannot find component values in '%s'", profile);
+ return;
+ }
+
+ *p++ = 0;
+ n = count_commas(p) + 1;
+ i = 0;
+ while (i < n)
+ {
+ samples[i++] = fz_atof(p);
+ p = strchr(p, ',');
+ if (!p)
+ break;
+ p ++;
+ if (*p == ' ')
+ p ++;
+ }
+ while (i < n)
+ {
+ samples[i++] = 0;
+ }
+
+ /* TODO: load ICC profile */
+ switch (n)
+ {
+ case 2: *csp = fz_device_gray(doc->ctx); break;
+ case 4: *csp = fz_device_rgb(doc->ctx); break;
+ case 5: *csp = fz_device_cmyk(doc->ctx); break;
+ default: *csp = fz_device_gray(doc->ctx); break;
+ }
+ }
+}
+
+void
+xps_set_color(xps_document *doc, fz_colorspace *colorspace, float *samples)
+{
+ int i;
+ doc->colorspace = colorspace;
+ for (i = 0; i < colorspace->n; i++)
+ doc->color[i] = samples[i + 1];
+ doc->alpha = samples[0] * doc->opacity[doc->opacity_top];
+}
diff --git a/source/xps/xps-doc.c b/source/xps/xps-doc.c
new file mode 100644
index 00000000..9953cb37
--- /dev/null
+++ b/source/xps/xps-doc.c
@@ -0,0 +1,534 @@
+#include "mupdf/xps.h"
+
+#define REL_START_PART \
+ "http://schemas.microsoft.com/xps/2005/06/fixedrepresentation"
+#define REL_DOC_STRUCTURE \
+ "http://schemas.microsoft.com/xps/2005/06/documentstructure"
+#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 REL_START_PART_OXPS \
+ "http://schemas.openxps.org/oxps/v1.0/fixedrepresentation"
+#define REL_DOC_STRUCTURE_OXPS \
+ "http://schemas.openxps.org/oxps/v1.0/documentstructure"
+
+static void
+xps_rels_for_part(char *buf, char *name, int buflen)
+{
+ char *p, *basename;
+ p = strrchr(name, '/');
+ basename = p ? p + 1 : name;
+ fz_strlcpy(buf, name, buflen);
+ p = strrchr(buf, '/');
+ if (p) *p = 0;
+ fz_strlcat(buf, "/_rels/", buflen);
+ fz_strlcat(buf, basename, buflen);
+ fz_strlcat(buf, ".rels", buflen);
+}
+
+/*
+ * The FixedDocumentSequence and FixedDocument parts determine
+ * which parts correspond to actual pages, and the page order.
+ */
+
+void
+xps_print_page_list(xps_document *doc)
+{
+ xps_fixdoc *fixdoc = doc->first_fixdoc;
+ xps_page *page = doc->first_page;
+
+ if (doc->start_part)
+ printf("start part %s\n", doc->start_part);
+
+ while (fixdoc)
+ {
+ printf("fixdoc %s\n", fixdoc->name);
+ fixdoc = fixdoc->next;
+ }
+
+ while (page)
+ {
+ printf("page[%d] %s w=%d h=%d\n", page->number, page->name, page->width, page->height);
+ page = page->next;
+ }
+}
+
+static void
+xps_add_fixed_document(xps_document *doc, char *name)
+{
+ xps_fixdoc *fixdoc;
+
+ /* Check for duplicates first */
+ for (fixdoc = doc->first_fixdoc; fixdoc; fixdoc = fixdoc->next)
+ if (!strcmp(fixdoc->name, name))
+ return;
+
+ fixdoc = fz_malloc_struct(doc->ctx, xps_fixdoc);
+ fixdoc->name = fz_strdup(doc->ctx, name);
+ fixdoc->outline = NULL;
+ fixdoc->next = NULL;
+
+ if (!doc->first_fixdoc)
+ {
+ doc->first_fixdoc = fixdoc;
+ doc->last_fixdoc = fixdoc;
+ }
+ else
+ {
+ doc->last_fixdoc->next = fixdoc;
+ doc->last_fixdoc = fixdoc;
+ }
+}
+
+void
+xps_add_link(xps_document *doc, const fz_rect *area, char *base_uri, char *target_uri)
+{
+ int len;
+ char *buffer = NULL;
+ char *uri;
+ xps_target *target;
+ fz_link_dest dest;
+ fz_link *link;
+ fz_context *ctx = doc->ctx;
+
+ fz_var(buffer);
+
+ if (doc->current_page == NULL || doc->current_page->links_resolved)
+ return;
+
+ fz_try(ctx)
+ {
+ len = 2 + (base_uri ? strlen(base_uri) : 0) +
+ (target_uri ? strlen(target_uri) : 0);
+ buffer = fz_malloc(doc->ctx, len);
+ xps_resolve_url(buffer, base_uri, target_uri, len);
+ if (xps_url_is_remote(buffer))
+ {
+ dest.kind = FZ_LINK_URI;
+ dest.ld.uri.is_map = 0;
+ dest.ld.uri.uri = buffer;
+ buffer = NULL;
+ }
+ else
+ {
+ uri = buffer;
+
+ /* FIXME: This won't work for remote docs */
+ /* Skip until we find the fragment marker */
+ while (*uri && *uri != '#')
+ uri++;
+ if (*uri == '#')
+ uri++;
+
+ for (target = doc->target; target; target = target->next)
+ if (!strcmp(target->name, uri))
+ break;
+
+ if (target == NULL)
+ break;
+
+ dest.kind = FZ_LINK_GOTO;
+ dest.ld.gotor.flags = 0;
+ dest.ld.gotor.lt.x = 0;
+ dest.ld.gotor.lt.y = 0;
+ dest.ld.gotor.rb.x = 0;
+ dest.ld.gotor.rb.y = 0;
+ dest.ld.gotor.page = target->page;
+ dest.ld.gotor.file_spec = NULL;
+ dest.ld.gotor.new_window = 0;
+ }
+
+ link = fz_new_link(doc->ctx, area, dest);
+ link->next = doc->current_page->links;
+ doc->current_page->links = link;
+ }
+ fz_always(ctx)
+ {
+ fz_free(doc->ctx, buffer);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+fz_link *
+xps_load_links(xps_document *doc, xps_page *page)
+{
+ if (!page->links_resolved)
+ fz_warn(doc->ctx, "xps_load_links before page has been executed!");
+ return fz_keep_link(doc->ctx, page->links);
+}
+
+static void
+xps_add_fixed_page(xps_document *doc, char *name, int width, int height)
+{
+ xps_page *page;
+
+ /* Check for duplicates first */
+ for (page = doc->first_page; page; page = page->next)
+ if (!strcmp(page->name, name))
+ return;
+
+ page = fz_malloc_struct(doc->ctx, xps_page);
+ page->name = fz_strdup(doc->ctx, name);
+ page->number = doc->page_count++;
+ page->width = width;
+ page->height = height;
+ page->links = NULL;
+ page->links_resolved = 0;
+ page->root = NULL;
+ page->next = NULL;
+
+ if (!doc->first_page)
+ {
+ doc->first_page = page;
+ doc->last_page = page;
+ }
+ else
+ {
+ doc->last_page->next = page;
+ doc->last_page = page;
+ }
+}
+
+static void
+xps_add_link_target(xps_document *doc, char *name)
+{
+ xps_page *page = doc->last_page;
+ xps_target *target = fz_malloc_struct(doc->ctx, xps_target);
+ target->name = fz_strdup(doc->ctx, name);
+ target->page = page->number;
+ target->next = doc->target;
+ doc->target = target;
+}
+
+int
+xps_lookup_link_target(xps_document *doc, char *target_uri)
+{
+ xps_target *target;
+ char *needle = strrchr(target_uri, '#');
+ needle = needle ? needle + 1 : target_uri;
+ for (target = doc->target; target; target = target->next)
+ if (!strcmp(target->name, needle))
+ return target->page;
+ return 0;
+}
+
+static void
+xps_free_link_targets(xps_document *doc)
+{
+ xps_target *target = doc->target, *next;
+ while (target)
+ {
+ next = target->next;
+ fz_free(doc->ctx, target->name);
+ fz_free(doc->ctx, target);
+ target = next;
+ }
+}
+
+static void
+xps_free_fixed_pages(xps_document *doc)
+{
+ xps_page *page = doc->first_page;
+ while (page)
+ {
+ xps_page *next = page->next;
+ xps_free_page(doc, page);
+ fz_drop_link(doc->ctx, page->links);
+ fz_free(doc->ctx, page->name);
+ fz_free(doc->ctx, page);
+ page = next;
+ }
+ doc->first_page = NULL;
+ doc->last_page = NULL;
+}
+
+static void
+xps_free_fixed_documents(xps_document *doc)
+{
+ xps_fixdoc *fixdoc = doc->first_fixdoc;
+ while (fixdoc)
+ {
+ xps_fixdoc *next = fixdoc->next;
+ fz_free(doc->ctx, fixdoc->name);
+ fz_free(doc->ctx, fixdoc->outline);
+ fz_free(doc->ctx, fixdoc);
+ fixdoc = next;
+ }
+ doc->first_fixdoc = NULL;
+ doc->last_fixdoc = NULL;
+}
+
+void
+xps_free_page_list(xps_document *doc)
+{
+ xps_free_fixed_documents(doc);
+ xps_free_fixed_pages(doc);
+ xps_free_link_targets(doc);
+}
+
+/*
+ * Parse the fixed document sequence structure and _rels/.rels to find the start part.
+ */
+
+static void
+xps_parse_metadata_imp(xps_document *doc, fz_xml *item, xps_fixdoc *fixdoc)
+{
+ while (item)
+ {
+ if (!strcmp(fz_xml_tag(item), "Relationship"))
+ {
+ char *target = fz_xml_att(item, "Target");
+ char *type = fz_xml_att(item, "Type");
+ if (target && type)
+ {
+ char tgtbuf[1024];
+ xps_resolve_url(tgtbuf, doc->base_uri, target, sizeof tgtbuf);
+ if (!strcmp(type, REL_START_PART) || !strcmp(type, REL_START_PART_OXPS))
+ doc->start_part = fz_strdup(doc->ctx, tgtbuf);
+ if ((!strcmp(type, REL_DOC_STRUCTURE) || !strcmp(type, REL_DOC_STRUCTURE_OXPS)) && fixdoc)
+ fixdoc->outline = fz_strdup(doc->ctx, tgtbuf);
+ if (!fz_xml_att(item, "Id"))
+ fz_warn(doc->ctx, "missing relationship id for %s", target);
+ }
+ }
+
+ if (!strcmp(fz_xml_tag(item), "DocumentReference"))
+ {
+ char *source = fz_xml_att(item, "Source");
+ if (source)
+ {
+ char srcbuf[1024];
+ xps_resolve_url(srcbuf, doc->base_uri, source, sizeof srcbuf);
+ xps_add_fixed_document(doc, srcbuf);
+ }
+ }
+
+ if (!strcmp(fz_xml_tag(item), "PageContent"))
+ {
+ char *source = fz_xml_att(item, "Source");
+ char *width_att = fz_xml_att(item, "Width");
+ char *height_att = fz_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_resolve_url(srcbuf, doc->base_uri, source, sizeof srcbuf);
+ xps_add_fixed_page(doc, srcbuf, width, height);
+ }
+ }
+
+ if (!strcmp(fz_xml_tag(item), "LinkTarget"))
+ {
+ char *name = fz_xml_att(item, "Name");
+ if (name)
+ xps_add_link_target(doc, name);
+ }
+
+ xps_parse_metadata_imp(doc, fz_xml_down(item), fixdoc);
+
+ item = fz_xml_next(item);
+ }
+}
+
+static void
+xps_parse_metadata(xps_document *doc, xps_part *part, xps_fixdoc *fixdoc)
+{
+ fz_xml *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;
+
+ doc->base_uri = buf;
+ doc->part_uri = part->name;
+
+ root = fz_parse_xml(doc->ctx, part->data, part->size);
+ xps_parse_metadata_imp(doc, root, fixdoc);
+ fz_free_xml(doc->ctx, root);
+
+ doc->base_uri = NULL;
+ doc->part_uri = NULL;
+}
+
+static void
+xps_read_and_process_metadata_part(xps_document *doc, char *name, xps_fixdoc *fixdoc)
+{
+ fz_context *ctx = doc->ctx;
+ xps_part *part;
+
+ if (!xps_has_part(doc, name))
+ return;
+
+ part = xps_read_part(doc, name);
+ fz_try(ctx)
+ {
+ xps_parse_metadata(doc, part, fixdoc);
+ }
+ fz_always(ctx)
+ {
+ xps_free_part(doc, part);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow(ctx);
+ }
+}
+
+void
+xps_read_page_list(xps_document *doc)
+{
+ xps_fixdoc *fixdoc;
+
+ xps_read_and_process_metadata_part(doc, "/_rels/.rels", NULL);
+
+ if (!doc->start_part)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "cannot find fixed document sequence start part");
+
+ xps_read_and_process_metadata_part(doc, doc->start_part, NULL);
+
+ for (fixdoc = doc->first_fixdoc; fixdoc; fixdoc = fixdoc->next)
+ {
+ char relbuf[1024];
+ fz_try(doc->ctx)
+ {
+ xps_rels_for_part(relbuf, fixdoc->name, sizeof relbuf);
+ xps_read_and_process_metadata_part(doc, relbuf, fixdoc);
+ }
+ fz_catch(doc->ctx)
+ {
+ /* FIXME: TryLater ? */
+ fz_warn(doc->ctx, "cannot process FixedDocument rels part");
+ }
+ xps_read_and_process_metadata_part(doc, fixdoc->name, fixdoc);
+ }
+}
+
+int
+xps_count_pages(xps_document *doc)
+{
+ return doc->page_count;
+}
+
+static void
+xps_load_fixed_page(xps_document *doc, xps_page *page)
+{
+ xps_part *part;
+ fz_xml *root;
+ char *width_att;
+ char *height_att;
+ fz_context *ctx = doc->ctx;
+
+ part = xps_read_part(doc, page->name);
+ fz_try(ctx)
+ {
+ root = fz_parse_xml(doc->ctx, part->data, part->size);
+ }
+ fz_always(ctx)
+ {
+ xps_free_part(doc, part);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater ? */
+ root = NULL;
+ }
+ if (!root)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "FixedPage missing root element");
+
+ if (!strcmp(fz_xml_tag(root), "mc:AlternateContent"))
+ {
+ fz_xml *node = xps_lookup_alternate_content(root);
+ if (!node)
+ {
+ fz_free_xml(doc->ctx, root);
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "FixedPage missing alternate root element");
+ }
+ fz_detach_xml(node);
+ fz_free_xml(doc->ctx, root);
+ root = node;
+ }
+
+ if (strcmp(fz_xml_tag(root), "FixedPage"))
+ {
+ fz_free_xml(doc->ctx, root);
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "expected FixedPage element");
+ }
+
+ width_att = fz_xml_att(root, "Width");
+ if (!width_att)
+ {
+ fz_free_xml(doc->ctx, root);
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "FixedPage missing required attribute: Width");
+ }
+
+ height_att = fz_xml_att(root, "Height");
+ if (!height_att)
+ {
+ fz_free_xml(doc->ctx, root);
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "FixedPage missing required attribute: Height");
+ }
+
+ page->width = atoi(width_att);
+ page->height = atoi(height_att);
+ page->root = root;
+}
+
+xps_page *
+xps_load_page(xps_document *doc, int number)
+{
+ xps_page *page;
+ int n = 0;
+
+ for (page = doc->first_page; page; page = page->next)
+ {
+ if (n == number)
+ {
+ doc->current_page = page;
+ if (!page->root)
+ xps_load_fixed_page(doc, page);
+ return page;
+ }
+ n ++;
+ }
+
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "cannot find page %d", number + 1);
+ return NULL;
+}
+
+fz_rect *
+xps_bound_page(xps_document *doc, xps_page *page, fz_rect *bounds)
+{
+ bounds->x0 = bounds->y0 = 0;
+ bounds->x1 = page->width * 72.0f / 96.0f;
+ bounds->y1 = page->height * 72.0f / 96.0f;
+ return bounds;
+}
+
+void
+xps_free_page(xps_document *doc, xps_page *page)
+{
+ if (page == NULL)
+ return;
+ /* only free the XML contents */
+ if (page->root)
+ fz_free_xml(doc->ctx, page->root);
+ page->root = NULL;
+}
diff --git a/source/xps/xps-glyphs.c b/source/xps/xps-glyphs.c
new file mode 100644
index 00000000..72f37ca1
--- /dev/null
+++ b/source/xps/xps-glyphs.c
@@ -0,0 +1,623 @@
+#include "mupdf/xps.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->ft_face;
+ return face->num_charmaps;
+}
+
+void
+xps_identify_font_encoding(fz_font *font, int idx, int *pid, int *eid)
+{
+ FT_Face face = font->ft_face;
+ *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->ft_face;
+ FT_Set_Charmap(face, face->charmaps[idx]);
+}
+
+int
+xps_encode_font_char(fz_font *font, int code)
+{
+ FT_Face face = font->ft_face;
+ int gid = FT_Get_Char_Index(face, code);
+ if (gid == 0 && face->charmap && 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_document *doc, 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->ft_face;
+ FT_Fixed hadv, vadv;
+ fz_context *ctx = doc->ctx;
+
+ fz_lock(ctx, FZ_LOCK_FREETYPE);
+ 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);
+ fz_unlock(ctx, FZ_LOCK_FREETYPE);
+
+ mtx->hadv = hadv / 65536.0f;
+ mtx->vadv = vadv / 65536.0f;
+ mtx->vorg = face->ascender / (float) face->units_per_EM;
+}
+
+static fz_font *
+xps_lookup_font(xps_document *doc, char *name)
+{
+ xps_font_cache *cache;
+ for (cache = doc->font_table; cache; cache = cache->next)
+ if (!xps_strcasecmp(cache->name, name))
+ return fz_keep_font(doc->ctx, cache->font);
+ return NULL;
+}
+
+static void
+xps_insert_font(xps_document *doc, char *name, fz_font *font)
+{
+ xps_font_cache *cache = fz_malloc_struct(doc->ctx, xps_font_cache);
+ cache->name = fz_strdup(doc->ctx, name);
+ cache->font = fz_keep_font(doc->ctx, font);
+ cache->next = doc->font_table;
+ doc->font_table = cache;
+}
+
+/*
+ * 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_document *doc, xps_part *part)
+{
+ unsigned char buf[33];
+ unsigned char 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(doc->ctx, "cannot extract GUID from obfuscated font part name");
+ return;
+ }
+
+ for (i = 0; i < 16; i++)
+ key[i] = unhex(buf[i*2+0]) * 16 + unhex(buf[i*2+1]);
+
+ for (i = 0; i < 16; i++)
+ {
+ part->data[i] ^= key[15-i];
+ part->data[i+16] ^= key[15-i];
+ }
+}
+
+static void
+xps_select_best_font_encoding(xps_document *doc, 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 */
+ { 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(doc->ctx, "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 = fz_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_document *doc, const 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(doc->ctx, "glyphs element with neither characters nor indices");
+
+ if (us)
+ {
+ if (us[0] == '{' && us[1] == '}')
+ us = us + 2;
+ un = strlen(us);
+ }
+
+ if (is_sideways)
+ {
+ fz_pre_scale(fz_rotate(&tm, 90), -size, size);
+ }
+ else
+ fz_scale(&tm, size, -size);
+
+ text = fz_new_text(doc->ctx, 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 text extraction */
+
+ while (code_count--)
+ {
+ if (us && un > 0)
+ {
+ int t = fz_chartorune(&char_code, us);
+ us += t; un -= t;
+ }
+ }
+
+ while (glyph_count--)
+ {
+ int glyph_index = -1;
+ float u_offset = 0;
+ float v_offset = 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(doc, font, glyph_index, &mtx);
+ if (is_sideways)
+ advance = mtx.vadv * 100;
+ else if (bidi_level & 1)
+ advance = -mtx.hadv * 100;
+ else
+ advance = mtx.hadv * 100;
+
+ if (font->ft_bold)
+ advance *= 1.02f;
+
+ 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.01f * size;
+ v_offset = v_offset * 0.01f * size;
+
+ if (is_sideways)
+ {
+ e = x + u_offset + (mtx.vorg * size);
+ f = y - v_offset + (mtx.hadv * 0.5f * size);
+ }
+ else
+ {
+ e = x + u_offset;
+ f = y - v_offset;
+ }
+
+ fz_add_text(doc->ctx, text, glyph_index, char_code, e, f);
+
+ x += advance * 0.01f * size;
+ }
+ }
+
+ return text;
+}
+
+void
+xps_parse_glyphs(xps_document *doc, const fz_matrix *ctm,
+ char *base_uri, xps_resource *dict, fz_xml *root)
+{
+ fz_xml *node;
+
+ char *fill_uri;
+ char *opacity_mask_uri;
+
+ char *bidi_level_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;
+ char *navigate_uri_att;
+
+ fz_xml *transform_tag = NULL;
+ fz_xml *clip_tag = NULL;
+ fz_xml *fill_tag = NULL;
+ fz_xml *opacity_mask_tag = NULL;
+
+ char *fill_opacity_att = NULL;
+
+ xps_part *part;
+ fz_font *font;
+
+ char partname[1024];
+ char fakename[1024];
+ char *subfont;
+
+ float font_size = 10;
+ int subfontid = 0;
+ int is_sideways = 0;
+ int bidi_level = 0;
+
+ fz_text *text;
+ fz_rect area;
+
+ fz_matrix local_ctm = *ctm;
+
+ /*
+ * Extract attributes and extended attributes.
+ */
+
+ bidi_level_att = fz_xml_att(root, "BidiLevel");
+ fill_att = fz_xml_att(root, "Fill");
+ font_size_att = fz_xml_att(root, "FontRenderingEmSize");
+ font_uri_att = fz_xml_att(root, "FontUri");
+ origin_x_att = fz_xml_att(root, "OriginX");
+ origin_y_att = fz_xml_att(root, "OriginY");
+ is_sideways_att = fz_xml_att(root, "IsSideways");
+ indices_att = fz_xml_att(root, "Indices");
+ unicode_att = fz_xml_att(root, "UnicodeString");
+ style_att = fz_xml_att(root, "StyleSimulations");
+ transform_att = fz_xml_att(root, "RenderTransform");
+ clip_att = fz_xml_att(root, "Clip");
+ opacity_att = fz_xml_att(root, "Opacity");
+ opacity_mask_att = fz_xml_att(root, "OpacityMask");
+ navigate_uri_att = fz_xml_att(root, "FixedPage.NavigateUri");
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "Glyphs.RenderTransform"))
+ transform_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "Glyphs.OpacityMask"))
+ opacity_mask_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "Glyphs.Clip"))
+ clip_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "Glyphs.Fill"))
+ fill_tag = fz_xml_down(node);
+ }
+
+ fill_uri = base_uri;
+ opacity_mask_uri = base_uri;
+
+ xps_resolve_resource_reference(doc, dict, &transform_att, &transform_tag, NULL);
+ xps_resolve_resource_reference(doc, dict, &clip_att, &clip_tag, NULL);
+ xps_resolve_resource_reference(doc, dict, &fill_att, &fill_tag, &fill_uri);
+ xps_resolve_resource_reference(doc, 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(doc->ctx, "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_resolve_url(partname, base_uri, font_uri_att, sizeof partname);
+ subfont = strrchr(partname, '#');
+ if (subfont)
+ {
+ subfontid = atoi(subfont + 1);
+ *subfont = 0;
+ }
+
+ /* Make a new part name for font with style simulation applied */
+ fz_strlcpy(fakename, partname, sizeof fakename);
+ if (style_att)
+ {
+ if (!strcmp(style_att, "BoldSimulation"))
+ fz_strlcat(fakename, "#Bold", sizeof fakename);
+ else if (!strcmp(style_att, "ItalicSimulation"))
+ fz_strlcat(fakename, "#Italic", sizeof fakename);
+ else if (!strcmp(style_att, "BoldItalicSimulation"))
+ fz_strlcat(fakename, "#BoldItalic", sizeof fakename);
+ }
+
+ font = xps_lookup_font(doc, fakename);
+ if (!font)
+ {
+ fz_try(doc->ctx)
+ {
+ part = xps_read_part(doc, partname);
+ }
+ fz_catch(doc->ctx)
+ {
+ /* FIXME: TryLater ? */
+ fz_warn(doc->ctx, "cannot find font resource part '%s'", partname);
+ return;
+ }
+
+ /* deobfuscate if necessary */
+ if (strstr(part->name, ".odttf"))
+ xps_deobfuscate_font_resource(doc, part);
+ if (strstr(part->name, ".ODTTF"))
+ xps_deobfuscate_font_resource(doc, part);
+
+ fz_try(doc->ctx)
+ {
+ font = fz_new_font_from_memory(doc->ctx, NULL, part->data, part->size, subfontid, 1);
+ }
+ fz_catch(doc->ctx)
+ {
+ /* FIXME: TryLater ? */
+ fz_warn(doc->ctx, "cannot load font resource '%s'", partname);
+ xps_free_part(doc, part);
+ return;
+ }
+
+ if (style_att)
+ {
+ font->ft_bold = !!strstr(style_att, "Bold");
+ font->ft_italic = !!strstr(style_att, "Italic");
+ }
+
+ xps_select_best_font_encoding(doc, font);
+
+ xps_insert_font(doc, fakename, font);
+
+ /* NOTE: we keep part->data in the font */
+ font->ft_data = part->data;
+ font->ft_size = part->size;
+ fz_free(doc->ctx, part->name);
+ fz_free(doc->ctx, part);
+ }
+
+ /*
+ * Set up graphics state.
+ */
+
+ if (transform_att || transform_tag)
+ {
+ fz_matrix transform;
+ if (transform_att)
+ xps_parse_render_transform(doc, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(doc, transform_tag, &transform);
+ fz_concat(&local_ctm, &transform, &local_ctm);
+ }
+
+ if (clip_att || clip_tag)
+ xps_clip(doc, &local_ctm, dict, clip_att, clip_tag);
+
+ font_size = fz_atof(font_size_att);
+
+ text = xps_parse_glyphs_imp(doc, &local_ctm, font, font_size,
+ fz_atof(origin_x_att), fz_atof(origin_y_att),
+ is_sideways, bidi_level, indices_att, unicode_att);
+
+ fz_bound_text(doc->ctx, text, NULL, &local_ctm, &area);
+
+ if (navigate_uri_att)
+ xps_add_link(doc, &area, base_uri, navigate_uri_att);
+
+ xps_begin_opacity(doc, &local_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(fz_xml_tag(fill_tag), "SolidColorBrush"))
+ {
+ fill_opacity_att = fz_xml_att(fill_tag, "Opacity");
+ fill_att = fz_xml_att(fill_tag, "Color");
+ fill_tag = NULL;
+ }
+
+ if (fill_att)
+ {
+ float samples[32];
+ fz_colorspace *colorspace;
+
+ xps_parse_color(doc, base_uri, fill_att, &colorspace, samples);
+ if (fill_opacity_att)
+ samples[0] *= fz_atof(fill_opacity_att);
+ xps_set_color(doc, colorspace, samples);
+
+ fz_fill_text(doc->dev, text, &local_ctm,
+ doc->colorspace, doc->color, doc->alpha);
+ }
+
+ /* If it's a complex brush, use the charpath as a clip mask */
+
+ if (fill_tag)
+ {
+ fz_clip_text(doc->dev, text, &local_ctm, 0);
+ xps_parse_brush(doc, &local_ctm, &area, fill_uri, dict, fill_tag);
+ fz_pop_clip(doc->dev);
+ }
+
+ xps_end_opacity(doc, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+
+ fz_free_text(doc->ctx, text);
+
+ if (clip_att || clip_tag)
+ fz_pop_clip(doc->dev);
+
+ fz_drop_font(doc->ctx, font);
+}
diff --git a/source/xps/xps-gradient.c b/source/xps/xps-gradient.c
new file mode 100644
index 00000000..88ed470b
--- /dev/null
+++ b/source/xps/xps-gradient.c
@@ -0,0 +1,525 @@
+#include "mupdf/xps.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_document *doc, char *base_uri, fz_xml *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(fz_xml_tag(node), "GradientStop"))
+ {
+ char *offset = fz_xml_att(node, "Offset");
+ char *color = fz_xml_att(node, "Color");
+ if (offset && color)
+ {
+ stops[count].offset = fz_atof(offset);
+
+ xps_parse_color(doc, base_uri, color, &colorspace, sample);
+
+ fz_convert_color(doc->ctx, fz_device_rgb(doc->ctx), rgb, colorspace, sample + 1);
+
+ stops[count].r = rgb[0];
+ stops[count].g = rgb[1];
+ stops[count].b = rgb[2];
+ stops[count].a = sample[0];
+
+ count ++;
+ }
+ }
+ node = fz_xml_next(node);
+ }
+
+ if (count == 0)
+ {
+ fz_warn(doc->ctx, "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(doc->ctx, "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[count-1].r = lerp(stops[count-2].r, stops[count-1].r, d);
+ stops[count-1].g = lerp(stops[count-2].g, stops[count-1].g, d);
+ stops[count-1].b = lerp(stops[count-2].b, stops[count-1].b, d);
+ stops[count-1].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_document *doc, const 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_struct(doc->ctx, fz_shade);
+ FZ_INIT_STORABLE(shade, 1, fz_free_shade_imp);
+ shade->colorspace = fz_device_rgb(doc->ctx);
+ shade->bbox = fz_infinite_rect;
+ shade->matrix = fz_identity;
+ shade->use_background = 0;
+ shade->use_function = 1;
+ shade->type = FZ_RADIAL;
+ shade->u.l_or_r.extend[0] = extend;
+ shade->u.l_or_r.extend[1] = extend;
+
+ xps_sample_gradient_stops(shade, stops, count);
+
+ shade->u.l_or_r.coords[0][0] = x0;
+ shade->u.l_or_r.coords[0][1] = y0;
+ shade->u.l_or_r.coords[0][2] = r0;
+ shade->u.l_or_r.coords[1][0] = x1;
+ shade->u.l_or_r.coords[1][1] = y1;
+ shade->u.l_or_r.coords[1][2] = r1;
+
+ fz_fill_shade(doc->dev, shade, ctm, 1);
+
+ fz_drop_shade(doc->ctx, shade);
+}
+
+/*
+ * Linear gradients.
+ */
+
+static void
+xps_draw_one_linear_gradient(xps_document *doc, const 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_struct(doc->ctx, fz_shade);
+ FZ_INIT_STORABLE(shade, 1, fz_free_shade_imp);
+ shade->colorspace = fz_device_rgb(doc->ctx);
+ shade->bbox = fz_infinite_rect;
+ shade->matrix = fz_identity;
+ shade->use_background = 0;
+ shade->use_function = 1;
+ shade->type = FZ_LINEAR;
+ shade->u.l_or_r.extend[0] = extend;
+ shade->u.l_or_r.extend[1] = extend;
+
+ xps_sample_gradient_stops(shade, stops, count);
+
+ shade->u.l_or_r.coords[0][0] = x0;
+ shade->u.l_or_r.coords[0][1] = y0;
+ shade->u.l_or_r.coords[0][2] = 0;
+ shade->u.l_or_r.coords[1][0] = x1;
+ shade->u.l_or_r.coords[1][1] = y1;
+ shade->u.l_or_r.coords[1][2] = 0;
+
+ fz_fill_shade(doc->dev, shade, ctm, doc->opacity[doc->opacity_top]);
+
+ fz_drop_shade(doc->ctx, 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_document *doc, const fz_matrix *ctm, const fz_rect *area,
+ struct stop *stops, int count,
+ fz_xml *root, int spread)
+{
+ float x0, y0, r0;
+ float x1, y1, r1;
+ float xrad = 1;
+ float yrad = 1;
+ float invscale;
+ int i, ma = 1;
+ fz_matrix local_ctm = *ctm;
+ fz_matrix inv;
+ fz_rect local_area = *area;
+
+ char *center_att = fz_xml_att(root, "Center");
+ char *origin_att = fz_xml_att(root, "GradientOrigin");
+ char *radius_x_att = fz_xml_att(root, "RadiusX");
+ char *radius_y_att = fz_xml_att(root, "RadiusY");
+
+ x0 = y0 = 0.0;
+ x1 = y1 = 1.0;
+ xrad = 1.0;
+ yrad = 1.0;
+
+ if (origin_att)
+ xps_parse_point(origin_att, &x0, &y0);
+ if (center_att)
+ xps_parse_point(center_att, &x1, &y1);
+ if (radius_x_att)
+ xrad = fz_atof(radius_x_att);
+ if (radius_y_att)
+ yrad = fz_atof(radius_y_att);
+
+ xrad = fz_max(0.01f, xrad);
+ yrad = fz_max(0.01f, yrad);
+
+ /* scale the ctm to make ellipses */
+ if (fz_abs(xrad) > FLT_EPSILON)
+ {
+ fz_pre_scale(&local_ctm, 1, yrad/xrad);
+ }
+
+ if (yrad != 0.0)
+ {
+ invscale = xrad / yrad;
+ y0 = y0 * invscale;
+ y1 = y1 * invscale;
+ }
+
+ r0 = 0;
+ r1 = xrad;
+
+ fz_transform_rect(&local_area, fz_invert_matrix(&inv, &local_ctm));
+ ma = fz_maxi(ma, ceilf(hypotf(local_area.x0 - x0, local_area.y0 - y0) / xrad));
+ ma = fz_maxi(ma, ceilf(hypotf(local_area.x1 - x0, local_area.y0 - y0) / xrad));
+ ma = fz_maxi(ma, ceilf(hypotf(local_area.x0 - x0, local_area.y1 - y0) / xrad));
+ ma = fz_maxi(ma, ceilf(hypotf(local_area.x1 - x0, local_area.y1 - y0) / xrad));
+
+ if (spread == SPREAD_REPEAT)
+ {
+ for (i = ma - 1; i >= 0; i--)
+ xps_draw_one_radial_gradient(doc, &local_ctm, stops, count, 0, x0, y0, r0 + i * xrad, x1, y1, r1 + i * xrad);
+ }
+ else if (spread == SPREAD_REFLECT)
+ {
+ if ((ma % 2) != 0)
+ ma++;
+ for (i = ma - 2; i >= 0; i -= 2)
+ {
+ xps_draw_one_radial_gradient(doc, &local_ctm, stops, count, 0, x0, y0, r0 + i * xrad, x1, y1, r1 + i * xrad);
+ xps_draw_one_radial_gradient(doc, &local_ctm, stops, count, 0, x0, y0, r0 + (i + 2) * xrad, x1, y1, r1 + i * xrad);
+ }
+ }
+ else
+ {
+ xps_draw_one_radial_gradient(doc, &local_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_document *doc, const fz_matrix *ctm, const fz_rect *area,
+ struct stop *stops, int count,
+ fz_xml *root, int spread)
+{
+ float x0, y0, x1, y1;
+ int i, mi, ma;
+ float dx, dy, x, y, k;
+ fz_point p1, p2;
+ fz_matrix inv;
+ fz_rect local_area = *area;
+
+ char *start_point_att = fz_xml_att(root, "StartPoint");
+ char *end_point_att = fz_xml_att(root, "EndPoint");
+
+ x0 = y0 = 0;
+ x1 = y1 = 1;
+
+ if (start_point_att)
+ xps_parse_point(start_point_att, &x0, &y0);
+ if (end_point_att)
+ xps_parse_point(end_point_att, &x1, &y1);
+
+ p1.x = x0; p1.y = y0; p2.x = x1; p2.y = y1;
+ fz_transform_rect(&local_area, fz_invert_matrix(&inv, ctm));
+ x = p2.x - p1.x; y = p2.y - p1.y;
+ k = ((local_area.x0 - p1.x) * x + (local_area.y0 - p1.y) * y) / (x * x + y * y);
+ mi = floorf(k); ma = ceilf(k);
+ k = ((local_area.x1 - p1.x) * x + (local_area.y0 - p1.y) * y) / (x * x + y * y);
+ mi = fz_mini(mi, floorf(k)); ma = fz_maxi(ma, ceilf(k));
+ k = ((local_area.x0 - p1.x) * x + (local_area.y1 - p1.y) * y) / (x * x + y * y);
+ mi = fz_mini(mi, floorf(k)); ma = fz_maxi(ma, ceilf(k));
+ k = ((local_area.x1 - p1.x) * x + (local_area.y1 - p1.y) * y) / (x * x + y * y);
+ mi = fz_mini(mi, floorf(k)); ma = fz_maxi(ma, ceilf(k));
+ dx = x1 - x0; dy = y1 - y0;
+
+ if (spread == SPREAD_REPEAT)
+ {
+ for (i = mi; i < ma; i++)
+ xps_draw_one_linear_gradient(doc, ctm, stops, count, 0, x0 + i * dx, y0 + i * dy, x1 + i * dx, y1 + i * dy);
+ }
+ else if (spread == SPREAD_REFLECT)
+ {
+ if ((mi % 2) != 0)
+ mi--;
+ for (i = mi; i < ma; i += 2)
+ {
+ xps_draw_one_linear_gradient(doc, ctm, stops, count, 0, x0 + i * dx, y0 + i * dy, x1 + i * dx, y1 + i * dy);
+ xps_draw_one_linear_gradient(doc, ctm, stops, count, 0, x0 + (i + 2) * dx, y0 + (i + 2) * dy, x1 + i * dx, y1 + i * dy);
+ }
+ }
+ else
+ {
+ xps_draw_one_linear_gradient(doc, 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_document *doc, const fz_matrix *ctm, const fz_rect *area,
+ char *base_uri, xps_resource *dict, fz_xml *root,
+ void (*draw)(xps_document *, const fz_matrix*, const fz_rect *, struct stop *, int, fz_xml *, int))
+{
+ fz_xml *node;
+
+ char *opacity_att;
+ char *spread_att;
+ char *transform_att;
+
+ fz_xml *transform_tag = NULL;
+ fz_xml *stop_tag = NULL;
+
+ struct stop stop_list[MAX_STOPS];
+ int stop_count;
+ fz_matrix transform;
+ int spread_method;
+
+ opacity_att = fz_xml_att(root, "Opacity");
+ spread_att = fz_xml_att(root, "SpreadMethod");
+ transform_att = fz_xml_att(root, "Transform");
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "LinearGradientBrush.Transform"))
+ transform_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "RadialGradientBrush.Transform"))
+ transform_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "LinearGradientBrush.GradientStops"))
+ stop_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "RadialGradientBrush.GradientStops"))
+ stop_tag = fz_xml_down(node);
+ }
+
+ xps_resolve_resource_reference(doc, 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(doc, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(doc, transform_tag, &transform);
+ fz_concat(&transform, &transform, ctm);
+
+ if (!stop_tag) {
+ fz_warn(doc->ctx, "missing gradient stops tag");
+ return;
+ }
+
+ stop_count = xps_parse_gradient_stops(doc, base_uri, stop_tag, stop_list, MAX_STOPS);
+ if (stop_count == 0)
+ {
+ fz_warn(doc->ctx, "no gradient stops found");
+ return;
+ }
+
+ xps_begin_opacity(doc, &transform, area, base_uri, dict, opacity_att, NULL);
+
+ draw(doc, &transform, area, stop_list, stop_count, root, spread_method);
+
+ xps_end_opacity(doc, base_uri, dict, opacity_att, NULL);
+}
+
+void
+xps_parse_linear_gradient_brush(xps_document *doc, const fz_matrix *ctm, const fz_rect *area,
+ char *base_uri, xps_resource *dict, fz_xml *root)
+{
+ xps_parse_gradient_brush(doc, ctm, area, base_uri, dict, root, xps_draw_linear_gradient);
+}
+
+void
+xps_parse_radial_gradient_brush(xps_document *doc, const fz_matrix *ctm, const fz_rect *area,
+ char *base_uri, xps_resource *dict, fz_xml *root)
+{
+ xps_parse_gradient_brush(doc, ctm, area, base_uri, dict, root, xps_draw_radial_gradient);
+}
diff --git a/source/xps/xps-image.c b/source/xps/xps-image.c
new file mode 100644
index 00000000..eb0d876f
--- /dev/null
+++ b/source/xps/xps-image.c
@@ -0,0 +1,128 @@
+#include "mupdf/xps.h"
+
+static fz_image *
+xps_load_image(fz_context *ctx, xps_part *part)
+{
+ /* Ownership of data always passes in here */
+ unsigned char *data = part->data;
+ part->data = NULL;
+ return fz_new_image_from_data(ctx, data, part->size);
+}
+
+/* FIXME: area unused! */
+static void
+xps_paint_image_brush(xps_document *doc, const fz_matrix *ctm, const fz_rect *area, char *base_uri, xps_resource *dict,
+ fz_xml *root, void *vimage)
+{
+ fz_image *image = vimage;
+ float xs, ys;
+ fz_matrix local_ctm = *ctm;
+
+ if (image->xres == 0 || image->yres == 0)
+ return;
+ xs = image->w * 96 / image->xres;
+ ys = image->h * 96 / image->yres;
+ fz_pre_scale(&local_ctm, xs, ys);
+ fz_fill_image(doc->dev, image, &local_ctm, doc->opacity[doc->opacity_top]);
+}
+
+static void
+xps_find_image_brush_source_part(xps_document *doc, char *base_uri, fz_xml *root, xps_part **image_part, xps_part **profile_part)
+{
+ char *image_source_att;
+ char buf[1024];
+ char partname[1024];
+ char *image_name;
+ char *profile_name;
+ char *p;
+
+ image_source_att = fz_xml_att(root, "ImageSource");
+ if (!image_source_att)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "cannot find image source attribute");
+
+ /* "{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)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "cannot find image source");
+
+ if (image_part)
+ {
+ xps_resolve_url(partname, base_uri, image_name, sizeof partname);
+ *image_part = xps_read_part(doc, partname);
+ }
+
+ if (profile_part)
+ {
+ if (profile_name)
+ {
+ xps_resolve_url(partname, base_uri, profile_name, sizeof partname);
+ *profile_part = xps_read_part(doc, partname);
+ }
+ else
+ *profile_part = NULL;
+ }
+}
+
+void
+xps_parse_image_brush(xps_document *doc, const fz_matrix *ctm, const fz_rect *area,
+ char *base_uri, xps_resource *dict, fz_xml *root)
+{
+ xps_part *part;
+ fz_image *image;
+
+ fz_try(doc->ctx)
+ {
+ xps_find_image_brush_source_part(doc, base_uri, root, &part, NULL);
+ }
+ fz_catch(doc->ctx)
+ {
+ /* FIXME: TryLater ? */
+ fz_warn(doc->ctx, "cannot find image source");
+ return;
+ }
+
+ fz_try(doc->ctx)
+ {
+ image = xps_load_image(doc->ctx, part);
+ }
+ fz_always(doc->ctx)
+ {
+ xps_free_part(doc, part);
+ }
+ fz_catch(doc->ctx)
+ {
+ /* FIXME: TryLater ? */
+ fz_warn(doc->ctx, "cannot decode image resource");
+ return;
+ }
+
+ xps_parse_tiling_brush(doc, ctm, area, base_uri, dict, root, xps_paint_image_brush, image);
+
+ fz_drop_image(doc->ctx, image);
+}
diff --git a/source/xps/xps-outline.c b/source/xps/xps-outline.c
new file mode 100644
index 00000000..b87460a4
--- /dev/null
+++ b/source/xps/xps-outline.c
@@ -0,0 +1,152 @@
+#include "mupdf/xps.h"
+
+/*
+ * Parse the document structure / outline parts referenced from fixdoc relationships.
+ */
+
+static fz_outline *
+xps_lookup_last_outline_at_level(fz_outline *node, int level, int target_level)
+{
+ while (node->next)
+ node = node->next;
+ if (level == target_level || !node->down)
+ return node;
+ return xps_lookup_last_outline_at_level(node->down, level + 1, target_level);
+}
+
+static fz_outline *
+xps_parse_document_outline(xps_document *doc, fz_xml *root)
+{
+ fz_xml *node;
+ fz_outline *head = NULL, *entry, *tail;
+ int last_level = 1, this_level;
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "OutlineEntry"))
+ {
+ char *level = fz_xml_att(node, "OutlineLevel");
+ char *target = fz_xml_att(node, "OutlineTarget");
+ char *description = fz_xml_att(node, "Description");
+ if (!target || !description)
+ continue;
+
+ entry = fz_malloc_struct(doc->ctx, fz_outline);
+ entry->title = fz_strdup(doc->ctx, description);
+ entry->dest.kind = FZ_LINK_GOTO;
+ entry->dest.ld.gotor.flags = 0;
+ entry->dest.ld.gotor.page = xps_lookup_link_target(doc, target);
+ entry->down = NULL;
+ entry->next = NULL;
+
+ this_level = level ? atoi(level) : 1;
+
+ if (!head)
+ {
+ head = entry;
+ }
+ else
+ {
+ tail = xps_lookup_last_outline_at_level(head, 1, this_level);
+ if (this_level > last_level)
+ tail->down = entry;
+ else
+ tail->next = entry;
+ }
+
+ last_level = this_level;
+ }
+ }
+ return head;
+}
+
+static fz_outline *
+xps_parse_document_structure(xps_document *doc, fz_xml *root)
+{
+ fz_xml *node;
+ if (!strcmp(fz_xml_tag(root), "DocumentStructure"))
+ {
+ node = fz_xml_down(root);
+ if (node && !strcmp(fz_xml_tag(node), "DocumentStructure.Outline"))
+ {
+ node = fz_xml_down(node);
+ if (node && !strcmp(fz_xml_tag(node), "DocumentOutline"))
+ return xps_parse_document_outline(doc, node);
+ }
+ }
+ return NULL;
+}
+
+static fz_outline *
+xps_load_document_structure(xps_document *doc, xps_fixdoc *fixdoc)
+{
+ xps_part *part;
+ fz_xml *root;
+ fz_outline *outline;
+
+ part = xps_read_part(doc, fixdoc->outline);
+ fz_try(doc->ctx)
+ {
+ root = fz_parse_xml(doc->ctx, part->data, part->size);
+ }
+ fz_always(doc->ctx)
+ {
+ xps_free_part(doc, part);
+ }
+ fz_catch(doc->ctx)
+ {
+ fz_rethrow(doc->ctx);
+ }
+ if (!root)
+ return NULL;
+
+ fz_try(doc->ctx)
+ {
+ outline = xps_parse_document_structure(doc, root);
+ }
+ fz_always(doc->ctx)
+ {
+ fz_free_xml(doc->ctx, root);
+ }
+ fz_catch(doc->ctx)
+ {
+ fz_rethrow(doc->ctx);
+ }
+
+ return outline;
+}
+
+fz_outline *
+xps_load_outline(xps_document *doc)
+{
+ xps_fixdoc *fixdoc;
+ fz_outline *head = NULL, *tail, *outline;
+
+ for (fixdoc = doc->first_fixdoc; fixdoc; fixdoc = fixdoc->next)
+ {
+ if (fixdoc->outline)
+ {
+ fz_try(doc->ctx)
+ {
+ outline = xps_load_document_structure(doc, fixdoc);
+ }
+ fz_catch(doc->ctx)
+ {
+ /* FIXME: TryLater ? */
+ outline = NULL;
+ }
+ if (!outline)
+ continue;
+
+ if (!head)
+ head = outline;
+ else
+ {
+ while (tail->next)
+ tail = tail->next;
+ tail->next = outline;
+ }
+ tail = outline;
+ }
+ }
+ return head;
+}
diff --git a/source/xps/xps-path.c b/source/xps/xps-path.c
new file mode 100644
index 00000000..371f52ab
--- /dev/null
+++ b/source/xps/xps-path.c
@@ -0,0 +1,1053 @@
+#include "mupdf/xps.h"
+
+static char *
+xps_parse_float_array(char *s, int num, float *x)
+{
+ int k = 0;
+
+ if (s == NULL || *s == 0)
+ return NULL;
+
+ while (*s)
+ {
+ while (*s == 0x0d || *s == '\t' || *s == ' ' || *s == 0x0a)
+ s++;
+ x[k] = (float)strtod(s, &s);
+ while (*s == 0x0d || *s == '\t' || *s == ' ' || *s == 0x0a)
+ s++;
+ if (*s == ',')
+ s++;
+ if (++k == num)
+ break;
+ }
+ return s;
+}
+
+char *
+xps_parse_point(char *s_in, float *x, float *y)
+{
+ char *s_out = s_in;
+ float xy[2];
+
+ s_out = xps_parse_float_array(s_out, 2, &xy[0]);
+ *x = xy[0];
+ *y = xy[1];
+ return s_out;
+}
+
+/* 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.
+ *
+ * We are guaranteed that on entry the point is at the point that would be
+ * calculated by th0, and on exit, a point is generated for us at th0.
+ */
+static void
+xps_draw_arc_segment(fz_context *doc, fz_path *path, const fz_matrix *mtx, float th0, float th1, int iscw)
+{
+ float t, d;
+ fz_point p;
+
+ while (th1 < th0)
+ th1 += (float)M_PI * 2;
+
+ d = (float)M_PI / 180; /* 1-degree precision */
+
+ if (iscw)
+ {
+ for (t = th0 + d; t < th1 - d/2; t += d)
+ {
+ p.x = cosf(t);
+ p.y = sinf(t);
+ fz_transform_point(&p, mtx);
+ fz_lineto(doc, path, p.x, p.y);
+ }
+ }
+ else
+ {
+ th0 += (float)M_PI * 2;
+ for (t = th0 - d; t > th1 + d/2; t -= d)
+ {
+ p.x = cosf(t);
+ p.y = sinf(t);
+ fz_transform_point(&p, mtx);
+ fz_lineto(doc, path, p.x, p.y);
+ }
+ }
+}
+
+/* Given two vectors find the angle between them. */
+static float
+angle_between(const fz_point u, const fz_point v)
+{
+ float det = u.x * v.y - u.y * v.x;
+ float sign = (det < 0 ? -1 : 1);
+ float magu = u.x * u.x + u.y * u.y;
+ float magv = v.x * v.x + v.y * v.y;
+ float udotv = u.x * v.x + u.y * v.y;
+ float t = udotv / (magu * magv);
+ /* guard against rounding errors when near |1| (where acos will return NaN) */
+ if (t < -1) t = -1;
+ if (t > 1) t = 1;
+ return sign * acosf(t);
+}
+
+/*
+ Some explaination of the parameters here is warranted. See:
+
+ http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
+
+ Add an arc segment to path, that describes a section of an elliptical
+ arc from the current point of path to (point_x,point_y), such that:
+
+ The arc segment is taken from an elliptical arc of semi major radius
+ size_x, semi minor radius size_y, where the semi major axis of the
+ ellipse is rotated by rotation_angle.
+
+ If is_large_arc, then the arc segment is selected to be > 180 degrees.
+
+ If is_clockwise, then the arc sweeps clockwise.
+*/
+static void
+xps_draw_arc(fz_context *doc, 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;
+ float rx, ry;
+ float x1, y1, x2, y2;
+ float x1t, y1t;
+ float cxt, cyt, cx, cy;
+ float t1, t2, t3;
+ float sign;
+ float th1, dth;
+
+ pt = fz_currentpoint(doc, 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;
+
+ fz_rotate(&rotmat, rotation_angle);
+ fz_rotate(&revmat, -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.001f || ry < 0.001f || (x1 == x2 && y1 == y2))
+ {
+ fz_lineto(doc, path, x2, y2);
+ return;
+ }
+
+ /* F.6.5.1 */
+ pt.x = (x1 - x2) / 2;
+ pt.y = (y1 - y2) / 2;
+ fz_transform_vector(&pt, &revmat);
+ 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)
+ {
+ 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) t3 = 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;
+ fz_transform_vector(&pt, &rotmat);
+ 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 += (((float)M_PI / 180) * 360);
+ if (dth > 0 && is_clockwise)
+ dth -= (((float)M_PI / 180) * 360);
+ }
+
+ fz_pre_scale(fz_pre_rotate(fz_translate(&mtx, cx, cy), rotation_angle), rx, ry);
+ xps_draw_arc_segment(doc, path, &mtx, th1, th1 + dth, is_clockwise);
+
+ fz_lineto(doc, 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_document *doc, 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_new_path(doc->ctx);
+
+ args = fz_malloc_array(doc->ctx, 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 = s;
+
+ n = pargs - args;
+ i = 0;
+
+ old = 0;
+
+ reset_smooth = 1;
+ smooth_x = 0;
+ smooth_y = 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;
+ smooth_y = 0;
+ }
+
+ reset_smooth = 1;
+
+ switch (cmd)
+ {
+ case 'F':
+ if (i >= n) break;
+ *fill_rule = atoi(args[i]);
+ i ++;
+ break;
+
+ case 'M':
+ if (i + 1 >= n) break;
+ fz_moveto(doc->ctx, path, fz_atof(args[i]), fz_atof(args[i+1]));
+ i += 2;
+ break;
+ case 'm':
+ if (i + 1 >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ fz_moveto(doc->ctx, path, pt.x + fz_atof(args[i]), pt.y + fz_atof(args[i+1]));
+ i += 2;
+ break;
+
+ case 'L':
+ if (i + 1 >= n) break;
+ fz_lineto(doc->ctx, path, fz_atof(args[i]), fz_atof(args[i+1]));
+ i += 2;
+ break;
+ case 'l':
+ if (i + 1 >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ fz_lineto(doc->ctx, path, pt.x + fz_atof(args[i]), pt.y + fz_atof(args[i+1]));
+ i += 2;
+ break;
+
+ case 'H':
+ if (i >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ fz_lineto(doc->ctx, path, fz_atof(args[i]), pt.y);
+ i += 1;
+ break;
+ case 'h':
+ if (i >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ fz_lineto(doc->ctx, path, pt.x + fz_atof(args[i]), pt.y);
+ i += 1;
+ break;
+
+ case 'V':
+ if (i >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ fz_lineto(doc->ctx, path, pt.x, fz_atof(args[i]));
+ i += 1;
+ break;
+ case 'v':
+ if (i >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ fz_lineto(doc->ctx, path, pt.x, pt.y + fz_atof(args[i]));
+ i += 1;
+ break;
+
+ case 'C':
+ if (i + 5 >= n) break;
+ x1 = fz_atof(args[i+0]);
+ y1 = fz_atof(args[i+1]);
+ x2 = fz_atof(args[i+2]);
+ y2 = fz_atof(args[i+3]);
+ x3 = fz_atof(args[i+4]);
+ y3 = fz_atof(args[i+5]);
+ fz_curveto(doc->ctx, path, x1, y1, x2, y2, x3, y3);
+ i += 6;
+ reset_smooth = 0;
+ smooth_x = x3 - x2;
+ smooth_y = y3 - y2;
+ break;
+
+ case 'c':
+ if (i + 5 >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ x1 = fz_atof(args[i+0]) + pt.x;
+ y1 = fz_atof(args[i+1]) + pt.y;
+ x2 = fz_atof(args[i+2]) + pt.x;
+ y2 = fz_atof(args[i+3]) + pt.y;
+ x3 = fz_atof(args[i+4]) + pt.x;
+ y3 = fz_atof(args[i+5]) + pt.y;
+ fz_curveto(doc->ctx, path, x1, y1, x2, y2, x3, y3);
+ i += 6;
+ reset_smooth = 0;
+ smooth_x = x3 - x2;
+ smooth_y = y3 - y2;
+ break;
+
+ case 'S':
+ if (i + 3 >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ x1 = fz_atof(args[i+0]);
+ y1 = fz_atof(args[i+1]);
+ x2 = fz_atof(args[i+2]);
+ y2 = fz_atof(args[i+3]);
+ fz_curveto(doc->ctx, 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':
+ if (i + 3 >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ x1 = fz_atof(args[i+0]) + pt.x;
+ y1 = fz_atof(args[i+1]) + pt.y;
+ x2 = fz_atof(args[i+2]) + pt.x;
+ y2 = fz_atof(args[i+3]) + pt.y;
+ fz_curveto(doc->ctx, 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':
+ if (i + 3 >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ x1 = fz_atof(args[i+0]);
+ y1 = fz_atof(args[i+1]);
+ x2 = fz_atof(args[i+2]);
+ y2 = fz_atof(args[i+3]);
+ fz_curveto(doc->ctx, 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':
+ if (i + 3 >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ x1 = fz_atof(args[i+0]) + pt.x;
+ y1 = fz_atof(args[i+1]) + pt.y;
+ x2 = fz_atof(args[i+2]) + pt.x;
+ y2 = fz_atof(args[i+3]) + pt.y;
+ fz_curveto(doc->ctx, 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':
+ if (i + 6 >= n) break;
+ xps_draw_arc(doc->ctx, path,
+ fz_atof(args[i+0]), fz_atof(args[i+1]), fz_atof(args[i+2]),
+ atoi(args[i+3]), atoi(args[i+4]),
+ fz_atof(args[i+5]), fz_atof(args[i+6]));
+ i += 7;
+ break;
+ case 'a':
+ if (i + 6 >= n) break;
+ pt = fz_currentpoint(doc->ctx, path);
+ xps_draw_arc(doc->ctx, path,
+ fz_atof(args[i+0]), fz_atof(args[i+1]), fz_atof(args[i+2]),
+ atoi(args[i+3]), atoi(args[i+4]),
+ fz_atof(args[i+5]) + pt.x, fz_atof(args[i+6]) + pt.y);
+ i += 7;
+ break;
+
+ case 'Z':
+ case 'z':
+ fz_closepath(doc->ctx, path);
+ break;
+
+ default:
+ /* eek */
+ fz_warn(doc->ctx, "ignoring invalid command '%c'", cmd);
+ /* Skip any trailing numbers to avoid an infinite loop */
+ while (i < n && (args[i][0] == '+' || args[i][0] == '.' || args[i][0] == '-' || (args[i][0] >= '0' && args[i][0] <= '9')))
+ i ++;
+ break;
+ }
+
+ old = cmd;
+ }
+
+ fz_free(doc->ctx, args);
+ return path;
+}
+
+static void
+xps_parse_arc_segment(fz_context *doc, fz_path *path, fz_xml *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 = fz_xml_att(root, "Point");
+ char *size_att = fz_xml_att(root, "Size");
+ char *rotation_angle_att = fz_xml_att(root, "RotationAngle");
+ char *is_large_arc_att = fz_xml_att(root, "IsLargeArc");
+ char *sweep_direction_att = fz_xml_att(root, "SweepDirection");
+ char *is_stroked_att = fz_xml_att(root, "IsStroked");
+
+ if (!point_att || !size_att || !rotation_angle_att || !is_large_arc_att || !sweep_direction_att)
+ {
+ fz_warn(doc, "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;
+
+ point_x = point_y = 0;
+ size_x = size_y = 0;
+
+ xps_parse_point(point_att, &point_x, &point_y);
+ xps_parse_point(size_att, &size_x, &size_y);
+ rotation_angle = fz_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(doc, path, point_x, point_y);
+ return;
+ }
+
+ xps_draw_arc(doc, 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_context *doc, fz_path *path, fz_xml *root, int stroking, int *skipped_stroke)
+{
+ char *points_att = fz_xml_att(root, "Points");
+ char *is_stroked_att = fz_xml_att(root, "IsStroked");
+ float x[2], y[2];
+ int is_stroked;
+ fz_point pt;
+ char *s;
+ int n;
+
+ if (!points_att)
+ {
+ fz_warn(doc, "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++;
+ s = xps_parse_point(s, &x[n], &y[n]);
+ n ++;
+ if (n == 2)
+ {
+ if (stroking && !is_stroked)
+ {
+ fz_moveto(doc, path, x[1], y[1]);
+ }
+ else
+ {
+ pt = fz_currentpoint(doc, path);
+ fz_curveto(doc, 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_context *doc, fz_path *path, fz_xml *root, int stroking, int *skipped_stroke)
+{
+ char *points_att = fz_xml_att(root, "Points");
+ char *is_stroked_att = fz_xml_att(root, "IsStroked");
+ float x[3], y[3];
+ int is_stroked;
+ char *s;
+ int n;
+
+ if (!points_att)
+ {
+ fz_warn(doc, "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++;
+ s = xps_parse_point(s, &x[n], &y[n]);
+ n ++;
+ if (n == 3)
+ {
+ if (stroking && !is_stroked)
+ fz_moveto(doc, path, x[2], y[2]);
+ else
+ fz_curveto(doc, path, x[0], y[0], x[1], y[1], x[2], y[2]);
+ n = 0;
+ }
+ }
+}
+
+static void
+xps_parse_poly_line_segment(fz_context *doc, fz_path *path, fz_xml *root, int stroking, int *skipped_stroke)
+{
+ char *points_att = fz_xml_att(root, "Points");
+ char *is_stroked_att = fz_xml_att(root, "IsStroked");
+ int is_stroked;
+ float x, y;
+ char *s;
+
+ if (!points_att)
+ {
+ fz_warn(doc, "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++;
+ s = xps_parse_point(s, &x, &y);
+ if (stroking && !is_stroked)
+ fz_moveto(doc, path, x, y);
+ else
+ fz_lineto(doc, path, x, y);
+ }
+}
+
+static void
+xps_parse_path_figure(fz_context *doc, fz_path *path, fz_xml *root, int stroking)
+{
+ fz_xml *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;
+ float start_y = 0;
+
+ int skipped_stroke = 0;
+
+ is_closed_att = fz_xml_att(root, "IsClosed");
+ start_point_att = fz_xml_att(root, "StartPoint");
+ is_filled_att = fz_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)
+ xps_parse_point(start_point_att, &start_x, &start_y);
+
+ if (!stroking && !is_filled) /* not filled, when filling */
+ return;
+
+ fz_moveto(doc, path, start_x, start_y);
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "ArcSegment"))
+ xps_parse_arc_segment(doc, path, node, stroking, &skipped_stroke);
+ if (!strcmp(fz_xml_tag(node), "PolyBezierSegment"))
+ xps_parse_poly_bezier_segment(doc, path, node, stroking, &skipped_stroke);
+ if (!strcmp(fz_xml_tag(node), "PolyLineSegment"))
+ xps_parse_poly_line_segment(doc, path, node, stroking, &skipped_stroke);
+ if (!strcmp(fz_xml_tag(node), "PolyQuadraticBezierSegment"))
+ xps_parse_poly_quadratic_bezier_segment(doc, path, node, stroking, &skipped_stroke);
+ }
+
+ if (is_closed)
+ {
+ if (stroking && skipped_stroke)
+ fz_lineto(doc, path, start_x, start_y); /* we've skipped using fz_moveto... */
+ else
+ fz_closepath(doc, path); /* no skipped segments, safe to closepath properly */
+ }
+}
+
+fz_path *
+xps_parse_path_geometry(xps_document *doc, xps_resource *dict, fz_xml *root, int stroking, int *fill_rule)
+{
+ fz_xml *node;
+
+ char *figures_att;
+ char *fill_rule_att;
+ char *transform_att;
+
+ fz_xml *transform_tag = NULL;
+ fz_xml *figures_tag = NULL; /* only used by resource */
+
+ fz_matrix transform;
+ fz_path *path;
+
+ figures_att = fz_xml_att(root, "Figures");
+ fill_rule_att = fz_xml_att(root, "FillRule");
+ transform_att = fz_xml_att(root, "Transform");
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "PathGeometry.Transform"))
+ transform_tag = fz_xml_down(node);
+ }
+
+ xps_resolve_resource_reference(doc, dict, &transform_att, &transform_tag, NULL);
+ xps_resolve_resource_reference(doc, 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(doc, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(doc, transform_tag, &transform);
+
+ if (figures_att)
+ path = xps_parse_abbreviated_geometry(doc, figures_att, fill_rule);
+ else
+ path = fz_new_path(doc->ctx);
+
+ if (figures_tag)
+ xps_parse_path_figure(doc->ctx, path, figures_tag, stroking);
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "PathFigure"))
+ xps_parse_path_figure(doc->ctx, path, node, stroking);
+ }
+
+ if (transform_att || transform_tag)
+ fz_transform_path(doc->ctx, 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;
+ }
+ return 0;
+}
+
+void
+xps_clip(xps_document *doc, const fz_matrix *ctm, xps_resource *dict, char *clip_att, fz_xml *clip_tag)
+{
+ fz_path *path;
+ int fill_rule = 0;
+
+ if (clip_att)
+ path = xps_parse_abbreviated_geometry(doc, clip_att, &fill_rule);
+ else if (clip_tag)
+ path = xps_parse_path_geometry(doc, dict, clip_tag, 0, &fill_rule);
+ else
+ path = fz_new_path(doc->ctx);
+ fz_clip_path(doc->dev, path, NULL, fill_rule == 0, ctm);
+ fz_free_path(doc->ctx, path);
+}
+
+/*
+ * Parse an XPS <Path> element, and call relevant ghostscript
+ * functions for drawing and/or clipping the child elements.
+ */
+
+void
+xps_parse_path(xps_document *doc, const fz_matrix *ctm, char *base_uri, xps_resource *dict, fz_xml *root)
+{
+ fz_xml *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;
+
+ fz_xml *transform_tag = NULL;
+ fz_xml *clip_tag = NULL;
+ fz_xml *data_tag = NULL;
+ fz_xml *fill_tag = NULL;
+ fz_xml *stroke_tag = NULL;
+ fz_xml *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;
+ char *navigate_uri_att;
+
+ fz_stroke_state *stroke = NULL;
+ fz_matrix transform;
+ float samples[32];
+ fz_colorspace *colorspace;
+ fz_path *path = NULL;
+ fz_path *stroke_path = NULL;
+ fz_rect area;
+ int fill_rule;
+ int dash_len = 0;
+ fz_matrix local_ctm;
+
+ /*
+ * Extract attributes and extended attributes.
+ */
+
+ transform_att = fz_xml_att(root, "RenderTransform");
+ clip_att = fz_xml_att(root, "Clip");
+ data_att = fz_xml_att(root, "Data");
+ fill_att = fz_xml_att(root, "Fill");
+ stroke_att = fz_xml_att(root, "Stroke");
+ opacity_att = fz_xml_att(root, "Opacity");
+ opacity_mask_att = fz_xml_att(root, "OpacityMask");
+
+ stroke_dash_array_att = fz_xml_att(root, "StrokeDashArray");
+ stroke_dash_cap_att = fz_xml_att(root, "StrokeDashCap");
+ stroke_dash_offset_att = fz_xml_att(root, "StrokeDashOffset");
+ stroke_end_line_cap_att = fz_xml_att(root, "StrokeEndLineCap");
+ stroke_start_line_cap_att = fz_xml_att(root, "StrokeStartLineCap");
+ stroke_line_join_att = fz_xml_att(root, "StrokeLineJoin");
+ stroke_miter_limit_att = fz_xml_att(root, "StrokeMiterLimit");
+ stroke_thickness_att = fz_xml_att(root, "StrokeThickness");
+ navigate_uri_att = fz_xml_att(root, "FixedPage.NavigateUri");
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "Path.RenderTransform"))
+ transform_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "Path.OpacityMask"))
+ opacity_mask_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "Path.Clip"))
+ clip_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "Path.Fill"))
+ fill_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "Path.Stroke"))
+ stroke_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "Path.Data"))
+ data_tag = fz_xml_down(node);
+ }
+
+ fill_uri = base_uri;
+ stroke_uri = base_uri;
+ opacity_mask_uri = base_uri;
+
+ xps_resolve_resource_reference(doc, dict, &data_att, &data_tag, NULL);
+ xps_resolve_resource_reference(doc, dict, &clip_att, &clip_tag, NULL);
+ xps_resolve_resource_reference(doc, dict, &transform_att, &transform_tag, NULL);
+ xps_resolve_resource_reference(doc, dict, &fill_att, &fill_tag, &fill_uri);
+ xps_resolve_resource_reference(doc, dict, &stroke_att, &stroke_tag, &stroke_uri);
+ xps_resolve_resource_reference(doc, 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(fz_xml_tag(fill_tag), "SolidColorBrush"))
+ {
+ fill_opacity_att = fz_xml_att(fill_tag, "Opacity");
+ fill_att = fz_xml_att(fill_tag, "Color");
+ fill_tag = NULL;
+ }
+
+ if (stroke_tag && !strcmp(fz_xml_tag(stroke_tag), "SolidColorBrush"))
+ {
+ stroke_opacity_att = fz_xml_att(stroke_tag, "Opacity");
+ stroke_att = fz_xml_att(stroke_tag, "Color");
+ stroke_tag = NULL;
+ }
+
+ if (stroke_att || stroke_tag)
+ {
+ if (stroke_dash_array_att)
+ {
+ char *s = stroke_dash_array_att;
+
+ while (*s)
+ {
+ while (*s == ' ')
+ s++;
+ if (*s) /* needed in case of a space before the last quote */
+ dash_len++;
+
+ while (*s && *s != ' ')
+ s++;
+ }
+ }
+ stroke = fz_new_stroke_state_with_len(doc->ctx, dash_len);
+ stroke->start_cap = xps_parse_line_cap(stroke_start_line_cap_att);
+ stroke->dash_cap = xps_parse_line_cap(stroke_dash_cap_att);
+ stroke->end_cap = xps_parse_line_cap(stroke_end_line_cap_att);
+
+ stroke->linejoin = FZ_LINEJOIN_MITER_XPS;
+ if (stroke_line_join_att)
+ {
+ if (!strcmp(stroke_line_join_att, "Miter")) stroke->linejoin = FZ_LINEJOIN_MITER_XPS;
+ if (!strcmp(stroke_line_join_att, "Round")) stroke->linejoin = FZ_LINEJOIN_ROUND;
+ if (!strcmp(stroke_line_join_att, "Bevel")) stroke->linejoin = FZ_LINEJOIN_BEVEL;
+ }
+
+ stroke->miterlimit = 10;
+ if (stroke_miter_limit_att)
+ stroke->miterlimit = fz_atof(stroke_miter_limit_att);
+
+ stroke->linewidth = 1;
+ if (stroke_thickness_att)
+ stroke->linewidth = fz_atof(stroke_thickness_att);
+
+ stroke->dash_phase = 0;
+ stroke->dash_len = 0;
+ if (stroke_dash_array_att)
+ {
+ char *s = stroke_dash_array_att;
+
+ if (stroke_dash_offset_att)
+ stroke->dash_phase = fz_atof(stroke_dash_offset_att) * stroke->linewidth;
+
+ while (*s)
+ {
+ while (*s == ' ')
+ s++;
+ if (*s) /* needed in case of a space before the last quote */
+ stroke->dash_list[stroke->dash_len++] = fz_atof(s) * stroke->linewidth;
+ while (*s && *s != ' ')
+ s++;
+ }
+ stroke->dash_len = dash_len;
+ }
+ }
+
+ transform = fz_identity;
+ if (transform_att)
+ xps_parse_render_transform(doc, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(doc, transform_tag, &transform);
+ fz_concat(&local_ctm, &transform, ctm);
+
+ if (clip_att || clip_tag)
+ xps_clip(doc, &local_ctm, dict, clip_att, clip_tag);
+
+ fill_rule = 0;
+ if (data_att)
+ path = xps_parse_abbreviated_geometry(doc, data_att, &fill_rule);
+ else if (data_tag)
+ {
+ path = xps_parse_path_geometry(doc, dict, data_tag, 0, &fill_rule);
+ if (stroke_att || stroke_tag)
+ stroke_path = xps_parse_path_geometry(doc, dict, data_tag, 1, &fill_rule);
+ }
+ if (!stroke_path)
+ stroke_path = path;
+
+ if (stroke_att || stroke_tag)
+ {
+ fz_bound_path(doc->ctx, stroke_path, stroke, &local_ctm, &area);
+ if (stroke_path != path && (fill_att || fill_tag)) {
+ fz_rect bounds;
+ fz_bound_path(doc->ctx, path, NULL, &local_ctm, &bounds);
+ fz_union_rect(&area, &bounds);
+ }
+ }
+ else
+ fz_bound_path(doc->ctx, path, NULL, &local_ctm, &area);
+
+ if (navigate_uri_att)
+ xps_add_link(doc, &area, base_uri, navigate_uri_att);
+
+ xps_begin_opacity(doc, &local_ctm, &area, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+
+ if (fill_att)
+ {
+ xps_parse_color(doc, base_uri, fill_att, &colorspace, samples);
+ if (fill_opacity_att)
+ samples[0] *= fz_atof(fill_opacity_att);
+ xps_set_color(doc, colorspace, samples);
+
+ fz_fill_path(doc->dev, path, fill_rule == 0, &local_ctm,
+ doc->colorspace, doc->color, doc->alpha);
+ }
+
+ if (fill_tag)
+ {
+ fz_clip_path(doc->dev, path, NULL, fill_rule == 0, &local_ctm);
+ xps_parse_brush(doc, &local_ctm, &area, fill_uri, dict, fill_tag);
+ fz_pop_clip(doc->dev);
+ }
+
+ if (stroke_att)
+ {
+ xps_parse_color(doc, base_uri, stroke_att, &colorspace, samples);
+ if (stroke_opacity_att)
+ samples[0] *= fz_atof(stroke_opacity_att);
+ xps_set_color(doc, colorspace, samples);
+
+ fz_stroke_path(doc->dev, stroke_path, stroke, &local_ctm,
+ doc->colorspace, doc->color, doc->alpha);
+ }
+
+ if (stroke_tag)
+ {
+ fz_clip_stroke_path(doc->dev, stroke_path, NULL, stroke, &local_ctm);
+ xps_parse_brush(doc, &local_ctm, &area, stroke_uri, dict, stroke_tag);
+ fz_pop_clip(doc->dev);
+ }
+
+ xps_end_opacity(doc, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+
+ if (stroke_path != path)
+ fz_free_path(doc->ctx, stroke_path);
+ fz_free_path(doc->ctx, path);
+ path = NULL;
+ fz_drop_stroke_state(doc->ctx, stroke);
+
+ if (clip_att || clip_tag)
+ fz_pop_clip(doc->dev);
+}
diff --git a/source/xps/xps-resource.c b/source/xps/xps-resource.c
new file mode 100644
index 00000000..5c927e1d
--- /dev/null
+++ b/source/xps/xps-resource.c
@@ -0,0 +1,172 @@
+#include "mupdf/xps.h"
+
+static fz_xml *
+xps_lookup_resource(xps_document *doc, 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 fz_xml *
+xps_parse_resource_reference(xps_document *doc, 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_lookup_resource(doc, dict, name, urip);
+}
+
+void
+xps_resolve_resource_reference(xps_document *doc, xps_resource *dict,
+ char **attp, fz_xml **tagp, char **urip)
+{
+ if (*attp)
+ {
+ fz_xml *rsrc = xps_parse_resource_reference(doc, dict, *attp, urip);
+ if (rsrc)
+ {
+ *attp = NULL;
+ *tagp = rsrc;
+ }
+ }
+}
+
+static xps_resource *
+xps_parse_remote_resource_dictionary(xps_document *doc, char *base_uri, char *source_att)
+{
+ char part_name[1024];
+ char part_uri[1024];
+ xps_resource *dict;
+ xps_part *part;
+ fz_xml *xml;
+ char *s;
+ fz_context *ctx = doc->ctx;
+
+ /* External resource dictionaries MUST NOT reference other resource dictionaries */
+ xps_resolve_url(part_name, base_uri, source_att, sizeof part_name);
+ part = xps_read_part(doc, part_name);
+ fz_try(ctx)
+ {
+ xml = fz_parse_xml(doc->ctx, part->data, part->size);
+ }
+ fz_always(ctx)
+ {
+ xps_free_part(doc, part);
+ }
+ fz_catch(ctx)
+ {
+ /* FIXME: TryLater ? */
+ xml = NULL;
+ }
+
+ if (!xml)
+ return NULL;
+
+ if (strcmp(fz_xml_tag(xml), "ResourceDictionary"))
+ {
+ fz_free_xml(doc->ctx, xml);
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "expected ResourceDictionary element");
+ }
+
+ fz_strlcpy(part_uri, part_name, sizeof part_uri);
+ s = strrchr(part_uri, '/');
+ if (s)
+ s[1] = 0;
+
+ dict = xps_parse_resource_dictionary(doc, part_uri, xml);
+ if (dict)
+ dict->base_xml = xml; /* pass on ownership */
+
+ return dict;
+}
+
+xps_resource *
+xps_parse_resource_dictionary(xps_document *doc, char *base_uri, fz_xml *root)
+{
+ xps_resource *head;
+ xps_resource *entry;
+ fz_xml *node;
+ char *source;
+ char *key;
+
+ source = fz_xml_att(root, "Source");
+ if (source)
+ return xps_parse_remote_resource_dictionary(doc, base_uri, source);
+
+ head = NULL;
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ key = fz_xml_att(node, "x:Key");
+ if (key)
+ {
+ entry = fz_malloc_struct(doc->ctx, 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(doc->ctx, base_uri);
+
+ return head;
+}
+
+void
+xps_free_resource_dictionary(xps_document *doc, xps_resource *dict)
+{
+ xps_resource *next;
+ while (dict)
+ {
+ next = dict->next;
+ if (dict->base_xml)
+ fz_free_xml(doc->ctx, dict->base_xml);
+ if (dict->base_uri)
+ fz_free(doc->ctx, dict->base_uri);
+ fz_free(doc->ctx, dict);
+ dict = next;
+ }
+}
+
+void
+xps_print_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_print_resource_dictionary(dict->parent);
+ printf("}\n");
+ }
+ dict = dict->next;
+ }
+}
diff --git a/source/xps/xps-tile.c b/source/xps/xps-tile.c
new file mode 100644
index 00000000..cbf3943d
--- /dev/null
+++ b/source/xps/xps-tile.c
@@ -0,0 +1,390 @@
+#include "mupdf/xps.h"
+
+#define TILE
+
+/*
+ * 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;
+ fz_xml *root;
+ void *user;
+ void (*func)(xps_document*, const fz_matrix *, const fz_rect *, char*, xps_resource*, fz_xml*, void*);
+};
+
+static void
+xps_paint_tiling_brush_clipped(xps_document *doc, const fz_matrix *ctm, const fz_rect *viewbox, struct closure *c)
+{
+ fz_path *path = fz_new_path(doc->ctx);
+ fz_moveto(doc->ctx, path, viewbox->x0, viewbox->y0);
+ fz_lineto(doc->ctx, path, viewbox->x0, viewbox->y1);
+ fz_lineto(doc->ctx, path, viewbox->x1, viewbox->y1);
+ fz_lineto(doc->ctx, path, viewbox->x1, viewbox->y0);
+ fz_closepath(doc->ctx, path);
+ fz_clip_path(doc->dev, path, NULL, 0, ctm);
+ fz_free_path(doc->ctx, path);
+ c->func(doc, ctm, viewbox, c->base_uri, c->dict, c->root, c->user);
+ fz_pop_clip(doc->dev);
+}
+
+static void
+xps_paint_tiling_brush(xps_document *doc, const fz_matrix *ctm, const fz_rect *viewbox, int tile_mode, struct closure *c)
+{
+ fz_matrix ttm;
+
+ xps_paint_tiling_brush_clipped(doc, ctm, viewbox, c);
+
+ if (tile_mode == TILE_FLIP_X || tile_mode == TILE_FLIP_X_Y)
+ {
+ ttm = *ctm;
+ fz_pre_scale(fz_pre_translate(&ttm, viewbox->x1 * 2, 0), -1, 1);
+ xps_paint_tiling_brush_clipped(doc, &ttm, viewbox, c);
+ }
+
+ if (tile_mode == TILE_FLIP_Y || tile_mode == TILE_FLIP_X_Y)
+ {
+ ttm = *ctm;
+ fz_pre_scale(fz_pre_translate(&ttm, 0, viewbox->y1 * 2), 1, -1);
+ xps_paint_tiling_brush_clipped(doc, &ttm, viewbox, c);
+ }
+
+ if (tile_mode == TILE_FLIP_X_Y)
+ {
+ ttm = *ctm;
+ fz_pre_scale(fz_pre_translate(&ttm, viewbox->x1 * 2, viewbox->y1 * 2), -1, -1);
+ xps_paint_tiling_brush_clipped(doc, &ttm, viewbox, c);
+ }
+}
+
+void
+xps_parse_tiling_brush(xps_document *doc, const fz_matrix *ctm, const fz_rect *area,
+ char *base_uri, xps_resource *dict, fz_xml *root,
+ void (*func)(xps_document*, const fz_matrix*, const fz_rect*, char*, xps_resource*, fz_xml*, void*), void *user)
+{
+ fz_xml *node;
+ struct closure c;
+
+ char *opacity_att;
+ char *transform_att;
+ char *viewbox_att;
+ char *viewport_att;
+ char *tile_mode_att;
+
+ fz_xml *transform_tag = NULL;
+
+ fz_matrix transform;
+ fz_rect viewbox;
+ fz_rect viewport;
+ float xstep, ystep;
+ float xscale, yscale;
+ int tile_mode;
+
+ opacity_att = fz_xml_att(root, "Opacity");
+ transform_att = fz_xml_att(root, "Transform");
+ viewbox_att = fz_xml_att(root, "Viewbox");
+ viewport_att = fz_xml_att(root, "Viewport");
+ tile_mode_att = fz_xml_att(root, "TileMode");
+
+ c.base_uri = base_uri;
+ c.dict = dict;
+ c.root = root;
+ c.user = user;
+ c.func = func;
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "ImageBrush.Transform"))
+ transform_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "VisualBrush.Transform"))
+ transform_tag = fz_xml_down(node);
+ }
+
+ xps_resolve_resource_reference(doc, dict, &transform_att, &transform_tag, NULL);
+
+ transform = fz_identity;
+ if (transform_att)
+ xps_parse_render_transform(doc, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(doc, transform_tag, &transform);
+ fz_concat(&transform, &transform, ctm);
+
+ viewbox = fz_unit_rect;
+ if (viewbox_att)
+ xps_parse_rectangle(doc, viewbox_att, &viewbox);
+
+ viewport = fz_unit_rect;
+ if (viewport_att)
+ xps_parse_rectangle(doc, viewport_att, &viewport);
+
+ if (fabsf(viewport.x1 - viewport.x0) < 0.01f || fabsf(viewport.y1 - viewport.y0) < 0.01f)
+ fz_warn(doc->ctx, "not drawing tile for viewport size %.4f x %.4f", viewport.x1 - viewport.x0, viewport.y1 - viewport.y0);
+ else if (fabsf(viewbox.x1 - viewbox.x0) < 0.01f || fabsf(viewbox.y1 - viewbox.y0) < 0.01f)
+ fz_warn(doc->ctx, "not drawing tile for viewbox size %.4f x %.4f", viewbox.x1 - viewbox.x0, viewbox.y1 - viewbox.y0);
+
+ /* some sanity checks on the viewport/viewbox size */
+ if (fabsf(viewport.x1 - viewport.x0) < 0.01f) return;
+ if (fabsf(viewport.y1 - viewport.y0) < 0.01f) return;
+ if (fabsf(viewbox.x1 - viewbox.x0) < 0.01f) return;
+ if (fabsf(viewbox.y1 - viewbox.y0) < 0.01f) 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(doc, &transform, area, base_uri, dict, opacity_att, NULL);
+
+ fz_pre_translate(&transform, viewport.x0, viewport.y0);
+ fz_pre_scale(&transform, xscale, yscale);
+ fz_pre_translate(&transform, -viewbox.x0, -viewbox.y0);
+
+ if (tile_mode != TILE_NONE)
+ {
+ int x0, y0, x1, y1;
+ fz_matrix invctm;
+ fz_rect local_area = *area;
+ area = fz_transform_rect(&local_area, fz_invert_matrix(&invctm, &transform));
+ x0 = floorf(local_area.x0 / xstep);
+ y0 = floorf(local_area.y0 / ystep);
+ x1 = ceilf(local_area.x1 / xstep);
+ y1 = ceilf(local_area.y1 / ystep);
+
+#ifdef TILE
+ if ((x1 - x0) * (y1 - y0) > 1)
+#else
+ if (0)
+#endif
+ {
+ fz_rect bigview = viewbox;
+ bigview.x1 = bigview.x0 + xstep;
+ bigview.y1 = bigview.y0 + ystep;
+ fz_begin_tile(doc->dev, &local_area, &bigview, xstep, ystep, &transform);
+ xps_paint_tiling_brush(doc, &transform, &viewbox, tile_mode, &c);
+ fz_end_tile(doc->dev);
+ }
+ else
+ {
+ int x, y;
+ for (y = y0; y < y1; y++)
+ {
+ for (x = x0; x < x1; x++)
+ {
+ fz_matrix ttm = transform;
+ fz_pre_translate(&ttm, xstep * x, ystep * y);
+ xps_paint_tiling_brush(doc, &ttm, &viewbox, tile_mode, &c);
+ }
+ }
+ }
+ }
+ else
+ {
+ xps_paint_tiling_brush(doc, &transform, &viewbox, tile_mode, &c);
+ }
+
+ xps_end_opacity(doc, base_uri, dict, opacity_att, NULL);
+}
+
+static void
+xps_paint_visual_brush(xps_document *doc, const fz_matrix *ctm, const fz_rect *area,
+ char *base_uri, xps_resource *dict, fz_xml *root, void *visual_tag)
+{
+ xps_parse_element(doc, ctm, area, base_uri, dict, (fz_xml *)visual_tag);
+}
+
+void
+xps_parse_visual_brush(xps_document *doc, const fz_matrix *ctm, const fz_rect *area,
+ char *base_uri, xps_resource *dict, fz_xml *root)
+{
+ fz_xml *node;
+
+ char *visual_uri;
+ char *visual_att;
+ fz_xml *visual_tag = NULL;
+
+ visual_att = fz_xml_att(root, "Visual");
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "VisualBrush.Visual"))
+ visual_tag = fz_xml_down(node);
+ }
+
+ visual_uri = base_uri;
+ xps_resolve_resource_reference(doc, dict, &visual_att, &visual_tag, &visual_uri);
+
+ if (visual_tag)
+ {
+ xps_parse_tiling_brush(doc, ctm, area,
+ visual_uri, dict, root, xps_paint_visual_brush, visual_tag);
+ }
+}
+
+void
+xps_parse_canvas(xps_document *doc, const fz_matrix *ctm, const fz_rect *area, char *base_uri, xps_resource *dict, fz_xml *root)
+{
+ xps_resource *new_dict = NULL;
+ fz_xml *node;
+ char *opacity_mask_uri;
+
+ char *transform_att;
+ char *clip_att;
+ char *opacity_att;
+ char *opacity_mask_att;
+ char *navigate_uri_att;
+
+ fz_xml *transform_tag = NULL;
+ fz_xml *clip_tag = NULL;
+ fz_xml *opacity_mask_tag = NULL;
+
+ fz_matrix transform;
+
+ transform_att = fz_xml_att(root, "RenderTransform");
+ clip_att = fz_xml_att(root, "Clip");
+ opacity_att = fz_xml_att(root, "Opacity");
+ opacity_mask_att = fz_xml_att(root, "OpacityMask");
+ navigate_uri_att = fz_xml_att(root, "FixedPage.NavigateUri");
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "Canvas.Resources") && fz_xml_down(node))
+ {
+ if (new_dict)
+ {
+ fz_warn(doc->ctx, "ignoring follow-up resource dictionaries");
+ }
+ else
+ {
+ new_dict = xps_parse_resource_dictionary(doc, base_uri, fz_xml_down(node));
+ if (new_dict)
+ {
+ new_dict->parent = dict;
+ dict = new_dict;
+ }
+ }
+ }
+
+ if (!strcmp(fz_xml_tag(node), "Canvas.RenderTransform"))
+ transform_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "Canvas.Clip"))
+ clip_tag = fz_xml_down(node);
+ if (!strcmp(fz_xml_tag(node), "Canvas.OpacityMask"))
+ opacity_mask_tag = fz_xml_down(node);
+ }
+
+ opacity_mask_uri = base_uri;
+ xps_resolve_resource_reference(doc, dict, &transform_att, &transform_tag, NULL);
+ xps_resolve_resource_reference(doc, dict, &clip_att, &clip_tag, NULL);
+ xps_resolve_resource_reference(doc, dict, &opacity_mask_att, &opacity_mask_tag, &opacity_mask_uri);
+
+ transform = fz_identity;
+ if (transform_att)
+ xps_parse_render_transform(doc, transform_att, &transform);
+ if (transform_tag)
+ xps_parse_matrix_transform(doc, transform_tag, &transform);
+ fz_concat(&transform, &transform, ctm);
+
+ if (navigate_uri_att)
+ xps_add_link(doc, area, base_uri, navigate_uri_att);
+
+ if (clip_att || clip_tag)
+ xps_clip(doc, &transform, dict, clip_att, clip_tag);
+
+ xps_begin_opacity(doc, &transform, area, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ {
+ xps_parse_element(doc, &transform, area, base_uri, dict, node);
+ }
+
+ xps_end_opacity(doc, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
+
+ if (clip_att || clip_tag)
+ fz_pop_clip(doc->dev);
+
+ if (new_dict)
+ xps_free_resource_dictionary(doc, new_dict);
+}
+
+void
+xps_parse_fixed_page(xps_document *doc, const fz_matrix *ctm, xps_page *page)
+{
+ fz_xml *node;
+ xps_resource *dict;
+ char base_uri[1024];
+ fz_rect area;
+ char *s;
+ fz_matrix scm;
+
+ fz_strlcpy(base_uri, page->name, sizeof base_uri);
+ s = strrchr(base_uri, '/');
+ if (s)
+ s[1] = 0;
+
+ dict = NULL;
+
+ doc->opacity_top = 0;
+ doc->opacity[0] = 1;
+
+ if (!page->root)
+ return;
+
+ area = fz_unit_rect;
+ fz_transform_rect(&area, fz_scale(&scm, page->width, page->height));
+
+ for (node = fz_xml_down(page->root); node; node = fz_xml_next(node))
+ {
+ if (!strcmp(fz_xml_tag(node), "FixedPage.Resources") && fz_xml_down(node))
+ {
+ if (dict)
+ fz_warn(doc->ctx, "ignoring follow-up resource dictionaries");
+ else
+ dict = xps_parse_resource_dictionary(doc, base_uri, fz_xml_down(node));
+ }
+ xps_parse_element(doc, ctm, &area, base_uri, dict, node);
+ }
+
+ if (dict)
+ xps_free_resource_dictionary(doc, dict);
+}
+
+void
+xps_run_page(xps_document *doc, xps_page *page, fz_device *dev, const fz_matrix *ctm, fz_cookie *cookie)
+{
+ fz_matrix page_ctm = *ctm;
+
+ fz_pre_scale(&page_ctm, 72.0f / 96.0f, 72.0f / 96.0f);
+
+ doc->cookie = cookie;
+ doc->dev = dev;
+ xps_parse_fixed_page(doc, &page_ctm, page);
+ doc->cookie = NULL;
+ doc->dev = NULL;
+ page->links_resolved = 1;
+}
diff --git a/source/xps/xps-util.c b/source/xps/xps-util.c
new file mode 100644
index 00000000..a74dc30f
--- /dev/null
+++ b/source/xps/xps-util.c
@@ -0,0 +1,165 @@
+#include "mupdf/xps.h"
+
+static inline int xps_tolower(int c)
+{
+ if (c >= 'A' && c <= 'Z')
+ return c + 32;
+ return c;
+}
+
+int
+xps_strcasecmp(char *a, char *b)
+{
+ while (xps_tolower(*a) == xps_tolower(*b))
+ {
+ if (*a++ == 0)
+ return 0;
+ b++;
+ }
+ return xps_tolower(*a) - xps_tolower(*b);
+}
+
+/* A URL is defined as consisting of a:
+ * SCHEME (e.g. http:)
+ * AUTHORITY (username, password, hostname, port, eg //test:passwd@mupdf.com:999)
+ * PATH (e.g. /download)
+ * QUERY (e.g. ?view=page)
+ * FRAGMENT (e.g. #fred) (not strictly part of the URL)
+ */
+static char *
+skip_scheme(char *path)
+{
+ char *p = path;
+
+ /* Skip over: alpha *(alpha | digit | "+" | "-" | ".") looking for : */
+ if (*p >= 'a' && *p <= 'z')
+ {}
+ else if (*p >= 'A' && *p <= 'Z')
+ {}
+ else
+ return path;
+
+ while (*++p)
+ {
+ if (*p >= 'a' && *p <= 'z')
+ {}
+ else if (*p >= 'A' && *p <= 'Z')
+ {}
+ else if (*p >= '0' && *p <= '9')
+ {}
+ else if (*p == '+')
+ {}
+ else if (*p == '-')
+ {}
+ else if (*p == '.')
+ {}
+ else if (*p == ':')
+ return p+1;
+ else
+ break;
+ }
+ return path;
+}
+
+static char *
+skip_authority(char *path)
+{
+ char *p = path;
+
+ /* Authority section must start with '//' */
+ if (p[0] != '/' || p[1] != '/')
+ return path;
+ p += 2;
+
+ /* Authority is terminated by end of URL, '/' or '?' */
+ while (*p && *p != '/' && *p != '?')
+ p++;
+
+ return p;
+}
+
+#define SEP(x) ((x)=='/' || (x) == 0)
+
+static char *
+xps_clean_path(char *name)
+{
+ char *p, *q, *dotdot, *start;
+ int rooted;
+
+ start = skip_scheme(name);
+ start = skip_authority(start);
+ rooted = start[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 = start + 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 != start)
+ *q++ = '/';
+ *q++ = '.';
+ *q++ = '.';
+ dotdot = q;
+ }
+ }
+ else /* real path element */
+ {
+ if (q != start+rooted)
+ *q++ = '/';
+ while ((*q = *p) != '/' && *q != 0)
+ p++, q++;
+ }
+ }
+
+ if (q == start) /* empty string is really "." */
+ *q++ = '.';
+ *q = '\0';
+
+ return name;
+}
+
+void
+xps_resolve_url(char *output, char *base_uri, char *path, int output_size)
+{
+ char *p = skip_authority(skip_scheme(path));
+
+ if (p != path || path[0] == '/')
+ {
+ fz_strlcpy(output, path, output_size);
+ }
+ else
+ {
+ int len = fz_strlcpy(output, base_uri, output_size);
+ if (len == 0 || output[len-1] != '/')
+ fz_strlcat(output, "/", output_size);
+ fz_strlcat(output, path, output_size);
+ }
+ xps_clean_path(output);
+}
+
+int
+xps_url_is_remote(char *path)
+{
+ char *p = skip_authority(skip_scheme(path));
+
+ return p != path;
+}
diff --git a/source/xps/xps-zip.c b/source/xps/xps-zip.c
new file mode 100644
index 00000000..70b643af
--- /dev/null
+++ b/source/xps/xps-zip.c
@@ -0,0 +1,697 @@
+#include "mupdf/xps.h"
+
+#include <zlib.h>
+
+#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
+
+#define ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG 0x07064b50
+#define ZIP64_END_OF_CENTRAL_DIRECTORY_SIG 0x06064b50
+#define ZIP64_EXTRA_FIELD_SIG 0x0001
+
+static void xps_init_document(xps_document *doc);
+
+xps_part *
+xps_new_part(xps_document *doc, char *name, int size)
+{
+ xps_part *part;
+
+ part = fz_malloc(doc->ctx, sizeof(xps_part));
+ part->name = fz_strdup(doc->ctx, name);
+ part->size = size;
+ part->data = fz_malloc(doc->ctx, size + 1);
+ part->data[size] = 0; /* null-terminate for xml parser */
+
+ return part;
+}
+
+void
+xps_free_part(xps_document *doc, xps_part *part)
+{
+ fz_free(doc->ctx, part->name);
+ fz_free(doc->ctx, part->data);
+ fz_free(doc->ctx, part);
+}
+
+static inline int getshort(fz_stream *file)
+{
+ int a = fz_read_byte(file);
+ int b = fz_read_byte(file);
+ return a | b << 8;
+}
+
+static inline int getlong(fz_stream *file)
+{
+ int a = fz_read_byte(file);
+ int b = fz_read_byte(file);
+ int c = fz_read_byte(file);
+ int d = fz_read_byte(file);
+ return a | b << 8 | c << 16 | d << 24;
+}
+
+static inline int getlong64(fz_stream *file)
+{
+ int a = getlong(file);
+ int b = getlong(file);
+ return b != 0 ? -1 : a;
+}
+
+static void *
+xps_zip_alloc_items(xps_document *doc, int items, int size)
+{
+ return fz_malloc_array(doc->ctx, items, size);
+}
+
+static void
+xps_zip_free(xps_document *doc, void *ptr)
+{
+ fz_free(doc->ctx, 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_lookup_zip_entry(xps_document *doc, char *name)
+{
+ int l = 0;
+ int r = doc->zip_count - 1;
+ while (l <= r)
+ {
+ int m = (l + r) >> 1;
+ int c = xps_strcasecmp(name, doc->zip_table[m].name);
+ if (c < 0)
+ r = m - 1;
+ else if (c > 0)
+ l = m + 1;
+ else
+ return &doc->zip_table[m];
+ }
+ return NULL;
+}
+
+static void
+xps_read_zip_entry(xps_document *doc, xps_entry *ent, unsigned char *outbuf)
+{
+ z_stream stream;
+ unsigned char *inbuf;
+ int sig;
+ int method;
+ int namelength, extralength;
+ int code;
+ fz_context *ctx = doc->ctx;
+
+ fz_seek(doc->file, ent->offset, 0);
+
+ sig = getlong(doc->file);
+ if (sig != ZIP_LOCAL_FILE_SIG)
+ {
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "wrong zip local file signature (0x%x)", sig);
+ }
+
+ (void) getshort(doc->file); /* version */
+ (void) getshort(doc->file); /* general */
+ method = getshort(doc->file);
+ (void) getshort(doc->file); /* file time */
+ (void) getshort(doc->file); /* file date */
+ (void) getlong(doc->file); /* crc-32 */
+ (void) getlong(doc->file); /* csize */
+ (void) getlong(doc->file); /* usize */
+ namelength = getshort(doc->file);
+ extralength = getshort(doc->file);
+
+ fz_seek(doc->file, namelength + extralength, 1);
+
+ if (method == 0)
+ {
+ fz_read(doc->file, outbuf, ent->usize);
+ }
+ else if (method == 8)
+ {
+ inbuf = fz_malloc(ctx, ent->csize);
+
+ fz_read(doc->file, inbuf, ent->csize);
+
+ memset(&stream, 0, sizeof(z_stream));
+ stream.zalloc = (alloc_func) xps_zip_alloc_items;
+ stream.zfree = (free_func) xps_zip_free;
+ stream.opaque = doc;
+ 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)
+ {
+ fz_free(ctx, inbuf);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "zlib inflateInit2 error: %s", stream.msg);
+ }
+ code = inflate(&stream, Z_FINISH);
+ if (code != Z_STREAM_END)
+ {
+ inflateEnd(&stream);
+ fz_free(ctx, inbuf);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "zlib inflate error: %s", stream.msg);
+ }
+ code = inflateEnd(&stream);
+ if (code != Z_OK)
+ {
+ fz_free(ctx, inbuf);
+ fz_throw(ctx, FZ_ERROR_GENERIC, "zlib inflateEnd error: %s", stream.msg);
+ }
+
+ fz_free(ctx, inbuf);
+ }
+ else
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "unknown compression method (%d)", method);
+ }
+}
+
+/*
+ * Read the central directory in a zip file.
+ */
+
+static void
+xps_read_zip_dir(xps_document *doc, int start_offset)
+{
+ int sig;
+ int offset, count;
+ int namesize, metasize, commentsize;
+ int i;
+
+ fz_seek(doc->file, start_offset, 0);
+
+ sig = getlong(doc->file);
+ if (sig != ZIP_END_OF_CENTRAL_DIRECTORY_SIG)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "wrong zip end of central directory signature (0x%x)", sig);
+
+ (void) getshort(doc->file); /* this disk */
+ (void) getshort(doc->file); /* start disk */
+ (void) getshort(doc->file); /* entries in this disk */
+ count = getshort(doc->file); /* entries in central directory disk */
+ (void) getlong(doc->file); /* size of central directory */
+ offset = getlong(doc->file); /* offset to central directory */
+
+ /* ZIP64 */
+ if (count == 0xFFFF)
+ {
+ fz_seek(doc->file, start_offset - 20, 0);
+
+ sig = getlong(doc->file);
+ if (sig != ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "wrong zip64 end of central directory locator signature (0x%x)", sig);
+
+ (void) getlong(doc->file); /* start disk */
+ offset = getlong64(doc->file); /* offset to end of central directory record */
+ if (offset < 0)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "zip64 files larger than 2 GB aren't supported");
+
+ fz_seek(doc->file, offset, 0);
+
+ sig = getlong(doc->file);
+ if (sig != ZIP64_END_OF_CENTRAL_DIRECTORY_SIG)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "wrong zip64 end of central directory signature (0x%x)", sig);
+
+ (void) getlong64(doc->file); /* size of record */
+ (void) getshort(doc->file); /* version made by */
+ (void) getshort(doc->file); /* version to extract */
+ (void) getlong(doc->file); /* disk number */
+ (void) getlong(doc->file); /* disk number start */
+ count = getlong64(doc->file); /* entries in central directory disk */
+ (void) getlong64(doc->file); /* entries in central directory */
+ (void) getlong64(doc->file); /* size of central directory */
+ offset = getlong64(doc->file); /* offset to central directory */
+
+ if (count < 0 || offset < 0)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "zip64 files larger than 2 GB aren't supported");
+ }
+
+ doc->zip_table = fz_malloc_array(doc->ctx, count, sizeof(xps_entry));
+ memset(doc->zip_table, 0, count * sizeof(xps_entry));
+ doc->zip_count = count;
+
+ fz_seek(doc->file, offset, 0);
+
+ for (i = 0; i < count; i++)
+ {
+ sig = getlong(doc->file);
+ if (sig != ZIP_CENTRAL_DIRECTORY_SIG)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "wrong zip central directory signature (0x%x)", sig);
+
+ (void) getshort(doc->file); /* version made by */
+ (void) getshort(doc->file); /* version to extract */
+ (void) getshort(doc->file); /* general */
+ (void) getshort(doc->file); /* method */
+ (void) getshort(doc->file); /* last mod file time */
+ (void) getshort(doc->file); /* last mod file date */
+ (void) getlong(doc->file); /* crc-32 */
+ doc->zip_table[i].csize = getlong(doc->file);
+ doc->zip_table[i].usize = getlong(doc->file);
+ namesize = getshort(doc->file);
+ metasize = getshort(doc->file);
+ commentsize = getshort(doc->file);
+ (void) getshort(doc->file); /* disk number start */
+ (void) getshort(doc->file); /* int file atts */
+ (void) getlong(doc->file); /* ext file atts */
+ doc->zip_table[i].offset = getlong(doc->file);
+
+ doc->zip_table[i].name = fz_malloc(doc->ctx, namesize + 1);
+ fz_read(doc->file, (unsigned char*)doc->zip_table[i].name, namesize);
+ doc->zip_table[i].name[namesize] = 0;
+
+ while (metasize > 0)
+ {
+ int type = getshort(doc->file);
+ int size = getshort(doc->file);
+ if (type == ZIP64_EXTRA_FIELD_SIG)
+ {
+ doc->zip_table[i].usize = getlong64(doc->file);
+ doc->zip_table[i].csize = getlong64(doc->file);
+ doc->zip_table[i].offset = getlong64(doc->file);
+ fz_seek(doc->file, -24, 1);
+ }
+ fz_seek(doc->file, size, 1);
+ metasize -= 4 + size;
+ }
+ if (doc->zip_table[i].usize < 0 || doc->zip_table[i].csize < 0 || doc->zip_table[i].offset < 0)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "zip64 files larger than 2 GB aren't supported");
+
+ fz_seek(doc->file, commentsize, 1);
+ }
+
+ qsort(doc->zip_table, count, sizeof(xps_entry), xps_compare_entries);
+}
+
+static void
+xps_find_and_read_zip_dir(xps_document *doc)
+{
+ unsigned char buf[512];
+ int file_size, back, maxback;
+ int i, n;
+ fz_context *ctx = doc->ctx;
+
+ fz_seek(doc->file, 0, SEEK_END);
+ file_size = fz_tell(doc->file);
+
+ maxback = fz_mini(file_size, 0xFFFF + sizeof buf);
+ back = fz_mini(maxback, sizeof buf);
+
+ while (back < maxback)
+ {
+ fz_seek(doc->file, file_size - back, 0);
+ n = fz_read(doc->file, buf, sizeof buf);
+ for (i = n - 4; i > 0; i--)
+ {
+ if (!memcmp(buf + i, "PK\5\6", 4))
+ {
+ xps_read_zip_dir(doc, file_size - back + i);
+ return;
+ }
+ }
+
+ back += sizeof buf - 4;
+ }
+
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find end of central directory");
+}
+
+/*
+ * Read and interleave split parts from a ZIP file.
+ */
+static xps_part *
+xps_read_zip_part(xps_document *doc, char *partname)
+{
+ char buf[2048];
+ xps_entry *ent;
+ xps_part *part;
+ int count, size, offset, i;
+ char *name;
+ int seen_last = 0;
+
+ name = partname;
+ if (name[0] == '/')
+ name ++;
+
+ /* All in one piece */
+ ent = xps_lookup_zip_entry(doc, name);
+ if (ent)
+ {
+ part = xps_new_part(doc, partname, ent->usize);
+ fz_try(doc->ctx)
+ {
+ xps_read_zip_entry(doc, ent, part->data);
+ }
+ fz_catch(doc->ctx)
+ {
+ xps_free_part(doc, part);
+ fz_rethrow(doc->ctx);
+ }
+ return part;
+ }
+
+ /* Count the number of pieces and their total size */
+ count = 0;
+ size = 0;
+ while (!seen_last)
+ {
+ sprintf(buf, "%s/[%d].piece", name, count);
+ ent = xps_lookup_zip_entry(doc, buf);
+ if (!ent)
+ {
+ sprintf(buf, "%s/[%d].last.piece", name, count);
+ ent = xps_lookup_zip_entry(doc, buf);
+ seen_last = (ent != NULL);
+ }
+ if (!ent)
+ break;
+ count ++;
+ size += ent->usize;
+ }
+ if (!seen_last)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "cannot find all pieces for part '%s'", partname);
+
+ /* Inflate the pieces */
+ if (count)
+ {
+ part = xps_new_part(doc, 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_lookup_zip_entry(doc, buf);
+ fz_try(doc->ctx)
+ {
+ xps_read_zip_entry(doc, ent, part->data + offset);
+ }
+ fz_catch(doc->ctx)
+ {
+ xps_free_part(doc, part);
+ fz_rethrow(doc->ctx);
+ }
+ offset += ent->usize;
+ }
+ return part;
+ }
+
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "cannot find part '%s'", partname);
+ return NULL;
+}
+
+static int
+xps_has_zip_part(xps_document *doc, char *name)
+{
+ char buf[2048];
+ if (name[0] == '/')
+ name++;
+ if (xps_lookup_zip_entry(doc, name))
+ return 1;
+ sprintf(buf, "%s/[0].piece", name);
+ if (xps_lookup_zip_entry(doc, buf))
+ return 1;
+ sprintf(buf, "%s/[0].last.piece", name);
+ if (xps_lookup_zip_entry(doc, buf))
+ return 1;
+ return 0;
+}
+
+/*
+ * Read and interleave split parts from files in the directory.
+ */
+static xps_part *
+xps_read_dir_part(xps_document *doc, char *name)
+{
+ char buf[2048];
+ xps_part *part;
+ FILE *file;
+ int count, size, offset, i, n;
+ int seen_last = 0;
+
+ fz_strlcpy(buf, doc->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(doc, 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 (!seen_last)
+ {
+ sprintf(buf, "%s%s/[%d].piece", doc->directory, name, count);
+ file = fopen(buf, "rb");
+ if (!file)
+ {
+ sprintf(buf, "%s%s/[%d].last.piece", doc->directory, name, count);
+ file = fopen(buf, "rb");
+ seen_last = (file != NULL);
+ }
+ if (!file)
+ break;
+ count ++;
+ fseek(file, 0, SEEK_END);
+ size += ftell(file);
+ fclose(file);
+ }
+ if (!seen_last)
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "cannot find all pieces for part '%s'", name);
+
+ /* Inflate the pieces */
+ if (count)
+ {
+ part = xps_new_part(doc, name, size);
+ offset = 0;
+ for (i = 0; i < count; i++)
+ {
+ if (i < count - 1)
+ sprintf(buf, "%s%s/[%d].piece", doc->directory, name, i);
+ else
+ sprintf(buf, "%s%s/[%d].last.piece", doc->directory, name, i);
+ file = fopen(buf, "rb");
+ if (!file)
+ {
+ xps_free_part(doc, part);
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "cannot open file '%s'", buf);
+ }
+ n = fread(part->data + offset, 1, size - offset, file);
+ offset += n;
+ fclose(file);
+ }
+ return part;
+ }
+
+ fz_throw(doc->ctx, FZ_ERROR_GENERIC, "cannot find part '%s'", name);
+ return NULL;
+}
+
+static int
+file_exists(xps_document *doc, char *name)
+{
+ char buf[2048];
+ FILE *file;
+ fz_strlcpy(buf, doc->directory, sizeof buf);
+ fz_strlcat(buf, name, sizeof buf);
+ file = fopen(buf, "rb");
+ if (file)
+ {
+ fclose(file);
+ return 1;
+ }
+ return 0;
+}
+
+static int
+xps_has_dir_part(xps_document *doc, char *name)
+{
+ char buf[2048];
+ if (file_exists(doc, name))
+ return 1;
+ sprintf(buf, "%s/[0].piece", name);
+ if (file_exists(doc, buf))
+ return 1;
+ sprintf(buf, "%s/[0].last.piece", name);
+ if (file_exists(doc, buf))
+ return 1;
+ return 0;
+}
+
+xps_part *
+xps_read_part(xps_document *doc, char *partname)
+{
+ if (doc->directory)
+ return xps_read_dir_part(doc, partname);
+ return xps_read_zip_part(doc, partname);
+}
+
+int
+xps_has_part(xps_document *doc, char *partname)
+{
+ if (doc->directory)
+ return xps_has_dir_part(doc, partname);
+ return xps_has_zip_part(doc, partname);
+}
+
+static xps_document *
+xps_open_document_with_directory(fz_context *ctx, const char *directory)
+{
+ xps_document *doc;
+
+ doc = fz_malloc_struct(ctx, xps_document);
+ xps_init_document(doc);
+ doc->ctx = ctx;
+ doc->directory = fz_strdup(ctx, directory);
+
+ fz_try(ctx)
+ {
+ xps_read_page_list(doc);
+ }
+ fz_catch(ctx)
+ {
+ xps_close_document(doc);
+ fz_rethrow(ctx);
+ }
+
+ return doc;
+}
+
+xps_document *
+xps_open_document_with_stream(fz_context *ctx, fz_stream *file)
+{
+ xps_document *doc;
+
+ doc = fz_malloc_struct(ctx, xps_document);
+ xps_init_document(doc);
+ doc->ctx = ctx;
+ doc->file = fz_keep_stream(file);
+
+ fz_try(ctx)
+ {
+ xps_find_and_read_zip_dir(doc);
+ xps_read_page_list(doc);
+ }
+ fz_catch(ctx)
+ {
+ xps_close_document(doc);
+ fz_rethrow(ctx);
+ }
+
+ return doc;
+}
+
+xps_document *
+xps_open_document(fz_context *ctx, const char *filename)
+{
+ char buf[2048];
+ fz_stream *file;
+ char *p;
+ xps_document *doc;
+
+ 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;
+ return xps_open_document_with_directory(ctx, buf);
+ }
+
+ file = fz_open_file(ctx, filename);
+ if (!file)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot open file '%s': %s", filename, strerror(errno));
+
+ fz_try(ctx)
+ {
+ doc = xps_open_document_with_stream(ctx, file);
+ }
+ fz_always(ctx)
+ {
+ fz_close(file);
+ }
+ fz_catch(ctx)
+ {
+ fz_rethrow_message(ctx, "cannot load document '%s'", filename);
+ }
+ return doc;
+}
+
+void
+xps_close_document(xps_document *doc)
+{
+ xps_font_cache *font, *next;
+ int i;
+
+ if (!doc)
+ return;
+
+ if (doc->file)
+ fz_close(doc->file);
+
+ for (i = 0; i < doc->zip_count; i++)
+ fz_free(doc->ctx, doc->zip_table[i].name);
+ fz_free(doc->ctx, doc->zip_table);
+
+ font = doc->font_table;
+ while (font)
+ {
+ next = font->next;
+ fz_drop_font(doc->ctx, font->font);
+ fz_free(doc->ctx, font->name);
+ fz_free(doc->ctx, font);
+ font = next;
+ }
+
+ xps_free_page_list(doc);
+
+ fz_free(doc->ctx, doc->start_part);
+ fz_free(doc->ctx, doc->directory);
+ fz_free(doc->ctx, doc);
+}
+
+static int
+xps_meta(xps_document *doc, int key, void *ptr, int size)
+{
+ switch (key)
+ {
+ case FZ_META_FORMAT_INFO:
+ sprintf((char *)ptr, "XPS");
+ return FZ_META_OK;
+ default:
+ return FZ_META_UNKNOWN_KEY;
+ }
+}
+
+static void
+xps_init_document(xps_document *doc)
+{
+ doc->super.close = (void*)xps_close_document;
+ doc->super.load_outline = (void*)xps_load_outline;
+ doc->super.count_pages = (void*)xps_count_pages;
+ doc->super.load_page = (void*)xps_load_page;
+ doc->super.load_links = (void*)xps_load_links;
+ doc->super.bound_page = (void*)xps_bound_page;
+ doc->super.run_page_contents = (void*)xps_run_page;
+ doc->super.free_page = (void*)xps_free_page;
+ doc->super.meta = (void*)xps_meta;
+}