Mercurial > hgrepos > Python2 > PyMuPDF
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 } |
