diff options
Diffstat (limited to 'source/xps')
-rw-r--r-- | source/xps/xps-common.c | 311 | ||||
-rw-r--r-- | source/xps/xps-doc.c | 534 | ||||
-rw-r--r-- | source/xps/xps-glyphs.c | 623 | ||||
-rw-r--r-- | source/xps/xps-gradient.c | 525 | ||||
-rw-r--r-- | source/xps/xps-image.c | 128 | ||||
-rw-r--r-- | source/xps/xps-outline.c | 152 | ||||
-rw-r--r-- | source/xps/xps-path.c | 1053 | ||||
-rw-r--r-- | source/xps/xps-resource.c | 172 | ||||
-rw-r--r-- | source/xps/xps-tile.c | 390 | ||||
-rw-r--r-- | source/xps/xps-util.c | 165 | ||||
-rw-r--r-- | source/xps/xps-zip.c | 697 |
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; +} |