Coverage Report

Created: 2025-12-31 07:06

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