pdf-link.c 39 KB

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