comparison mupdf-source/source/pdf/pdf-link.c @ 2:b50eed0cc0ef upstream

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