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, &params);
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 }