Coverage Report

Created: 2024-07-05 06:13

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