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 | |
21 | static void |
22 | xps_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 | |
40 | static void |
41 | xps_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 | |
75 | static void |
76 | xps_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 | |
115 | static void |
116 | xps_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 | |
136 | int |
137 | xps_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 | |
149 | static void |
150 | xps_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 | |
162 | static void |
163 | xps_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 | |
177 | static void |
178 | xps_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 | |
193 | void |
194 | xps_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 | |
205 | static void |
206 | xps_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 | |
269 | static void |
270 | xps_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 | |
308 | static void |
309 | xps_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 | |
331 | void |
332 | xps_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 | |
360 | int |
361 | xps_count_pages(fz_context *ctx, fz_document *doc_) |
362 | { |
363 | xps_document *doc = (xps_document*)doc_; |
364 | return doc->page_count; |
365 | } |
366 | |
367 | static fz_xml_doc * |
368 | xps_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 | |
419 | static fz_rect |
420 | xps_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 | |
430 | static void |
431 | xps_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 | |
438 | fz_page * |
439 | xps_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 | |
479 | static int |
480 | xps_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 | |
487 | static const char *xps_extensions[] = |
488 | { |
489 | "oxps" , |
490 | "xps" , |
491 | NULL |
492 | }; |
493 | |
494 | static const char *xps_mimetypes[] = |
495 | { |
496 | "application/oxps" , |
497 | "application/vnd.ms-xpsdocument" , |
498 | "application/xps" , |
499 | NULL |
500 | }; |
501 | |
502 | fz_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 | |