// Copyright (C) 2004-2021 Artifex Software, Inc. // // This file is part of MuPDF. // // MuPDF is free software: you can redistribute it and/or modify it under the // terms of the GNU Affero General Public License as published by the Free // Software Foundation, either version 3 of the License, or (at your option) // any later version. // // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more // details. // // You should have received a copy of the GNU Affero General Public License // along with MuPDF. If not, see // // Alternative licensing terms are available from the licensor. // For commercial licensing, see or contact // Artifex Software, Inc., 1305 Grant Avenue - Suite 200, Novato, // CA 94945, U.S.A., +1(415)492-9861, for further information. #include "mupdf/fitz.h" #include "xps-imp.h" #include #include #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(fz_context *ctx, xps_document *doc, 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. */ static void xps_add_fixed_document(fz_context *ctx, 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(ctx, xps_fixdoc); fz_try(ctx) { fixdoc->name = fz_strdup(ctx, name); fixdoc->outline = NULL; fixdoc->next = NULL; } fz_catch(ctx) { fz_free(ctx, fixdoc); fz_rethrow(ctx); } if (!doc->first_fixdoc) { doc->first_fixdoc = fixdoc; doc->last_fixdoc = fixdoc; } else { doc->last_fixdoc->next = fixdoc; doc->last_fixdoc = fixdoc; } } static void xps_add_fixed_page(fz_context *ctx, xps_document *doc, char *name, int width, int height) { xps_fixpage *page; /* Check for duplicates first */ for (page = doc->first_page; page; page = page->next) if (!strcmp(page->name, name)) return; page = fz_malloc_struct(ctx, xps_fixpage); page->name = NULL; fz_try(ctx) { page->name = fz_strdup(ctx, name); page->number = doc->page_count++; page->width = width; page->height = height; page->next = NULL; } fz_catch(ctx) { fz_free(ctx, page->name); fz_free(ctx, page); fz_rethrow(ctx); } 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(fz_context *ctx, xps_document *doc, char *name) { xps_fixpage *page = doc->last_page; xps_target *target; if (page == NULL) { fz_warn(ctx, "Dropping link target with no page"); return; } target = fz_malloc_struct(ctx, xps_target); fz_try(ctx) { target->name = fz_strdup(ctx, name); target->page = page->number; target->next = doc->target; } fz_catch(ctx) { fz_free(ctx, target); fz_rethrow(ctx); } doc->target = target; } fz_link_dest xps_lookup_link_target(fz_context *ctx, fz_document *doc_, const char *target_uri) { xps_document *doc = (xps_document*)doc_; xps_target *target; const 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 fz_make_link_dest_xyz(0, target->page, 0, 0, 0); return fz_make_link_dest_none(); } static void xps_drop_link_targets(fz_context *ctx, xps_document *doc) { xps_target *target = doc->target, *next; while (target) { next = target->next; fz_free(ctx, target->name); fz_free(ctx, target); target = next; } } static void xps_drop_fixed_pages(fz_context *ctx, xps_document *doc) { xps_fixpage *page = doc->first_page; while (page) { xps_fixpage *next = page->next; fz_free(ctx, page->name); fz_free(ctx, page); page = next; } doc->first_page = NULL; doc->last_page = NULL; } static void xps_drop_fixed_documents(fz_context *ctx, xps_document *doc) { xps_fixdoc *fixdoc = doc->first_fixdoc; while (fixdoc) { xps_fixdoc *next = fixdoc->next; fz_free(ctx, fixdoc->name); fz_free(ctx, fixdoc->outline); fz_free(ctx, fixdoc); fixdoc = next; } doc->first_fixdoc = NULL; doc->last_fixdoc = NULL; } void xps_drop_page_list(fz_context *ctx, xps_document *doc) { xps_drop_fixed_documents(ctx, doc); xps_drop_fixed_pages(ctx, doc); xps_drop_link_targets(ctx, doc); } /* * Parse the fixed document sequence structure and _rels/.rels to find the start part. */ static void xps_parse_metadata_imp(fz_context *ctx, xps_document *doc, fz_xml *item, xps_fixdoc *fixdoc) { while (item) { if (fz_xml_is_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(ctx, doc, tgtbuf, doc->base_uri, target, sizeof tgtbuf); if (!strcmp(type, REL_START_PART) || !strcmp(type, REL_START_PART_OXPS)) { fz_free(ctx, doc->start_part); doc->start_part = fz_strdup(ctx, tgtbuf); } if ((!strcmp(type, REL_DOC_STRUCTURE) || !strcmp(type, REL_DOC_STRUCTURE_OXPS)) && fixdoc) fixdoc->outline = fz_strdup(ctx, tgtbuf); if (!fz_xml_att(item, "Id")) fz_warn(ctx, "missing relationship id for %s", target); } } if (fz_xml_is_tag(item, "DocumentReference")) { char *source = fz_xml_att(item, "Source"); if (source) { char srcbuf[1024]; xps_resolve_url(ctx, doc, srcbuf, doc->base_uri, source, sizeof srcbuf); xps_add_fixed_document(ctx, doc, srcbuf); } } if (fz_xml_is_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(ctx, doc, srcbuf, doc->base_uri, source, sizeof srcbuf); xps_add_fixed_page(ctx, doc, srcbuf, width, height); } } if (fz_xml_is_tag(item, "LinkTarget")) { char *name = fz_xml_att(item, "Name"); if (name) xps_add_link_target(ctx, doc, name); } xps_parse_metadata_imp(ctx, doc, fz_xml_down(item), fixdoc); item = fz_xml_next(item); } } static void xps_parse_metadata(fz_context *ctx, xps_document *doc, xps_part *part, xps_fixdoc *fixdoc) { fz_xml_doc *xml; 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; xml = fz_parse_xml(ctx, part->data, 0); fz_try(ctx) { xps_parse_metadata_imp(ctx, doc, fz_xml_root(xml), fixdoc); } fz_always(ctx) { fz_drop_xml(ctx, xml); doc->base_uri = NULL; doc->part_uri = NULL; } fz_catch(ctx) fz_rethrow(ctx); } static void xps_read_and_process_metadata_part(fz_context *ctx, xps_document *doc, char *name, xps_fixdoc *fixdoc) { xps_part *part; if (!xps_has_part(ctx, doc, name)) return; part = xps_read_part(ctx, doc, name); fz_try(ctx) { xps_parse_metadata(ctx, doc, part, fixdoc); } fz_always(ctx) { xps_drop_part(ctx, doc, part); } fz_catch(ctx) { fz_rethrow(ctx); } } void xps_read_page_list(fz_context *ctx, xps_document *doc) { xps_fixdoc *fixdoc; xps_read_and_process_metadata_part(ctx, doc, "/_rels/.rels", NULL); if (!doc->start_part) fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find fixed document sequence start part"); xps_read_and_process_metadata_part(ctx, doc, doc->start_part, NULL); for (fixdoc = doc->first_fixdoc; fixdoc; fixdoc = fixdoc->next) { char relbuf[1024]; fz_try(ctx) { xps_rels_for_part(ctx, doc, relbuf, fixdoc->name, sizeof relbuf); xps_read_and_process_metadata_part(ctx, doc, relbuf, fixdoc); } fz_catch(ctx) { fz_rethrow_if(ctx, FZ_ERROR_TRYLATER); fz_warn(ctx, "cannot process FixedDocument rels part"); } xps_read_and_process_metadata_part(ctx, doc, fixdoc->name, fixdoc); } } int xps_count_pages(fz_context *ctx, fz_document *doc_, int chapter) { xps_document *doc = (xps_document*)doc_; return doc->page_count; } static fz_xml_doc * xps_load_fixed_page(fz_context *ctx, xps_document *doc, xps_fixpage *page) { xps_part *part; fz_xml_doc *xml = NULL; fz_xml *root; char *width_att; char *height_att; part = xps_read_part(ctx, doc, page->name); fz_try(ctx) { xml = fz_parse_xml(ctx, part->data, 0); root = fz_xml_root(xml); if (!root) fz_throw(ctx, FZ_ERROR_GENERIC, "FixedPage missing root element"); if (fz_xml_is_tag(root, "AlternateContent")) { fz_xml *node = xps_lookup_alternate_content(ctx, doc, root); if (!node) fz_throw(ctx, FZ_ERROR_GENERIC, "FixedPage missing alternate root element"); fz_detach_xml(ctx, node); root = node; } if (!fz_xml_is_tag(root, "FixedPage")) fz_throw(ctx, FZ_ERROR_GENERIC, "expected FixedPage element"); width_att = fz_xml_att(root, "Width"); if (!width_att) fz_throw(ctx, FZ_ERROR_GENERIC, "FixedPage missing required attribute: Width"); height_att = fz_xml_att(root, "Height"); if (!height_att) fz_throw(ctx, FZ_ERROR_GENERIC, "FixedPage missing required attribute: Height"); page->width = atoi(width_att); page->height = atoi(height_att); } fz_always(ctx) { xps_drop_part(ctx, doc, part); } fz_catch(ctx) { fz_drop_xml(ctx, xml); fz_rethrow(ctx); } return xml; } static fz_rect xps_bound_page(fz_context *ctx, fz_page *page_) { xps_page *page = (xps_page*)page_; fz_rect bounds; bounds.x0 = bounds.y0 = 0; bounds.x1 = page->fix->width * 72.0f / 96.0f; bounds.y1 = page->fix->height * 72.0f / 96.0f; return bounds; } static void xps_drop_page_imp(fz_context *ctx, fz_page *page_) { xps_page *page = (xps_page*)page_; fz_drop_xml(ctx, page->xml); } fz_page * xps_load_page(fz_context *ctx, fz_document *doc_, int chapter, int number) { xps_document *doc = (xps_document*)doc_; xps_page *page = NULL; xps_fixpage *fix; fz_xml_doc *xml; int n = 0; fz_var(page); for (fix = doc->first_page; fix; fix = fix->next) { if (n == number) { xml = xps_load_fixed_page(ctx, doc, fix); fz_try(ctx) { page = fz_new_derived_page(ctx, xps_page, doc_); page->super.load_links = xps_load_links; page->super.bound_page = xps_bound_page; page->super.run_page_contents = xps_run_page; page->super.drop_page = xps_drop_page_imp; page->fix = fix; page->xml = xml; } fz_catch(ctx) { fz_drop_xml(ctx, xml); fz_rethrow(ctx); } return (fz_page*)page; } n ++; } fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find page %d", number + 1); } static int xps_recognize(fz_context *ctx, const char *magic) { if (strstr(magic, "/_rels/.rels") || strstr(magic, "\\_rels\\.rels")) return 100; return 0; } static const char *xps_extensions[] = { "oxps", "xps", NULL }; static const char *xps_mimetypes[] = { "application/oxps", "application/vnd.ms-xpsdocument", "application/xps", NULL }; fz_document_handler xps_document_handler = { xps_recognize, xps_open_document, xps_open_document_with_stream, xps_extensions, xps_mimetypes, NULL, NULL };