1#include "mupdf/fitz.h"
2#include "xps-imp.h"
3
4#include <string.h>
5#include <stdlib.h>
6
7#define REL_START_PART \
8 "http://schemas.microsoft.com/xps/2005/06/fixedrepresentation"
9#define REL_DOC_STRUCTURE \
10 "http://schemas.microsoft.com/xps/2005/06/documentstructure"
11#define REL_REQUIRED_RESOURCE \
12 "http://schemas.microsoft.com/xps/2005/06/required-resource"
13#define REL_REQUIRED_RESOURCE_RECURSIVE \
14 "http://schemas.microsoft.com/xps/2005/06/required-resource#recursive"
15
16#define REL_START_PART_OXPS \
17 "http://schemas.openxps.org/oxps/v1.0/fixedrepresentation"
18#define REL_DOC_STRUCTURE_OXPS \
19 "http://schemas.openxps.org/oxps/v1.0/documentstructure"
20
21static void
22xps_rels_for_part(fz_context *ctx, xps_document *doc, char *buf, char *name, int buflen)
23{
24 char *p, *basename;
25 p = strrchr(name, '/');
26 basename = p ? p + 1 : name;
27 fz_strlcpy(buf, name, buflen);
28 p = strrchr(buf, '/');
29 if (p) *p = 0;
30 fz_strlcat(buf, "/_rels/", buflen);
31 fz_strlcat(buf, basename, buflen);
32 fz_strlcat(buf, ".rels", buflen);
33}
34
35/*
36 * The FixedDocumentSequence and FixedDocument parts determine
37 * which parts correspond to actual pages, and the page order.
38 */
39
40static void
41xps_add_fixed_document(fz_context *ctx, xps_document *doc, char *name)
42{
43 xps_fixdoc *fixdoc;
44
45 /* Check for duplicates first */
46 for (fixdoc = doc->first_fixdoc; fixdoc; fixdoc = fixdoc->next)
47 if (!strcmp(fixdoc->name, name))
48 return;
49
50 fixdoc = fz_malloc_struct(ctx, xps_fixdoc);
51 fz_try(ctx)
52 {
53 fixdoc->name = fz_strdup(ctx, name);
54 fixdoc->outline = NULL;
55 fixdoc->next = NULL;
56 }
57 fz_catch(ctx)
58 {
59 fz_free(ctx, fixdoc);
60 fz_rethrow(ctx);
61 }
62
63 if (!doc->first_fixdoc)
64 {
65 doc->first_fixdoc = fixdoc;
66 doc->last_fixdoc = fixdoc;
67 }
68 else
69 {
70 doc->last_fixdoc->next = fixdoc;
71 doc->last_fixdoc = fixdoc;
72 }
73}
74
75static void
76xps_add_fixed_page(fz_context *ctx, xps_document *doc, char *name, int width, int height)
77{
78 xps_fixpage *page;
79
80 /* Check for duplicates first */
81 for (page = doc->first_page; page; page = page->next)
82 if (!strcmp(page->name, name))
83 return;
84
85 page = fz_malloc_struct(ctx, xps_fixpage);
86 page->name = NULL;
87
88 fz_try(ctx)
89 {
90 page->name = fz_strdup(ctx, name);
91 page->number = doc->page_count++;
92 page->width = width;
93 page->height = height;
94 page->next = NULL;
95 }
96 fz_catch(ctx)
97 {
98 fz_free(ctx, page->name);
99 fz_free(ctx, page);
100 fz_rethrow(ctx);
101 }
102
103 if (!doc->first_page)
104 {
105 doc->first_page = page;
106 doc->last_page = page;
107 }
108 else
109 {
110 doc->last_page->next = page;
111 doc->last_page = page;
112 }
113}
114
115static void
116xps_add_link_target(fz_context *ctx, xps_document *doc, char *name)
117{
118 xps_fixpage *page = doc->last_page;
119 xps_target *target = fz_malloc_struct(ctx, xps_target);
120
121 fz_try(ctx)
122 {
123 target->name = fz_strdup(ctx, name);
124 target->page = page->number;
125 target->next = doc->target;
126 }
127 fz_catch(ctx)
128 {
129 fz_free(ctx, target);
130 fz_rethrow(ctx);
131 }
132
133 doc->target = target;
134}
135
136int
137xps_lookup_link_target(fz_context *ctx, fz_document *doc_, const char *target_uri, float *xp, float *yp)
138{
139 xps_document *doc = (xps_document*)doc_;
140 xps_target *target;
141 const char *needle = strrchr(target_uri, '#');
142 needle = needle ? needle + 1 : target_uri;
143 for (target = doc->target; target; target = target->next)
144 if (!strcmp(target->name, needle))
145 return target->page;
146 return 0;
147}
148
149static void
150xps_drop_link_targets(fz_context *ctx, xps_document *doc)
151{
152 xps_target *target = doc->target, *next;
153 while (target)
154 {
155 next = target->next;
156 fz_free(ctx, target->name);
157 fz_free(ctx, target);
158 target = next;
159 }
160}
161
162static void
163xps_drop_fixed_pages(fz_context *ctx, xps_document *doc)
164{
165 xps_fixpage *page = doc->first_page;
166 while (page)
167 {
168 xps_fixpage *next = page->next;
169 fz_free(ctx, page->name);
170 fz_free(ctx, page);
171 page = next;
172 }
173 doc->first_page = NULL;
174 doc->last_page = NULL;
175}
176
177static void
178xps_drop_fixed_documents(fz_context *ctx, xps_document *doc)
179{
180 xps_fixdoc *fixdoc = doc->first_fixdoc;
181 while (fixdoc)
182 {
183 xps_fixdoc *next = fixdoc->next;
184 fz_free(ctx, fixdoc->name);
185 fz_free(ctx, fixdoc->outline);
186 fz_free(ctx, fixdoc);
187 fixdoc = next;
188 }
189 doc->first_fixdoc = NULL;
190 doc->last_fixdoc = NULL;
191}
192
193void
194xps_drop_page_list(fz_context *ctx, xps_document *doc)
195{
196 xps_drop_fixed_documents(ctx, doc);
197 xps_drop_fixed_pages(ctx, doc);
198 xps_drop_link_targets(ctx, doc);
199}
200
201/*
202 * Parse the fixed document sequence structure and _rels/.rels to find the start part.
203 */
204
205static void
206xps_parse_metadata_imp(fz_context *ctx, xps_document *doc, fz_xml *item, xps_fixdoc *fixdoc)
207{
208 while (item)
209 {
210 if (fz_xml_is_tag(item, "Relationship"))
211 {
212 char *target = fz_xml_att(item, "Target");
213 char *type = fz_xml_att(item, "Type");
214 if (target && type)
215 {
216 char tgtbuf[1024];
217 xps_resolve_url(ctx, doc, tgtbuf, doc->base_uri, target, sizeof tgtbuf);
218 if (!strcmp(type, REL_START_PART) || !strcmp(type, REL_START_PART_OXPS))
219 {
220 fz_free(ctx, doc->start_part);
221 doc->start_part = fz_strdup(ctx, tgtbuf);
222 }
223 if ((!strcmp(type, REL_DOC_STRUCTURE) || !strcmp(type, REL_DOC_STRUCTURE_OXPS)) && fixdoc)
224 fixdoc->outline = fz_strdup(ctx, tgtbuf);
225 if (!fz_xml_att(item, "Id"))
226 fz_warn(ctx, "missing relationship id for %s", target);
227 }
228 }
229
230 if (fz_xml_is_tag(item, "DocumentReference"))
231 {
232 char *source = fz_xml_att(item, "Source");
233 if (source)
234 {
235 char srcbuf[1024];
236 xps_resolve_url(ctx, doc, srcbuf, doc->base_uri, source, sizeof srcbuf);
237 xps_add_fixed_document(ctx, doc, srcbuf);
238 }
239 }
240
241 if (fz_xml_is_tag(item, "PageContent"))
242 {
243 char *source = fz_xml_att(item, "Source");
244 char *width_att = fz_xml_att(item, "Width");
245 char *height_att = fz_xml_att(item, "Height");
246 int width = width_att ? atoi(width_att) : 0;
247 int height = height_att ? atoi(height_att) : 0;
248 if (source)
249 {
250 char srcbuf[1024];
251 xps_resolve_url(ctx, doc, srcbuf, doc->base_uri, source, sizeof srcbuf);
252 xps_add_fixed_page(ctx, doc, srcbuf, width, height);
253 }
254 }
255
256 if (fz_xml_is_tag(item, "LinkTarget"))
257 {
258 char *name = fz_xml_att(item, "Name");
259 if (name)
260 xps_add_link_target(ctx, doc, name);
261 }
262
263 xps_parse_metadata_imp(ctx, doc, fz_xml_down(item), fixdoc);
264
265 item = fz_xml_next(item);
266 }
267}
268
269static void
270xps_parse_metadata(fz_context *ctx, xps_document *doc, xps_part *part, xps_fixdoc *fixdoc)
271{
272 fz_xml_doc *xml;
273 char buf[1024];
274 char *s;
275
276 /* Save directory name part */
277 fz_strlcpy(buf, part->name, sizeof buf);
278 s = strrchr(buf, '/');
279 if (s)
280 s[0] = 0;
281
282 /* _rels parts are voodoo: their URI references are from
283 * the part they are associated with, not the actual _rels
284 * part being parsed.
285 */
286 s = strstr(buf, "/_rels");
287 if (s)
288 *s = 0;
289
290 doc->base_uri = buf;
291 doc->part_uri = part->name;
292
293 xml = fz_parse_xml(ctx, part->data, 0);
294 fz_try(ctx)
295 {
296 xps_parse_metadata_imp(ctx, doc, fz_xml_root(xml), fixdoc);
297 }
298 fz_always(ctx)
299 {
300 fz_drop_xml(ctx, xml);
301 doc->base_uri = NULL;
302 doc->part_uri = NULL;
303 }
304 fz_catch(ctx)
305 fz_rethrow(ctx);
306}
307
308static void
309xps_read_and_process_metadata_part(fz_context *ctx, xps_document *doc, char *name, xps_fixdoc *fixdoc)
310{
311 xps_part *part;
312
313 if (!xps_has_part(ctx, doc, name))
314 return;
315
316 part = xps_read_part(ctx, doc, name);
317 fz_try(ctx)
318 {
319 xps_parse_metadata(ctx, doc, part, fixdoc);
320 }
321 fz_always(ctx)
322 {
323 xps_drop_part(ctx, doc, part);
324 }
325 fz_catch(ctx)
326 {
327 fz_rethrow(ctx);
328 }
329}
330
331void
332xps_read_page_list(fz_context *ctx, xps_document *doc)
333{
334 xps_fixdoc *fixdoc;
335
336 xps_read_and_process_metadata_part(ctx, doc, "/_rels/.rels", NULL);
337
338 if (!doc->start_part)
339 fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find fixed document sequence start part");
340
341 xps_read_and_process_metadata_part(ctx, doc, doc->start_part, NULL);
342
343 for (fixdoc = doc->first_fixdoc; fixdoc; fixdoc = fixdoc->next)
344 {
345 char relbuf[1024];
346 fz_try(ctx)
347 {
348 xps_rels_for_part(ctx, doc, relbuf, fixdoc->name, sizeof relbuf);
349 xps_read_and_process_metadata_part(ctx, doc, relbuf, fixdoc);
350 }
351 fz_catch(ctx)
352 {
353 fz_rethrow_if(ctx, FZ_ERROR_TRYLATER);
354 fz_warn(ctx, "cannot process FixedDocument rels part");
355 }
356 xps_read_and_process_metadata_part(ctx, doc, fixdoc->name, fixdoc);
357 }
358}
359
360int
361xps_count_pages(fz_context *ctx, fz_document *doc_)
362{
363 xps_document *doc = (xps_document*)doc_;
364 return doc->page_count;
365}
366
367static fz_xml_doc *
368xps_load_fixed_page(fz_context *ctx, xps_document *doc, xps_fixpage *page)
369{
370 xps_part *part;
371 fz_xml_doc *xml = NULL;
372 fz_xml *root;
373 char *width_att;
374 char *height_att;
375
376 part = xps_read_part(ctx, doc, page->name);
377 fz_try(ctx)
378 {
379 xml = fz_parse_xml(ctx, part->data, 0);
380
381 root = fz_xml_root(xml);
382 if (!root)
383 fz_throw(ctx, FZ_ERROR_GENERIC, "FixedPage missing root element");
384
385 if (fz_xml_is_tag(root, "AlternateContent"))
386 {
387 fz_xml *node = xps_lookup_alternate_content(ctx, doc, root);
388 if (!node)
389 fz_throw(ctx, FZ_ERROR_GENERIC, "FixedPage missing alternate root element");
390 fz_detach_xml(ctx, xml, node);
391 root = node;
392 }
393
394 if (!fz_xml_is_tag(root, "FixedPage"))
395 fz_throw(ctx, FZ_ERROR_GENERIC, "expected FixedPage element");
396 width_att = fz_xml_att(root, "Width");
397 if (!width_att)
398 fz_throw(ctx, FZ_ERROR_GENERIC, "FixedPage missing required attribute: Width");
399 height_att = fz_xml_att(root, "Height");
400 if (!height_att)
401 fz_throw(ctx, FZ_ERROR_GENERIC, "FixedPage missing required attribute: Height");
402
403 page->width = atoi(width_att);
404 page->height = atoi(height_att);
405 }
406 fz_always(ctx)
407 {
408 xps_drop_part(ctx, doc, part);
409 }
410 fz_catch(ctx)
411 {
412 fz_drop_xml(ctx, xml);
413 fz_rethrow(ctx);
414 }
415
416 return xml;
417}
418
419static fz_rect
420xps_bound_page(fz_context *ctx, fz_page *page_)
421{
422 xps_page *page = (xps_page*)page_;
423 fz_rect bounds;
424 bounds.x0 = bounds.y0 = 0;
425 bounds.x1 = page->fix->width * 72.0f / 96.0f;
426 bounds.y1 = page->fix->height * 72.0f / 96.0f;
427 return bounds;
428}
429
430static void
431xps_drop_page_imp(fz_context *ctx, fz_page *page_)
432{
433 xps_page *page = (xps_page*)page_;
434 fz_drop_document(ctx, &page->doc->super);
435 fz_drop_xml(ctx, page->xml);
436}
437
438fz_page *
439xps_load_page(fz_context *ctx, fz_document *doc_, int number)
440{
441 xps_document *doc = (xps_document*)doc_;
442 xps_page *page = NULL;
443 xps_fixpage *fix;
444 fz_xml_doc *xml;
445 int n = 0;
446
447 fz_var(page);
448
449 for (fix = doc->first_page; fix; fix = fix->next)
450 {
451 if (n == number)
452 {
453 xml = xps_load_fixed_page(ctx, doc, fix);
454 fz_try(ctx)
455 {
456 page = fz_new_derived_page(ctx, xps_page);
457 page->super.load_links = xps_load_links;
458 page->super.bound_page = xps_bound_page;
459 page->super.run_page_contents = xps_run_page;
460 page->super.drop_page = xps_drop_page_imp;
461
462 page->doc = (xps_document*) fz_keep_document(ctx, (fz_document*)doc);
463 page->fix = fix;
464 page->xml = xml;
465 }
466 fz_catch(ctx)
467 {
468 fz_drop_xml(ctx, xml);
469 fz_rethrow(ctx);
470 }
471 return (fz_page*)page;
472 }
473 n ++;
474 }
475
476 fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find page %d", number + 1);
477}
478
479static int
480xps_recognize(fz_context *ctx, const char *magic)
481{
482 if (strstr(magic, "/_rels/.rels") || strstr(magic, "\\_rels\\.rels"))
483 return 100;
484 return 0;
485}
486
487static const char *xps_extensions[] =
488{
489 "oxps",
490 "xps",
491 NULL
492};
493
494static const char *xps_mimetypes[] =
495{
496 "application/oxps",
497 "application/vnd.ms-xpsdocument",
498 "application/xps",
499 NULL
500};
501
502fz_document_handler xps_document_handler =
503{
504 xps_recognize,
505 xps_open_document,
506 xps_open_document_with_stream,
507 xps_extensions,
508 xps_mimetypes
509};
510