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