Coverage Report

Created: 2026-11-12 07:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mupdf/source/pdf/pdf-link.c
Line
Count
Source
1
// Copyright (C) 2004-2026 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
#include "pdf-annot-imp.h"
25
26
#include <string.h>
27
#include <math.h>
28
29
static pdf_obj *
30
resolve_dest_rec(fz_context *ctx, pdf_document *doc, pdf_obj *dest, int depth)
31
0
{
32
0
  if (depth > 10) /* Arbitrary to avoid infinite recursion */
33
0
    return NULL;
34
35
0
  if (pdf_is_name(ctx, dest) || pdf_is_string(ctx, dest))
36
0
  {
37
0
    dest = pdf_lookup_dest(ctx, doc, dest);
38
0
    dest = resolve_dest_rec(ctx, doc, dest, depth+1);
39
0
    return dest;
40
0
  }
41
42
0
  else if (pdf_is_array(ctx, dest))
43
0
  {
44
0
    return dest;
45
0
  }
46
47
0
  else if (pdf_is_dict(ctx, dest))
48
0
  {
49
0
    dest = pdf_dict_get(ctx, dest, PDF_NAME(D));
50
0
    return resolve_dest_rec(ctx, doc, dest, depth+1);
51
0
  }
52
53
0
  else if (pdf_is_indirect(ctx, dest))
54
0
    return dest;
55
56
0
  return NULL;
57
0
}
58
59
static pdf_obj *
60
resolve_dest(fz_context *ctx, pdf_document *doc, pdf_obj *dest)
61
0
{
62
0
  return resolve_dest_rec(ctx, doc, dest, 0);
63
0
}
64
65
static void
66
populate_destination(fz_context *ctx, pdf_document *doc, pdf_obj *dest, int is_remote, fz_link_dest *destination)
67
0
{
68
0
  pdf_obj *arg1 = pdf_array_get(ctx, dest, 2);
69
0
  pdf_obj *arg2 = pdf_array_get(ctx, dest, 3);
70
0
  pdf_obj *arg3 = pdf_array_get(ctx, dest, 4);
71
0
  pdf_obj *arg4 = pdf_array_get(ctx, dest, 5);
72
0
  float arg1v = pdf_to_real(ctx, arg1);
73
0
  float arg2v = pdf_to_real(ctx, arg2);
74
0
  float arg3v = pdf_to_real(ctx, arg3);
75
0
  float arg4v = pdf_to_real(ctx, arg4);
76
0
  pdf_obj *type, *page = NULL;
77
0
  fz_matrix ctm = fz_identity;
78
0
  fz_rect rect;
79
0
  fz_point p;
80
0
  int pageno;
81
82
0
  if (is_remote)
83
0
    pageno = pdf_array_get_int(ctx, dest, 0);
84
0
  else
85
0
  {
86
0
    page = pdf_array_get(ctx, dest, 0);
87
0
    if (pdf_is_int(ctx, page))
88
0
    {
89
0
      pageno = pdf_to_int(ctx, page);
90
0
      page = pdf_lookup_page_obj(ctx, doc, pageno);
91
0
    }
92
0
    else
93
0
      pageno = pdf_lookup_page_number(ctx, doc, page);
94
0
    pageno = fz_clampi(pageno, 0, pdf_count_pages(ctx, doc) - 1);
95
0
    if (pdf_is_dict(ctx, page))
96
0
      pdf_page_obj_transform(ctx, page, NULL, &ctm);
97
0
  }
98
99
0
  destination->loc.page = pageno;
100
101
0
  type = pdf_array_get(ctx, dest, 1);
102
0
  if (type == PDF_NAME(XYZ))
103
0
    destination->type = FZ_LINK_DEST_XYZ;
104
0
  else if (type == PDF_NAME(Fit))
105
0
    destination->type = FZ_LINK_DEST_FIT;
106
0
  else if (type == PDF_NAME(FitH))
107
0
    destination->type = FZ_LINK_DEST_FIT_H;
108
0
  else if (type == PDF_NAME(FitV))
109
0
    destination->type = FZ_LINK_DEST_FIT_V;
110
0
  else if (type == PDF_NAME(FitR))
111
0
    destination->type = FZ_LINK_DEST_FIT_R;
112
0
  else if (type == PDF_NAME(FitB))
113
0
    destination->type = FZ_LINK_DEST_FIT_B;
114
0
  else if (type == PDF_NAME(FitBH))
115
0
    destination->type = FZ_LINK_DEST_FIT_BH;
116
0
  else if (type == PDF_NAME(FitBV))
117
0
    destination->type = FZ_LINK_DEST_FIT_BV;
118
0
  else
119
0
    destination->type = FZ_LINK_DEST_XYZ;
120
121
0
  switch (destination->type)
122
0
  {
123
0
  default:
124
0
  case FZ_LINK_DEST_FIT:
125
0
  case FZ_LINK_DEST_FIT_B:
126
0
    break;
127
0
  case FZ_LINK_DEST_FIT_H:
128
0
  case FZ_LINK_DEST_FIT_BH:
129
0
    p = fz_transform_point_xy(0, arg1v, ctm);
130
0
    destination->y = arg1 ? p.y : NAN;
131
0
    break;
132
0
  case FZ_LINK_DEST_FIT_V:
133
0
  case FZ_LINK_DEST_FIT_BV:
134
0
    p = fz_transform_point_xy(arg1v, 0, ctm);
135
0
    destination->x = arg1 ? p.x : NAN;
136
0
    break;
137
0
  case FZ_LINK_DEST_XYZ:
138
0
    p = fz_transform_point_xy(arg1v, arg2v, ctm);
139
0
    destination->x = arg1 ? p.x : NAN;
140
0
    destination->y = arg2 ? p.y : NAN;
141
0
    destination->zoom = arg3 ? (arg3v > 0 ? (arg3v * 100) : NAN) : NAN;
142
0
    break;
143
0
  case FZ_LINK_DEST_FIT_R:
144
0
    rect.x0 = arg1v;
145
0
    rect.y0 = arg2v;
146
0
    rect.x1 = arg3v;
147
0
    rect.y1 = arg4v;
148
0
    rect = fz_transform_rect(rect, ctm);
149
0
    destination->x = fz_min(rect.x0, rect.x1);
150
0
    destination->y = fz_min(rect.y0, rect.y1);
151
0
    destination->w = fz_abs(rect.x1 - rect.x0);
152
0
    destination->h = fz_abs(rect.y1 - rect.y0);
153
0
    break;
154
0
  }
155
0
}
156
157
static char *
158
pdf_parse_link_dest_to_file_with_uri(fz_context *ctx, pdf_document *doc, const char *uri, pdf_obj *dest)
159
0
{
160
0
  if (pdf_is_array(ctx, dest) && pdf_array_len(ctx, dest) >= 1)
161
0
  {
162
0
    fz_link_dest destination = fz_make_link_dest_none();
163
0
    populate_destination(ctx, doc, dest, 1, &destination);
164
0
    return pdf_append_explicit_dest_to_uri(ctx, uri, destination);
165
0
  }
166
0
  else if (pdf_is_name(ctx, dest))
167
0
  {
168
0
    const char *name = pdf_to_name(ctx, dest);
169
0
    return pdf_append_named_dest_to_uri(ctx, uri, name);
170
0
  }
171
0
  else if (pdf_is_string(ctx, dest))
172
0
  {
173
0
    const char *name = pdf_to_text_string(ctx, dest);
174
0
    return pdf_append_named_dest_to_uri(ctx, uri, name);
175
0
  }
176
0
  else
177
0
  {
178
0
    fz_warn(ctx, "invalid link destination");
179
0
    return NULL;
180
0
  }
181
0
}
182
183
static char *
184
pdf_parse_link_dest_to_file_with_path(fz_context *ctx, pdf_document *doc, const char *path, pdf_obj *dest, int is_remote)
185
0
{
186
0
  if (pdf_is_array(ctx, dest) && pdf_array_len(ctx, dest) >= 1)
187
0
  {
188
0
    fz_link_dest destination = fz_make_link_dest_none();
189
0
    if (!is_remote)
190
0
      dest = resolve_dest(ctx, doc, dest);
191
0
    populate_destination(ctx, doc, dest, is_remote, &destination);
192
0
    return pdf_new_uri_from_path_and_explicit_dest(ctx, path, destination);
193
0
  }
194
0
  else if (pdf_is_name(ctx, dest))
195
0
  {
196
0
    const char *name = pdf_to_name(ctx, dest);
197
0
    return pdf_new_uri_from_path_and_named_dest(ctx, path, name);
198
0
  }
199
0
  else if (pdf_is_string(ctx, dest))
200
0
  {
201
0
    const char *name = pdf_to_text_string(ctx, dest);
202
0
    return pdf_new_uri_from_path_and_named_dest(ctx, path, name);
203
0
  }
204
0
  else if (path)
205
0
  {
206
0
    fz_link_dest destination = fz_make_link_dest_none();
207
0
    return pdf_new_uri_from_path_and_explicit_dest(ctx, path, destination);
208
0
  }
209
0
  else
210
0
  {
211
0
    fz_warn(ctx, "invalid link destination");
212
0
    return NULL;
213
0
  }
214
0
}
215
216
/* Look at an FS object, and find a name. Find any embedded
217
 * file stream object that corresponds to that and return it.
218
 * Optionally return the name.
219
 *
220
 * Note that for NON-embedded files, this function will return
221
 * NULL, but may still return a filename.
222
 *
223
 * We will never return a file unless we also found a name.
224
 */
225
static pdf_obj *
226
get_file_stream_and_name(fz_context *ctx, pdf_obj *fs, pdf_obj **namep)
227
0
{
228
0
  pdf_obj *ef = pdf_dict_get(ctx, fs, PDF_NAME(EF));
229
0
  pdf_obj *name = pdf_dict_get(ctx, fs, PDF_NAME(UF));
230
0
  pdf_obj *file = pdf_dict_get(ctx, ef, PDF_NAME(UF));
231
0
  pdf_obj *any_name = name;
232
233
0
  if (!name && !file)
234
0
  {
235
0
    name = pdf_dict_get(ctx, fs, PDF_NAME(F));
236
0
    if (any_name == NULL)
237
0
      any_name = name;
238
0
    file = pdf_dict_get(ctx, ef, PDF_NAME(F));
239
0
  }
240
0
  if (!name && !file)
241
0
  {
242
0
    name = pdf_dict_get(ctx, fs, PDF_NAME(Unix));
243
0
    if (any_name == NULL)
244
0
      any_name = name;
245
0
    file = pdf_dict_get(ctx, ef, PDF_NAME(Unix));
246
0
  }
247
0
  if (!name && !file)
248
0
  {
249
0
    name = pdf_dict_get(ctx, fs, PDF_NAME(DOS));
250
0
    if (any_name == NULL)
251
0
      any_name = name;
252
0
    file = pdf_dict_get(ctx, ef, PDF_NAME(DOS));
253
0
  }
254
0
  if (!name && !file)
255
0
  {
256
0
    name = pdf_dict_get(ctx, fs, PDF_NAME(Mac));
257
0
    if (any_name == NULL)
258
0
      any_name = name;
259
0
    file = pdf_dict_get(ctx, ef, PDF_NAME(Mac));
260
0
  }
261
262
  /* bug708587: Some bad files have the name under one
263
   * entry (e.g. UF), and the entry in EF under another
264
   * (e.g. F). Strictly speaking this is against the
265
   * spec, but we'd rather find the embedded file than
266
   * not. */
267
0
  if (any_name && !file)
268
0
  {
269
0
    name = any_name;
270
0
    file = pdf_dict_get(ctx, ef, PDF_NAME(UF));
271
0
    if (file == NULL)
272
0
      file = pdf_dict_get(ctx, ef, PDF_NAME(F));
273
0
    if (file == NULL)
274
0
      file = pdf_dict_get(ctx, ef, PDF_NAME(Unix));
275
0
    if (file == NULL)
276
0
      file = pdf_dict_get(ctx, ef, PDF_NAME(DOS));
277
0
    if (file == NULL)
278
0
      file = pdf_dict_get(ctx, ef, PDF_NAME(Mac));
279
0
  }
280
281
0
  if (namep)
282
0
    *namep = name;
283
284
0
  return name ? file : NULL;
285
0
}
286
287
static char *
288
convert_file_spec_to_URI(fz_context *ctx, pdf_document *doc, pdf_obj *file_spec, pdf_obj *dest, int is_remote)
289
0
{
290
0
  pdf_obj *str = NULL;
291
0
  int is_url;
292
293
0
  if (pdf_is_string(ctx, file_spec))
294
0
    str = file_spec;
295
0
  else if (pdf_is_dict(ctx, file_spec))
296
0
    (void)get_file_stream_and_name(ctx, file_spec, &str);
297
298
0
  if (!pdf_is_string(ctx, str))
299
0
  {
300
0
    fz_warn(ctx, "cannot parse file specification");
301
0
    return NULL;
302
0
  }
303
304
0
  is_url = pdf_dict_get(ctx, file_spec, PDF_NAME(FS)) == PDF_NAME(URL);
305
306
0
  if (is_url)
307
0
    return pdf_parse_link_dest_to_file_with_uri(ctx, doc, pdf_to_text_string(ctx, str), dest);
308
0
  else
309
0
    return pdf_parse_link_dest_to_file_with_path(ctx, doc, pdf_to_text_string(ctx, str), dest, is_remote);
310
0
}
311
312
int
313
pdf_is_filespec(fz_context *ctx, pdf_obj *fs)
314
20.7k
{
315
20.7k
  pdf_obj *name;
316
20.7k
  pdf_obj *type = pdf_dict_get(ctx, fs, PDF_NAME(Type));
317
318
  // Note: allow "FileSpec" as a common mis-capitalization
319
20.7k
  if (type != PDF_NAME(Filespec) && type != PDF_NAME(FileSpec))
320
20.7k
    return 0;
321
322
0
  (void)get_file_stream_and_name(ctx, fs, &name);
323
324
0
  return name != NULL;
325
20.7k
}
326
327
int
328
pdf_is_embedded_file(fz_context *ctx, pdf_obj *fs)
329
0
{
330
0
  pdf_obj *type = pdf_dict_get(ctx, fs, PDF_NAME(Type));
331
332
  // Note: allow "FileSpec" as a common mis-capitalization
333
0
  if (type != PDF_NAME(Filespec) && type != PDF_NAME(FileSpec))
334
0
    return 0;
335
336
0
  return pdf_is_stream(ctx, get_file_stream_and_name(ctx, fs, NULL));
337
0
}
338
339
void
340
pdf_get_filespec_params(fz_context *ctx, pdf_obj *fs, pdf_filespec_params *out)
341
0
{
342
0
  pdf_obj *file, *params, *filename, *subtype;
343
0
  if (!out)
344
0
    return;
345
346
0
  memset(out, 0, sizeof(*out));
347
0
  out->created = -1;
348
0
  out->modified = -1;
349
0
  out->size = -1;
350
351
0
  file = get_file_stream_and_name(ctx, fs, &filename);
352
0
  if (!pdf_is_stream(ctx, file))
353
0
    return;
354
355
0
  params = pdf_dict_get(ctx, file, PDF_NAME(Params));
356
0
  out->filename = pdf_to_text_string(ctx, filename);
357
358
0
  subtype = pdf_dict_get(ctx, file, PDF_NAME(Subtype));
359
0
  if (!subtype)
360
0
    out->mimetype = "application/octet-stream";
361
0
  else
362
0
    out->mimetype = pdf_to_name(ctx, subtype);
363
0
  out->size = pdf_dict_get_int(ctx, params, PDF_NAME(Size));
364
0
  out->created = pdf_dict_get_date(ctx, params, PDF_NAME(CreationDate));
365
0
  out->modified = pdf_dict_get_date(ctx, params, PDF_NAME(ModDate));
366
0
}
367
368
fz_buffer *
369
pdf_load_embedded_file_contents(fz_context *ctx, pdf_obj *fs)
370
0
{
371
0
  pdf_obj *file = get_file_stream_and_name(ctx, fs, NULL);
372
373
0
  if (!pdf_is_stream(ctx, file))
374
0
    return NULL;
375
376
0
  return pdf_load_stream(ctx, file);
377
0
}
378
379
int
380
pdf_verify_embedded_file_checksum(fz_context *ctx, pdf_obj *fs)
381
0
{
382
0
  unsigned char digest[16];
383
0
  pdf_obj *params;
384
0
  const char *checksum;
385
0
  fz_buffer *contents = NULL;
386
0
  int valid = 0;
387
0
  size_t len;
388
0
  pdf_obj *file = get_file_stream_and_name(ctx, fs, NULL);
389
390
0
  fz_var(contents);
391
392
0
  if (!pdf_is_stream(ctx, file))
393
0
    return 1;
394
395
0
  params = pdf_dict_get(ctx, file, PDF_NAME(Params));
396
0
  checksum = pdf_dict_get_string(ctx, params, PDF_NAME(CheckSum), &len);
397
0
  if (!checksum || strlen(checksum) == 0)
398
0
    return 1;
399
400
0
  valid = 0;
401
402
0
  fz_try(ctx)
403
0
  {
404
0
    contents = pdf_load_stream(ctx, file);
405
0
    fz_md5_buffer(ctx, contents, digest);
406
0
    if (len == nelem(digest) && !memcmp(digest, checksum, nelem(digest)))
407
0
      valid = 1;
408
0
  }
409
0
  fz_always(ctx)
410
0
    fz_drop_buffer(ctx, contents);
411
0
  fz_catch(ctx)
412
0
    fz_rethrow(ctx);
413
414
0
  return valid;
415
0
}
416
417
static const char *
418
pdf_guess_mime_type_from_file_name(fz_context *ctx, const char *filename)
419
0
{
420
0
  const char *ext = filename ? strrchr(filename, '.') : NULL;
421
0
  if (ext)
422
0
  {
423
0
    if (!fz_strcasecmp(ext, ".pdf")) return "application/pdf";
424
0
    if (!fz_strcasecmp(ext, ".xml")) return "application/xml";
425
0
    if (!fz_strcasecmp(ext, ".zip")) return "application/zip";
426
0
    if (!fz_strcasecmp(ext, ".tar")) return "application/x-tar";
427
428
    /* Text */
429
0
    if (!fz_strcasecmp(ext, ".txt")) return "text/plain";
430
0
    if (!fz_strcasecmp(ext, ".rtf")) return "application/rtf";
431
0
    if (!fz_strcasecmp(ext, ".csv")) return "text/csv";
432
0
    if (!fz_strcasecmp(ext, ".html")) return "text/html";
433
0
    if (!fz_strcasecmp(ext, ".htm")) return "text/html";
434
0
    if (!fz_strcasecmp(ext, ".css")) return "text/css";
435
436
    /* Office */
437
0
    if (!fz_strcasecmp(ext, ".doc")) return "application/msword";
438
0
    if (!fz_strcasecmp(ext, ".ppt")) return "application/vnd.ms-powerpoint";
439
0
    if (!fz_strcasecmp(ext, ".xls")) return "application/vnd.ms-excel";
440
0
    if (!fz_strcasecmp(ext, ".docx")) return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
441
0
    if (!fz_strcasecmp(ext, ".pptx")) return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
442
0
    if (!fz_strcasecmp(ext, ".xlsx")) return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
443
0
    if (!fz_strcasecmp(ext, ".odt")) return "application/vnd.oasis.opendocument.text";
444
0
    if (!fz_strcasecmp(ext, ".odp")) return "application/vnd.oasis.opendocument.presentation";
445
0
    if (!fz_strcasecmp(ext, ".ods")) return "application/vnd.oasis.opendocument.spreadsheet";
446
447
    /* Image */
448
0
    if (!fz_strcasecmp(ext, ".bmp")) return "image/bmp";
449
0
    if (!fz_strcasecmp(ext, ".gif")) return "image/gif";
450
0
    if (!fz_strcasecmp(ext, ".jpeg")) return "image/jpeg";
451
0
    if (!fz_strcasecmp(ext, ".jpg")) return "image/jpeg";
452
0
    if (!fz_strcasecmp(ext, ".png")) return "image/png";
453
0
    if (!fz_strcasecmp(ext, ".svg")) return "image/svg+xml";
454
0
    if (!fz_strcasecmp(ext, ".tif")) return "image/tiff";
455
0
    if (!fz_strcasecmp(ext, ".tiff")) return "image/tiff";
456
457
    /* Sound */
458
0
    if (!fz_strcasecmp(ext, ".flac")) return "audio/flac";
459
0
    if (!fz_strcasecmp(ext, ".mp3")) return "audio/mpeg";
460
0
    if (!fz_strcasecmp(ext, ".ogg")) return "audio/ogg";
461
0
    if (!fz_strcasecmp(ext, ".wav")) return "audio/wav";
462
463
    /* Movie */
464
0
    if (!fz_strcasecmp(ext, ".avi")) return "video/x-msvideo";
465
0
    if (!fz_strcasecmp(ext, ".mov")) return "video/quicktime";
466
0
    if (!fz_strcasecmp(ext, ".mp4")) return "video/mp4";
467
0
    if (!fz_strcasecmp(ext, ".webm")) return "video/webm";
468
0
  }
469
0
  return "application/octet-stream";
470
0
}
471
472
pdf_obj *
473
pdf_add_embedded_file(fz_context *ctx, pdf_document *doc,
474
  const char *filename, const char *mimetype, fz_buffer *contents,
475
  int64_t created, int64_t modified, int add_checksum)
476
0
{
477
0
  pdf_obj *file = NULL;
478
0
  pdf_obj *filespec = NULL;
479
0
  pdf_obj *params = NULL;
480
481
0
  fz_var(file);
482
0
  fz_var(filespec);
483
484
0
  if (!mimetype)
485
0
    mimetype = pdf_guess_mime_type_from_file_name(ctx, filename);
486
487
0
  pdf_begin_operation(ctx, doc, "Embed file");
488
0
  fz_try(ctx)
489
0
  {
490
0
    file = pdf_add_new_dict(ctx, doc, 3);
491
0
    pdf_dict_put(ctx, file, PDF_NAME(Type), PDF_NAME(EmbeddedFile));
492
0
    pdf_dict_put_name(ctx, file, PDF_NAME(Subtype), mimetype);
493
0
    pdf_update_stream(ctx, doc, file, contents, 0);
494
495
0
    params = pdf_dict_put_dict(ctx, file, PDF_NAME(Params), 4);
496
0
    pdf_dict_put_int(ctx, params, PDF_NAME(Size), fz_buffer_storage(ctx, contents, NULL));
497
0
    if (created >= 0)
498
0
        pdf_dict_put_date(ctx, params, PDF_NAME(CreationDate), created);
499
0
    if (modified >= 0)
500
0
        pdf_dict_put_date(ctx, params, PDF_NAME(ModDate), modified);
501
0
    if (add_checksum)
502
0
    {
503
0
      unsigned char digest[16];
504
0
      fz_md5_buffer(ctx, contents, digest);
505
0
        pdf_dict_put_string(ctx, params, PDF_NAME(CheckSum), (const char *) digest, nelem(digest));
506
0
    }
507
508
0
    filespec = pdf_add_filespec(ctx, doc, filename, file);
509
510
0
    pdf_end_operation(ctx, doc);
511
0
  }
512
0
  fz_always(ctx)
513
0
    pdf_drop_obj(ctx, file);
514
0
  fz_catch(ctx)
515
0
  {
516
0
    pdf_drop_obj(ctx, filespec);
517
0
    pdf_abandon_operation(ctx, doc);
518
0
    fz_rethrow(ctx);
519
0
  }
520
521
0
  return filespec;
522
0
}
523
524
char *
525
pdf_parse_link_action(fz_context *ctx, pdf_document *doc, pdf_obj *action, int pagenum)
526
0
{
527
0
  pdf_obj *obj, *dest, *file_spec;
528
529
0
  if (!action)
530
0
    return NULL;
531
532
0
  obj = pdf_dict_get(ctx, action, PDF_NAME(S));
533
0
  if (pdf_name_eq(ctx, PDF_NAME(GoTo), obj))
534
0
  {
535
0
    dest = pdf_dict_get(ctx, action, PDF_NAME(D));
536
0
    return pdf_parse_link_dest(ctx, doc, dest);
537
0
  }
538
0
  else if (pdf_name_eq(ctx, PDF_NAME(URI), obj))
539
0
  {
540
    /* URI entries are ASCII strings */
541
0
    const char *uri = pdf_dict_get_text_string(ctx, action, PDF_NAME(URI));
542
0
    if (!fz_is_external_link(ctx, uri))
543
0
    {
544
0
      pdf_obj *uri_base_obj = pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Root/URI/Base");
545
0
      const char *uri_base = uri_base_obj ? pdf_to_text_string(ctx, uri_base_obj) : "file://";
546
0
      char *new_uri = Memento_label(fz_malloc(ctx, strlen(uri_base) + strlen(uri) + 1), "link_action");
547
0
      strcpy(new_uri, uri_base);
548
0
      strcat(new_uri, uri);
549
0
      return new_uri;
550
0
    }
551
0
    return fz_strdup(ctx, uri);
552
0
  }
553
0
  else if (pdf_name_eq(ctx, PDF_NAME(Launch), obj))
554
0
  {
555
0
    file_spec = pdf_dict_get(ctx, action, PDF_NAME(F));
556
0
    return convert_file_spec_to_URI(ctx, doc, file_spec, NULL, 0);
557
0
  }
558
0
  else if (pdf_name_eq(ctx, PDF_NAME(GoToR), obj))
559
0
  {
560
0
    dest = pdf_dict_get(ctx, action, PDF_NAME(D));
561
0
    file_spec = pdf_dict_get(ctx, action, PDF_NAME(F));
562
0
    return convert_file_spec_to_URI(ctx, doc, file_spec, dest, 1);
563
0
  }
564
0
  else if (pdf_name_eq(ctx, PDF_NAME(Named), obj))
565
0
  {
566
0
    dest = pdf_dict_get(ctx, action, PDF_NAME(N));
567
568
0
    if (pdf_name_eq(ctx, PDF_NAME(FirstPage), dest))
569
0
      pagenum = 0;
570
0
    else if (pdf_name_eq(ctx, PDF_NAME(LastPage), dest))
571
0
      pagenum = pdf_count_pages(ctx, doc) - 1;
572
0
    else if (pdf_name_eq(ctx, PDF_NAME(PrevPage), dest) && pagenum >= 0)
573
0
    {
574
0
      if (pagenum > 0)
575
0
        pagenum--;
576
0
    }
577
0
    else if (pdf_name_eq(ctx, PDF_NAME(NextPage), dest) && pagenum >= 0)
578
0
    {
579
0
      if (pagenum < pdf_count_pages(ctx, doc) - 1)
580
0
        pagenum++;
581
0
    }
582
0
    else
583
0
      return NULL;
584
585
0
    return fz_asprintf(ctx, "#page=%d", pagenum + 1);
586
0
  }
587
588
0
  return NULL;
589
0
}
590
591
static void pdf_drop_link_imp(fz_context *ctx, fz_link *link)
592
0
{
593
0
  pdf_drop_obj(ctx, ((pdf_link *) link)->obj);
594
0
}
595
596
static void pdf_set_link_rect(fz_context *ctx, fz_link *link_, fz_rect rect)
597
0
{
598
0
  pdf_link *link = (pdf_link *) link_;
599
0
  if (link == NULL)
600
0
    return;
601
602
0
  if (!link->page)
603
0
    fz_throw(ctx, FZ_ERROR_ARGUMENT, "link not bound to a page");
604
605
0
  pdf_begin_operation(ctx, link->page->doc, "Set link rectangle");
606
607
0
  fz_try(ctx)
608
0
  {
609
0
    pdf_dict_put_rect(ctx, link->obj, PDF_NAME(Rect), rect);
610
0
    link->super.rect = rect;
611
0
    pdf_end_operation(ctx, link->page->doc);
612
0
  }
613
0
  fz_catch(ctx)
614
0
  {
615
0
    pdf_abandon_operation(ctx, link->page->doc);
616
0
    fz_rethrow(ctx);
617
0
  }
618
0
}
619
620
static void pdf_set_link_uri(fz_context *ctx, fz_link *link_, const char *uri)
621
0
{
622
0
  pdf_link *link = (pdf_link *) link_;
623
0
  if (link == NULL)
624
0
    return;
625
626
0
  if (!link->page)
627
0
    fz_throw(ctx, FZ_ERROR_ARGUMENT, "link not bound to a page");
628
629
0
  pdf_begin_operation(ctx, link->page->doc, "Set link uri");
630
631
0
  fz_try(ctx)
632
0
  {
633
0
    pdf_dict_put_drop(ctx, link->obj, PDF_NAME(A),
634
0
        pdf_new_action_from_link(ctx, link->page->doc, uri));
635
0
    fz_free(ctx, link->super.uri);
636
0
    link->super.uri = fz_strdup(ctx, uri);
637
0
    pdf_end_operation(ctx, link->page->doc);
638
0
  }
639
0
  fz_catch(ctx)
640
0
  {
641
0
    pdf_abandon_operation(ctx, link->page->doc);
642
0
    fz_rethrow(ctx);
643
0
  }
644
0
}
645
646
fz_link *pdf_new_link(fz_context *ctx, pdf_page *page, fz_rect rect, const char *uri, pdf_obj *obj)
647
0
{
648
0
  pdf_link *link = fz_new_derived_link(ctx, pdf_link, rect, uri);
649
0
  link->super.drop = pdf_drop_link_imp;
650
0
  link->super.set_rect_fn = pdf_set_link_rect;
651
0
  link->super.set_uri_fn = pdf_set_link_uri;
652
0
  link->page = page; /* only borrowed, as the page owns the link */
653
0
  link->obj = pdf_keep_obj(ctx, obj);
654
0
  return &link->super;
655
0
}
656
657
static fz_link *
658
pdf_load_link(fz_context *ctx, pdf_document *doc, pdf_page *page, pdf_obj *dict, int pagenum, fz_matrix page_ctm)
659
549
{
660
549
  pdf_obj *action;
661
549
  pdf_obj *obj;
662
549
  fz_rect bbox;
663
549
  char *uri;
664
549
  fz_link *link = NULL;
665
666
549
  obj = pdf_dict_get(ctx, dict, PDF_NAME(Subtype));
667
549
  if (!pdf_name_eq(ctx, obj, PDF_NAME(Link)))
668
549
    return NULL;
669
670
0
  obj = pdf_dict_get(ctx, dict, PDF_NAME(Rect));
671
0
  if (!obj)
672
0
    return NULL;
673
674
0
  bbox = pdf_to_rect(ctx, obj);
675
0
  bbox = fz_transform_rect(bbox, page_ctm);
676
677
0
  obj = pdf_dict_get(ctx, dict, PDF_NAME(Dest));
678
0
  if (obj)
679
0
    uri = pdf_parse_link_dest(ctx, doc, obj);
680
0
  else
681
0
  {
682
0
    action = pdf_dict_get(ctx, dict, PDF_NAME(A));
683
    /* fall back to additional action button's down/up action */
684
0
    if (!action)
685
0
      action = pdf_dict_geta(ctx, pdf_dict_get(ctx, dict, PDF_NAME(AA)), PDF_NAME(U), PDF_NAME(D));
686
0
    uri = pdf_parse_link_action(ctx, doc, action, pagenum);
687
0
  }
688
689
0
  if (!uri)
690
0
    return NULL;
691
692
0
  fz_try(ctx)
693
0
    link = (fz_link *) pdf_new_link(ctx, page, bbox, uri, dict);
694
0
  fz_always(ctx)
695
0
    fz_free(ctx, uri);
696
0
  fz_catch(ctx)
697
0
    fz_rethrow(ctx);
698
699
0
  return link;
700
0
}
701
702
fz_link *
703
pdf_load_link_annots(fz_context *ctx, pdf_document *doc, pdf_page *page, pdf_obj *annots, int pagenum, fz_matrix page_ctm)
704
33
{
705
33
  fz_link *head, *tail;
706
33
  pdf_obj *obj;
707
33
  int i, n;
708
709
33
  fz_var(head);
710
711
33
  head = tail = NULL;
712
713
33
  n = pdf_array_len(ctx, annots);
714
582
  for (i = 0; i < n; i++)
715
549
  {
716
549
    fz_link *link;
717
718
    /* FIXME: Move the try/catch out of the loop for performance? */
719
1.09k
    fz_try(ctx)
720
1.09k
    {
721
549
      obj = pdf_array_get(ctx, annots, i);
722
549
      link = pdf_load_link(ctx, doc, page, obj, pagenum, page_ctm);
723
549
      if (!head)
724
549
        head = tail = link;
725
0
      else if (link)
726
0
      {
727
0
        tail->next = link;
728
0
        tail = link;
729
0
      }
730
549
    }
731
1.09k
    fz_catch(ctx)
732
0
    {
733
0
      if (fz_caught(ctx) == FZ_ERROR_TRYLATER || fz_caught(ctx) == FZ_ERROR_SYSTEM)
734
0
      {
735
0
        fz_drop_link(ctx, head);
736
0
        fz_rethrow(ctx);
737
0
      }
738
0
      fz_report_error(ctx);
739
0
    }
740
741
549
  }
742
743
33
  return head;
744
33
}
745
746
void pdf_nuke_links(fz_context *ctx, pdf_page *page)
747
0
{
748
0
  pdf_link *link;
749
0
  link = (pdf_link *) page->links;
750
0
  while (link)
751
0
  {
752
0
    pdf_drop_obj(ctx, link->obj);
753
0
    link->obj = NULL;
754
0
    link = (pdf_link *) link->super.next;
755
0
  }
756
0
  fz_drop_link(ctx, page->links);
757
0
  page->links = NULL;
758
0
}
759
760
void pdf_sync_links(fz_context *ctx, pdf_page *page)
761
0
{
762
0
  pdf_obj *annots;
763
764
0
  pdf_nuke_links(ctx, page);
765
766
0
  annots = pdf_dict_get(ctx, page->obj, PDF_NAME(Annots));
767
0
  if (annots)
768
0
  {
769
0
    fz_rect page_cropbox;
770
0
    fz_matrix page_ctm;
771
0
    pdf_page_transform(ctx, page, &page_cropbox, &page_ctm);
772
0
    page->links = pdf_load_link_annots(ctx, page->doc, page, annots, page->super.number, page_ctm);
773
0
  }
774
0
}
775
776
0
#define isnanorzero(x) (isnan(x) || (x) == 0)
777
778
static char*
779
format_explicit_dest_link_uri(fz_context *ctx, const char *schema, const char *uri, fz_link_dest dest)
780
0
{
781
0
  int pageno = dest.loc.page < 0 ? 1 : dest.loc.page + 1;
782
0
  int has_frag;
783
784
0
  if (!schema)
785
0
    schema = "";
786
0
  if (!uri)
787
0
    uri = "";
788
789
0
  has_frag = !!strchr(uri, '#');
790
791
0
  switch (dest.type)
792
0
  {
793
0
  default:
794
0
    return fz_asprintf(ctx, "%s%s%cpage=%d", schema, uri, "#&"[has_frag], pageno);
795
0
  case FZ_LINK_DEST_FIT:
796
0
    return fz_asprintf(ctx, "%s%s%cpage=%d&view=Fit", schema, uri, "#&"[has_frag], pageno);
797
0
  case FZ_LINK_DEST_FIT_B:
798
0
    return fz_asprintf(ctx, "%s%s%cpage=%d&view=FitB", schema, uri, "#&"[has_frag], pageno);
799
0
  case FZ_LINK_DEST_FIT_H:
800
0
    if (isnan(dest.y))
801
0
      return fz_asprintf(ctx, "%s%s%cpage=%d&view=FitH", schema, uri, "#&"[has_frag], pageno);
802
0
    else
803
0
      return fz_asprintf(ctx, "%s%s%cpage=%d&view=FitH,%g", schema, uri, "#&"[has_frag], pageno, dest.y);
804
0
  case FZ_LINK_DEST_FIT_BH:
805
0
    if (isnan(dest.y))
806
0
      return fz_asprintf(ctx, "%s%s%cpage=%d&view=FitBH", schema, uri, "#&"[has_frag], pageno);
807
0
    else
808
0
      return fz_asprintf(ctx, "%s%s%cpage=%d&view=FitBH,%g", schema, uri, "#&"[has_frag], pageno, dest.y);
809
0
  case FZ_LINK_DEST_FIT_V:
810
0
    if (isnan(dest.x))
811
0
      return fz_asprintf(ctx, "%s%s%cpage=%d&view=FitV", schema, uri, "#&"[has_frag], pageno);
812
0
    else
813
0
      return fz_asprintf(ctx, "%s%s%cpage=%d&view=FitV,%g", schema, uri, "#&"[has_frag], pageno, dest.x);
814
0
  case FZ_LINK_DEST_FIT_BV:
815
0
    if (isnan(dest.x))
816
0
      return fz_asprintf(ctx, "%s%s%cpage=%d&view=FitBV", schema, uri, "#&"[has_frag], pageno);
817
0
    else
818
0
      return fz_asprintf(ctx, "%s%s%cpage=%d&view=FitBV,%g", schema, uri, "#&"[has_frag], pageno, dest.x);
819
0
  case FZ_LINK_DEST_XYZ:
820
0
    if (!isnanorzero(dest.zoom) && !isnan(dest.x) && !isnan(dest.y))
821
0
      return fz_asprintf(ctx, "%s%s%cpage=%d&zoom=%g,%g,%g", schema, uri, "#&"[has_frag], pageno, dest.zoom, dest.x, dest.y);
822
0
    else if (!isnanorzero(dest.zoom) && !isnan(dest.x) && isnan(dest.y))
823
0
      return fz_asprintf(ctx, "%s%s%cpage=%d&zoom=%g,%g,nan", schema, uri, "#&"[has_frag], pageno, dest.zoom, dest.x);
824
0
    else if (!isnanorzero(dest.zoom) && isnan(dest.x) && !isnan(dest.y))
825
0
      return fz_asprintf(ctx, "%s%s%cpage=%d&zoom=%g,nan,%g", schema, uri, "#&"[has_frag], pageno, dest.zoom, dest.y);
826
0
    else if (!isnanorzero(dest.zoom) && isnan(dest.x) && isnan(dest.y))
827
0
      return fz_asprintf(ctx, "%s%s%cpage=%d&zoom=%g,nan,nan", schema, uri, "#&"[has_frag], pageno, dest.zoom);
828
0
    else if (isnanorzero(dest.zoom)&& !isnan(dest.x) && !isnan(dest.y))
829
0
      return fz_asprintf(ctx, "%s%s%cpage=%d&zoom=nan,%g,%g", schema, uri, "#&"[has_frag], pageno, dest.x, dest.y);
830
0
    else if (isnanorzero(dest.zoom) && !isnan(dest.x) && isnan(dest.y))
831
0
      return fz_asprintf(ctx, "%s%s%cpage=%d&zoom=nan,%g,nan", schema, uri, "#&"[has_frag], pageno, dest.x);
832
0
    else if (isnanorzero(dest.zoom) && isnan(dest.x) && !isnan(dest.y))
833
0
      return fz_asprintf(ctx, "%s%s%cpage=%d&zoom=nan,nan,%g", schema, uri, "#&"[has_frag], pageno, dest.y);
834
0
    else
835
0
      return fz_asprintf(ctx, "%s%s%cpage=%d", schema, uri, "#&"[has_frag], pageno);
836
0
  case FZ_LINK_DEST_FIT_R:
837
0
    return fz_asprintf(ctx, "%s%s%cpage=%d&viewrect=%g,%g,%g,%g", schema, uri, "#&"[has_frag], pageno,
838
0
      dest.x, dest.y, dest.w, dest.h);
839
0
  }
840
0
}
841
842
static char*
843
format_named_dest_link_uri(fz_context *ctx, const char *schema, const char *path, const char *name)
844
0
{
845
0
  if (!schema)
846
0
    schema = "";
847
0
  if (!path)
848
0
    path = "";
849
0
  return fz_asprintf(ctx, "%s%s#nameddest=%s", schema, path, name);
850
0
}
851
852
static char *
853
pdf_format_remote_link_uri_from_name(fz_context *ctx, const char *name)
854
0
{
855
0
  char *encoded_name = NULL;
856
0
  char *uri = NULL;
857
858
0
  encoded_name = fz_encode_uri_component(ctx, name);
859
0
  fz_try(ctx)
860
0
    uri = format_named_dest_link_uri(ctx, NULL, NULL, encoded_name);
861
0
  fz_always(ctx)
862
0
    fz_free(ctx, encoded_name);
863
0
  fz_catch(ctx)
864
0
    fz_rethrow(ctx);
865
866
0
  return uri;
867
0
}
868
869
static int
870
is_file_uri(fz_context *ctx, const char *uri)
871
0
{
872
0
  return uri && !strncmp(uri, "file:", 5);
873
0
}
874
875
static int
876
has_explicit_dest(fz_context *ctx, const char *uri)
877
0
{
878
0
  const char *fragment;
879
0
  if (uri == NULL || (fragment = strchr(uri, '#')) == NULL)
880
0
    return 0;
881
0
  return strstr(fragment, "page=") != NULL;
882
0
}
883
884
static int
885
has_named_dest(fz_context *ctx, const char *uri)
886
0
{
887
0
  const char *fragment;
888
0
  if (uri == NULL || (fragment = strchr(uri, '#')) == NULL)
889
0
    return 0;
890
0
  return (strstr(fragment, "nameddest=") || !has_explicit_dest(ctx, uri));
891
0
}
892
893
static char *
894
parse_file_uri_path(fz_context *ctx, const char *uri)
895
0
{
896
0
  char *frag, *path, *temp;
897
898
0
  temp = fz_strdup(ctx, uri + 5);
899
0
  fz_try(ctx)
900
0
  {
901
0
    frag = strchr(temp, '#');
902
0
    if (frag)
903
0
      *frag = 0;
904
0
    path = fz_decode_uri_component(ctx, temp);
905
0
    fz_cleanname(path);
906
0
  }
907
0
  fz_always(ctx)
908
0
    fz_free(ctx, temp);
909
0
  fz_catch(ctx)
910
0
    fz_rethrow(ctx);
911
912
0
  return path;
913
0
}
914
915
static char *
916
parse_uri_named_dest(fz_context *ctx, const char *uri)
917
0
{
918
0
  const char *nameddest_s = strstr(uri, "nameddest=");
919
0
  if (nameddest_s)
920
0
  {
921
0
    char *temp = fz_strdup(ctx, nameddest_s + 10);
922
0
    char *dest;
923
0
    fz_try(ctx)
924
0
    {
925
0
      char *ampersand = strchr(temp, '&');
926
0
      if (ampersand)
927
0
        *ampersand = 0;
928
0
      dest = fz_decode_uri_component(ctx, temp);
929
0
    }
930
0
    fz_always(ctx)
931
0
      fz_free(ctx, temp);
932
0
    fz_catch(ctx)
933
0
      fz_rethrow(ctx);
934
0
    return dest;
935
0
  }
936
937
  // We know there must be a # because of the check in has_named_dest
938
0
  return fz_decode_uri_component(ctx, strchr(uri, '#') + 1);
939
0
}
940
941
static float next_float(const char *str, int eatcomma, const char **end)
942
0
{
943
0
  if (eatcomma && *str == ',')
944
0
    ++str;
945
0
  return fz_strtof(str, (char **) end);
946
0
}
947
948
static fz_link_dest
949
pdf_new_explicit_dest_from_uri(fz_context *ctx, pdf_document *doc, const char *uri)
950
0
{
951
0
  const char *page, *rect, *zoom, *view;
952
0
  fz_link_dest val = fz_make_link_dest_none();
953
954
0
  uri = uri ? strchr(uri, '#') : NULL;
955
956
0
  page = uri ? strstr(uri, "page=") : NULL;
957
0
  rect = uri ? strstr(uri, "viewrect=") : NULL;
958
0
  zoom = uri ? strstr(uri, "zoom=") : NULL;
959
0
  view = uri ? strstr(uri, "view=") : NULL;
960
961
0
  val.loc.chapter = 0;
962
963
0
  if (page)
964
0
  {
965
0
    val.loc.page = fz_atoi(page+5) - 1;
966
0
    val.loc.page = fz_maxi(val.loc.page, 0);
967
0
  }
968
0
  else
969
0
    val.loc.page = 0;
970
971
0
  if (rect)
972
0
  {
973
0
    rect += 9;
974
0
    val.type = FZ_LINK_DEST_FIT_R;
975
0
    val.x = next_float(rect, 0, &rect);
976
0
    val.y = next_float(rect, 1, &rect);
977
0
    val.w = next_float(rect, 1, &rect);
978
0
    val.h = next_float(rect, 1, &rect);
979
0
  }
980
0
  else if (zoom)
981
0
  {
982
0
    zoom += 5;
983
0
    val.type = FZ_LINK_DEST_XYZ;
984
0
    val.zoom = next_float(zoom, 0, &zoom);
985
0
    val.x = next_float(zoom, 1, &zoom);
986
0
    val.y = next_float(zoom, 1, &zoom);
987
0
    if (val.zoom <= 0 || isinf(val.zoom))
988
0
      val.zoom = 100;
989
0
  }
990
0
  else if (view)
991
0
  {
992
0
    view += 5;
993
0
    if (!fz_strncasecmp(view, "FitH", 4))
994
0
    {
995
0
      view += 4;
996
0
      val.type = FZ_LINK_DEST_FIT_H;
997
0
      val.y = strchr(view, ',') ? next_float(view, 1, &view) : NAN;
998
0
    }
999
0
    else if (!fz_strncasecmp(view, "FitBH", 5))
1000
0
    {
1001
0
      view += 5;
1002
0
      val.type = FZ_LINK_DEST_FIT_BH;
1003
0
      val.y = strchr(view, ',') ? next_float(view, 1, &view) : NAN;
1004
0
    }
1005
0
    else if (!fz_strncasecmp(view, "FitV", 4))
1006
0
    {
1007
0
      view += 4;
1008
0
      val.type = FZ_LINK_DEST_FIT_V;
1009
0
      val.x = strchr(view, ',') ? next_float(view, 1, &view) : NAN;
1010
0
    }
1011
0
    else if (!fz_strncasecmp(view, "FitBV", 5))
1012
0
    {
1013
0
      view += 5;
1014
0
      val.type = FZ_LINK_DEST_FIT_BV;
1015
0
      val.x = strchr(view, ',') ? next_float(view, 1, &view) : NAN;
1016
0
    }
1017
0
    else if (!fz_strncasecmp(view, "FitB", 4))
1018
0
    {
1019
0
      val.type = FZ_LINK_DEST_FIT_B;
1020
0
    }
1021
0
    else if (!fz_strncasecmp(view, "Fit", 3))
1022
0
    {
1023
0
      val.type = FZ_LINK_DEST_FIT;
1024
0
    }
1025
0
  }
1026
1027
0
  return val;
1028
0
}
1029
1030
char *
1031
pdf_append_named_dest_to_uri(fz_context *ctx, const char *uri, const char *name)
1032
0
{
1033
0
  char *encoded_name = NULL;
1034
0
  char *new_uri = NULL;
1035
0
  int has_frag;
1036
1037
0
  if (!uri)
1038
0
    uri = "";
1039
1040
0
  has_frag = !!strchr(uri, '#');
1041
1042
0
  encoded_name = fz_encode_uri_component(ctx, name);
1043
0
  fz_try(ctx)
1044
0
    new_uri = fz_asprintf(ctx, "%s%cnameddest=%s", uri, "#&"[has_frag], encoded_name);
1045
0
  fz_always(ctx)
1046
0
    fz_free(ctx, encoded_name);
1047
0
  fz_catch(ctx)
1048
0
    fz_rethrow(ctx);
1049
1050
0
  return new_uri;
1051
0
}
1052
1053
char *
1054
pdf_append_explicit_dest_to_uri(fz_context *ctx, const char *uri, fz_link_dest dest)
1055
0
{
1056
0
  return format_explicit_dest_link_uri(ctx, NULL, uri, dest);
1057
0
}
1058
1059
char *
1060
pdf_new_uri_from_path_and_named_dest(fz_context *ctx, const char *path, const char *name)
1061
0
{
1062
0
  const char *schema = NULL;
1063
0
  char *encoded_name = NULL;
1064
0
  char *encoded_path = NULL;
1065
0
  char *uri = NULL;
1066
1067
0
  fz_var(encoded_name);
1068
0
  fz_var(encoded_path);
1069
1070
0
  fz_try(ctx)
1071
0
  {
1072
0
    if (path && strlen(path) > 0)
1073
0
    {
1074
0
      if (path[0] == '/')
1075
0
        schema = "file://";
1076
0
      else
1077
0
        schema = "file:";
1078
0
      encoded_path = fz_encode_uri_pathname(ctx, path);
1079
0
      fz_cleanname(encoded_path);
1080
0
    }
1081
1082
0
    encoded_name = fz_encode_uri_component(ctx, name);
1083
0
    uri = format_named_dest_link_uri(ctx, schema, encoded_path, encoded_name);
1084
0
  }
1085
0
  fz_always(ctx)
1086
0
  {
1087
0
    fz_free(ctx, encoded_name);
1088
0
    fz_free(ctx, encoded_path);
1089
0
  }
1090
0
  fz_catch(ctx)
1091
0
    fz_rethrow(ctx);
1092
1093
0
  return uri;
1094
0
}
1095
1096
char *
1097
pdf_new_uri_from_path_and_explicit_dest(fz_context *ctx, const char *path, fz_link_dest dest)
1098
0
{
1099
0
  const char *schema = NULL;
1100
0
  char *encoded_path = NULL;
1101
0
  char *uri = NULL;
1102
1103
0
  fz_var(encoded_path);
1104
1105
0
  fz_try(ctx)
1106
0
  {
1107
0
    if (path && strlen(path) > 0)
1108
0
    {
1109
0
      if (path[0] == '/')
1110
0
        schema = "file://";
1111
0
      else
1112
0
        schema = "file:";
1113
0
      encoded_path = fz_encode_uri_pathname(ctx, path);
1114
0
      fz_cleanname(encoded_path);
1115
0
    }
1116
1117
0
    uri = format_explicit_dest_link_uri(ctx, schema, encoded_path, dest);
1118
0
  }
1119
0
  fz_always(ctx)
1120
0
    fz_free(ctx, encoded_path);
1121
0
  fz_catch(ctx)
1122
0
    fz_rethrow(ctx);
1123
1124
0
  return uri;
1125
0
}
1126
1127
char *
1128
pdf_new_uri_from_explicit_dest(fz_context *ctx, fz_link_dest dest)
1129
0
{
1130
0
  return format_explicit_dest_link_uri(ctx, NULL, NULL, dest);
1131
0
}
1132
1133
char *
1134
pdf_parse_link_dest(fz_context *ctx, pdf_document *doc, pdf_obj *dest)
1135
0
{
1136
0
  if (pdf_is_array(ctx, dest) && pdf_array_len(ctx, dest) >= 1)
1137
0
  {
1138
0
    fz_link_dest destination = fz_make_link_dest_none();
1139
0
    populate_destination(ctx, doc, dest, 0, &destination);
1140
0
    return format_explicit_dest_link_uri(ctx, NULL, NULL, destination);
1141
0
  }
1142
0
  else if (pdf_is_name(ctx, dest))
1143
0
  {
1144
0
    const char *name = pdf_to_name(ctx, dest);
1145
0
    return pdf_format_remote_link_uri_from_name(ctx, name);
1146
0
  }
1147
0
  else if (pdf_is_string(ctx, dest))
1148
0
  {
1149
0
    const char *name = pdf_to_text_string(ctx, dest);
1150
0
    return pdf_format_remote_link_uri_from_name(ctx, name);
1151
0
  }
1152
0
  else
1153
0
  {
1154
0
    fz_warn(ctx, "invalid link destination");
1155
0
    return NULL;
1156
0
  }
1157
0
}
1158
1159
static pdf_obj *
1160
pdf_add_filespec_from_link(fz_context *ctx, pdf_document *doc, const char *uri)
1161
0
{
1162
0
  char *file = NULL;
1163
0
  pdf_obj *filespec = NULL;
1164
0
  fz_var(file);
1165
0
  fz_try(ctx)
1166
0
  {
1167
0
    if (is_file_uri(ctx, uri))
1168
0
    {
1169
0
      file = parse_file_uri_path(ctx, uri);
1170
0
      filespec = pdf_add_filespec(ctx, doc, file, NULL);
1171
0
    }
1172
0
    else if (fz_is_external_link(ctx, uri))
1173
0
      filespec = pdf_add_url_filespec(ctx, doc, uri);
1174
0
    else
1175
0
      fz_throw(ctx, FZ_ERROR_ARGUMENT, "can not add non-uri as file specification");
1176
0
  }
1177
0
  fz_always(ctx)
1178
0
    fz_free(ctx, file);
1179
0
  fz_catch(ctx)
1180
0
    fz_rethrow(ctx);
1181
0
  return filespec;
1182
0
}
1183
1184
1185
pdf_obj *
1186
pdf_new_action_from_link(fz_context *ctx, pdf_document *doc, const char *uri)
1187
0
{
1188
0
  pdf_obj *action = pdf_new_dict(ctx, doc, 2);
1189
0
  char *file = NULL;
1190
1191
0
  fz_var(file);
1192
1193
0
  if (uri == NULL)
1194
0
    return NULL;
1195
1196
0
  fz_try(ctx)
1197
0
  {
1198
0
    if (uri[0] == '#')
1199
0
    {
1200
0
      pdf_dict_put(ctx, action, PDF_NAME(S), PDF_NAME(GoTo));
1201
0
      pdf_dict_put_drop(ctx, action, PDF_NAME(D),
1202
0
        pdf_new_dest_from_link(ctx, doc, uri, 0));
1203
0
    }
1204
0
    else if (!strncmp(uri, "file:", 5))
1205
0
    {
1206
0
      pdf_dict_put(ctx, action, PDF_NAME(S), PDF_NAME(GoToR));
1207
0
      pdf_dict_put_drop(ctx, action, PDF_NAME(D),
1208
0
        pdf_new_dest_from_link(ctx, doc, uri, 1));
1209
0
      pdf_dict_put_drop(ctx, action, PDF_NAME(F),
1210
0
        pdf_add_filespec_from_link(ctx, doc, uri));
1211
0
    }
1212
0
    else if (fz_is_external_link(ctx, uri))
1213
0
    {
1214
0
      pdf_dict_put(ctx, action, PDF_NAME(S), PDF_NAME(URI));
1215
0
      pdf_dict_put_text_string(ctx, action, PDF_NAME(URI), uri);
1216
0
    }
1217
0
    else
1218
0
      fz_throw(ctx, FZ_ERROR_ARGUMENT, "unsupported link URI type");
1219
0
  }
1220
0
  fz_always(ctx)
1221
0
    fz_free(ctx, file);
1222
0
  fz_catch(ctx)
1223
0
  {
1224
0
    pdf_drop_obj(ctx, action);
1225
0
    fz_rethrow(ctx);
1226
0
  }
1227
1228
0
  return action;
1229
0
}
1230
1231
1232
pdf_obj *pdf_add_filespec(fz_context *ctx, pdf_document *doc, const char *filename, pdf_obj *embedded_file)
1233
0
{
1234
0
  pdf_obj *filespec = NULL;
1235
0
  char *asciiname = NULL;
1236
0
  const char *s;
1237
0
  size_t len, i;
1238
1239
0
  if (!filename)
1240
0
    filename = "";
1241
1242
0
  fz_var(asciiname);
1243
0
  fz_var(filespec);
1244
1245
0
  fz_try(ctx)
1246
0
  {
1247
0
    len = strlen(filename) + 1;
1248
0
    asciiname = fz_malloc(ctx, len);
1249
1250
0
    for (i = 0, s = filename; *s && i + 1 < len; ++i)
1251
0
    {
1252
0
      int c;
1253
0
      s += fz_chartorune(&c, s);
1254
0
      asciiname[i] = (c >= 32 && c <= 126) ? c : '_';
1255
0
    }
1256
0
    asciiname[i] = 0;
1257
1258
0
    filespec = pdf_add_new_dict(ctx, doc, 4);
1259
0
    pdf_dict_put(ctx, filespec, PDF_NAME(Type), PDF_NAME(Filespec));
1260
0
    pdf_dict_put_text_string(ctx, filespec, PDF_NAME(F), asciiname);
1261
0
    pdf_dict_put_text_string(ctx, filespec, PDF_NAME(UF), filename);
1262
0
    if (embedded_file)
1263
0
    {
1264
0
      pdf_obj *ef = pdf_dict_put_dict(ctx, filespec, PDF_NAME(EF), 1);
1265
0
      pdf_dict_put(ctx, ef, PDF_NAME(F), embedded_file);
1266
0
      pdf_dict_put(ctx, ef, PDF_NAME(UF), embedded_file);
1267
0
    }
1268
0
  }
1269
0
  fz_always(ctx)
1270
0
    fz_free(ctx, asciiname);
1271
0
  fz_catch(ctx)
1272
0
    fz_rethrow(ctx);
1273
1274
0
  return filespec;
1275
0
}
1276
1277
pdf_obj *pdf_add_url_filespec(fz_context *ctx, pdf_document *doc, const char *url)
1278
0
{
1279
0
  pdf_obj *filespec = pdf_add_new_dict(ctx, doc, 3);
1280
0
  fz_try(ctx)
1281
0
  {
1282
0
    pdf_dict_put(ctx, filespec, PDF_NAME(Type), PDF_NAME(Filespec));
1283
0
    pdf_dict_put(ctx, filespec, PDF_NAME(FS), PDF_NAME(URL));
1284
0
    pdf_dict_put_text_string(ctx, filespec, PDF_NAME(F), url);
1285
0
  }
1286
0
  fz_catch(ctx)
1287
0
  {
1288
0
    pdf_drop_obj(ctx, filespec);
1289
0
    fz_rethrow(ctx);
1290
0
  }
1291
0
  return filespec;
1292
0
}
1293
1294
pdf_obj *
1295
pdf_new_dest_from_link(fz_context *ctx, pdf_document *doc, const char *uri, int is_remote)
1296
0
{
1297
0
  pdf_obj *dest = NULL;
1298
1299
0
  fz_var(dest);
1300
1301
0
  if (has_named_dest(ctx, uri))
1302
0
  {
1303
0
    char *name = parse_uri_named_dest(ctx, uri);
1304
1305
0
    fz_try(ctx)
1306
0
      dest = pdf_new_text_string(ctx, name);
1307
0
    fz_always(ctx)
1308
0
      fz_free(ctx, name);
1309
0
    fz_catch(ctx)
1310
0
      fz_rethrow(ctx);
1311
0
  }
1312
0
  else
1313
0
  {
1314
0
    fz_matrix ctm, invctm;
1315
0
    fz_link_dest val;
1316
0
    pdf_obj *pageobj;
1317
0
    fz_point p;
1318
0
    fz_rect r;
1319
1320
0
    fz_try(ctx)
1321
0
    {
1322
0
      val = pdf_new_explicit_dest_from_uri(ctx, doc, uri);
1323
1324
0
      dest = pdf_new_array(ctx, doc, 6);
1325
1326
0
      if (is_remote)
1327
0
      {
1328
0
        pdf_array_push_int(ctx, dest, val.loc.page);
1329
0
        invctm = fz_identity;
1330
0
      }
1331
0
      else
1332
0
      {
1333
0
        pageobj = pdf_lookup_page_obj(ctx, doc, val.loc.page);
1334
0
        pdf_array_push(ctx, dest, pageobj);
1335
1336
0
        pdf_page_obj_transform(ctx, pageobj, NULL, &ctm);
1337
0
        invctm = fz_invert_matrix(ctm);
1338
0
      }
1339
1340
0
      switch (val.type)
1341
0
      {
1342
0
      default:
1343
0
      case FZ_LINK_DEST_FIT:
1344
0
        pdf_array_push(ctx, dest, PDF_NAME(Fit));
1345
0
        break;
1346
0
      case FZ_LINK_DEST_FIT_H:
1347
0
        p = fz_transform_point_xy(0, val.y, invctm);
1348
0
        pdf_array_push(ctx, dest, PDF_NAME(FitH));
1349
0
        if (isnan(p.y))
1350
0
          pdf_array_push(ctx, dest, PDF_NULL);
1351
0
        else
1352
0
          pdf_array_push_real(ctx, dest, p.y);
1353
0
        break;
1354
0
      case FZ_LINK_DEST_FIT_BH:
1355
0
        p = fz_transform_point_xy(0, val.y, invctm);
1356
0
        pdf_array_push(ctx, dest, PDF_NAME(FitBH));
1357
0
        if (isnan(p.y))
1358
0
          pdf_array_push(ctx, dest, PDF_NULL);
1359
0
        else
1360
0
          pdf_array_push_real(ctx, dest, p.y);
1361
0
        break;
1362
0
      case FZ_LINK_DEST_FIT_V:
1363
0
        p = fz_transform_point_xy(val.x, 0, invctm);
1364
0
        pdf_array_push(ctx, dest, PDF_NAME(FitV));
1365
0
        if (isnan(p.x))
1366
0
          pdf_array_push(ctx, dest, PDF_NULL);
1367
0
        else
1368
0
          pdf_array_push_real(ctx, dest, p.x);
1369
0
        break;
1370
0
      case FZ_LINK_DEST_FIT_BV:
1371
0
        p = fz_transform_point_xy(val.x, 0, invctm);
1372
0
        pdf_array_push(ctx, dest, PDF_NAME(FitBV));
1373
0
        if (isnan(p.x))
1374
0
          pdf_array_push(ctx, dest, PDF_NULL);
1375
0
        else
1376
0
          pdf_array_push_real(ctx, dest, p.x);
1377
0
        break;
1378
0
      case FZ_LINK_DEST_XYZ:
1379
0
        if (invctm.a == 0 && invctm.d == 0)
1380
0
        {
1381
          /* Rotating by 90 or 270 degrees. */
1382
0
          p = fz_transform_point_xy(isnan(val.x) ? 0 : val.x, isnan(val.y) ? 0 : val.y, invctm);
1383
0
          if (isnan(val.x))
1384
0
            p.y = val.x;
1385
0
          if (isnan(val.y))
1386
0
            p.x = val.y;
1387
0
        }
1388
0
        else if (invctm.b == 0 && invctm.c == 0)
1389
0
        {
1390
          /* No rotation, or 180 degrees. */
1391
0
          p = fz_transform_point_xy(isnan(val.x) ? 0 : val.x, isnan(val.y) ? 0 : val.y, invctm);
1392
0
          if (isnan(val.x))
1393
0
            p.x = val.x;
1394
0
          if (isnan(val.y))
1395
0
            p.y = val.y;
1396
0
        }
1397
0
        else
1398
0
          p = fz_transform_point_xy(val.x, val.y, invctm);
1399
0
        pdf_array_push(ctx, dest, PDF_NAME(XYZ));
1400
0
        if (isnan(p.x))
1401
0
          pdf_array_push(ctx, dest, PDF_NULL);
1402
0
        else
1403
0
          pdf_array_push_real(ctx, dest, p.x);
1404
0
        if (isnan(p.y))
1405
0
          pdf_array_push(ctx, dest, PDF_NULL);
1406
0
        else
1407
0
          pdf_array_push_real(ctx, dest, p.y);
1408
0
        if (isnan(val.zoom))
1409
0
          pdf_array_push(ctx, dest, PDF_NULL);
1410
0
        else
1411
0
          pdf_array_push_real(ctx, dest, val.zoom / 100);
1412
0
        break;
1413
0
      case FZ_LINK_DEST_FIT_R:
1414
0
        r.x0 = val.x;
1415
0
        r.y0 = val.y;
1416
0
        r.x1 = val.x + val.w;
1417
0
        r.y1 = val.y + val.h;
1418
0
        r = fz_transform_rect(r, invctm);
1419
0
        pdf_array_push(ctx, dest, PDF_NAME(FitR));
1420
0
        pdf_array_push_real(ctx, dest, r.x0);
1421
0
        pdf_array_push_real(ctx, dest, r.y0);
1422
0
        pdf_array_push_real(ctx, dest, r.x1);
1423
0
        pdf_array_push_real(ctx, dest, r.y1);
1424
0
        break;
1425
0
      }
1426
0
    }
1427
0
    fz_catch(ctx)
1428
0
    {
1429
0
      pdf_drop_obj(ctx, dest);
1430
0
      fz_rethrow(ctx);
1431
0
    }
1432
0
  }
1433
1434
0
  return dest;
1435
0
}
1436
1437
fz_link_dest
1438
pdf_resolve_link_dest(fz_context *ctx, pdf_document *doc, const char *uri)
1439
0
{
1440
0
  fz_link_dest dest = fz_make_link_dest_none();
1441
0
  pdf_obj *page_obj;
1442
0
  fz_matrix page_ctm;
1443
0
  fz_rect mediabox;
1444
0
  pdf_obj *needle = NULL;
1445
0
  char *name = NULL;
1446
0
  char *desturi = NULL;
1447
0
  pdf_obj *destobj = NULL;
1448
1449
0
  fz_var(needle);
1450
0
  fz_var(name);
1451
0
  fz_var(desturi);
1452
1453
0
  fz_try(ctx)
1454
0
  {
1455
0
    if (has_explicit_dest(ctx, uri))
1456
0
    {
1457
0
      dest = pdf_new_explicit_dest_from_uri(ctx, doc, uri);
1458
0
      if (!isnan(dest.x) || !isnan(dest.y) || !isnan(dest.w) || !isnan(dest.h))
1459
0
      {
1460
0
        page_obj = pdf_lookup_page_obj(ctx, doc, dest.loc.page);
1461
0
        pdf_page_obj_transform(ctx, page_obj, &mediabox, &page_ctm);
1462
0
        mediabox = fz_transform_rect(mediabox, page_ctm);
1463
1464
        /* clamp coordinates to remain on page */
1465
0
        dest.x = fz_clamp(dest.x, 0, mediabox.x1 - mediabox.x0);
1466
0
        dest.y = fz_clamp(dest.y, 0, mediabox.y1 - mediabox.y0);
1467
0
        dest.w = fz_clamp(dest.w, 0, mediabox.x1 - dest.x);
1468
0
        dest.h = fz_clamp(dest.h, 0, mediabox.y1 - dest.y);
1469
0
      }
1470
0
    }
1471
0
    else if (has_named_dest(ctx, uri))
1472
0
    {
1473
0
      name = parse_uri_named_dest(ctx, uri);
1474
1475
0
      needle = pdf_new_text_string(ctx, name);
1476
0
      destobj = resolve_dest(ctx, doc, needle);
1477
0
      if (destobj)
1478
0
      {
1479
0
        fz_link_dest destdest;
1480
0
        desturi = pdf_parse_link_dest(ctx, doc, destobj);
1481
0
        destdest = pdf_resolve_link_dest(ctx, doc, desturi);
1482
0
        if (dest.type == FZ_LINK_DEST_XYZ && isnan(dest.x) && isnan(dest.y) && isnan(dest.zoom))
1483
0
          dest = destdest;
1484
0
        else
1485
0
          dest.loc = destdest.loc;
1486
0
      }
1487
0
    }
1488
0
    else
1489
0
      dest.loc.page = fz_atoi(uri) - 1;
1490
0
  }
1491
0
  fz_always(ctx)
1492
0
  {
1493
0
    fz_free(ctx, desturi);
1494
0
    fz_free(ctx, name);
1495
0
    pdf_drop_obj(ctx, needle);
1496
0
  }
1497
0
  fz_catch(ctx)
1498
0
    fz_rethrow(ctx);
1499
1500
0
  return dest.loc.page >= 0 ? dest : fz_make_link_dest_none();
1501
0
}
1502
1503
int
1504
pdf_resolve_link(fz_context *ctx, pdf_document *doc, const char *uri, float *xp, float *yp)
1505
0
{
1506
0
  fz_link_dest dest = pdf_resolve_link_dest(ctx, doc, uri);
1507
0
  if (xp) *xp = dest.x;
1508
0
  if (yp) *yp = dest.y;
1509
0
  return dest.loc.page;
1510
0
}