comparison mupdf-source/source/fitz/document.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 // Copyright (C) 2004-2025 Artifex Software, Inc.
2 //
3 // This file is part of MuPDF.
4 //
5 // MuPDF is free software: you can redistribute it and/or modify it under the
6 // terms of the GNU Affero General Public License as published by the Free
7 // Software Foundation, either version 3 of the License, or (at your option)
8 // any later version.
9 //
10 // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
11 // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13 // details.
14 //
15 // You should have received a copy of the GNU Affero General Public License
16 // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
17 //
18 // Alternative licensing terms are available from the licensor.
19 // For commercial licensing, see <https://www.artifex.com/> or contact
20 // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
21 // CA 94129, USA, for further information.
22
23 #include "mupdf/fitz.h"
24
25 #include "context-imp.h"
26
27 #include <string.h>
28 #ifndef _WIN32
29 #include <unistd.h> /* For unlink */
30 #endif
31 #include <errno.h>
32
33 static void fz_reap_dead_pages(fz_context *ctx, fz_document *doc);
34
35 enum
36 {
37 FZ_DOCUMENT_HANDLER_MAX = 32
38 };
39
40 #define DEFW (450)
41 #define DEFH (600)
42 #define DEFEM (12)
43
44 static fz_output *
45 fz_new_output_to_tempfile(fz_context *ctx, char **namep)
46 {
47 fz_output *out = NULL;
48 #ifdef _WIN32
49 char namebuf[L_tmpnam];
50 int attempts = 0;
51 #else
52 char namebuf[] = "/tmp/fztmpXXXXXX";
53 #endif
54
55
56 fz_var(out);
57
58 #ifdef _WIN32
59 /* Windows has no mkstemp command, so we have to use the old-style
60 * tmpnam based system, and retry in the case of races. */
61 do
62 {
63 if (tmpnam(namebuf) == NULL)
64 fz_throw(ctx, FZ_ERROR_SYSTEM, "tmpnam failed");
65 fz_try(ctx)
66 out = fz_new_output_with_path(ctx, namebuf, 0);
67 fz_catch(ctx)
68 {
69 /* We might hit a race condition and not be able to
70 * open the file because someone beats us to it. We'd
71 * be unbearably unlucky to hit this 10 times in a row. */
72 attempts++;
73 if (attempts >= 10)
74 fz_rethrow(ctx);
75 else
76 fz_ignore_error(ctx);
77 }
78 }
79 while (out == NULL);
80 #else
81 {
82 FILE *file;
83 int fd = mkstemp(namebuf);
84
85 if (fd == -1)
86 fz_throw(ctx, FZ_ERROR_SYSTEM, "Cannot mkstemp: %s", strerror(errno));
87 file = fdopen(fd, "w");
88 if (file == NULL)
89 fz_throw(ctx, FZ_ERROR_SYSTEM, "Failed to open temporary file");
90 out = fz_new_output_with_file_ptr(ctx, file);
91 }
92 #endif
93
94 if (namep)
95 {
96 fz_try(ctx)
97 *namep = fz_strdup(ctx, namebuf);
98 fz_catch(ctx)
99 {
100 fz_drop_output(ctx, out);
101 unlink(namebuf);
102 fz_rethrow(ctx);
103 }
104 }
105
106 return out;
107 }
108
109 static char *
110 fz_new_tmpfile_from_stream(fz_context *ctx, fz_stream *stm)
111 {
112 char *name;
113 fz_output *out = fz_new_output_to_tempfile(ctx, &name);
114
115 fz_try(ctx)
116 {
117 fz_write_stream(ctx, out, stm);
118 fz_close_output(ctx, out);
119 }
120 fz_always(ctx)
121 fz_drop_output(ctx, out);
122 fz_catch(ctx)
123 {
124 fz_free(ctx, name);
125 fz_rethrow(ctx);
126 }
127
128 return name;
129 }
130
131 static fz_stream *
132 fz_file_backed_stream(fz_context *ctx, fz_stream *stream)
133 {
134 const char *oname = fz_stream_filename(ctx, stream);
135 char *name;
136
137 /* If the file has a name, it's already a file-backed stream.*/
138 if (oname)
139 return stream;
140
141 /* Otherwise we need to make it one. */
142 name = fz_new_tmpfile_from_stream(ctx, stream);
143 fz_try(ctx)
144 stream = fz_open_file_autodelete(ctx, name);
145 fz_always(ctx)
146 fz_free(ctx, name);
147 fz_catch(ctx)
148 fz_rethrow(ctx);
149
150 return stream;
151 }
152
153 struct fz_document_handler_context
154 {
155 int refs;
156 int count;
157 const fz_document_handler *handler[FZ_DOCUMENT_HANDLER_MAX];
158 };
159
160 void fz_new_document_handler_context(fz_context *ctx)
161 {
162 ctx->handler = fz_malloc_struct(ctx, fz_document_handler_context);
163 ctx->handler->refs = 1;
164 }
165
166 fz_document_handler_context *fz_keep_document_handler_context(fz_context *ctx)
167 {
168 if (!ctx || !ctx->handler)
169 return NULL;
170 return fz_keep_imp(ctx, ctx->handler, &ctx->handler->refs);
171 }
172
173 void fz_drop_document_handler_context(fz_context *ctx)
174 {
175 int i;
176
177 if (!ctx || !ctx->handler)
178 return;
179
180 for (i = 0; i < ctx->handler->count; i++)
181 {
182 if (ctx->handler->handler[i]->fin)
183 {
184 fz_try(ctx)
185 ctx->handler->handler[i]->fin(ctx, ctx->handler->handler[i]);
186 fz_catch(ctx)
187 fz_ignore_error(ctx);
188 }
189 }
190
191 if (fz_drop_imp(ctx, ctx->handler, &ctx->handler->refs))
192 {
193 fz_free(ctx, ctx->handler);
194 ctx->handler = NULL;
195 }
196 }
197
198 void fz_register_document_handler(fz_context *ctx, const fz_document_handler *handler)
199 {
200 fz_document_handler_context *dc;
201 int i;
202
203 if (!handler)
204 return;
205
206 dc = ctx->handler;
207 if (dc == NULL)
208 fz_throw(ctx, FZ_ERROR_ARGUMENT, "Document handler list not found");
209
210 for (i = 0; i < dc->count; i++)
211 if (dc->handler[i] == handler)
212 return;
213
214 if (dc->count >= FZ_DOCUMENT_HANDLER_MAX)
215 fz_throw(ctx, FZ_ERROR_LIMIT, "Too many document handlers");
216
217 dc->handler[dc->count++] = handler;
218 }
219
220 const fz_document_handler *
221 fz_recognize_document_stream_content(fz_context *ctx, fz_stream *stream, const char *magic)
222 {
223 return fz_recognize_document_stream_and_dir_content(ctx, stream, NULL, magic);
224 }
225
226 const fz_document_handler *
227 do_recognize_document_stream_and_dir_content(fz_context *ctx, fz_stream **streamp, fz_archive *dir, const char *magic, void **handler_state, fz_document_recognize_state_free_fn **handler_free_state)
228 {
229 fz_document_handler_context *dc;
230 int i, best_score, best_i;
231 void *best_state = NULL;
232 fz_document_recognize_state_free_fn *best_free_state = NULL;
233 const char *ext;
234 int drop_stream = 0;
235 fz_stream *stream = *streamp;
236
237 if (handler_state)
238 *handler_state = NULL;
239 if (handler_free_state)
240 *handler_free_state = NULL;
241
242 dc = ctx->handler;
243 if (dc->count == 0)
244 fz_throw(ctx, FZ_ERROR_ARGUMENT, "No document handlers registered");
245
246 if (magic == NULL)
247 magic = "";
248 ext = strrchr(magic, '.');
249 if (ext)
250 ext = ext + 1;
251 else
252 ext = magic;
253
254 best_score = 0;
255 best_i = -1;
256
257 /* If we're handed a stream, check to see if any of our document handlers
258 * need a file. If so, change the stream to be a file-backed one. */
259 if (stream)
260 {
261 int wants_file = 0;
262 for (i = 0; i < dc->count; i++)
263 wants_file |= dc->handler[i]->wants_file;
264
265 /* Convert the stream into a file_backed stream. */
266 if (wants_file)
267 {
268 fz_stream *stream2 = fz_file_backed_stream(ctx, stream);
269 if (stream2 != stream)
270 {
271 /* Either we need to pass this back to our caller, or we
272 * need to drop it. */
273 drop_stream = 1;
274 stream = stream2;
275 }
276 }
277 }
278
279 fz_try(ctx)
280 {
281 int can_recognize_stream = ((stream && stream->seek != NULL) || (stream == NULL && dir != NULL));
282
283 for (i = 0; i < dc->count; i++)
284 {
285 void *state = NULL;
286 fz_document_recognize_state_free_fn *free_state = NULL;
287 int score = 0;
288 int magic_score = 0;
289 const char **entry;
290
291 /* Get a score from recognizing the stream */
292 if (dc->handler[i]->recognize_content && can_recognize_stream)
293 {
294 if (stream)
295 fz_seek(ctx, stream, 0, SEEK_SET);
296 fz_try(ctx)
297 {
298 score = dc->handler[i]->recognize_content(ctx, dc->handler[i], stream, dir, &state, &free_state);
299 }
300 fz_catch(ctx)
301 {
302 /* in case of zip errors when recognizing EPUB/XPS/DOCX files */
303 fz_rethrow_unless(ctx, FZ_ERROR_FORMAT);
304 (void)fz_convert_error(ctx, NULL); /* ugly hack to silence the error message */
305 score = 0;
306 }
307 }
308
309 /* Now get a score from recognizing the magic */
310 if (dc->handler[i]->recognize)
311 magic_score = dc->handler[i]->recognize(ctx, dc->handler[i], magic);
312
313 for (entry = &dc->handler[i]->mimetypes[0]; *entry; entry++)
314 if (!fz_strcasecmp(magic, *entry))
315 {
316 magic_score = 100;
317 break;
318 }
319
320 if (ext)
321 {
322 for (entry = &dc->handler[i]->extensions[0]; *entry; entry++)
323 if (!fz_strcasecmp(ext, *entry))
324 {
325 magic_score = 100;
326 break;
327 }
328 }
329
330 /* If we recognized the format (at least partially), and the magic_score matches, then that's
331 * definitely the one we want to use. Use 100 + score here, to allow for having multiple
332 * handlers that support a given magic, where one agent is better than the other. */
333 if (score > 0 && magic_score > 0)
334 score = 100 + score;
335 /* Otherwise, if we didn't recognize the format, we'll weakly believe in the magic, but
336 * we won't let it override anything that actually will cope. */
337 else if (magic_score > 0)
338 score = 1;
339 if (best_score < score)
340 {
341 best_score = score;
342 best_i = i;
343 if (best_free_state)
344 best_free_state(ctx, best_state);
345 best_free_state = free_state;
346 best_state = state;
347 }
348 else if (free_state)
349 free_state(ctx, state);
350 }
351 if (stream)
352 fz_seek(ctx, stream, 0, SEEK_SET);
353 }
354 fz_catch(ctx)
355 {
356 if (best_free_state)
357 best_free_state(ctx, best_state);
358 if (drop_stream)
359 fz_drop_stream(ctx, stream);
360 fz_rethrow(ctx);
361 }
362
363 if (best_i < 0)
364 {
365 if (drop_stream)
366 fz_drop_stream(ctx, stream);
367 return NULL;
368 }
369
370 /* Only if we found a handler, do we make our modified stream available to the
371 * caller. */
372 *streamp = stream;
373
374 if (handler_state && handler_free_state)
375 {
376 *handler_state = best_state;
377 *handler_free_state = best_free_state;
378 }
379 else if (best_free_state)
380 best_free_state(ctx, best_state);
381
382 return dc->handler[best_i];
383 }
384
385 const fz_document_handler *
386 fz_recognize_document_stream_and_dir_content(fz_context *ctx, fz_stream *stream, fz_archive *dir, const char *magic)
387 {
388 fz_stream *stm = stream;
389 const fz_document_handler *res;
390
391 res = do_recognize_document_stream_and_dir_content(ctx, &stm, dir, magic, NULL, NULL);
392
393 if (stm != stream)
394 fz_drop_stream(ctx, stm);
395
396 return res;
397 }
398
399 static const fz_document_handler *do_recognize_document_content(fz_context *ctx, const char *filename, void **handler_state, fz_document_recognize_state_free_fn **handler_free_state)
400 {
401 fz_stream *stream = NULL;
402 const fz_document_handler *handler = NULL;
403 fz_archive *zip = NULL;
404 fz_stream *stm;
405
406 if (fz_is_directory(ctx, filename))
407 zip = fz_open_directory(ctx, filename);
408 else
409 stream = fz_open_file(ctx, filename);
410
411 stm = stream;
412 fz_try(ctx)
413 handler = do_recognize_document_stream_and_dir_content(ctx, &stm, zip, filename, handler_state, handler_free_state);
414 fz_always(ctx)
415 {
416 if (stm != stream)
417 fz_drop_stream(ctx, stm);
418 fz_drop_stream(ctx, stream);
419 fz_drop_archive(ctx, zip);
420 }
421 fz_catch(ctx)
422 fz_rethrow(ctx);
423
424 return handler;
425 }
426
427 const fz_document_handler *fz_recognize_document_content(fz_context* ctx, const char* filename)
428 {
429 return do_recognize_document_content(ctx, filename, NULL, NULL);
430 }
431
432 const fz_document_handler *
433 fz_recognize_document(fz_context *ctx, const char *magic)
434 {
435 return fz_recognize_document_stream_and_dir_content(ctx, NULL, NULL, magic);
436 }
437
438 #if FZ_ENABLE_PDF
439 extern fz_document_handler pdf_document_handler;
440 #endif
441
442 fz_document *
443 fz_open_accelerated_document_with_stream_and_dir(fz_context *ctx, const char *magic, fz_stream *stream, fz_stream *accel, fz_archive *dir)
444 {
445 const fz_document_handler *handler;
446 fz_stream *wrapped_stream = stream;
447 fz_document *ret;
448 void *state = NULL;
449 fz_document_recognize_state_free_fn *free_state = NULL;
450
451 if (stream == NULL && dir == NULL)
452 fz_throw(ctx, FZ_ERROR_ARGUMENT, "no document to open");
453 if (magic == NULL)
454 fz_throw(ctx, FZ_ERROR_ARGUMENT, "missing file type");
455
456 /* If this finds a handler, then this might wrap stream. If it does, we reuse the wrapped one in
457 * the open call (hence avoiding us having to 'file-back' a stream twice), but we must free it. */
458 handler = do_recognize_document_stream_and_dir_content(ctx, &wrapped_stream, dir, magic, &state, &free_state);
459 if (!handler)
460 fz_throw(ctx, FZ_ERROR_UNSUPPORTED, "cannot find document handler for file type: '%s'", magic);
461 fz_try(ctx)
462 ret = handler->open(ctx, handler, wrapped_stream, accel, dir, state);
463 fz_always(ctx)
464 {
465 if (wrapped_stream != stream)
466 fz_drop_stream(ctx, wrapped_stream);
467 if (free_state && state)
468 free_state(ctx, state);
469 }
470 fz_catch(ctx)
471 fz_rethrow(ctx);
472
473 return ret;
474 }
475
476 fz_document *
477 fz_open_accelerated_document_with_stream(fz_context *ctx, const char *magic, fz_stream *stream, fz_stream *accel)
478 {
479 return fz_open_accelerated_document_with_stream_and_dir(ctx, magic, stream, accel, NULL);
480 }
481
482 fz_document *
483 fz_open_document_with_stream(fz_context *ctx, const char *magic, fz_stream *stream)
484 {
485 return fz_open_accelerated_document_with_stream(ctx, magic, stream, NULL);
486 }
487
488 fz_document *
489 fz_open_document_with_stream_and_dir(fz_context *ctx, const char *magic, fz_stream *stream, fz_archive *dir)
490 {
491 return fz_open_accelerated_document_with_stream_and_dir(ctx, magic, stream, NULL, dir);
492 }
493
494 fz_document *
495 fz_open_document_with_buffer(fz_context *ctx, const char *magic, fz_buffer *buffer)
496 {
497 fz_document *doc;
498 fz_stream *stream = fz_open_buffer(ctx, buffer);
499 fz_try(ctx)
500 doc = fz_open_document_with_stream(ctx, magic, stream);
501 fz_always(ctx)
502 fz_drop_stream(ctx, stream);
503 fz_catch(ctx)
504 fz_rethrow(ctx);
505 return doc;
506 }
507
508 fz_document *
509 fz_open_accelerated_document(fz_context *ctx, const char *filename, const char *accel)
510 {
511 const fz_document_handler *handler;
512 fz_stream *file = NULL;
513 fz_stream *afile = NULL;
514 fz_document *doc = NULL;
515 fz_archive *dir = NULL;
516 char dirname[PATH_MAX];
517 void *state = NULL;
518 fz_document_recognize_state_free_fn *free_state = NULL;
519
520 if (filename == NULL)
521 fz_throw(ctx, FZ_ERROR_ARGUMENT, "no document to open");
522
523 if (fz_is_directory(ctx, filename))
524 {
525 /* Cannot accelerate directories, currently. */
526 dir = fz_open_directory(ctx, filename);
527
528 fz_try(ctx)
529 doc = fz_open_accelerated_document_with_stream_and_dir(ctx, filename, NULL, NULL, dir);
530 fz_always(ctx)
531 fz_drop_archive(ctx, dir);
532 fz_catch(ctx)
533 fz_rethrow(ctx);
534
535 return doc;
536 }
537
538 handler = do_recognize_document_content(ctx, filename, &state, &free_state);
539 if (!handler)
540 fz_throw(ctx, FZ_ERROR_UNSUPPORTED, "cannot find document handler for file: %s", filename);
541
542 fz_var(afile);
543 fz_var(file);
544
545 fz_try(ctx)
546 {
547 file = fz_open_file(ctx, filename);
548
549 if (accel)
550 afile = fz_open_file(ctx, accel);
551 if (handler->wants_dir)
552 {
553 fz_dirname(dirname, filename, sizeof dirname);
554 dir = fz_open_directory(ctx, dirname);
555 }
556 doc = handler->open(ctx, handler, file, afile, dir, state);
557 }
558 fz_always(ctx)
559 {
560 if (free_state)
561 free_state(ctx, state);
562 fz_drop_archive(ctx, dir);
563 fz_drop_stream(ctx, afile);
564 fz_drop_stream(ctx, file);
565 }
566 fz_catch(ctx)
567 fz_rethrow(ctx);
568
569 return doc;
570 }
571
572 fz_document *
573 fz_open_document(fz_context *ctx, const char *filename)
574 {
575 return fz_open_accelerated_document(ctx, filename, NULL);
576 }
577
578 void fz_save_accelerator(fz_context *ctx, fz_document *doc, const char *accel)
579 {
580 if (doc == NULL)
581 return;
582 if (doc->output_accelerator == NULL)
583 return;
584
585 fz_output_accelerator(ctx, doc, fz_new_output_with_path(ctx, accel, 0));
586 }
587
588 void fz_output_accelerator(fz_context *ctx, fz_document *doc, fz_output *accel)
589 {
590 if (doc == NULL || accel == NULL)
591 return;
592 if (doc->output_accelerator == NULL)
593 {
594 fz_drop_output(ctx, accel);
595 fz_throw(ctx, FZ_ERROR_ARGUMENT, "Document does not support writing an accelerator");
596 }
597
598 doc->output_accelerator(ctx, doc, accel);
599 }
600
601 int fz_document_supports_accelerator(fz_context *ctx, fz_document *doc)
602 {
603 if (doc == NULL)
604 return 0;
605 return (doc->output_accelerator) != NULL;
606 }
607
608 void *
609 fz_new_document_of_size(fz_context *ctx, int size)
610 {
611 fz_document *doc = fz_calloc(ctx, 1, size);
612 doc->refs = 1;
613 doc->id = fz_new_document_id(ctx);
614
615 fz_log_activity(ctx, FZ_ACTIVITY_NEW_DOC, NULL);
616
617 return doc;
618 }
619
620 fz_document *
621 fz_keep_document(fz_context *ctx, fz_document *doc)
622 {
623 return fz_keep_imp(ctx, doc, &doc->refs);
624 }
625
626 void
627 fz_drop_document(fz_context *ctx, fz_document *doc)
628 {
629 if (fz_drop_imp(ctx, doc, &doc->refs))
630 {
631 fz_reap_dead_pages(ctx, doc);
632 if (doc->open)
633 fz_warn(ctx, "There are still open pages in the document!");
634 if (doc->drop_document)
635 doc->drop_document(ctx, doc);
636 fz_free(ctx, doc);
637 }
638 }
639
640 static void
641 fz_ensure_layout(fz_context *ctx, fz_document *doc)
642 {
643 if (doc && doc->layout && !doc->did_layout)
644 {
645 doc->layout(ctx, doc, DEFW, DEFH, DEFEM);
646 doc->did_layout = 1;
647 }
648 }
649
650 int
651 fz_is_document_reflowable(fz_context *ctx, fz_document *doc)
652 {
653 return doc ? doc->is_reflowable : 0;
654 }
655
656 fz_bookmark fz_make_bookmark(fz_context *ctx, fz_document *doc, fz_location loc)
657 {
658 if (doc && doc->make_bookmark)
659 return doc->make_bookmark(ctx, doc, loc);
660 return (loc.chapter<<16) + loc.page;
661 }
662
663 fz_location fz_lookup_bookmark(fz_context *ctx, fz_document *doc, fz_bookmark mark)
664 {
665 if (doc && doc->lookup_bookmark)
666 return doc->lookup_bookmark(ctx, doc, mark);
667 return fz_make_location((mark>>16) & 0xffff, mark & 0xffff);
668 }
669
670 int
671 fz_needs_password(fz_context *ctx, fz_document *doc)
672 {
673 if (doc && doc->needs_password)
674 return doc->needs_password(ctx, doc);
675 return 0;
676 }
677
678 int
679 fz_authenticate_password(fz_context *ctx, fz_document *doc, const char *password)
680 {
681 if (doc && doc->authenticate_password)
682 return doc->authenticate_password(ctx, doc, password);
683 return 1;
684 }
685
686 int
687 fz_has_permission(fz_context *ctx, fz_document *doc, fz_permission p)
688 {
689 if (doc && doc->has_permission)
690 return doc->has_permission(ctx, doc, p);
691 return 1;
692 }
693
694 fz_outline *
695 fz_load_outline(fz_context *ctx, fz_document *doc)
696 {
697 if (doc == NULL)
698 return NULL;
699 fz_ensure_layout(ctx, doc);
700 if (doc->load_outline)
701 return doc->load_outline(ctx, doc);
702 if (doc->outline_iterator == NULL)
703 return NULL;
704 return fz_load_outline_from_iterator(ctx, doc->outline_iterator(ctx, doc));
705 }
706
707 fz_outline_iterator *
708 fz_new_outline_iterator(fz_context *ctx, fz_document *doc)
709 {
710 if (doc == NULL)
711 return NULL;
712 if (doc->outline_iterator)
713 return doc->outline_iterator(ctx, doc);
714 if (doc->load_outline == NULL)
715 return NULL;
716 return fz_outline_iterator_from_outline(ctx, fz_load_outline(ctx, doc));
717 }
718
719 fz_link_dest
720 fz_resolve_link_dest(fz_context *ctx, fz_document *doc, const char *uri)
721 {
722 fz_ensure_layout(ctx, doc);
723 if (doc && doc->resolve_link_dest)
724 return doc->resolve_link_dest(ctx, doc, uri);
725 return fz_make_link_dest_none();
726 }
727
728 char *
729 fz_format_link_uri(fz_context *ctx, fz_document *doc, fz_link_dest dest)
730 {
731 if (doc && doc->format_link_uri)
732 return doc->format_link_uri(ctx, doc, dest);
733 fz_throw(ctx, FZ_ERROR_ARGUMENT, "cannot create internal links for this document type");
734 }
735
736 fz_location
737 fz_resolve_link(fz_context *ctx, fz_document *doc, const char *uri, float *xp, float *yp)
738 {
739 fz_link_dest dest = fz_resolve_link_dest(ctx, doc, uri);
740 if (xp) *xp = dest.x;
741 if (yp) *yp = dest.y;
742 return dest.loc;
743 }
744
745 void
746 fz_layout_document(fz_context *ctx, fz_document *doc, float w, float h, float em)
747 {
748 if (doc && doc->layout)
749 {
750 doc->layout(ctx, doc, w, h, em);
751 doc->did_layout = 1;
752 }
753 }
754
755 int
756 fz_count_chapters(fz_context *ctx, fz_document *doc)
757 {
758 fz_ensure_layout(ctx, doc);
759 if (doc && doc->count_chapters)
760 return doc->count_chapters(ctx, doc);
761 return 1;
762 }
763
764 int
765 fz_count_chapter_pages(fz_context *ctx, fz_document *doc, int chapter)
766 {
767 fz_ensure_layout(ctx, doc);
768 if (doc && doc->count_pages)
769 return doc->count_pages(ctx, doc, chapter);
770 return 0;
771 }
772
773 int
774 fz_count_pages(fz_context *ctx, fz_document *doc)
775 {
776 int i, c, n = 0;
777 c = fz_count_chapters(ctx, doc);
778 for (i = 0; i < c; ++i)
779 n += fz_count_chapter_pages(ctx, doc, i);
780 return n;
781 }
782
783 fz_page *
784 fz_load_page(fz_context *ctx, fz_document *doc, int number)
785 {
786 int i, n = fz_count_chapters(ctx, doc);
787 int start = 0;
788 for (i = 0; i < n; ++i)
789 {
790 int m = fz_count_chapter_pages(ctx, doc, i);
791 if (number < start + m)
792 return fz_load_chapter_page(ctx, doc, i, number - start);
793 start += m;
794 }
795 fz_throw(ctx, FZ_ERROR_ARGUMENT, "invalid page number: %d", number+1);
796 }
797
798 fz_location fz_last_page(fz_context *ctx, fz_document *doc)
799 {
800 int nc = fz_count_chapters(ctx, doc);
801 int np = fz_count_chapter_pages(ctx, doc, nc-1);
802 return fz_make_location(nc-1, np-1);
803 }
804
805 fz_location fz_next_page(fz_context *ctx, fz_document *doc, fz_location loc)
806 {
807 int nc = fz_count_chapters(ctx, doc);
808 int np = fz_count_chapter_pages(ctx, doc, loc.chapter);
809 if (loc.page + 1 == np)
810 {
811 if (loc.chapter + 1 < nc)
812 {
813 return fz_make_location(loc.chapter + 1, 0);
814 }
815 }
816 else
817 {
818 return fz_make_location(loc.chapter, loc.page + 1);
819 }
820 return loc;
821 }
822
823 fz_location fz_previous_page(fz_context *ctx, fz_document *doc, fz_location loc)
824 {
825 if (loc.page == 0)
826 {
827 if (loc.chapter > 0)
828 {
829 int np = fz_count_chapter_pages(ctx, doc, loc.chapter - 1);
830 return fz_make_location(loc.chapter - 1, np - 1);
831 }
832 }
833 else
834 {
835 return fz_make_location(loc.chapter, loc.page - 1);
836 }
837 return loc;
838 }
839
840 fz_location fz_clamp_location(fz_context *ctx, fz_document *doc, fz_location loc)
841 {
842 int nc = fz_count_chapters(ctx, doc);
843 int np;
844 if (loc.chapter < 0) loc.chapter = 0;
845 if (loc.chapter >= nc) loc.chapter = nc - 1;
846 np = fz_count_chapter_pages(ctx, doc, loc.chapter);
847 if (loc.page < 0) loc.page = 0;
848 if (loc.page >= np) loc.page = np - 1;
849 return loc;
850 }
851
852 fz_location fz_location_from_page_number(fz_context *ctx, fz_document *doc, int number)
853 {
854 int i, m = 0, n = fz_count_chapters(ctx, doc);
855 int start = 0;
856 if (number < 0)
857 number = 0;
858 for (i = 0; i < n; ++i)
859 {
860 m = fz_count_chapter_pages(ctx, doc, i);
861 if (number < start + m)
862 return fz_make_location(i, number - start);
863 start += m;
864 }
865 return fz_make_location(i-1, m-1);
866 }
867
868 int fz_page_number_from_location(fz_context *ctx, fz_document *doc, fz_location loc)
869 {
870 int i, n, start = 0;
871 n = fz_count_chapters(ctx, doc);
872 for (i = 0; i < n; ++i)
873 {
874 if (i == loc.chapter)
875 return start + loc.page;
876 start += fz_count_chapter_pages(ctx, doc, i);
877 }
878 return -1;
879 }
880
881 int
882 fz_lookup_metadata(fz_context *ctx, fz_document *doc, const char *key, char *buf, size_t size)
883 {
884 if (buf && size > 0)
885 buf[0] = 0;
886 if (doc && doc->lookup_metadata)
887 return doc->lookup_metadata(ctx, doc, key, buf, size);
888 return -1;
889 }
890
891 void
892 fz_set_metadata(fz_context *ctx, fz_document *doc, const char *key, const char *value)
893 {
894 if (doc && doc->set_metadata)
895 doc->set_metadata(ctx, doc, key, value);
896 }
897
898 fz_colorspace *
899 fz_document_output_intent(fz_context *ctx, fz_document *doc)
900 {
901 if (doc && doc->get_output_intent)
902 return doc->get_output_intent(ctx, doc);
903 return NULL;
904 }
905
906 static void
907 fz_reap_dead_pages(fz_context *ctx, fz_document *doc)
908 {
909 fz_page *page;
910 fz_page *next_page;
911
912 for (page = doc->open; page; page = next_page)
913 {
914 next_page = page->next;
915 if (!page->doc)
916 {
917 if (page->next != NULL)
918 page->next->prev = page->prev;
919 if (page->prev != NULL)
920 *page->prev = page->next;
921 fz_free(ctx, page);
922 if (page == doc->open)
923 doc->open = next_page;
924 }
925 }
926 }
927
928 fz_page *
929 fz_load_chapter_page(fz_context *ctx, fz_document *doc, int chapter, int number)
930 {
931 fz_page *page;
932
933 if (doc == NULL)
934 return NULL;
935
936 fz_ensure_layout(ctx, doc);
937
938 // Trigger reaping dead pages when we load a new page.
939 fz_reap_dead_pages(ctx, doc);
940
941 /* Protect modifications to the page list to cope with
942 * destruction of pages on other threads. */
943 for (page = doc->open; page; page = page->next)
944 {
945 if (page->chapter == chapter && page->number == number)
946 {
947 fz_keep_page(ctx, page);
948 return page;
949 }
950 }
951
952 if (doc->load_page)
953 {
954 page = doc->load_page(ctx, doc, chapter, number);
955 page->chapter = chapter;
956 page->number = number;
957
958 /* Insert new page at the head of the list of open pages. */
959 if (!page->incomplete)
960 {
961 if ((page->next = doc->open) != NULL)
962 doc->open->prev = &page->next;
963 doc->open = page;
964 page->prev = &doc->open;
965 page->in_doc = 1;
966 }
967 return page;
968 }
969
970 return NULL;
971 }
972
973 fz_link *
974 fz_load_links(fz_context *ctx, fz_page *page)
975 {
976 if (page && page->load_links)
977 return page->load_links(ctx, page);
978 return NULL;
979 }
980
981 fz_rect
982 fz_bound_page(fz_context *ctx, fz_page *page)
983 {
984 if (page && page->bound_page)
985 return page->bound_page(ctx, page, FZ_CROP_BOX);
986 return fz_empty_rect;
987 }
988
989 fz_rect
990 fz_bound_page_box(fz_context *ctx, fz_page *page, fz_box_type box)
991 {
992 if (page && page->bound_page)
993 return page->bound_page(ctx, page, box);
994 return fz_empty_rect;
995 }
996
997 void
998 fz_run_document_structure(fz_context *ctx, fz_document *doc, fz_device *dev, fz_cookie *cookie)
999 {
1000 if (doc && doc->run_structure)
1001 {
1002 fz_try(ctx)
1003 {
1004 doc->run_structure(ctx, doc, dev, cookie);
1005 }
1006 fz_catch(ctx)
1007 {
1008 dev->close_device = NULL; /* aborted run, don't warn about unclosed device */
1009 fz_rethrow_unless(ctx, FZ_ERROR_ABORT);
1010 fz_ignore_error(ctx);
1011 }
1012 }
1013 }
1014
1015 void
1016 fz_run_page_contents(fz_context *ctx, fz_page *page, fz_device *dev, fz_matrix transform, fz_cookie *cookie)
1017 {
1018 if (page && page->run_page_contents)
1019 {
1020 fz_try(ctx)
1021 {
1022 page->run_page_contents(ctx, page, dev, transform, cookie);
1023 }
1024 fz_catch(ctx)
1025 {
1026 dev->close_device = NULL; /* aborted run, don't warn about unclosed device */
1027 fz_rethrow_unless(ctx, FZ_ERROR_ABORT);
1028 fz_ignore_error(ctx);
1029 }
1030 }
1031 }
1032
1033 void
1034 fz_run_page_annots(fz_context *ctx, fz_page *page, fz_device *dev, fz_matrix transform, fz_cookie *cookie)
1035 {
1036 if (page && page->run_page_annots)
1037 {
1038 fz_try(ctx)
1039 {
1040 page->run_page_annots(ctx, page, dev, transform, cookie);
1041 }
1042 fz_catch(ctx)
1043 {
1044 dev->close_device = NULL; /* aborted run, don't warn about unclosed device */
1045 fz_rethrow_unless(ctx, FZ_ERROR_ABORT);
1046 fz_ignore_error(ctx);
1047 }
1048 }
1049 }
1050
1051 void
1052 fz_run_page_widgets(fz_context *ctx, fz_page *page, fz_device *dev, fz_matrix transform, fz_cookie *cookie)
1053 {
1054 if (page && page->run_page_widgets)
1055 {
1056 fz_try(ctx)
1057 {
1058 page->run_page_widgets(ctx, page, dev, transform, cookie);
1059 }
1060 fz_catch(ctx)
1061 {
1062 dev->close_device = NULL; /* aborted run, don't warn about unclosed device */
1063 fz_rethrow_unless(ctx, FZ_ERROR_ABORT);
1064 fz_ignore_error(ctx);
1065 }
1066 }
1067 }
1068
1069 void
1070 fz_run_page(fz_context *ctx, fz_page *page, fz_device *dev, fz_matrix transform, fz_cookie *cookie)
1071 {
1072 fz_run_page_contents(ctx, page, dev, transform, cookie);
1073 fz_run_page_annots(ctx, page, dev, transform, cookie);
1074 fz_run_page_widgets(ctx, page, dev, transform, cookie);
1075 }
1076
1077 fz_page *
1078 fz_new_page_of_size(fz_context *ctx, int size, fz_document *doc)
1079 {
1080 fz_page *page = Memento_label(fz_calloc(ctx, 1, size), "fz_page");
1081 page->refs = 1;
1082 page->doc = fz_keep_document(ctx, doc);
1083 return page;
1084 }
1085
1086 fz_page *
1087 fz_keep_page(fz_context *ctx, fz_page *page)
1088 {
1089 return fz_keep_imp(ctx, page, &page->refs);
1090 }
1091
1092 void
1093 fz_drop_page(fz_context *ctx, fz_page *page)
1094 {
1095 if (fz_drop_imp(ctx, page, &page->refs))
1096 {
1097 fz_document *doc = page->doc;
1098
1099 if (page->drop_page)
1100 page->drop_page(ctx, page);
1101
1102 // Mark the page as dead so we can reap the struct allocation later.
1103 page->doc = NULL;
1104 page->chapter = -1;
1105 page->number = -1;
1106
1107 // If page has never been added to the list of open pages in a document,
1108 // it will not get be reaped upon document freeing; instead free the page
1109 // immediately.
1110 if (!page->in_doc)
1111 fz_free(ctx, page);
1112
1113 fz_drop_document(ctx, doc);
1114 }
1115 }
1116
1117 fz_transition *
1118 fz_page_presentation(fz_context *ctx, fz_page *page, fz_transition *transition, float *duration)
1119 {
1120 float dummy;
1121 if (duration)
1122 *duration = 0;
1123 else
1124 duration = &dummy;
1125 if (page && page->page_presentation && page)
1126 return page->page_presentation(ctx, page, transition, duration);
1127 return NULL;
1128 }
1129
1130 fz_separations *
1131 fz_page_separations(fz_context *ctx, fz_page *page)
1132 {
1133 if (page && page->separations)
1134 return page->separations(ctx, page);
1135 return NULL;
1136 }
1137
1138 int fz_page_uses_overprint(fz_context *ctx, fz_page *page)
1139 {
1140 if (page && page->overprint)
1141 return page->overprint(ctx, page);
1142 return 0;
1143 }
1144
1145 fz_link *fz_create_link(fz_context *ctx, fz_page *page, fz_rect bbox, const char *uri)
1146 {
1147 if (page == NULL || uri == NULL)
1148 return NULL;
1149 if (page->create_link == NULL)
1150 fz_throw(ctx, FZ_ERROR_ARGUMENT, "This format of document does not support creating links");
1151 return page->create_link(ctx, page, bbox, uri);
1152 }
1153
1154 void fz_delete_link(fz_context *ctx, fz_page *page, fz_link *link)
1155 {
1156 if (page == NULL || link == NULL)
1157 return;
1158 if (page->delete_link == NULL)
1159 fz_throw(ctx, FZ_ERROR_ARGUMENT, "This format of document does not support deleting links");
1160 page->delete_link(ctx, page, link);
1161 }
1162
1163 void fz_set_link_rect(fz_context *ctx, fz_link *link, fz_rect rect)
1164 {
1165 if (link == NULL)
1166 return;
1167 if (link->set_rect_fn == NULL)
1168 fz_throw(ctx, FZ_ERROR_ARGUMENT, "This format of document does not support updating link bounds");
1169 link->set_rect_fn(ctx, link, rect);
1170 }
1171
1172 void fz_set_link_uri(fz_context *ctx, fz_link *link, const char *uri)
1173 {
1174 if (link == NULL)
1175 return;
1176 if (link->set_uri_fn == NULL)
1177 fz_throw(ctx, FZ_ERROR_ARGUMENT, "This format of document does not support updating link uri");
1178 link->set_uri_fn(ctx, link, uri);
1179 }
1180
1181 void *
1182 fz_process_opened_pages(fz_context *ctx, fz_document *doc, fz_process_opened_page_fn *process_opened_page, void *state)
1183 {
1184 fz_page *page;
1185 void *ret;
1186
1187 for (page = doc->open; page != NULL; page = page->next)
1188 {
1189 // Skip dead pages.
1190 if (page->doc == NULL)
1191 continue;
1192
1193 ret = process_opened_page(ctx, page, state);
1194 if (ret)
1195 return ret;
1196 }
1197
1198 return NULL;
1199 }
1200
1201 const char *
1202 fz_page_label(fz_context *ctx, fz_page *page, char *buf, int size)
1203 {
1204 fz_document *doc = page->doc;
1205 if (doc->page_label)
1206 doc->page_label(ctx, page->doc, page->chapter, page->number, buf, size);
1207 else if (fz_count_chapters(ctx, page->doc) > 1)
1208 fz_snprintf(buf, size, "%d/%d", page->chapter + 1, page->number + 1);
1209 else
1210 fz_snprintf(buf, size, "%d", page->number + 1);
1211 return buf;
1212 }
1213
1214
1215 fz_box_type fz_box_type_from_string(const char *name)
1216 {
1217 if (!fz_strcasecmp(name, "MediaBox"))
1218 return FZ_MEDIA_BOX;
1219 if (!fz_strcasecmp(name, "CropBox"))
1220 return FZ_CROP_BOX;
1221 if (!fz_strcasecmp(name, "BleedBox"))
1222 return FZ_BLEED_BOX;
1223 if (!fz_strcasecmp(name, "TrimBox"))
1224 return FZ_TRIM_BOX;
1225 if (!fz_strcasecmp(name, "ArtBox"))
1226 return FZ_ART_BOX;
1227 return FZ_UNKNOWN_BOX;
1228 }
1229
1230 const char *fz_string_from_box_type(fz_box_type box)
1231 {
1232 switch (box)
1233 {
1234 case FZ_MEDIA_BOX: return "MediaBox";
1235 case FZ_CROP_BOX: return "CropBox";
1236 case FZ_BLEED_BOX: return "BleedBox";
1237 case FZ_TRIM_BOX: return "TrimBox";
1238 case FZ_ART_BOX: return "ArtBox";
1239 default: return "UnknownBox";
1240 }
1241 }