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