gl-annotate.c 60 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308
  1. // Copyright (C) 2004-2025 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 "gl-app.h"
  23. #include <string.h>
  24. #include <stdlib.h>
  25. #include <stdio.h>
  26. #include <time.h>
  27. #include <limits.h>
  28. static int is_draw_mode = 0;
  29. static int new_contents = 0;
  30. static char save_filename[PATH_MAX];
  31. static pdf_write_options save_opts;
  32. static struct input opwinput;
  33. static struct input upwinput;
  34. static int do_high_security;
  35. static int hs_resolution = 200;
  36. static struct input ocr_language_input;
  37. static int ocr_language_input_initialised = 0;
  38. static pdf_document *pdf_has_redactions_doc = NULL;
  39. static int pdf_has_redactions;
  40. static int do_snapshot;
  41. static int label_slider(const char *label, int *value, int min, int max)
  42. {
  43. int changed;
  44. ui_panel_begin(0, ui.gridsize, 0, 0, 0);
  45. ui_layout(R, NONE, CENTER, 0, 0);
  46. changed = ui_slider(value, min, max, 100);
  47. ui_layout(L, X, CENTER, 0, 0);
  48. ui_label("%s: %d", label, *value);
  49. ui_panel_end();
  50. return changed;
  51. }
  52. static int label_select(const char *label, const char *id, const char *current, const char *options[], int n)
  53. {
  54. int changed;
  55. ui_panel_begin(0, ui.gridsize, 0, 0, 0);
  56. ui_layout(L, NONE, CENTER, 0, 0);
  57. ui_label("%s: ", label);
  58. ui_layout(T, X, CENTER, 0, 0);
  59. changed = ui_select(id, current, options, n);
  60. ui_panel_end();
  61. return changed;
  62. }
  63. static int pdf_filter(const char *fn)
  64. {
  65. const char *extension = strrchr(fn, '.');
  66. if (extension && !fz_strcasecmp(extension, ".pdf"))
  67. return 1;
  68. return 0;
  69. }
  70. static void init_save_pdf_options(void)
  71. {
  72. save_opts = pdf_default_write_options;
  73. if (pdf->redacted)
  74. save_opts.do_garbage = 1;
  75. if (pdf_can_be_saved_incrementally(ctx, pdf))
  76. save_opts.do_incremental = 1;
  77. save_opts.do_compress = 1;
  78. save_opts.do_compress_images = 1;
  79. save_opts.do_compress_fonts = 1;
  80. do_high_security = 0;
  81. ui_input_init(&opwinput, "");
  82. ui_input_init(&upwinput, "");
  83. if (!ocr_language_input_initialised)
  84. {
  85. ui_input_init(&ocr_language_input, "eng");
  86. ocr_language_input_initialised = 1;
  87. }
  88. }
  89. static const char *cryptalgo_names[] = {
  90. "Keep",
  91. "None",
  92. "RC4, 40 bit",
  93. "RC4, 128 bit",
  94. "AES, 128 bit",
  95. "AES, 256 bit",
  96. };
  97. static void save_pdf_options(void)
  98. {
  99. const char *cryptalgo = cryptalgo_names[save_opts.do_encrypt];
  100. int choice;
  101. int can_be_incremental;
  102. ui_layout(T, X, NW, ui.padsize, ui.padsize);
  103. ui_label("PDF write options:");
  104. ui_layout(T, X, NW, ui.padsize*2, ui.padsize);
  105. can_be_incremental = pdf_can_be_saved_incrementally(ctx, pdf);
  106. ui_checkbox("Snapshot", &do_snapshot);
  107. if (do_snapshot)
  108. return; /* ignore normal PDF options */
  109. ui_checkbox("High Security", &do_high_security);
  110. if (do_high_security)
  111. {
  112. int res200 = (hs_resolution == 200);
  113. int res300 = (hs_resolution == 300);
  114. int res600 = (hs_resolution == 600);
  115. int res1200 = (hs_resolution == 1200);
  116. ui_label("WARNING: High Security saving is a lossy procedure! Keep your original file safe.");
  117. ui_label("Resolution:");
  118. ui_checkbox("200", &res200);
  119. ui_checkbox("300", &res300);
  120. ui_checkbox("600", &res600);
  121. ui_checkbox("1200", &res1200);
  122. if (res200 && hs_resolution != 200)
  123. hs_resolution = 200;
  124. else if (res300 && hs_resolution != 300)
  125. hs_resolution = 300;
  126. else if (res600 && hs_resolution != 600)
  127. hs_resolution = 600;
  128. else if (res1200 && hs_resolution != 1200)
  129. hs_resolution = 1200;
  130. else if (!res200 && !res300 && !res600 && !res1200)
  131. hs_resolution = 200;
  132. ui_label("OCR Language:");
  133. ui_input(&ocr_language_input, 32, 1);
  134. return; /* ignore normal PDF options */
  135. }
  136. ui_checkbox_aux("Incremental", &save_opts.do_incremental, !can_be_incremental);
  137. fz_try(ctx)
  138. {
  139. if (pdf_count_signatures(ctx, pdf) && !save_opts.do_incremental)
  140. {
  141. if (can_be_incremental)
  142. ui_label("WARNING: Saving non-incrementally will break existing signatures");
  143. else
  144. ui_label("WARNING: Saving will break existing signatures");
  145. }
  146. }
  147. fz_catch(ctx)
  148. {
  149. /* Ignore the error. */
  150. }
  151. ui_spacer();
  152. ui_checkbox("Pretty-print", &save_opts.do_pretty);
  153. ui_checkbox("Ascii", &save_opts.do_ascii);
  154. ui_checkbox("Decompress", &save_opts.do_decompress);
  155. ui_checkbox("Compress", &save_opts.do_compress);
  156. ui_checkbox("Compress images", &save_opts.do_compress_images);
  157. ui_checkbox("Compress fonts", &save_opts.do_compress_fonts);
  158. ui_checkbox("Object streams", &save_opts.do_use_objstms);
  159. ui_checkbox("Preserve metadata", &save_opts.do_preserve_metadata);
  160. if (save_opts.do_incremental)
  161. {
  162. save_opts.do_garbage = 0;
  163. save_opts.do_linear = 0;
  164. save_opts.do_clean = 0;
  165. save_opts.do_sanitize = 0;
  166. save_opts.do_encrypt = PDF_ENCRYPT_KEEP;
  167. }
  168. else
  169. {
  170. ui_spacer();
  171. ui_checkbox("Linearize", &save_opts.do_linear);
  172. ui_checkbox_aux("Garbage collect", &save_opts.do_garbage, pdf->redacted);
  173. ui_checkbox("Clean syntax", &save_opts.do_clean);
  174. ui_checkbox("Sanitize syntax", &save_opts.do_sanitize);
  175. ui_spacer();
  176. ui_label("Encryption:");
  177. choice = ui_select("Encryption", cryptalgo, cryptalgo_names, nelem(cryptalgo_names));
  178. if (choice != -1)
  179. save_opts.do_encrypt = choice;
  180. }
  181. if (save_opts.do_encrypt >= PDF_ENCRYPT_RC4_40)
  182. {
  183. ui_spacer();
  184. ui_label("User password:");
  185. if (ui_input(&upwinput, 32, 1) >= UI_INPUT_EDIT)
  186. fz_strlcpy(save_opts.upwd_utf8, upwinput.text, nelem(save_opts.upwd_utf8));
  187. ui_label("Owner password:");
  188. if (ui_input(&opwinput, 32, 1) >= UI_INPUT_EDIT)
  189. fz_strlcpy(save_opts.opwd_utf8, opwinput.text, nelem(save_opts.opwd_utf8));
  190. }
  191. }
  192. struct {
  193. int n;
  194. int i;
  195. char *operation_text;
  196. char *progress_text;
  197. int (*step)(int cancel);
  198. int display;
  199. } ui_slow_operation_state;
  200. static int run_slow_operation_step(int cancel)
  201. {
  202. fz_try(ctx)
  203. {
  204. int i = ui_slow_operation_state.step(cancel);
  205. if (ui_slow_operation_state.i == 0)
  206. {
  207. ui_slow_operation_state.i = 1;
  208. ui_slow_operation_state.n = i;
  209. }
  210. else
  211. {
  212. ui_slow_operation_state.i = i;
  213. }
  214. }
  215. fz_catch(ctx)
  216. {
  217. ui_slow_operation_state.i = -1;
  218. ui_show_warning_dialog("%s failed: %s",
  219. ui_slow_operation_state.operation_text,
  220. fz_caught_message(ctx));
  221. fz_report_error(ctx);
  222. /* Call to cancel. */
  223. fz_try(ctx)
  224. ui_slow_operation_state.step(1);
  225. fz_catch(ctx)
  226. {
  227. /* Ignore any error from cancelling */
  228. }
  229. return 1;
  230. }
  231. return 0;
  232. }
  233. static void slow_operation_dialog(void)
  234. {
  235. int start_time;
  236. int errored = 0;
  237. ui_dialog_begin(16 * ui.gridsize, 4 * ui.gridsize);
  238. ui_layout(T, X, NW, ui.padsize, ui.padsize);
  239. ui_label("%s", ui_slow_operation_state.operation_text);
  240. ui_spacer();
  241. if (ui_slow_operation_state.i == 0)
  242. ui_label("Initializing.");
  243. else if (ui_slow_operation_state.i > ui_slow_operation_state.n)
  244. ui_label("Finalizing.");
  245. else
  246. ui_label("%s: %d / %d",
  247. ui_slow_operation_state.progress_text,
  248. ui_slow_operation_state.i,
  249. ui_slow_operation_state.n);
  250. ui_spacer();
  251. ui_panel_begin(0, ui.gridsize, 0, 0, 0);
  252. {
  253. ui_layout(R, NONE, S, 0, 0);
  254. if (ui_button("Cancel"))
  255. {
  256. ui.dialog = NULL;
  257. run_slow_operation_step(1);
  258. return;
  259. }
  260. }
  261. ui_panel_end();
  262. /* Only run the operations every other time. This ensures we
  263. * actually see the update for page i before page i is
  264. * processed. */
  265. ui_slow_operation_state.display = !ui_slow_operation_state.display;
  266. if (ui_slow_operation_state.display == 0)
  267. {
  268. /* Run steps for 200ms or until we're done. */
  269. start_time = glutGet(GLUT_ELAPSED_TIME);
  270. while (!errored && ui_slow_operation_state.i >= 0 &&
  271. glutGet(GLUT_ELAPSED_TIME) < start_time + 200)
  272. {
  273. errored = run_slow_operation_step(0);
  274. }
  275. }
  276. if (!errored && ui_slow_operation_state.i == -1)
  277. ui.dialog = NULL;
  278. /* ... and trigger a redisplay */
  279. glutPostRedisplay();
  280. }
  281. static void
  282. ui_start_slow_operation(char *operation, char *progress, int (*step)(int))
  283. {
  284. ui.dialog = slow_operation_dialog;
  285. ui_slow_operation_state.operation_text = operation;
  286. ui_slow_operation_state.progress_text = progress;
  287. ui_slow_operation_state.i = 0;
  288. ui_slow_operation_state.step = step;
  289. }
  290. struct {
  291. int i;
  292. int n;
  293. fz_document_writer *writer;
  294. } hss_state;
  295. static int step_high_security_save(int cancel)
  296. {
  297. fz_page *page = NULL;
  298. fz_device *dev;
  299. /* Called with i=0 for init. i=1...n for pages 1 to n inclusive.
  300. * i=n+1 for finalisation. */
  301. /* If cancelling, tidy up state. */
  302. if (cancel)
  303. {
  304. fz_drop_document_writer(ctx, hss_state.writer);
  305. hss_state.writer = NULL;
  306. return -1;
  307. }
  308. /* If initing, open the file, count the pages, return the number
  309. * of pages ("number of steps in the operation"). */
  310. if (hss_state.i == 0)
  311. {
  312. char options[1024];
  313. fz_snprintf(options, sizeof(options),
  314. "compression=flate,resolution=%d,ocr-language=%s",
  315. hs_resolution, ocr_language_input.text);
  316. hss_state.writer = fz_new_pdfocr_writer(ctx, save_filename, options);
  317. hss_state.i = 1;
  318. hss_state.n = fz_count_pages(ctx, (fz_document *)pdf);
  319. return hss_state.n;
  320. }
  321. /* If we've done all the pages, finish the write. */
  322. if (hss_state.i > hss_state.n)
  323. {
  324. fz_close_document_writer(ctx, hss_state.writer);
  325. fz_drop_document_writer(ctx, hss_state.writer);
  326. hss_state.writer = NULL;
  327. fz_strlcpy(filename, save_filename, PATH_MAX);
  328. reload_document();
  329. return -1;
  330. }
  331. /* Otherwise, do the next page. */
  332. page = fz_load_page(ctx, (fz_document *)pdf, hss_state.i-1);
  333. fz_try(ctx)
  334. {
  335. dev = fz_begin_page(ctx, hss_state.writer, fz_bound_page(ctx, page));
  336. fz_run_page(ctx, page, dev, fz_identity, NULL);
  337. fz_drop_page(ctx, page);
  338. page = NULL;
  339. fz_end_page(ctx, hss_state.writer);
  340. }
  341. fz_catch(ctx)
  342. {
  343. fz_drop_page(ctx, page);
  344. fz_rethrow(ctx);
  345. }
  346. return ++hss_state.i;
  347. }
  348. static void save_high_security(void)
  349. {
  350. /* FIXME */
  351. trace_action("//doc.hsredact(%q);\n", save_filename);
  352. memset(&hss_state, 0, sizeof(hss_state));
  353. ui_start_slow_operation("High Security Save", "Page", step_high_security_save);
  354. }
  355. static void do_save_pdf_dialog(int for_signing)
  356. {
  357. if (ui_save_file(save_filename, save_pdf_options,
  358. do_snapshot ?
  359. "Select where to save the snapshot:" :
  360. do_high_security ?
  361. "Select where to save the redacted document:" :
  362. for_signing ?
  363. "Select where to save the signed document:" :
  364. "Select where to save the document:"))
  365. {
  366. ui.dialog = NULL;
  367. if (save_filename[0] != 0)
  368. {
  369. if (do_high_security)
  370. {
  371. save_high_security();
  372. return;
  373. }
  374. if (for_signing && !do_sign())
  375. return;
  376. if (save_opts.do_garbage)
  377. save_opts.do_garbage = 2;
  378. fz_try(ctx)
  379. {
  380. static char opts_string[4096];
  381. pdf_format_write_options(ctx, opts_string, sizeof(opts_string), &save_opts);
  382. trace_action("doc.save(%q,%q);\n", save_filename, opts_string);
  383. if (do_snapshot)
  384. {
  385. pdf_save_snapshot(ctx, pdf, save_filename);
  386. fz_strlcat(save_filename, ".journal", PATH_MAX);
  387. pdf_save_journal(ctx, pdf, save_filename);
  388. }
  389. else
  390. {
  391. pdf_save_document(ctx, pdf, save_filename, &save_opts);
  392. fz_strlcpy(filename, save_filename, PATH_MAX);
  393. fz_strlcat(save_filename, ".journal", PATH_MAX);
  394. #ifdef _WIN32
  395. fz_remove_utf8(save_filename);
  396. #else
  397. remove(save_filename);
  398. #endif
  399. reload_document();
  400. }
  401. }
  402. fz_catch(ctx)
  403. {
  404. ui_show_warning_dialog("%s", fz_caught_message(ctx));
  405. fz_report_error(ctx);
  406. }
  407. }
  408. }
  409. }
  410. static void save_pdf_dialog(void)
  411. {
  412. do_save_pdf_dialog(0);
  413. }
  414. static void save_signed_pdf_dialog(void)
  415. {
  416. do_save_pdf_dialog(1);
  417. }
  418. void do_save_signed_pdf_file(void)
  419. {
  420. init_save_pdf_options();
  421. ui_init_save_file(filename, pdf_filter);
  422. ui.dialog = save_signed_pdf_dialog;
  423. }
  424. void do_save_pdf_file(void)
  425. {
  426. if (pdf)
  427. {
  428. init_save_pdf_options();
  429. ui_init_save_file(filename, pdf_filter);
  430. ui.dialog = save_pdf_dialog;
  431. }
  432. }
  433. static char attach_filename[PATH_MAX];
  434. static void save_attachment_dialog(void)
  435. {
  436. if (ui_save_file(attach_filename, NULL, "Save attachment as:"))
  437. {
  438. ui.dialog = NULL;
  439. if (attach_filename[0] != 0)
  440. {
  441. fz_try(ctx)
  442. {
  443. pdf_obj *fs = pdf_dict_get(ctx, pdf_annot_obj(ctx, ui.selected_annot), PDF_NAME(FS));
  444. fz_buffer *buf = pdf_load_embedded_file_contents(ctx, fs);
  445. fz_save_buffer(ctx, buf, attach_filename);
  446. fz_drop_buffer(ctx, buf);
  447. trace_action("tmp = annot.getFileSpec()\n");
  448. trace_action("doc.getEmbeddedFileContents(tmp).save(\"%s\");\n", attach_filename);
  449. trace_action("tmp = doc.verifyEmbeddedFileChecksum(tmp);\n");
  450. trace_action("if (tmp != true)\n");
  451. trace_action(" throw new RegressionError('Embedded file checksum:', tmp, 'expected:', true);\n");
  452. }
  453. fz_catch(ctx)
  454. {
  455. ui_show_warning_dialog("%s", fz_caught_message(ctx));
  456. fz_report_error(ctx);
  457. }
  458. }
  459. }
  460. }
  461. static void open_attachment_dialog(void)
  462. {
  463. if (ui_open_file(attach_filename, "Select file to attach:"))
  464. {
  465. ui.dialog = NULL;
  466. if (attach_filename[0] != 0)
  467. {
  468. pdf_obj *fs = NULL;
  469. pdf_begin_operation(ctx, pdf, "Embed file attachment");
  470. fz_var(fs);
  471. fz_try(ctx)
  472. {
  473. int64_t created, modified;
  474. fz_buffer *contents;
  475. const char *filename;
  476. filename = fz_basename(attach_filename);
  477. contents = fz_read_file(ctx, attach_filename);
  478. created = fz_stat_ctime(attach_filename);
  479. modified = fz_stat_mtime(attach_filename);
  480. fs = pdf_add_embedded_file(ctx, pdf, filename, NULL, contents,
  481. created, modified, 0);
  482. pdf_set_annot_filespec(ctx, ui.selected_annot, fs);
  483. fz_drop_buffer(ctx, contents);
  484. trace_action("annot.setFileSpec(doc.addEmbeddedFile(\"%s\", null, readFile(\"%s\"), new Date(%d).getTime(), new Date(%d).getTime(), false));\n", filename, attach_filename, created, modified);
  485. }
  486. fz_always(ctx)
  487. {
  488. pdf_drop_obj(ctx, fs);
  489. pdf_end_operation(ctx, pdf);
  490. }
  491. fz_catch(ctx)
  492. {
  493. ui_show_warning_dialog("%s", fz_caught_message(ctx));
  494. fz_report_error(ctx);
  495. }
  496. }
  497. }
  498. }
  499. static char stamp_image_filename[PATH_MAX];
  500. static void open_stamp_image_dialog(void)
  501. {
  502. if (ui_open_file(stamp_image_filename, "Select file for customized stamp:"))
  503. {
  504. ui.dialog = NULL;
  505. if (stamp_image_filename[0] != 0)
  506. {
  507. fz_image *img = NULL;
  508. fz_var(img);
  509. fz_try(ctx)
  510. {
  511. fz_rect rect = pdf_annot_rect(ctx, ui.selected_annot);
  512. trace_action("tmp = new Image(%q);\n", stamp_image_filename);
  513. img = fz_new_image_from_file(ctx, stamp_image_filename);
  514. trace_action("annot.setAppearance(tmp);\n");
  515. pdf_set_annot_stamp_image(ctx, ui.selected_annot, img);
  516. pdf_set_annot_rect(ctx, ui.selected_annot, fz_make_rect(
  517. rect.x0, rect.y0,
  518. rect.x0 + img->w * 72 / img->xres,
  519. rect.y0 + img->h * 72 / img->yres)
  520. );
  521. trace_action("annot.setIcon(%q);\n", fz_basename(stamp_image_filename));
  522. pdf_set_annot_icon_name(ctx, ui.selected_annot, fz_basename(stamp_image_filename));
  523. }
  524. fz_always(ctx)
  525. {
  526. fz_drop_image(ctx, img);
  527. }
  528. fz_catch(ctx)
  529. {
  530. ui_show_warning_dialog("%s", fz_caught_message(ctx));
  531. fz_report_error(ctx);
  532. }
  533. }
  534. }
  535. }
  536. static int rects_differ(fz_rect a, fz_rect b, float threshold)
  537. {
  538. if (fz_abs(a.x0 - b.x0) > threshold) return 1;
  539. if (fz_abs(a.y0 - b.y0) > threshold) return 1;
  540. if (fz_abs(a.x1 - b.x1) > threshold) return 1;
  541. if (fz_abs(a.y1 - b.y1) > threshold) return 1;
  542. return 0;
  543. }
  544. static int points_differ(fz_point a, fz_point b, float threshold)
  545. {
  546. if (fz_abs(a.x - b.x) > threshold) return 1;
  547. if (fz_abs(a.y - b.y) > threshold) return 1;
  548. return 0;
  549. }
  550. static const char *getuser(void)
  551. {
  552. const char *u;
  553. u = getenv("USER");
  554. if (!u) u = getenv("USERNAME");
  555. if (!u) u = "user";
  556. return u;
  557. }
  558. static void new_annot(int type)
  559. {
  560. char msg[100];
  561. trace_action("annot = page.createAnnotation(%q);\n", pdf_string_from_annot_type(ctx, type));
  562. fz_snprintf(msg, sizeof msg, "Create %s Annotation", pdf_string_from_annot_type(ctx, type));
  563. pdf_begin_operation(ctx, pdf, msg);
  564. ui_select_annot(pdf_create_annot(ctx, page, type));
  565. pdf_set_annot_modification_date(ctx, ui.selected_annot, time(NULL));
  566. if (pdf_annot_has_author(ctx, ui.selected_annot))
  567. pdf_set_annot_author(ctx, ui.selected_annot, getuser());
  568. pdf_end_operation(ctx, pdf);
  569. switch (type)
  570. {
  571. case PDF_ANNOT_REDACT:
  572. pdf_has_redactions_doc = pdf;
  573. pdf_has_redactions = 1;
  574. /* fallthrough */
  575. case PDF_ANNOT_INK:
  576. case PDF_ANNOT_POLYGON:
  577. case PDF_ANNOT_POLY_LINE:
  578. case PDF_ANNOT_HIGHLIGHT:
  579. case PDF_ANNOT_UNDERLINE:
  580. case PDF_ANNOT_STRIKE_OUT:
  581. case PDF_ANNOT_SQUIGGLY:
  582. is_draw_mode = 1;
  583. break;
  584. }
  585. }
  586. static const char *color_names[] = {
  587. "None",
  588. "Aqua",
  589. "Black",
  590. "Blue",
  591. "Fuchsia",
  592. "Gray",
  593. "Green",
  594. "Lime",
  595. "Maroon",
  596. "Navy",
  597. "Olive",
  598. "Orange",
  599. "Purple",
  600. "Red",
  601. "Silver",
  602. "Teal",
  603. "White",
  604. "Yellow",
  605. };
  606. static unsigned int color_values[] = {
  607. 0x00000000, /* transparent */
  608. 0xff00ffff, /* aqua */
  609. 0xff000000, /* black */
  610. 0xff0000ff, /* blue */
  611. 0xffff00ff, /* fuchsia */
  612. 0xff808080, /* gray */
  613. 0xff008000, /* green */
  614. 0xff00ff00, /* lime */
  615. 0xff800000, /* maroon */
  616. 0xff000080, /* navy */
  617. 0xff808000, /* olive */
  618. 0xffffa500, /* orange */
  619. 0xff800080, /* purple */
  620. 0xffff0000, /* red */
  621. 0xffc0c0c0, /* silver */
  622. 0xff008080, /* teal */
  623. 0xffffffff, /* white */
  624. 0xffffff00, /* yellow */
  625. };
  626. static unsigned int hex_from_color(int n, float color[4])
  627. {
  628. float rgb[4];
  629. int r, g, b;
  630. switch (n)
  631. {
  632. default:
  633. return 0x00000000;
  634. case 1:
  635. r = color[0] * 255;
  636. return 0xff000000 | (r<<16) | (r<<8) | r;
  637. case 3:
  638. r = color[0] * 255;
  639. g = color[1] * 255;
  640. b = color[2] * 255;
  641. return 0xff000000 | (r<<16) | (g<<8) | b;
  642. case 4:
  643. fz_convert_color(ctx, fz_device_cmyk(ctx), color, fz_device_rgb(ctx), rgb, NULL, fz_default_color_params);
  644. r = rgb[0] * 255;
  645. g = rgb[1] * 255;
  646. b = rgb[2] * 255;
  647. return 0xff000000 | (r<<16) | (g<<8) | b;
  648. }
  649. }
  650. static const char *name_from_hex(unsigned int hex)
  651. {
  652. static char buf[10];
  653. int i;
  654. for (i = 0; i < (int)nelem(color_names); ++i)
  655. if (color_values[i] == hex)
  656. return color_names[i];
  657. fz_snprintf(buf, sizeof buf, "#%06x", hex & 0xffffff);
  658. return buf;
  659. }
  660. static void do_annotate_color(char *label,
  661. void (*get_color)(fz_context *ctx, pdf_annot *annot, int *n, float color[4]),
  662. void (*set_color)(fz_context *ctx, pdf_annot *annot, int n, const float color[4]))
  663. {
  664. float color[4];
  665. int hex, choice, n;
  666. get_color(ctx, ui.selected_annot, &n, color);
  667. choice = label_select(label, label, name_from_hex(hex_from_color(n, color)), color_names, nelem(color_names));
  668. if (choice != -1)
  669. {
  670. hex = color_values[choice];
  671. if (hex == 0)
  672. {
  673. trace_action("annot.set%s([]);\n", label);
  674. set_color(ctx, ui.selected_annot, 0, color);
  675. }
  676. else
  677. {
  678. color[0] = ((hex>>16)&0xff) / 255.0f;
  679. color[1] = ((hex>>8)&0xff) / 255.0f;
  680. color[2] = ((hex)&0xff) / 255.0f;
  681. trace_action("annot.set%s([%g, %g, %g]);\n", label, color[0], color[1], color[2]);
  682. set_color(ctx, ui.selected_annot, 3, color);
  683. }
  684. }
  685. }
  686. static void do_annotate_author(void)
  687. {
  688. if (pdf_annot_has_author(ctx, ui.selected_annot))
  689. {
  690. const char *author = pdf_annot_author(ctx, ui.selected_annot);
  691. if (strlen(author) > 0)
  692. ui_label("Author: %s", author);
  693. }
  694. }
  695. static void do_annotate_date(void)
  696. {
  697. const char *s = format_date(pdf_annot_modification_date(ctx, ui.selected_annot));
  698. if (s)
  699. ui_label("Date: %s", s);
  700. }
  701. static const char *intent_names[] = {
  702. "Default", // all
  703. "FreeTextCallout", // freetext
  704. "FreeTextTypewriter", // freetext
  705. "LineArrow", // line
  706. "LineDimension", // line
  707. "PolyLineDimension", // polyline
  708. "PolygonCloud", // polygon
  709. "PolygonDimension", // polygon
  710. "StampImage", // stamp
  711. "StampSnapshot", // stamp
  712. };
  713. static enum pdf_intent do_annotate_intent(void)
  714. {
  715. enum pdf_intent intent;
  716. const char *intent_name;
  717. int choice;
  718. if (!pdf_annot_has_intent(ctx, ui.selected_annot))
  719. return PDF_ANNOT_IT_DEFAULT;
  720. intent = pdf_annot_intent(ctx, ui.selected_annot);
  721. if (intent == PDF_ANNOT_IT_UNKNOWN)
  722. intent_name = "Unknown";
  723. else
  724. intent_name = intent_names[intent];
  725. choice = label_select("Intent", "IT", intent_name, intent_names, nelem(intent_names));
  726. if (choice != -1)
  727. {
  728. trace_action("annot.setIntent(%d);\n", choice);
  729. pdf_set_annot_intent(ctx, ui.selected_annot, choice);
  730. intent = choice;
  731. // Changed intent!
  732. if (intent == PDF_ANNOT_IT_FREETEXT_CALLOUT)
  733. {
  734. pdf_set_annot_callout_point(ctx, ui.selected_annot, fz_make_point(0, 0));
  735. pdf_set_annot_callout_style(ctx, ui.selected_annot, PDF_ANNOT_LE_OPEN_ARROW);
  736. }
  737. }
  738. // Press 'c' to move Callout line to current cursor position.
  739. if (intent == PDF_ANNOT_IT_FREETEXT_CALLOUT)
  740. {
  741. if (!ui.focus && ui.key && ui.plain)
  742. {
  743. if (ui.key == 'c')
  744. {
  745. fz_point p = fz_transform_point(fz_make_point(ui.x, ui.y), view_page_inv_ctm);
  746. pdf_set_annot_callout_point(ctx, ui.selected_annot, p);
  747. }
  748. }
  749. }
  750. return intent;
  751. }
  752. static int do_annotate_contents(void)
  753. {
  754. static int is_same_edit_operation = 1;
  755. static pdf_annot *last_annot = NULL;
  756. static struct input input;
  757. const char *contents;
  758. if (ui.focus != &input)
  759. is_same_edit_operation = 0;
  760. if (ui.selected_annot != last_annot || new_contents)
  761. {
  762. is_same_edit_operation = 0;
  763. last_annot = ui.selected_annot;
  764. contents = pdf_annot_contents(ctx, ui.selected_annot);
  765. ui_input_init(&input, contents);
  766. new_contents = 0;
  767. }
  768. ui_label("Contents:");
  769. if (ui_input(&input, 0, 5) >= UI_INPUT_EDIT)
  770. {
  771. trace_action("annot.setContents(%q);\n", input.text);
  772. if (is_same_edit_operation)
  773. {
  774. pdf_begin_implicit_operation(ctx, pdf);
  775. pdf_set_annot_contents(ctx, ui.selected_annot, input.text);
  776. pdf_end_operation(ctx, pdf);
  777. }
  778. else
  779. {
  780. pdf_set_annot_contents(ctx, ui.selected_annot, input.text);
  781. is_same_edit_operation = 1;
  782. }
  783. }
  784. return input.text[0] != 0;
  785. }
  786. static const char *file_attachment_icons[] = { "Graph", "Paperclip", "PushPin", "Tag" };
  787. static const char *sound_icons[] = { "Speaker", "Mic" };
  788. static const char *stamp_icons[] = {
  789. "Approved", "AsIs", "Confidential", "Departmental", "Draft",
  790. "Experimental", "Expired", "Final", "ForComment", "ForPublicRelease",
  791. "NotApproved", "NotForPublicRelease", "Sold", "TopSecret" };
  792. static const char *text_icons[] = {
  793. "Comment", "Help", "Insert", "Key", "NewParagraph", "Note", "Paragraph" };
  794. static const char *line_ending_styles[] = {
  795. "None", "Square", "Circle", "Diamond", "OpenArrow", "ClosedArrow", "Butt",
  796. "ROpenArrow", "RClosedArrow", "Slash" };
  797. static const char *quadding_names[] = { "Left", "Center", "Right" };
  798. static const char *font_names[] = { "Cour", "Helv", "TiRo" };
  799. static const char *lang_names[] = { "", "ja", "ko", "zh-Hans", "zh-Hant" };
  800. static const char *im_redact_names[] = { "Keep images", "Remove images", "Erase pixels" };
  801. static const char *la_redact_names[] = { "Keep line art", "Remove covered line art", "Remove touched line art" };
  802. static const char *tx_redact_names[] = { "Remove text", "Keep text" };
  803. static const char *border_styles[] = { "Solid", "Dashed", "Dotted" };
  804. static const char *border_intensities[] = { "None", "Small clouds", "Large clouds", "Enormous clouds" };
  805. static int should_edit_color(enum pdf_annot_type subtype)
  806. {
  807. switch (subtype) {
  808. default:
  809. return 0;
  810. case PDF_ANNOT_STAMP:
  811. case PDF_ANNOT_TEXT:
  812. case PDF_ANNOT_FILE_ATTACHMENT:
  813. case PDF_ANNOT_SOUND:
  814. case PDF_ANNOT_CARET:
  815. return 1;
  816. case PDF_ANNOT_FREE_TEXT:
  817. return 1;
  818. case PDF_ANNOT_INK:
  819. case PDF_ANNOT_LINE:
  820. case PDF_ANNOT_SQUARE:
  821. case PDF_ANNOT_CIRCLE:
  822. case PDF_ANNOT_POLYGON:
  823. case PDF_ANNOT_POLY_LINE:
  824. return 1;
  825. case PDF_ANNOT_HIGHLIGHT:
  826. case PDF_ANNOT_UNDERLINE:
  827. case PDF_ANNOT_STRIKE_OUT:
  828. case PDF_ANNOT_SQUIGGLY:
  829. return 1;
  830. }
  831. }
  832. static int should_edit_icolor(enum pdf_annot_type subtype)
  833. {
  834. switch (subtype) {
  835. default:
  836. return 0;
  837. case PDF_ANNOT_LINE:
  838. case PDF_ANNOT_POLYGON:
  839. case PDF_ANNOT_POLY_LINE:
  840. case PDF_ANNOT_SQUARE:
  841. case PDF_ANNOT_CIRCLE:
  842. return 1;
  843. }
  844. }
  845. struct {
  846. int n;
  847. int i;
  848. pdf_redact_options opts;
  849. } rap_state;
  850. static int
  851. step_redact_all_pages(int cancel)
  852. {
  853. pdf_page *pg;
  854. /* Called with i=0 for init. i=1...n for pages 1 to n inclusive.
  855. * i=n+1 for finalisation. */
  856. if (cancel)
  857. return -1;
  858. if (rap_state.i == 0)
  859. {
  860. rap_state.i = 1;
  861. rap_state.n = pdf_count_pages(ctx, pdf);
  862. return rap_state.n;
  863. }
  864. else if (rap_state.i > rap_state.n)
  865. {
  866. trace_action("page = tmp;\n");
  867. trace_page_update();
  868. pdf_has_redactions = 0;
  869. load_page();
  870. return -1;
  871. }
  872. trace_action("page = doc.loadPage(%d);\n", rap_state.i-1);
  873. trace_action("page.applyRedactions(%s, %d);\n",
  874. rap_state.opts.black_boxes ? "true" : "false",
  875. rap_state.opts.image_method);
  876. pg = pdf_load_page(ctx, pdf, rap_state.i-1);
  877. fz_try(ctx)
  878. pdf_redact_page(ctx, pdf, pg, &rap_state.opts);
  879. fz_always(ctx)
  880. fz_drop_page(ctx, (fz_page *)pg);
  881. fz_catch(ctx)
  882. fz_rethrow(ctx);
  883. return ++rap_state.i;
  884. }
  885. static void redact_all_pages(pdf_redact_options *opts)
  886. {
  887. trace_action("tmp = page;\n");
  888. memset(&rap_state, 0, sizeof(rap_state));
  889. rap_state.opts = *opts;
  890. ui_start_slow_operation("Redacting all pages in document.", "Page", step_redact_all_pages);
  891. }
  892. static int
  893. document_has_redactions(void)
  894. {
  895. int i, n;
  896. pdf_page *page = NULL;
  897. pdf_annot *annot;
  898. int has_redact = 0;
  899. fz_var(page);
  900. fz_var(has_redact);
  901. fz_try(ctx)
  902. {
  903. n = pdf_count_pages(ctx, pdf);
  904. for (i = 0; i < n && !has_redact; i++)
  905. {
  906. page = pdf_load_page(ctx, pdf, i);
  907. for (annot = pdf_first_annot(ctx, page);
  908. annot != NULL;
  909. annot = pdf_next_annot(ctx, annot))
  910. {
  911. if (pdf_annot_type(ctx, annot) == PDF_ANNOT_REDACT)
  912. {
  913. has_redact = 1;
  914. break;
  915. }
  916. }
  917. fz_drop_page(ctx, (fz_page *)page);
  918. page = NULL;
  919. }
  920. }
  921. fz_catch(ctx)
  922. {
  923. /* Ignore the error, and assume no redactions */
  924. fz_drop_page(ctx, (fz_page *)page);
  925. }
  926. return has_redact;
  927. }
  928. static int detect_border_style(enum pdf_border_style style, float width)
  929. {
  930. if (style == PDF_BORDER_STYLE_DASHED)
  931. {
  932. int count = pdf_annot_border_dash_count(ctx, ui.selected_annot);
  933. float dashlen = pdf_annot_border_dash_item(ctx, ui.selected_annot, 0);
  934. if ((count == 1 || count == 2) && dashlen < 2 * width)
  935. return 2;
  936. return 1;
  937. }
  938. return 0;
  939. }
  940. static void do_border(void)
  941. {
  942. static int width;
  943. static int choice;
  944. enum pdf_border_style style;
  945. ui_spacer();
  946. width = pdf_annot_border_width(ctx, ui.selected_annot);
  947. style = pdf_annot_border_style(ctx, ui.selected_annot);
  948. width = fz_clampi(width, 0, 12);
  949. if (label_slider("Border", &width, 0, 12))
  950. {
  951. pdf_set_annot_border_width(ctx, ui.selected_annot, width);
  952. trace_action("annot.setBorderWidth(%d);\n", width);
  953. }
  954. width = fz_max(width, 1);
  955. choice = detect_border_style(style, width);
  956. choice = label_select("Style", "BorderStyle", border_styles[choice], border_styles, nelem(border_styles));
  957. if (choice != -1)
  958. {
  959. pdf_clear_annot_border_dash(ctx, ui.selected_annot);
  960. trace_action("annot.clearBorderDash();\n");
  961. if (choice == 0)
  962. {
  963. pdf_set_annot_border_style(ctx, ui.selected_annot, PDF_BORDER_STYLE_SOLID);
  964. trace_action("annot.setBorderType('Solid');\n");
  965. }
  966. else if (choice == 1)
  967. {
  968. pdf_set_annot_border_style(ctx, ui.selected_annot, PDF_BORDER_STYLE_DASHED);
  969. pdf_add_annot_border_dash_item(ctx, ui.selected_annot, 3.0f * width);
  970. trace_action("annot.setBorderType('Dashed');\n");
  971. trace_action("annot.addBorderDashItem(%g);\n", 3.0f * width);
  972. }
  973. else if (choice == 2)
  974. {
  975. pdf_set_annot_border_style(ctx, ui.selected_annot, PDF_BORDER_STYLE_DASHED);
  976. pdf_add_annot_border_dash_item(ctx, ui.selected_annot, 1.0f * width);
  977. trace_action("annot.setBorderType('Dashed');\n");
  978. trace_action("annot.addBorderDashItem(%g);\n", 1.0f * width);
  979. }
  980. }
  981. if (pdf_annot_has_border_effect(ctx, ui.selected_annot))
  982. {
  983. static int intensity;
  984. intensity = fz_clampi(pdf_annot_border_effect_intensity(ctx, ui.selected_annot), 0, 3);
  985. if (pdf_annot_border_effect(ctx, ui.selected_annot) == PDF_BORDER_EFFECT_NONE)
  986. intensity = 0;
  987. intensity = label_select("Effect", "BorderEffect", border_intensities[intensity], border_intensities, nelem(border_intensities));
  988. if (intensity != -1)
  989. {
  990. enum pdf_border_effect effect = intensity ? PDF_BORDER_EFFECT_CLOUDY : PDF_BORDER_EFFECT_NONE;
  991. pdf_set_annot_border_effect(ctx, ui.selected_annot, effect);
  992. pdf_set_annot_border_effect_intensity(ctx, ui.selected_annot, intensity);
  993. trace_action("annot.setBorderEffect('%s');\n", effect ? "Cloudy" : "None");
  994. trace_action("annot.setBorderEffectIntensity(%d);\n", intensity);
  995. }
  996. }
  997. }
  998. static int image_file_filter(const char *fn)
  999. {
  1000. return !!strstr(fn, ".jpg") || !!strstr(fn, ".jpeg") || !!strstr(fn, ".png");
  1001. }
  1002. void do_annotate_panel(void)
  1003. {
  1004. static struct list annot_list;
  1005. enum pdf_annot_type subtype;
  1006. enum pdf_intent intent;
  1007. pdf_annot *annot;
  1008. int idx;
  1009. int n;
  1010. ui_layout(T, X, NW, ui.padsize, ui.padsize);
  1011. if (ui_popup("CreateAnnotPopup", "Create...", 1, 16))
  1012. {
  1013. if (ui_popup_item("Text")) new_annot(PDF_ANNOT_TEXT);
  1014. if (ui_popup_item("FreeText")) new_annot(PDF_ANNOT_FREE_TEXT);
  1015. if (ui_popup_item("Stamp")) new_annot(PDF_ANNOT_STAMP);
  1016. if (ui_popup_item("Caret")) new_annot(PDF_ANNOT_CARET);
  1017. if (ui_popup_item("Ink")) new_annot(PDF_ANNOT_INK);
  1018. if (ui_popup_item("Square")) new_annot(PDF_ANNOT_SQUARE);
  1019. if (ui_popup_item("Circle")) new_annot(PDF_ANNOT_CIRCLE);
  1020. if (ui_popup_item("Line")) new_annot(PDF_ANNOT_LINE);
  1021. if (ui_popup_item("Polygon")) new_annot(PDF_ANNOT_POLYGON);
  1022. if (ui_popup_item("PolyLine")) new_annot(PDF_ANNOT_POLY_LINE);
  1023. if (ui_popup_item("Highlight")) new_annot(PDF_ANNOT_HIGHLIGHT);
  1024. if (ui_popup_item("Underline")) new_annot(PDF_ANNOT_UNDERLINE);
  1025. if (ui_popup_item("StrikeOut")) new_annot(PDF_ANNOT_STRIKE_OUT);
  1026. if (ui_popup_item("Squiggly")) new_annot(PDF_ANNOT_SQUIGGLY);
  1027. if (ui_popup_item("FileAttachment")) new_annot(PDF_ANNOT_FILE_ATTACHMENT);
  1028. if (ui_popup_item("Redact")) new_annot(PDF_ANNOT_REDACT);
  1029. ui_popup_end();
  1030. }
  1031. n = 0;
  1032. for (annot = pdf_first_annot(ctx, page); annot; annot = pdf_next_annot(ctx, annot))
  1033. ++n;
  1034. ui_list_begin(&annot_list, n, 0, ui.lineheight * 6 + 4);
  1035. for (idx=0, annot = pdf_first_annot(ctx, page); annot; ++idx, annot = pdf_next_annot(ctx, annot))
  1036. {
  1037. char buf[256];
  1038. int num = pdf_to_num(ctx, pdf_annot_obj(ctx, annot));
  1039. subtype = pdf_annot_type(ctx, annot);
  1040. fz_snprintf(buf, sizeof buf, "%d: %s", num, pdf_string_from_annot_type(ctx, subtype));
  1041. if (ui_list_item(&annot_list, pdf_annot_obj(ctx, annot), buf, ui.selected_annot == annot))
  1042. {
  1043. trace_action("annot = page.getAnnotations()[%d];\n", idx);
  1044. ui_select_annot(pdf_keep_annot(ctx, annot));
  1045. }
  1046. }
  1047. ui_list_end(&annot_list);
  1048. if (ui.selected_annot && (subtype = pdf_annot_type(ctx, ui.selected_annot)) != PDF_ANNOT_WIDGET)
  1049. {
  1050. int n, choice, has_content;
  1051. pdf_obj *obj;
  1052. /* common annotation properties */
  1053. ui_spacer();
  1054. do_annotate_author();
  1055. do_annotate_date();
  1056. obj = pdf_dict_get(ctx, pdf_annot_obj(ctx, ui.selected_annot), PDF_NAME(Popup));
  1057. if (obj)
  1058. ui_label("Popup: %d 0 R", pdf_to_num(ctx, obj));
  1059. has_content = do_annotate_contents();
  1060. intent = do_annotate_intent();
  1061. if (subtype == PDF_ANNOT_FREE_TEXT && intent == PDF_ANNOT_IT_FREETEXT_CALLOUT)
  1062. {
  1063. enum pdf_line_ending s;
  1064. int s_choice;
  1065. s = pdf_annot_callout_style(ctx, ui.selected_annot);
  1066. s_choice = label_select("Callout", "CL", line_ending_styles[s], line_ending_styles, nelem(line_ending_styles));
  1067. if (s_choice != -1)
  1068. {
  1069. s = s_choice;
  1070. trace_action("annot.setCalloutStyle(%q);\n", line_ending_styles[s]);
  1071. pdf_set_annot_callout_style(ctx, ui.selected_annot, s);
  1072. }
  1073. }
  1074. if (subtype == PDF_ANNOT_FREE_TEXT)
  1075. {
  1076. int lang_choice, font_choice, color_choice, size_changed;
  1077. int q;
  1078. const char *text_lang;
  1079. const char *text_font;
  1080. char text_font_buf[20];
  1081. char lang_buf[8];
  1082. static float text_size_f, text_color[4];
  1083. static int text_size;
  1084. ui_spacer();
  1085. text_lang = fz_string_from_text_language(lang_buf, pdf_annot_language(ctx, ui.selected_annot));
  1086. lang_choice = label_select("Language", "DA/Lang", text_lang, lang_names, nelem(lang_names));
  1087. if (lang_choice != -1)
  1088. {
  1089. text_lang = lang_names[lang_choice];
  1090. trace_action("annot.setLanguage(%q);\n", text_lang);
  1091. pdf_set_annot_language(ctx, ui.selected_annot, fz_text_language_from_string(text_lang));
  1092. }
  1093. q = pdf_annot_quadding(ctx, ui.selected_annot);
  1094. choice = label_select("Text Align", "Q", quadding_names[q], quadding_names, nelem(quadding_names));
  1095. if (choice != -1)
  1096. {
  1097. trace_action("annot.setQuadding(%d);\n", choice);
  1098. pdf_set_annot_quadding(ctx, ui.selected_annot, choice);
  1099. }
  1100. pdf_annot_default_appearance_unmapped(ctx, ui.selected_annot, text_font_buf, sizeof text_font_buf, &text_size_f, &n, text_color);
  1101. text_size = text_size_f;
  1102. font_choice = label_select("Text Font", "DA/Font", text_font_buf, font_names, nelem(font_names));
  1103. size_changed = label_slider("Text Size", &text_size, 8, 36);
  1104. color_choice = label_select("Text Color", "DA/Color", name_from_hex(hex_from_color(n, text_color)), color_names+1, nelem(color_names)-1);
  1105. if (font_choice != -1 || color_choice != -1 || size_changed)
  1106. {
  1107. if (font_choice != -1)
  1108. text_font = font_names[font_choice];
  1109. else
  1110. text_font = text_font_buf;
  1111. if (color_choice != -1)
  1112. {
  1113. n = 3;
  1114. text_color[0] = ((color_values[color_choice+1]>>16) & 0xff) / 255.0f;
  1115. text_color[1] = ((color_values[color_choice+1]>>8) & 0xff) / 255.0f;
  1116. text_color[2] = ((color_values[color_choice+1]) & 0xff) / 255.0f;
  1117. if (text_color[0] == text_color[1] && text_color[1] == text_color[2])
  1118. n = 1;
  1119. }
  1120. if (n == 1)
  1121. trace_action("annot.setDefaultAppearance(%q, %d, [%g]);\n",
  1122. text_font, text_size, text_color[0]);
  1123. else if (n == 3)
  1124. trace_action("annot.setDefaultAppearance(%q, %d, [%g, %g, %g]);\n",
  1125. text_font, text_size, text_color[0], text_color[1], text_color[2]);
  1126. else if (n == 4)
  1127. trace_action("annot.setDefaultAppearance(%q, %d, [%g, %g, %g, %g]);\n",
  1128. text_font, text_size, text_color[0], text_color[1], text_color[2], text_color[3]);
  1129. else
  1130. trace_action("annot.setDefaultAppearance(%q, %d, []);\n",
  1131. text_font, text_size);
  1132. pdf_set_annot_default_appearance(ctx, ui.selected_annot, text_font, text_size, n, text_color);
  1133. }
  1134. }
  1135. if (subtype == PDF_ANNOT_LINE || subtype == PDF_ANNOT_POLY_LINE)
  1136. {
  1137. enum pdf_line_ending s, e;
  1138. int s_choice, e_choice;
  1139. ui_spacer();
  1140. pdf_annot_line_ending_styles(ctx, ui.selected_annot, &s, &e);
  1141. s_choice = label_select("Line End 1", "LE0", line_ending_styles[s], line_ending_styles, nelem(line_ending_styles));
  1142. e_choice = label_select("Line End 2", "LE1", line_ending_styles[e], line_ending_styles, nelem(line_ending_styles));
  1143. if (s_choice != -1 || e_choice != -1)
  1144. {
  1145. if (s_choice != -1) s = s_choice;
  1146. if (e_choice != -1) e = e_choice;
  1147. trace_action("annot.setLineEndingStyles(%q, %q);\n", line_ending_styles[s], line_ending_styles[e]);
  1148. pdf_set_annot_line_ending_styles(ctx, ui.selected_annot, s, e);
  1149. }
  1150. }
  1151. if (subtype == PDF_ANNOT_LINE)
  1152. {
  1153. static int ll, lle, llo;
  1154. static int cap;
  1155. ll = pdf_annot_line_leader(ctx, ui.selected_annot);
  1156. if (label_slider("Leader", &ll, -20, 20))
  1157. {
  1158. pdf_set_annot_line_leader(ctx, ui.selected_annot, ll);
  1159. trace_action("annot.setLineLeader(%d);\n", ll);
  1160. }
  1161. if (ll)
  1162. {
  1163. lle = pdf_annot_line_leader_extension(ctx, ui.selected_annot);
  1164. if (label_slider(" LLE", &lle, 0, 20))
  1165. {
  1166. pdf_set_annot_line_leader_extension(ctx, ui.selected_annot, lle);
  1167. trace_action("annot.setLineLeaderExtension(%d);\n", ll);
  1168. }
  1169. llo = pdf_annot_line_leader_offset(ctx, ui.selected_annot);
  1170. if (label_slider(" LLO", &llo, 0, 20))
  1171. {
  1172. pdf_set_annot_line_leader_offset(ctx, ui.selected_annot, llo);
  1173. trace_action("annot.setLineLeaderOffset(%d);\n", ll);
  1174. }
  1175. }
  1176. if (has_content)
  1177. {
  1178. cap = pdf_annot_line_caption(ctx, ui.selected_annot);
  1179. if (ui_checkbox("Caption", &cap))
  1180. {
  1181. pdf_set_annot_line_caption(ctx, ui.selected_annot, cap);
  1182. trace_action("annot.setLineCaption(%s);\n", cap ? "true" : "false");
  1183. }
  1184. }
  1185. }
  1186. if (pdf_annot_has_icon_name(ctx, ui.selected_annot))
  1187. {
  1188. const char *name = pdf_annot_icon_name(ctx, ui.selected_annot);
  1189. switch (pdf_annot_type(ctx, ui.selected_annot))
  1190. {
  1191. default:
  1192. break;
  1193. case PDF_ANNOT_TEXT:
  1194. ui_spacer();
  1195. choice = label_select("Icon", "Icon", name, text_icons, nelem(text_icons));
  1196. if (choice != -1)
  1197. {
  1198. trace_action("annot.setIcon(%q);\n", text_icons[choice]);
  1199. pdf_set_annot_icon_name(ctx, ui.selected_annot, text_icons[choice]);
  1200. }
  1201. break;
  1202. case PDF_ANNOT_FILE_ATTACHMENT:
  1203. ui_spacer();
  1204. choice = label_select("Icon", "Icon", name, file_attachment_icons, nelem(file_attachment_icons));
  1205. if (choice != -1)
  1206. {
  1207. trace_action("annot.setIcon(%q);\n", file_attachment_icons[choice]);
  1208. pdf_set_annot_icon_name(ctx, ui.selected_annot, file_attachment_icons[choice]);
  1209. }
  1210. break;
  1211. case PDF_ANNOT_SOUND:
  1212. ui_spacer();
  1213. choice = label_select("Icon", "Icon", name, sound_icons, nelem(sound_icons));
  1214. if (choice != -1)
  1215. {
  1216. trace_action("annot.setIcon(%q);\n", sound_icons[choice]);
  1217. pdf_set_annot_icon_name(ctx, ui.selected_annot, sound_icons[choice]);
  1218. }
  1219. break;
  1220. case PDF_ANNOT_STAMP:
  1221. ui_spacer();
  1222. choice = label_select("Icon", "Icon", name, stamp_icons, nelem(stamp_icons));
  1223. if (choice != -1)
  1224. {
  1225. trace_action("annot.setIcon(%q);\n", stamp_icons[choice]);
  1226. pdf_set_annot_icon_name(ctx, ui.selected_annot, stamp_icons[choice]);
  1227. }
  1228. break;
  1229. }
  1230. }
  1231. if (pdf_annot_has_border(ctx, ui.selected_annot))
  1232. do_border();
  1233. ui_spacer();
  1234. if (should_edit_color(subtype))
  1235. do_annotate_color("Color", pdf_annot_color, pdf_set_annot_color);
  1236. if (should_edit_icolor(subtype))
  1237. do_annotate_color("InteriorColor", pdf_annot_interior_color, pdf_set_annot_interior_color);
  1238. {
  1239. static int opacity;
  1240. opacity = pdf_annot_opacity(ctx, ui.selected_annot) * 100;
  1241. if (label_slider("Opacity", &opacity, 0, 100))
  1242. {
  1243. trace_action("annot.setOpacity(%g);\n", opacity / 100.0f);
  1244. pdf_set_annot_opacity(ctx, ui.selected_annot, opacity / 100.0f);
  1245. }
  1246. }
  1247. if (pdf_annot_has_quad_points(ctx, ui.selected_annot))
  1248. {
  1249. ui_spacer();
  1250. if (is_draw_mode)
  1251. {
  1252. n = pdf_annot_quad_point_count(ctx, ui.selected_annot);
  1253. ui_label("QuadPoints: %d", n);
  1254. if (ui_button("Clear"))
  1255. {
  1256. trace_action("annot.clearQuadPoints();\n");
  1257. pdf_clear_annot_quad_points(ctx, ui.selected_annot);
  1258. }
  1259. if (ui_button("Done"))
  1260. is_draw_mode = 0;
  1261. }
  1262. else
  1263. {
  1264. if (ui_button("Edit"))
  1265. is_draw_mode = 1;
  1266. }
  1267. }
  1268. if (pdf_annot_has_vertices(ctx, ui.selected_annot))
  1269. {
  1270. ui_spacer();
  1271. if (is_draw_mode)
  1272. {
  1273. n = pdf_annot_vertex_count(ctx, ui.selected_annot);
  1274. ui_label("Vertices: %d", n);
  1275. if (ui_button("Clear"))
  1276. {
  1277. trace_action("annot.clearVertices();\n");
  1278. pdf_clear_annot_vertices(ctx, ui.selected_annot);
  1279. }
  1280. if (ui_button("Done"))
  1281. is_draw_mode = 0;
  1282. }
  1283. else
  1284. {
  1285. if (ui_button("Edit"))
  1286. is_draw_mode = 1;
  1287. }
  1288. }
  1289. if (pdf_annot_has_ink_list(ctx, ui.selected_annot))
  1290. {
  1291. ui_spacer();
  1292. if (is_draw_mode)
  1293. {
  1294. n = pdf_annot_ink_list_count(ctx, ui.selected_annot);
  1295. ui_label("InkList: %d strokes", n);
  1296. if (ui_button("Clear"))
  1297. {
  1298. trace_action("annot.clearInkList();\n");
  1299. pdf_clear_annot_ink_list(ctx, ui.selected_annot);
  1300. }
  1301. if (ui_button("Done"))
  1302. is_draw_mode = 0;
  1303. }
  1304. else
  1305. {
  1306. if (ui_button("Edit"))
  1307. is_draw_mode = 1;
  1308. }
  1309. }
  1310. if (pdf_annot_type(ctx, ui.selected_annot) == PDF_ANNOT_STAMP)
  1311. {
  1312. char attname[PATH_MAX];
  1313. ui_spacer();
  1314. if (ui_button("Image..."))
  1315. {
  1316. fz_dirname(attname, filename, sizeof attname);
  1317. ui_init_open_file(attname, image_file_filter);
  1318. ui.dialog = open_stamp_image_dialog;
  1319. }
  1320. }
  1321. if (pdf_annot_type(ctx, ui.selected_annot) == PDF_ANNOT_FILE_ATTACHMENT)
  1322. {
  1323. pdf_embedded_file_params params;
  1324. char attname[PATH_MAX];
  1325. pdf_obj *fs = pdf_annot_filespec(ctx, ui.selected_annot);
  1326. ui_spacer();
  1327. if (pdf_is_embedded_file(ctx, fs))
  1328. {
  1329. if (ui_button("Save..."))
  1330. {
  1331. fz_dirname(attname, filename, sizeof attname);
  1332. fz_strlcat(attname, "/", sizeof attname);
  1333. pdf_get_embedded_file_params(ctx, fs, &params);
  1334. fz_strlcat(attname, params.filename, sizeof attname);
  1335. ui_init_save_file(attname, NULL);
  1336. ui.dialog = save_attachment_dialog;
  1337. }
  1338. }
  1339. if (ui_button("Embed..."))
  1340. {
  1341. fz_dirname(attname, filename, sizeof attname);
  1342. ui_init_open_file(attname, NULL);
  1343. ui.dialog = open_attachment_dialog;
  1344. }
  1345. }
  1346. ui_spacer();
  1347. if (ui_button("Delete"))
  1348. {
  1349. trace_action("page.deleteAnnotation(annot);\n");
  1350. pdf_delete_annot(ctx, page, ui.selected_annot);
  1351. page_annots_changed = 1;
  1352. ui_select_annot(NULL);
  1353. return;
  1354. }
  1355. }
  1356. ui_layout(B, X, NW, ui.padsize, ui.padsize);
  1357. if (ui_button("Save PDF..."))
  1358. do_save_pdf_file();
  1359. }
  1360. static void new_redaction(pdf_page *page, fz_quad q)
  1361. {
  1362. pdf_annot *annot;
  1363. pdf_begin_operation(ctx, pdf, "Create Redaction");
  1364. annot = pdf_create_annot(ctx, page, PDF_ANNOT_REDACT);
  1365. fz_try(ctx)
  1366. {
  1367. pdf_set_annot_modification_date(ctx, annot, time(NULL));
  1368. if (pdf_annot_has_author(ctx, annot))
  1369. pdf_set_annot_author(ctx, annot, getuser());
  1370. pdf_add_annot_quad_point(ctx, annot, q);
  1371. pdf_set_annot_contents(ctx, annot, search_needle);
  1372. trace_action("annot = page.createAnnotation(%q);\n", "Redact");
  1373. trace_action("annot.addQuadPoint([%g, %g, %g, %g, %g, %g, %g, %g]);\n",
  1374. q.ul.x, q.ul.y,
  1375. q.ur.x, q.ur.y,
  1376. q.ll.x, q.ll.y,
  1377. q.lr.x, q.lr.y);
  1378. trace_action("annot.setContents(%q);\n", search_needle);
  1379. }
  1380. fz_always(ctx)
  1381. pdf_drop_annot(ctx, annot);
  1382. fz_catch(ctx)
  1383. fz_rethrow(ctx);
  1384. pdf_has_redactions_doc = pdf;
  1385. pdf_has_redactions = 1;
  1386. pdf_end_operation(ctx, pdf);
  1387. }
  1388. static struct { int i, n; } rds_state;
  1389. static int mark_search_step(int cancel)
  1390. {
  1391. fz_quad quads[500];
  1392. int i, count;
  1393. if (rds_state.i == 0)
  1394. return ++rds_state.i, rds_state.n;
  1395. if (cancel || rds_state.i > rds_state.n)
  1396. {
  1397. trace_action("page = tmp;\n");
  1398. load_page();
  1399. return -1;
  1400. }
  1401. count = fz_search_page_number(ctx, (fz_document*)pdf, rds_state.i-1, search_needle, NULL, quads, nelem(quads));
  1402. if (count > 0)
  1403. {
  1404. pdf_page *page = pdf_load_page(ctx, pdf, rds_state.i-1);
  1405. trace_action("page = doc.loadPage(%d);\n", rds_state.i-1);
  1406. for (i = 0; i < count; i++)
  1407. new_redaction(page, quads[i]);
  1408. fz_drop_page(ctx, (fz_page*)page);
  1409. }
  1410. return ++rds_state.i;
  1411. }
  1412. void mark_all_search_results(void)
  1413. {
  1414. rds_state.i = 0;
  1415. rds_state.n = pdf_count_pages(ctx, pdf);
  1416. trace_action("tmp = page;\n");
  1417. ui_start_slow_operation("Marking all search results for redaction.", "Page", mark_search_step);
  1418. }
  1419. void do_redact_panel(void)
  1420. {
  1421. static struct list annot_list;
  1422. enum pdf_annot_type subtype;
  1423. pdf_annot *annot;
  1424. int idx;
  1425. int im_choice;
  1426. int la_choice;
  1427. int tx_choice;
  1428. int i;
  1429. int num_redact = 0;
  1430. static pdf_redact_options redact_opts = { 1, PDF_REDACT_IMAGE_PIXELS, PDF_REDACT_LINE_ART_REMOVE_IF_TOUCHED };
  1431. int search_valid;
  1432. if (pdf_has_redactions_doc != pdf)
  1433. {
  1434. pdf_has_redactions_doc = pdf;
  1435. pdf_has_redactions = document_has_redactions();
  1436. }
  1437. num_redact = 0;
  1438. for (annot = pdf_first_annot(ctx, page); annot; annot = pdf_next_annot(ctx, annot))
  1439. if (pdf_annot_type(ctx, annot) == PDF_ANNOT_REDACT)
  1440. ++num_redact;
  1441. ui_layout(T, X, NW, ui.padsize, ui.padsize);
  1442. if (ui_button("Add Redaction"))
  1443. new_annot(PDF_ANNOT_REDACT);
  1444. search_valid = search_has_results();
  1445. if (ui_button_aux("Mark search in page", !search_valid))
  1446. {
  1447. for (i = 0; i < search_hit_count; i++)
  1448. new_redaction(page, search_hit_quads[i]);
  1449. search_hit_count = 0;
  1450. ui_select_annot(NULL);
  1451. }
  1452. if (ui_button_aux("Mark search in document", search_needle == NULL))
  1453. {
  1454. mark_all_search_results();
  1455. search_hit_count = 0;
  1456. ui_select_annot(NULL);
  1457. }
  1458. ui_spacer();
  1459. ui_label("When Redacting:");
  1460. ui_checkbox("Draw black boxes", &redact_opts.black_boxes);
  1461. im_choice = ui_select("Redact/IM", im_redact_names[redact_opts.image_method], im_redact_names, nelem(im_redact_names));
  1462. if (im_choice != -1)
  1463. redact_opts.image_method = im_choice;
  1464. la_choice = ui_select("Redact/LA", la_redact_names[redact_opts.line_art], la_redact_names, nelem(la_redact_names));
  1465. if (la_choice != -1)
  1466. redact_opts.line_art = la_choice;
  1467. tx_choice = ui_select("Redact/TX", tx_redact_names[redact_opts.text], tx_redact_names, nelem(tx_redact_names));
  1468. if (tx_choice != -1)
  1469. redact_opts.text = tx_choice;
  1470. ui_spacer();
  1471. if (ui_button_aux("Redact Page", num_redact == 0))
  1472. {
  1473. ui_select_annot(NULL);
  1474. trace_action("page.applyRedactions(%s, %d, %d);\n",
  1475. redact_opts.black_boxes ? "true" : "false",
  1476. redact_opts.image_method,
  1477. redact_opts.line_art);
  1478. pdf_redact_page(ctx, pdf, page, &redact_opts);
  1479. trace_page_update();
  1480. load_page();
  1481. }
  1482. if (ui_button_aux("Redact Document", !pdf_has_redactions))
  1483. {
  1484. ui_select_annot(NULL);
  1485. redact_all_pages(&redact_opts);
  1486. }
  1487. ui_spacer();
  1488. ui_list_begin(&annot_list, num_redact, 0, ui.lineheight * 6 + 4);
  1489. for (idx=0, annot = pdf_first_annot(ctx, page); annot; ++idx, annot = pdf_next_annot(ctx, annot))
  1490. {
  1491. char buf[50];
  1492. int num = pdf_to_num(ctx, pdf_annot_obj(ctx, annot));
  1493. subtype = pdf_annot_type(ctx, annot);
  1494. if (subtype == PDF_ANNOT_REDACT)
  1495. {
  1496. const char *contents = pdf_annot_contents(ctx, annot);
  1497. fz_snprintf(buf, sizeof buf, "%d: %s", num, contents[0] ? contents : "Redact");
  1498. if (ui_list_item(&annot_list, pdf_annot_obj(ctx, annot), buf, ui.selected_annot == annot))
  1499. {
  1500. trace_action("annot = page.getAnnotations()[%d];\n", idx);
  1501. ui_select_annot(pdf_keep_annot(ctx, annot));
  1502. }
  1503. }
  1504. }
  1505. ui_list_end(&annot_list);
  1506. ui_spacer();
  1507. if (ui.selected_annot && (subtype = pdf_annot_type(ctx, ui.selected_annot)) == PDF_ANNOT_REDACT)
  1508. {
  1509. int n;
  1510. do_annotate_author();
  1511. do_annotate_date();
  1512. ui_spacer();
  1513. if (is_draw_mode)
  1514. {
  1515. n = pdf_annot_quad_point_count(ctx, ui.selected_annot);
  1516. ui_label("QuadPoints: %d", n);
  1517. if (ui_button("Clear"))
  1518. {
  1519. trace_action("annot.clearQuadPoints();\n");
  1520. pdf_clear_annot_quad_points(ctx, ui.selected_annot);
  1521. }
  1522. if (ui_button("Done"))
  1523. is_draw_mode = 0;
  1524. }
  1525. else
  1526. {
  1527. if (ui_button("Edit"))
  1528. is_draw_mode = 1;
  1529. }
  1530. ui_spacer();
  1531. if (ui_button("Delete"))
  1532. {
  1533. trace_action("page.deleteAnnotation(annot);\n");
  1534. pdf_delete_annot(ctx, page, ui.selected_annot);
  1535. page_annots_changed = 1;
  1536. ui_select_annot(NULL);
  1537. return;
  1538. }
  1539. }
  1540. ui_layout(B, X, NW, ui.padsize, ui.padsize);
  1541. if (ui_button("Save PDF..."))
  1542. do_save_pdf_file();
  1543. }
  1544. static void do_edit_icon(fz_irect canvas_area, fz_irect area, fz_rect *rect)
  1545. {
  1546. static fz_point start_pt;
  1547. static float w, h;
  1548. static int moving = 0;
  1549. if (ui_mouse_inside(canvas_area) && ui_mouse_inside(area))
  1550. {
  1551. ui.hot = ui.selected_annot;
  1552. if (!ui.active && ui.down)
  1553. {
  1554. ui.active = ui.selected_annot;
  1555. start_pt.x = rect->x0;
  1556. start_pt.y = rect->y0;
  1557. w = rect->x1 - rect->x0;
  1558. h = rect->y1 - rect->y0;
  1559. moving = 1;
  1560. }
  1561. }
  1562. if (ui.active == ui.selected_annot && moving)
  1563. {
  1564. rect->x0 = start_pt.x + (ui.x - ui.down_x);
  1565. rect->y0 = start_pt.y + (ui.y - ui.down_y);
  1566. /* Clamp to fit on page */
  1567. rect->x0 = fz_clamp(rect->x0, view_page_area.x0, view_page_area.x1-w);
  1568. rect->y0 = fz_clamp(rect->y0, view_page_area.y0, view_page_area.y1-h);
  1569. rect->x1 = rect->x0 + w;
  1570. rect->y1 = rect->y0 + h;
  1571. /* cancel on right click */
  1572. if (ui.right)
  1573. moving = 0;
  1574. /* Commit movement on mouse-up */
  1575. if (!ui.down)
  1576. {
  1577. fz_point dp = { rect->x0 - start_pt.x, rect->y0 - start_pt.y };
  1578. moving = 0;
  1579. if (fz_abs(dp.x) > 0.1f || fz_abs(dp.y) > 0.1f)
  1580. {
  1581. fz_rect trect = pdf_annot_rect(ctx, ui.selected_annot);
  1582. dp = fz_transform_vector(dp, view_page_inv_ctm);
  1583. trect.x0 += dp.x; trect.x1 += dp.x;
  1584. trect.y0 += dp.y; trect.y1 += dp.y;
  1585. trace_action("annot.setRect([%g, %g, %g, %g]);\n", trect.x0, trect.y0, trect.x1, trect.y1);
  1586. pdf_set_annot_rect(ctx, ui.selected_annot, trect);
  1587. }
  1588. }
  1589. }
  1590. }
  1591. static void do_edit_rect(fz_irect canvas_area, fz_irect area, fz_rect *rect, int lock_aspect)
  1592. {
  1593. enum {
  1594. ER_N=1, ER_E=2, ER_S=4, ER_W=8,
  1595. ER_NONE = 0,
  1596. ER_NW = ER_N|ER_W,
  1597. ER_NE = ER_N|ER_E,
  1598. ER_SW = ER_S|ER_W,
  1599. ER_SE = ER_S|ER_E,
  1600. ER_MOVE = ER_N|ER_E|ER_S|ER_W,
  1601. };
  1602. static fz_rect start_rect;
  1603. static int state = ER_NONE;
  1604. area = fz_expand_irect(area, 5);
  1605. if (ui_mouse_inside(canvas_area) && ui_mouse_inside(area))
  1606. {
  1607. ui.hot = ui.selected_annot;
  1608. if (!ui.active && ui.down)
  1609. {
  1610. ui.active = ui.selected_annot;
  1611. start_rect = *rect;
  1612. state = ER_NONE;
  1613. if (ui.x <= area.x0 + 10) state |= ER_W;
  1614. if (ui.x >= area.x1 - 10) state |= ER_E;
  1615. if (ui.y <= area.y0 + 10) state |= ER_N;
  1616. if (ui.y >= area.y1 - 10) state |= ER_S;
  1617. if (!state) state = ER_MOVE;
  1618. }
  1619. }
  1620. if (ui.active == ui.selected_annot && state != ER_NONE)
  1621. {
  1622. *rect = start_rect;
  1623. if (state & ER_W) rect->x0 += (ui.x - ui.down_x);
  1624. if (state & ER_E) rect->x1 += (ui.x - ui.down_x);
  1625. if (state & ER_N) rect->y0 += (ui.y - ui.down_y);
  1626. if (state & ER_S) rect->y1 += (ui.y - ui.down_y);
  1627. if (rect->x1 < rect->x0) { float t = rect->x1; rect->x1 = rect->x0; rect->x0 = t; }
  1628. if (rect->y1 < rect->y0) { float t = rect->y1; rect->y1 = rect->y0; rect->y0 = t; }
  1629. if (rect->x1 < rect->x0 + 10) rect->x1 = rect->x0 + 10;
  1630. if (rect->y1 < rect->y0 + 10) rect->y1 = rect->y0 + 10;
  1631. if (lock_aspect)
  1632. {
  1633. float aspect = (start_rect.x1 - start_rect.x0) / (start_rect.y1 - start_rect.y0);
  1634. switch (state)
  1635. {
  1636. case ER_SW:
  1637. case ER_NW:
  1638. rect->x0 = rect->x1 - (rect->y1 - rect->y0) * aspect;
  1639. break;
  1640. case ER_NE:
  1641. case ER_SE:
  1642. case ER_N:
  1643. case ER_S:
  1644. rect->x1 = rect->x0 + (rect->y1 - rect->y0) * aspect;
  1645. break;
  1646. case ER_E:
  1647. case ER_W:
  1648. rect->y1 = rect->y0 + (rect->x1 - rect->x0) / aspect;
  1649. break;
  1650. }
  1651. }
  1652. /* cancel on right click */
  1653. if (ui.right)
  1654. state = ER_NONE;
  1655. /* commit on mouse-up */
  1656. if (!ui.down)
  1657. {
  1658. state = ER_NONE;
  1659. if (rects_differ(start_rect, *rect, 1))
  1660. {
  1661. fz_rect trect = fz_transform_rect(*rect, view_page_inv_ctm);
  1662. trace_action("annot.setRect([%g, %g, %g, %g]);\n", trect.x0, trect.y0, trect.x1, trect.y1);
  1663. pdf_set_annot_rect(ctx, ui.selected_annot, trect);
  1664. }
  1665. }
  1666. }
  1667. }
  1668. static void do_edit_line(fz_irect canvas_area, fz_irect area, fz_rect *rect)
  1669. {
  1670. enum { EL_NONE, EL_A=1, EL_B=2, EL_MOVE=EL_A|EL_B };
  1671. static fz_point start_a, start_b;
  1672. static int state = EL_NONE;
  1673. fz_irect a_grab, b_grab;
  1674. fz_point a, b;
  1675. float lw;
  1676. area = fz_expand_irect(area, 5);
  1677. if (ui_mouse_inside(canvas_area) && ui_mouse_inside(area))
  1678. {
  1679. ui.hot = ui.selected_annot;
  1680. if (!ui.active && ui.down)
  1681. {
  1682. ui.active = ui.selected_annot;
  1683. pdf_annot_line(ctx, ui.selected_annot, &start_a, &start_b);
  1684. start_a = fz_transform_point(start_a, view_page_ctm);
  1685. start_b = fz_transform_point(start_b, view_page_ctm);
  1686. a_grab = fz_make_irect(start_a.x, start_a.y, start_a.x, start_a.y);
  1687. b_grab = fz_make_irect(start_b.x, start_b.y, start_b.x, start_b.y);
  1688. a_grab = fz_expand_irect(a_grab, 10);
  1689. b_grab = fz_expand_irect(b_grab, 10);
  1690. state = EL_NONE;
  1691. if (ui_mouse_inside(a_grab)) state |= EL_A;
  1692. if (ui_mouse_inside(b_grab)) state |= EL_B;
  1693. if (!state) state = EL_MOVE;
  1694. }
  1695. }
  1696. if (ui.active == ui.selected_annot && state != 0)
  1697. {
  1698. a = start_a;
  1699. b = start_b;
  1700. if (state & EL_A) { a.x += (ui.x - ui.down_x); a.y += (ui.y - ui.down_y); }
  1701. if (state & EL_B) { b.x += (ui.x - ui.down_x); b.y += (ui.y - ui.down_y); }
  1702. glBegin(GL_LINES);
  1703. glColor4f(1, 0, 0, 1);
  1704. glVertex2f(a.x, a.y);
  1705. glVertex2f(b.x, b.y);
  1706. glEnd();
  1707. rect->x0 = fz_min(a.x, b.x);
  1708. rect->y0 = fz_min(a.y, b.y);
  1709. rect->x1 = fz_max(a.x, b.x);
  1710. rect->y1 = fz_max(a.y, b.y);
  1711. lw = pdf_annot_border_width(ctx, ui.selected_annot);
  1712. *rect = fz_expand_rect(*rect, fz_matrix_expansion(view_page_ctm) * lw);
  1713. /* cancel on right click */
  1714. if (ui.right)
  1715. state = EL_NONE;
  1716. /* commit on mouse-up */
  1717. if (!ui.down)
  1718. {
  1719. state = EL_NONE;
  1720. if (points_differ(start_a, a, 1) || points_differ(start_b, b, 1))
  1721. {
  1722. a = fz_transform_point(a, view_page_inv_ctm);
  1723. b = fz_transform_point(b, view_page_inv_ctm);
  1724. trace_action("annot.setLine([%g, %g], [%g, %g]);\n", a.x, a.y, b.x, b.y);
  1725. pdf_set_annot_line(ctx, ui.selected_annot, a, b);
  1726. }
  1727. }
  1728. }
  1729. }
  1730. static void do_edit_polygon(fz_irect canvas_area, int close)
  1731. {
  1732. static int drawing = 0;
  1733. fz_point a, p;
  1734. if (ui_mouse_inside(canvas_area) && ui_mouse_inside(view_page_area))
  1735. {
  1736. ui.hot = ui.selected_annot;
  1737. if (!ui.active || ui.active == ui.selected_annot)
  1738. ui.cursor = GLUT_CURSOR_CROSSHAIR;
  1739. if (!ui.active && ui.down)
  1740. {
  1741. ui.active = ui.selected_annot;
  1742. drawing = 1;
  1743. }
  1744. }
  1745. if (ui.active == ui.selected_annot && drawing)
  1746. {
  1747. int n = pdf_annot_vertex_count(ctx, ui.selected_annot);
  1748. if (n > 0)
  1749. {
  1750. p = pdf_annot_vertex(ctx, ui.selected_annot, n-1);
  1751. p = fz_transform_point(p, view_page_ctm);
  1752. if (close)
  1753. {
  1754. a = pdf_annot_vertex(ctx, ui.selected_annot, 0);
  1755. a = fz_transform_point(a, view_page_ctm);
  1756. }
  1757. glBegin(GL_LINE_STRIP);
  1758. glColor4f(1, 0, 0, 1);
  1759. glVertex2f(p.x, p.y);
  1760. glVertex2f(ui.x, ui.y);
  1761. if (close)
  1762. glVertex2f(a.x, a.y);
  1763. glEnd();
  1764. }
  1765. glColor4f(1, 0, 0, 1);
  1766. glPointSize(4);
  1767. glBegin(GL_POINTS);
  1768. glVertex2f(ui.x, ui.y);
  1769. glEnd();
  1770. /* cancel on right click */
  1771. if (ui.right)
  1772. drawing = 0;
  1773. /* commit point on mouse-up */
  1774. if (!ui.down)
  1775. {
  1776. fz_point p = fz_transform_point_xy(ui.x, ui.y, view_page_inv_ctm);
  1777. trace_action("annot.addVertex(%g, %g);\n", p.x, p.y);
  1778. pdf_add_annot_vertex(ctx, ui.selected_annot, p);
  1779. drawing = 0;
  1780. }
  1781. }
  1782. }
  1783. static void do_edit_ink(fz_irect canvas_area)
  1784. {
  1785. static int drawing = 0;
  1786. static fz_point p[1000];
  1787. static int n, last_x, last_y;
  1788. int i;
  1789. if (ui_mouse_inside(canvas_area) && ui_mouse_inside(view_page_area))
  1790. {
  1791. ui.hot = ui.selected_annot;
  1792. if (!ui.active || ui.active == ui.selected_annot)
  1793. ui.cursor = GLUT_CURSOR_CROSSHAIR;
  1794. if (!ui.active && ui.down)
  1795. {
  1796. ui.active = ui.selected_annot;
  1797. drawing = 1;
  1798. n = 0;
  1799. last_x = INT_MIN;
  1800. last_y = INT_MIN;
  1801. }
  1802. }
  1803. if (ui.active == ui.selected_annot && drawing)
  1804. {
  1805. if (n < (int)nelem(p) && (ui.x != last_x || ui.y != last_y))
  1806. {
  1807. p[n].x = fz_clamp(ui.x, view_page_area.x0, view_page_area.x1);
  1808. p[n].y = fz_clamp(ui.y, view_page_area.y0, view_page_area.y1);
  1809. ++n;
  1810. }
  1811. last_x = ui.x;
  1812. last_y = ui.y;
  1813. if (n > 1)
  1814. {
  1815. glBegin(GL_LINE_STRIP);
  1816. glColor4f(1, 0, 0, 1);
  1817. for (i = 0; i < n; ++i)
  1818. glVertex2f(p[i].x, p[i].y);
  1819. glEnd();
  1820. }
  1821. /* cancel on right click */
  1822. if (ui.right)
  1823. {
  1824. drawing = 0;
  1825. n = 0;
  1826. }
  1827. /* commit stroke on mouse-up */
  1828. if (!ui.down)
  1829. {
  1830. if (n > 1)
  1831. {
  1832. trace_action("annot.addInkList([");
  1833. for (i = 0; i < n; ++i)
  1834. {
  1835. p[i] = fz_transform_point(p[i], view_page_inv_ctm);
  1836. trace_action("%s[%g, %g]", (i > 0 ? ", " : ""), p[i].x, p[i].y);
  1837. }
  1838. trace_action("]);\n");
  1839. pdf_add_annot_ink_list(ctx, ui.selected_annot, n, p);
  1840. }
  1841. drawing = 0;
  1842. n = 0;
  1843. }
  1844. }
  1845. }
  1846. static void do_edit_quad_points(void)
  1847. {
  1848. static fz_point pt = { 0, 0 };
  1849. static int marking = 0;
  1850. static fz_quad hits[1000];
  1851. fz_rect rect;
  1852. char *text;
  1853. int i, n;
  1854. if (ui_mouse_inside(view_page_area))
  1855. {
  1856. ui.hot = ui.selected_annot;
  1857. if (!ui.active || ui.active == ui.selected_annot)
  1858. ui.cursor = GLUT_CURSOR_TEXT;
  1859. if (!ui.active && ui.down)
  1860. {
  1861. ui.active = ui.selected_annot;
  1862. marking = 1;
  1863. pt.x = ui.x;
  1864. pt.y = ui.y;
  1865. }
  1866. }
  1867. if (ui.active == ui.selected_annot && marking)
  1868. {
  1869. fz_point page_a = { pt.x, pt.y };
  1870. fz_point page_b = { ui.x, ui.y };
  1871. page_a = fz_transform_point(page_a, view_page_inv_ctm);
  1872. page_b = fz_transform_point(page_b, view_page_inv_ctm);
  1873. if (ui.mod == GLUT_ACTIVE_CTRL)
  1874. fz_snap_selection(ctx, page_text, &page_a, &page_b, FZ_SELECT_WORDS);
  1875. else if (ui.mod == GLUT_ACTIVE_CTRL + GLUT_ACTIVE_SHIFT)
  1876. fz_snap_selection(ctx, page_text, &page_a, &page_b, FZ_SELECT_LINES);
  1877. if (ui.mod == GLUT_ACTIVE_SHIFT)
  1878. {
  1879. rect = fz_make_rect(
  1880. fz_min(page_a.x, page_b.x),
  1881. fz_min(page_a.y, page_b.y),
  1882. fz_max(page_a.x, page_b.x),
  1883. fz_max(page_a.y, page_b.y));
  1884. n = 1;
  1885. hits[0] = fz_quad_from_rect(rect);
  1886. }
  1887. else
  1888. {
  1889. n = fz_highlight_selection(ctx, page_text, page_a, page_b, hits, nelem(hits));
  1890. }
  1891. glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO); /* invert destination color */
  1892. glEnable(GL_BLEND);
  1893. glColor4f(1, 1, 1, 1);
  1894. glBegin(GL_QUADS);
  1895. for (i = 0; i < n; ++i)
  1896. {
  1897. fz_quad thit = fz_transform_quad(hits[i], view_page_ctm);
  1898. glVertex2f(thit.ul.x, thit.ul.y);
  1899. glVertex2f(thit.ur.x, thit.ur.y);
  1900. glVertex2f(thit.lr.x, thit.lr.y);
  1901. glVertex2f(thit.ll.x, thit.ll.y);
  1902. }
  1903. glEnd();
  1904. glDisable(GL_BLEND);
  1905. /* cancel on right click */
  1906. if (ui.right)
  1907. marking = 0;
  1908. if (!ui.down)
  1909. {
  1910. if (n > 0)
  1911. {
  1912. pdf_begin_operation(ctx, pdf, "Edit quad points");
  1913. trace_action("annot.clearQuadPoints();\n");
  1914. pdf_clear_annot_quad_points(ctx, ui.selected_annot);
  1915. for (i = 0; i < n; ++i)
  1916. {
  1917. trace_action("annot.addQuadPoint([%g, %g, %g, %g, %g, %g, %g, %g]);\n",
  1918. hits[i].ul.x, hits[i].ul.y,
  1919. hits[i].ur.x, hits[i].ur.y,
  1920. hits[i].ll.x, hits[i].ll.y,
  1921. hits[i].lr.x, hits[i].lr.y);
  1922. pdf_add_annot_quad_point(ctx, ui.selected_annot, hits[i]);
  1923. }
  1924. if (ui.mod == GLUT_ACTIVE_SHIFT)
  1925. text = fz_copy_rectangle(ctx, page_text, rect, 0);
  1926. else
  1927. text = fz_copy_selection(ctx, page_text, page_a, page_b, 0);
  1928. trace_action("annot.setContents(%q);\n", text);
  1929. pdf_set_annot_contents(ctx, ui.selected_annot, text);
  1930. new_contents = 1;
  1931. fz_free(ctx, text);
  1932. pdf_end_operation(ctx, pdf);
  1933. }
  1934. marking = 0;
  1935. }
  1936. }
  1937. }
  1938. void do_annotate_canvas(fz_irect canvas_area)
  1939. {
  1940. fz_rect bounds;
  1941. fz_irect area;
  1942. pdf_annot *annot;
  1943. const void *nothing = ui.hot;
  1944. int idx;
  1945. for (idx=0, annot = pdf_first_annot(ctx, page); annot; ++idx, annot = pdf_next_annot(ctx, annot))
  1946. {
  1947. enum pdf_annot_type subtype = pdf_annot_type(ctx, annot);
  1948. if (pdf_annot_has_rect(ctx, annot))
  1949. bounds = pdf_annot_rect(ctx, annot);
  1950. else
  1951. bounds = pdf_bound_annot(ctx, annot);
  1952. bounds = fz_transform_rect(bounds, view_page_ctm);
  1953. area = fz_irect_from_rect(bounds);
  1954. if (ui_mouse_inside(canvas_area) && ui_mouse_inside(area))
  1955. {
  1956. pdf_set_annot_hot(ctx, annot, 1);
  1957. ui.hot = annot;
  1958. if (!ui.active && ui.down)
  1959. {
  1960. if (ui.selected_annot != annot)
  1961. {
  1962. trace_action("annot = page.getAnnotations()[%d];\n", idx);
  1963. if (!ui.selected_annot && !showannotate)
  1964. toggle_annotate(ANNOTATE_MODE_NORMAL);
  1965. ui.active = annot;
  1966. ui_select_annot(pdf_keep_annot(ctx, annot));
  1967. }
  1968. }
  1969. }
  1970. else
  1971. {
  1972. pdf_set_annot_hot(ctx, annot, 0);
  1973. }
  1974. if (annot == ui.selected_annot)
  1975. {
  1976. switch (subtype)
  1977. {
  1978. default:
  1979. break;
  1980. /* Popup window */
  1981. case PDF_ANNOT_POPUP:
  1982. do_edit_rect(canvas_area, area, &bounds, 0);
  1983. break;
  1984. /* Icons */
  1985. case PDF_ANNOT_TEXT:
  1986. case PDF_ANNOT_CARET:
  1987. case PDF_ANNOT_FILE_ATTACHMENT:
  1988. case PDF_ANNOT_SOUND:
  1989. do_edit_icon(canvas_area, area, &bounds);
  1990. break;
  1991. case PDF_ANNOT_STAMP:
  1992. do_edit_rect(canvas_area, area, &bounds, 1);
  1993. break;
  1994. case PDF_ANNOT_FREE_TEXT:
  1995. do_edit_rect(canvas_area, area, &bounds, 0);
  1996. break;
  1997. /* Drawings */
  1998. case PDF_ANNOT_LINE:
  1999. do_edit_line(canvas_area, area, &bounds);
  2000. break;
  2001. case PDF_ANNOT_CIRCLE:
  2002. case PDF_ANNOT_SQUARE:
  2003. do_edit_rect(canvas_area, area, &bounds, 0);
  2004. break;
  2005. case PDF_ANNOT_POLYGON:
  2006. if (is_draw_mode)
  2007. do_edit_polygon(canvas_area, 1);
  2008. break;
  2009. case PDF_ANNOT_POLY_LINE:
  2010. if (is_draw_mode)
  2011. do_edit_polygon(canvas_area, 0);
  2012. break;
  2013. case PDF_ANNOT_INK:
  2014. if (is_draw_mode)
  2015. do_edit_ink(canvas_area);
  2016. break;
  2017. case PDF_ANNOT_HIGHLIGHT:
  2018. case PDF_ANNOT_UNDERLINE:
  2019. case PDF_ANNOT_STRIKE_OUT:
  2020. case PDF_ANNOT_SQUIGGLY:
  2021. case PDF_ANNOT_REDACT:
  2022. if (is_draw_mode)
  2023. do_edit_quad_points();
  2024. break;
  2025. }
  2026. glLineStipple(1, 0xAAAA);
  2027. glEnable(GL_LINE_STIPPLE);
  2028. glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
  2029. glEnable(GL_BLEND);
  2030. glColor4f(1, 1, 1, 1);
  2031. glBegin(GL_LINE_LOOP);
  2032. area = fz_irect_from_rect(bounds);
  2033. glVertex2f(area.x0-0.5f, area.y0-0.5f);
  2034. glVertex2f(area.x1+0.5f, area.y0-0.5f);
  2035. glVertex2f(area.x1+0.5f, area.y1+0.5f);
  2036. glVertex2f(area.x0-0.5f, area.y1+0.5f);
  2037. glEnd();
  2038. glDisable(GL_BLEND);
  2039. glDisable(GL_LINE_STIPPLE);
  2040. }
  2041. }
  2042. if (ui_mouse_inside(canvas_area) && ui.down)
  2043. {
  2044. if (!ui.active && ui.hot == nothing)
  2045. ui_select_annot(NULL);
  2046. }
  2047. if (ui.right)
  2048. is_draw_mode = 0;
  2049. }