1#include "gl-app.h"
2
3#include <string.h>
4#include <stdlib.h>
5#include <stdio.h>
6#include <time.h>
7#include <limits.h>
8
9#ifndef PATH_MAX
10#define PATH_MAX 2048
11#endif
12
13static int is_draw_mode = 0;
14
15static char save_filename[PATH_MAX];
16static pdf_write_options save_opts;
17static struct input opwinput;
18static struct input upwinput;
19
20static int pdf_filter(const char *fn)
21{
22 const char *extension = strrchr(fn, '.');
23 if (extension && !fz_strcasecmp(extension, ".pdf"))
24 return 1;
25 return 0;
26}
27
28static void init_save_pdf_options(void)
29{
30 save_opts = pdf_default_write_options;
31 save_opts.do_compress = 1;
32 save_opts.do_compress_images = 1;
33 save_opts.do_compress_fonts = 1;
34}
35
36static const char *cryptalgo_names[] = {
37 "Keep",
38 "None",
39 "RC4, 40 bit",
40 "RC4, 128 bit",
41 "AES, 128 bit",
42 "AES, 256 bit",
43};
44
45static void save_pdf_options(void)
46{
47 const char *cryptalgo = cryptalgo_names[save_opts.do_encrypt];
48 int choice;
49
50 ui_layout(T, X, NW, 2, 2);
51 ui_label("PDF write options:");
52 ui_layout(T, X, NW, 4, 2);
53
54 ui_checkbox("Incremental", &save_opts.do_incremental);
55 ui_spacer();
56 ui_checkbox("Pretty-print", &save_opts.do_pretty);
57 ui_checkbox("Ascii", &save_opts.do_ascii);
58 ui_checkbox("Decompress", &save_opts.do_decompress);
59 ui_checkbox("Compress", &save_opts.do_compress);
60 ui_checkbox("Compress images", &save_opts.do_compress_images);
61 ui_checkbox("Compress fonts", &save_opts.do_compress_fonts);
62 if (save_opts.do_incremental)
63 {
64 save_opts.do_garbage = 0;
65 save_opts.do_linear = 0;
66 save_opts.do_clean = 0;
67 save_opts.do_sanitize = 0;
68 save_opts.do_encrypt = PDF_ENCRYPT_KEEP;
69 }
70 else
71 {
72 ui_spacer();
73 ui_checkbox("Linearize", &save_opts.do_linear);
74 ui_checkbox("Garbage collect", &save_opts.do_garbage);
75 ui_checkbox("Clean syntax", &save_opts.do_clean);
76 ui_checkbox("Sanitize syntax", &save_opts.do_sanitize);
77
78 ui_spacer();
79 ui_label("Encryption:");
80 choice = ui_select("Encryption", cryptalgo, cryptalgo_names, nelem(cryptalgo_names));
81 if (choice != -1)
82 save_opts.do_encrypt = choice;
83 }
84
85 if (save_opts.do_encrypt >= PDF_ENCRYPT_RC4_40)
86 {
87 ui_spacer();
88 ui_label("User password:");
89 if (ui_input(&upwinput, 32, 1) >= UI_INPUT_EDIT)
90 fz_strlcpy(save_opts.upwd_utf8, upwinput.text, nelem(save_opts.upwd_utf8));
91 ui_label("Owner password:");
92 if (ui_input(&opwinput, 32, 1) >= UI_INPUT_EDIT)
93 fz_strlcpy(save_opts.opwd_utf8, opwinput.text, nelem(save_opts.opwd_utf8));
94 }
95}
96
97static void save_pdf_dialog(void)
98{
99 ui_input_init(&opwinput, "");
100 ui_input_init(&upwinput, "");
101
102 if (ui_save_file(save_filename, save_pdf_options))
103 {
104 ui.dialog = NULL;
105 if (save_filename[0] != 0)
106 {
107 if (save_opts.do_garbage)
108 save_opts.do_garbage = 2;
109 fz_try(ctx)
110 {
111 pdf_save_document(ctx, pdf, save_filename, &save_opts);
112 fz_strlcpy(filename, save_filename, PATH_MAX);
113 update_title();
114 }
115 fz_catch(ctx)
116 {
117 ui_show_warning_dialog("%s", fz_caught_message(ctx));
118 }
119 }
120 }
121}
122
123void do_save_pdf_file(void)
124{
125 if (pdf)
126 {
127 init_save_pdf_options();
128 ui_init_save_file(filename, pdf_filter);
129 ui.dialog = save_pdf_dialog;
130 }
131}
132
133static int rects_differ(fz_rect a, fz_rect b, float threshold)
134{
135 if (fz_abs(a.x0 - b.x0) > threshold) return 1;
136 if (fz_abs(a.y0 - b.y0) > threshold) return 1;
137 if (fz_abs(a.x1 - b.x1) > threshold) return 1;
138 if (fz_abs(a.y1 - b.y1) > threshold) return 1;
139 return 0;
140}
141
142static int points_differ(fz_point a, fz_point b, float threshold)
143{
144 if (fz_abs(a.x - b.x) > threshold) return 1;
145 if (fz_abs(a.y - b.y) > threshold) return 1;
146 return 0;
147}
148
149static const char *getuser(void)
150{
151 const char *u;
152 u = getenv("USER");
153 if (!u) u = getenv("USERNAME");
154 if (!u) u = "user";
155 return u;
156}
157
158static void new_annot(int type)
159{
160 selected_annot = pdf_create_annot(ctx, page, type);
161
162 pdf_set_annot_modification_date(ctx, selected_annot, time(NULL));
163 if (pdf_annot_has_author(ctx, selected_annot))
164 pdf_set_annot_author(ctx, selected_annot, getuser());
165
166 pdf_update_appearance(ctx, selected_annot);
167
168 switch (type)
169 {
170 case PDF_ANNOT_INK:
171 case PDF_ANNOT_POLYGON:
172 case PDF_ANNOT_POLY_LINE:
173 case PDF_ANNOT_HIGHLIGHT:
174 case PDF_ANNOT_UNDERLINE:
175 case PDF_ANNOT_STRIKE_OUT:
176 case PDF_ANNOT_SQUIGGLY:
177 case PDF_ANNOT_REDACT:
178 is_draw_mode = 1;
179 break;
180 }
181
182 render_page();
183}
184
185static void do_annotate_flags(void)
186{
187 char buf[4096];
188 int f = pdf_annot_flags(ctx, selected_annot);
189 fz_strlcpy(buf, "Flags:", sizeof buf);
190 if (f & PDF_ANNOT_IS_INVISIBLE) fz_strlcat(buf, " inv", sizeof buf);
191 if (f & PDF_ANNOT_IS_HIDDEN) fz_strlcat(buf, " hidden", sizeof buf);
192 if (f & PDF_ANNOT_IS_PRINT) fz_strlcat(buf, " print", sizeof buf);
193 if (f & PDF_ANNOT_IS_NO_ZOOM) fz_strlcat(buf, " nz", sizeof buf);
194 if (f & PDF_ANNOT_IS_NO_ROTATE) fz_strlcat(buf, " nr", sizeof buf);
195 if (f & PDF_ANNOT_IS_NO_VIEW) fz_strlcat(buf, " nv", sizeof buf);
196 if (f & PDF_ANNOT_IS_READ_ONLY) fz_strlcat(buf, " ro", sizeof buf);
197 if (f & PDF_ANNOT_IS_LOCKED) fz_strlcat(buf, " lock", sizeof buf);
198 if (f & PDF_ANNOT_IS_TOGGLE_NO_VIEW) fz_strlcat(buf, " tnv", sizeof buf);
199 if (f & PDF_ANNOT_IS_LOCKED_CONTENTS) fz_strlcat(buf, " lc", sizeof buf);
200 if (!f) fz_strlcat(buf, " none", sizeof buf);
201 ui_label("%s", buf);
202}
203
204static const char *color_names[] = {
205 "None",
206 "Aqua",
207 "Black",
208 "Blue",
209 "Fuchsia",
210 "Gray",
211 "Green",
212 "Lime",
213 "Maroon",
214 "Navy",
215 "Olive",
216 "Orange",
217 "Purple",
218 "Red",
219 "Silver",
220 "Teal",
221 "White",
222 "Yellow",
223};
224
225static unsigned int color_values[] = {
226 0x00000000, /* transparent */
227 0xff00ffff, /* aqua */
228 0xff000000, /* black */
229 0xff0000ff, /* blue */
230 0xffff00ff, /* fuchsia */
231 0xff808080, /* gray */
232 0xff008000, /* green */
233 0xff00ff00, /* lime */
234 0xff800000, /* maroon */
235 0xff000080, /* navy */
236 0xff808000, /* olive */
237 0xffffa500, /* orange */
238 0xff800080, /* purple */
239 0xffff0000, /* red */
240 0xffc0c0c0, /* silver */
241 0xff008080, /* teal */
242 0xffffffff, /* white */
243 0xffffff00, /* yellow */
244};
245
246static unsigned int hex_from_color(int n, float color[4])
247{
248 float rgb[4];
249 int r, g, b;
250 switch (n)
251 {
252 default:
253 return 0;
254 case 1:
255 r = color[0] * 255;
256 return 0xff000000 | (r<<16) | (r<<8) | r;
257 case 3:
258 r = color[0] * 255;
259 g = color[1] * 255;
260 b = color[2] * 255;
261 return 0xff000000 | (r<<16) | (g<<8) | b;
262 case 4:
263 fz_convert_color(ctx, fz_device_cmyk(ctx), color, fz_device_rgb(ctx), rgb, NULL, fz_default_color_params);
264 r = rgb[0] * 255;
265 g = rgb[1] * 255;
266 b = rgb[2] * 255;
267 return 0xff000000 | (r<<16) | (g<<8) | b;
268 }
269}
270
271static const char *name_from_hex(unsigned int hex)
272{
273 static char buf[10];
274 int i;
275 for (i = 0; i < nelem(color_names); ++i)
276 if (color_values[i] == hex)
277 return color_names[i];
278 fz_snprintf(buf, sizeof buf, "#%06x", hex & 0xffffff);
279 return buf;
280}
281
282static void do_annotate_color(char *label,
283 void (*get_color)(fz_context *ctx, pdf_annot *annot, int *n, float color[4]),
284 void (*set_color)(fz_context *ctx, pdf_annot *annot, int n, const float color[4]))
285{
286 float color[4];
287 int hex, choice, n;
288 get_color(ctx, selected_annot, &n, color);
289 ui_label("%s:", label);
290 choice = ui_select(label, name_from_hex(hex_from_color(n, color)), color_names, nelem(color_names));
291 if (choice != -1)
292 {
293 hex = color_values[choice];
294 if (hex == 0)
295 set_color(ctx, selected_annot, 0, color);
296 else
297 {
298 color[0] = ((hex>>16)&0xff) / 255.0f;
299 color[1] = ((hex>>8)&0xff) / 255.0f;
300 color[2] = ((hex)&0xff) / 255.0f;
301 set_color(ctx, selected_annot, 3, color);
302 }
303 }
304}
305
306static void do_annotate_author(void)
307{
308 if (pdf_annot_has_author(ctx, selected_annot))
309 {
310 const char *author = pdf_annot_author(ctx, selected_annot);
311 if (strlen(author) > 0)
312 ui_label("Author: %s", author);
313 }
314}
315
316static void do_annotate_date(void)
317{
318 time_t secs = pdf_annot_modification_date(ctx, selected_annot);
319 if (secs > 0)
320 {
321#ifdef _POSIX_SOURCE
322 struct tm tmbuf, *tm = gmtime_r(&secs, &tmbuf);
323#else
324 struct tm *tm = gmtime(&secs);
325#endif
326 char buf[100];
327 if (tm)
328 {
329 strftime(buf, sizeof buf, "%Y-%m-%d %H:%M UTC", tm);
330 ui_label("Date: %s", buf);
331 }
332 }
333}
334
335static void do_annotate_contents(void)
336{
337 static pdf_annot *last_annot = NULL;
338 static struct input input;
339 const char *contents;
340
341 if (selected_annot != last_annot)
342 {
343 last_annot = selected_annot;
344 contents = pdf_annot_contents(ctx, selected_annot);
345 ui_input_init(&input, contents);
346 }
347
348 ui_label("Contents:");
349 if (ui_input(&input, 0, 5) >= UI_INPUT_EDIT)
350 pdf_set_annot_contents(ctx, selected_annot, input.text);
351}
352
353static const char *file_attachment_icons[] = { "Graph", "Paperclip", "PushPin", "Tag" };
354static const char *sound_icons[] = { "Speaker", "Mic" };
355static const char *stamp_icons[] = {
356 "Approved", "AsIs", "Confidential", "Departmental", "Draft",
357 "Experimental", "Expired", "Final", "ForComment", "ForPublicRelease",
358 "NotApproved", "NotForPublicRelease", "Sold", "TopSecret" };
359static const char *text_icons[] = {
360 "Comment", "Help", "Insert", "Key", "NewParagraph", "Note", "Paragraph" };
361static const char *line_ending_styles[] = {
362 "None", "Square", "Circle", "Diamond", "OpenArrow", "ClosedArrow", "Butt",
363 "ROpenArrow", "RClosedArrow", "Slash" };
364static const char *quadding_names[] = { "Left", "Center", "Right" };
365static const char *font_names[] = { "Cour", "Helv", "TiRo", "Symb", "ZaDb", };
366
367static int should_edit_border(enum pdf_annot_type subtype)
368{
369 switch (subtype) {
370 default:
371 return 0;
372 case PDF_ANNOT_FREE_TEXT:
373 return 1;
374 case PDF_ANNOT_INK:
375 case PDF_ANNOT_LINE:
376 case PDF_ANNOT_SQUARE:
377 case PDF_ANNOT_CIRCLE:
378 case PDF_ANNOT_POLYGON:
379 case PDF_ANNOT_POLY_LINE:
380 return 1;
381 }
382}
383
384static int should_edit_color(enum pdf_annot_type subtype)
385{
386 switch (subtype) {
387 default:
388 return 0;
389 case PDF_ANNOT_STAMP:
390 case PDF_ANNOT_TEXT:
391 case PDF_ANNOT_FILE_ATTACHMENT:
392 case PDF_ANNOT_SOUND:
393 case PDF_ANNOT_CARET:
394 return 1;
395 case PDF_ANNOT_FREE_TEXT:
396 return 1;
397 case PDF_ANNOT_INK:
398 case PDF_ANNOT_LINE:
399 case PDF_ANNOT_SQUARE:
400 case PDF_ANNOT_CIRCLE:
401 case PDF_ANNOT_POLYGON:
402 case PDF_ANNOT_POLY_LINE:
403 return 1;
404 case PDF_ANNOT_HIGHLIGHT:
405 case PDF_ANNOT_UNDERLINE:
406 case PDF_ANNOT_STRIKE_OUT:
407 case PDF_ANNOT_SQUIGGLY:
408 return 1;
409 }
410}
411
412static int should_edit_icolor(enum pdf_annot_type subtype)
413{
414 switch (subtype) {
415 default:
416 return 0;
417 case PDF_ANNOT_LINE:
418 case PDF_ANNOT_SQUARE:
419 case PDF_ANNOT_CIRCLE:
420 return 1;
421 }
422}
423
424void do_annotate_panel(void)
425{
426 static struct list annot_list;
427 enum pdf_annot_type subtype;
428 pdf_annot *annot;
429 int n;
430
431 int has_redact = 0;
432 int was_dirty = pdf->dirty;
433
434 ui_layout(T, X, NW, 2, 2);
435
436 if (ui_popup("CreateAnnotPopup", "Create...", 1, 15))
437 {
438 if (ui_popup_item("Text")) new_annot(PDF_ANNOT_TEXT);
439 if (ui_popup_item("FreeText")) new_annot(PDF_ANNOT_FREE_TEXT);
440 if (ui_popup_item("Stamp")) new_annot(PDF_ANNOT_STAMP);
441 if (ui_popup_item("Caret")) new_annot(PDF_ANNOT_CARET);
442 if (ui_popup_item("Ink")) new_annot(PDF_ANNOT_INK);
443 if (ui_popup_item("Square")) new_annot(PDF_ANNOT_SQUARE);
444 if (ui_popup_item("Circle")) new_annot(PDF_ANNOT_CIRCLE);
445 if (ui_popup_item("Line")) new_annot(PDF_ANNOT_LINE);
446 if (ui_popup_item("Polygon")) new_annot(PDF_ANNOT_POLYGON);
447 if (ui_popup_item("PolyLine")) new_annot(PDF_ANNOT_POLY_LINE);
448 if (ui_popup_item("Highlight")) new_annot(PDF_ANNOT_HIGHLIGHT);
449 if (ui_popup_item("Underline")) new_annot(PDF_ANNOT_UNDERLINE);
450 if (ui_popup_item("StrikeOut")) new_annot(PDF_ANNOT_STRIKE_OUT);
451 if (ui_popup_item("Squiggly")) new_annot(PDF_ANNOT_SQUIGGLY);
452 if (ui_popup_item("Redact")) new_annot(PDF_ANNOT_REDACT);
453 ui_popup_end();
454 }
455
456 n = 0;
457 for (annot = pdf_first_annot(ctx, page); annot; annot = pdf_next_annot(ctx, annot))
458 ++n;
459
460 ui_list_begin(&annot_list, n, 0, ui.lineheight * 10 + 4);
461 for (annot = pdf_first_annot(ctx, page); annot; annot = pdf_next_annot(ctx, annot))
462 {
463 char buf[256];
464 int num = pdf_to_num(ctx, annot->obj);
465 subtype = pdf_annot_type(ctx, annot);
466 fz_snprintf(buf, sizeof buf, "%d: %s", num, pdf_string_from_annot_type(ctx, subtype));
467 if (ui_list_item(&annot_list, annot->obj, buf, selected_annot == annot))
468 selected_annot = annot;
469 if (subtype == PDF_ANNOT_REDACT)
470 has_redact = 1;
471 }
472 ui_list_end(&annot_list);
473
474 if (selected_annot && (subtype = pdf_annot_type(ctx, selected_annot)) != PDF_ANNOT_WIDGET)
475 {
476 fz_rect rect;
477 fz_irect irect;
478 int n, choice;
479 pdf_obj *obj;
480
481 if (ui_button("Delete"))
482 {
483 pdf_delete_annot(ctx, page, selected_annot);
484 selected_annot = NULL;
485 render_page();
486 return;
487 }
488
489 ui_spacer();
490
491 /* common annotation properties */
492
493 rect = pdf_annot_rect(ctx, selected_annot);
494 irect = fz_irect_from_rect(rect);
495 ui_label("Rect: %d %d %d %d", irect.x0, irect.y0, irect.x1, irect.y1);
496
497 do_annotate_flags();
498 do_annotate_author();
499 do_annotate_date();
500
501 obj = pdf_dict_get(ctx, selected_annot->obj, PDF_NAME(Popup));
502 if (obj)
503 ui_label("Popup: %d 0 R", pdf_to_num(ctx, obj));
504
505 ui_spacer();
506
507 do_annotate_contents();
508
509 ui_spacer();
510
511 if (subtype == PDF_ANNOT_FREE_TEXT)
512 {
513 int font_choice, color_choice, size_changed;
514 int q = pdf_annot_quadding(ctx, selected_annot);
515 const char *text_font;
516 static float text_size_f, text_color[3];
517 static int text_size;
518 ui_label("Text Alignment:");
519 choice = ui_select("Q", quadding_names[q], quadding_names, nelem(quadding_names));
520 if (choice != -1)
521 pdf_set_annot_quadding(ctx, selected_annot, choice);
522
523 pdf_annot_default_appearance(ctx, selected_annot, &text_font, &text_size_f, text_color);
524 text_size = text_size_f;
525
526 ui_label("Text Font:");
527 font_choice = ui_select("DA/Font", text_font, font_names, nelem(font_names));
528 ui_label("Text Size: %d", text_size);
529 size_changed = ui_slider(&text_size, 8, 36, 256);
530 ui_label("Text Color:");
531 color_choice = ui_select("DA/Color", name_from_hex(hex_from_color(3, text_color)), color_names+1, nelem(color_names)-1);
532 if (font_choice != -1 || color_choice != -1 || size_changed)
533 {
534 if (font_choice != -1)
535 text_font = font_names[font_choice];
536 if (color_choice != -1)
537 {
538 text_color[0] = ((color_values[color_choice+1]>>16) & 0xff) / 255.0f;
539 text_color[1] = ((color_values[color_choice+1]>>8) & 0xff) / 255.0f;
540 text_color[2] = ((color_values[color_choice+1]) & 0xff) / 255.0f;
541 }
542 pdf_set_annot_default_appearance(ctx, selected_annot, text_font, text_size, text_color);
543 }
544 ui_spacer();
545 }
546
547 if (subtype == PDF_ANNOT_LINE)
548 {
549 enum pdf_line_ending s, e;
550 int s_choice, e_choice;
551
552 pdf_annot_line_ending_styles(ctx, selected_annot, &s, &e);
553
554 ui_label("Line Start:");
555 s_choice = ui_select("LE0", line_ending_styles[s], line_ending_styles, nelem(line_ending_styles));
556
557 ui_label("Line End:");
558 e_choice = ui_select("LE1", line_ending_styles[e], line_ending_styles, nelem(line_ending_styles));
559
560 if (s_choice != -1 || e_choice != -1)
561 {
562 if (s_choice != -1) s = s_choice;
563 if (e_choice != -1) e = e_choice;
564 pdf_set_annot_line_ending_styles(ctx, selected_annot, s, e);
565 }
566 }
567
568 if (pdf_annot_has_icon_name(ctx, selected_annot))
569 {
570 const char *name = pdf_annot_icon_name(ctx, selected_annot);
571 ui_label("Icon:");
572 switch (pdf_annot_type(ctx, selected_annot))
573 {
574 default:
575 break;
576 case PDF_ANNOT_TEXT:
577 choice = ui_select("Icon", name, text_icons, nelem(text_icons));
578 if (choice != -1)
579 pdf_set_annot_icon_name(ctx, selected_annot, text_icons[choice]);
580 break;
581 case PDF_ANNOT_FILE_ATTACHMENT:
582 choice = ui_select("Icon", name, file_attachment_icons, nelem(file_attachment_icons));
583 if (choice != -1)
584 pdf_set_annot_icon_name(ctx, selected_annot, file_attachment_icons[choice]);
585 break;
586 case PDF_ANNOT_SOUND:
587 choice = ui_select("Icon", name, sound_icons, nelem(sound_icons));
588 if (choice != -1)
589 pdf_set_annot_icon_name(ctx, selected_annot, sound_icons[choice]);
590 break;
591 case PDF_ANNOT_STAMP:
592 choice = ui_select("Icon", name, stamp_icons, nelem(stamp_icons));
593 if (choice != -1)
594 pdf_set_annot_icon_name(ctx, selected_annot, stamp_icons[choice]);
595 break;
596 }
597 }
598
599 if (should_edit_border(subtype))
600 {
601 static int border;
602 border = pdf_annot_border(ctx, selected_annot);
603 ui_label("Border: %d", border);
604 if (ui_slider(&border, 0, 12, 100))
605 pdf_set_annot_border(ctx, selected_annot, border);
606 }
607
608 if (should_edit_color(subtype))
609 do_annotate_color("Color", pdf_annot_color, pdf_set_annot_color);
610 if (should_edit_icolor(subtype))
611 do_annotate_color("Interior Color", pdf_annot_interior_color, pdf_set_annot_interior_color);
612
613 if (subtype == PDF_ANNOT_HIGHLIGHT)
614 {
615 static int opacity;
616 opacity = pdf_annot_opacity(ctx, selected_annot) * 255;
617 ui_label("Opacity:");
618 if (ui_slider(&opacity, 0, 255, 256))
619 pdf_set_annot_opacity(ctx, selected_annot, opacity / 255.0f);
620 }
621
622 if (pdf_annot_has_open(ctx, selected_annot))
623 {
624 int is_open = pdf_annot_is_open(ctx, selected_annot);
625 int start_is_open = is_open;
626 ui_checkbox("Open", &is_open);
627 if (start_is_open != is_open)
628 pdf_set_annot_is_open(ctx, selected_annot, is_open);
629 }
630
631 ui_spacer();
632
633 if (pdf_annot_has_quad_points(ctx, selected_annot))
634 {
635 if (is_draw_mode)
636 {
637 n = pdf_annot_quad_point_count(ctx, selected_annot);
638 ui_label("QuadPoints: %d", n);
639 if (ui_button("Clear"))
640 pdf_clear_annot_quad_points(ctx, selected_annot);
641 if (ui_button("Done"))
642 is_draw_mode = 0;
643 }
644 else
645 {
646 if (ui_button("Edit"))
647 is_draw_mode = 1;
648 }
649 }
650
651 if (pdf_annot_has_vertices(ctx, selected_annot))
652 {
653 if (is_draw_mode)
654 {
655 n = pdf_annot_vertex_count(ctx, selected_annot);
656 ui_label("Vertices: %d", n);
657 if (ui_button("Clear"))
658 pdf_clear_annot_vertices(ctx, selected_annot);
659 if (ui_button("Done"))
660 is_draw_mode = 0;
661 }
662 else
663 {
664 if (ui_button("Edit"))
665 is_draw_mode = 1;
666 }
667 }
668
669 if (pdf_annot_has_ink_list(ctx, selected_annot))
670 {
671 if (is_draw_mode)
672 {
673 n = pdf_annot_ink_list_count(ctx, selected_annot);
674 ui_label("InkList: %d strokes", n);
675 if (ui_button("Clear"))
676 pdf_clear_annot_ink_list(ctx, selected_annot);
677 if (ui_button("Done"))
678 is_draw_mode = 0;
679 }
680 else
681 {
682 if (ui_button("Edit"))
683 is_draw_mode = 1;
684 }
685 }
686
687 if (selected_annot && selected_annot->needs_new_ap)
688 {
689 pdf_update_appearance(ctx, selected_annot);
690 render_page();
691 }
692 }
693
694 ui_layout(B, X, NW, 2, 2);
695
696 if (ui_button("Save PDF..."))
697 do_save_pdf_file();
698
699 if (has_redact)
700 {
701 if (ui_button("Redact"))
702 {
703 selected_annot = NULL;
704 pdf_redact_page(ctx, pdf, page, NULL);
705 load_page();
706 render_page();
707 }
708 }
709
710 if (was_dirty != pdf->dirty)
711 update_title();
712}
713
714static void do_edit_icon(fz_irect canvas_area, fz_irect area, fz_rect *rect)
715{
716 static fz_point start_pt;
717 static float w, h;
718 static int moving = 0;
719
720 if (ui_mouse_inside(canvas_area) && ui_mouse_inside(area))
721 {
722 ui.hot = selected_annot;
723 if (!ui.active && ui.down)
724 {
725 ui.active = selected_annot;
726 start_pt.x = rect->x0;
727 start_pt.y = rect->y0;
728 w = rect->x1 - rect->x0;
729 h = rect->y1 - rect->y0;
730 moving = 1;
731 }
732 }
733
734 if (ui.active == selected_annot && moving)
735 {
736 rect->x0 = start_pt.x + (ui.x - ui.down_x);
737 rect->y0 = start_pt.y + (ui.y - ui.down_y);
738
739 /* Clamp to fit on page */
740 rect->x0 = fz_clamp(rect->x0, view_page_area.x0, view_page_area.x1-w);
741 rect->y0 = fz_clamp(rect->y0, view_page_area.y0, view_page_area.y1-h);
742 rect->x1 = rect->x0 + w;
743 rect->y1 = rect->y0 + h;
744
745 /* cancel on right click */
746 if (ui.right)
747 moving = 0;
748
749 /* Commit movement on mouse-up */
750 if (!ui.down)
751 {
752 fz_point dp = { rect->x0 - start_pt.x, rect->y0 - start_pt.y };
753 moving = 0;
754 if (fz_abs(dp.x) > 0.1f || fz_abs(dp.y) > 0.1f)
755 {
756 fz_rect trect = pdf_annot_rect(ctx, selected_annot);
757 dp = fz_transform_vector(dp, view_page_inv_ctm);
758 trect.x0 += dp.x; trect.x1 += dp.x;
759 trect.y0 += dp.y; trect.y1 += dp.y;
760 pdf_set_annot_rect(ctx, selected_annot, trect);
761 }
762 }
763 }
764}
765
766static void do_edit_rect(fz_irect canvas_area, fz_irect area, fz_rect *rect)
767{
768 enum {
769 ER_N=1, ER_E=2, ER_S=4, ER_W=8,
770 ER_NONE = 0,
771 ER_NW = ER_N|ER_W,
772 ER_NE = ER_N|ER_E,
773 ER_SW = ER_S|ER_W,
774 ER_SE = ER_S|ER_E,
775 ER_MOVE = ER_N|ER_E|ER_S|ER_W,
776 };
777 static fz_rect start_rect;
778 static int state = ER_NONE;
779
780 area = fz_expand_irect(area, 5);
781 if (ui_mouse_inside(canvas_area) && ui_mouse_inside(area))
782 {
783 ui.hot = selected_annot;
784 if (!ui.active && ui.down)
785 {
786 ui.active = selected_annot;
787 start_rect = *rect;
788 state = ER_NONE;
789 if (ui.x <= area.x0 + 10) state |= ER_W;
790 if (ui.x >= area.x1 - 10) state |= ER_E;
791 if (ui.y <= area.y0 + 10) state |= ER_N;
792 if (ui.y >= area.y1 - 10) state |= ER_S;
793 if (!state) state = ER_MOVE;
794 }
795 }
796
797 if (ui.active == selected_annot && state != ER_NONE)
798 {
799 *rect = start_rect;
800 if (state & ER_W) rect->x0 += (ui.x - ui.down_x);
801 if (state & ER_E) rect->x1 += (ui.x - ui.down_x);
802 if (state & ER_N) rect->y0 += (ui.y - ui.down_y);
803 if (state & ER_S) rect->y1 += (ui.y - ui.down_y);
804 if (rect->x1 < rect->x0) { float t = rect->x1; rect->x1 = rect->x0; rect->x0 = t; }
805 if (rect->y1 < rect->y0) { float t = rect->y1; rect->y1 = rect->y0; rect->y0 = t; }
806 if (rect->x1 < rect->x0 + 10) rect->x1 = rect->x0 + 10;
807 if (rect->y1 < rect->y0 + 10) rect->y1 = rect->y0 + 10;
808
809 /* cancel on right click */
810 if (ui.right)
811 state = ER_NONE;
812
813 /* commit on mouse-up */
814 if (!ui.down)
815 {
816 state = ER_NONE;
817 if (rects_differ(start_rect, *rect, 1))
818 {
819 fz_rect trect = fz_transform_rect(*rect, view_page_inv_ctm);
820 pdf_set_annot_rect(ctx, selected_annot, trect);
821 }
822 }
823 }
824}
825
826static void do_edit_line(fz_irect canvas_area, fz_irect area, fz_rect *rect)
827{
828 enum { EL_NONE, EL_A=1, EL_B=2, EL_MOVE=EL_A|EL_B };
829 static fz_point start_a, start_b;
830 static int state = EL_NONE;
831 fz_irect a_grab, b_grab;
832 fz_point a, b;
833 float lw;
834
835 area = fz_expand_irect(area, 5);
836 if (ui_mouse_inside(canvas_area) && ui_mouse_inside(area))
837 {
838 ui.hot = selected_annot;
839 if (!ui.active && ui.down)
840 {
841 ui.active = selected_annot;
842 pdf_annot_line(ctx, selected_annot, &start_a, &start_b);
843 start_a = fz_transform_point(start_a, view_page_ctm);
844 start_b = fz_transform_point(start_b, view_page_ctm);
845 a_grab = fz_make_irect(start_a.x, start_a.y, start_a.x, start_a.y);
846 b_grab = fz_make_irect(start_b.x, start_b.y, start_b.x, start_b.y);
847 a_grab = fz_expand_irect(a_grab, 10);
848 b_grab = fz_expand_irect(b_grab, 10);
849 state = EL_NONE;
850 if (ui_mouse_inside(a_grab)) state |= EL_A;
851 if (ui_mouse_inside(b_grab)) state |= EL_B;
852 if (!state) state = EL_MOVE;
853 }
854 }
855
856 if (ui.active == selected_annot && state != 0)
857 {
858 a = start_a;
859 b = start_b;
860 if (state & EL_A) { a.x += (ui.x - ui.down_x); a.y += (ui.y - ui.down_y); }
861 if (state & EL_B) { b.x += (ui.x - ui.down_x); b.y += (ui.y - ui.down_y); }
862
863 glBegin(GL_LINES);
864 glColor4f(1, 0, 0, 1);
865 glVertex2f(a.x, a.y);
866 glVertex2f(b.x, b.y);
867 glEnd();
868
869 rect->x0 = fz_min(a.x, b.x);
870 rect->y0 = fz_min(a.y, b.y);
871 rect->x1 = fz_max(a.x, b.x);
872 rect->y1 = fz_max(a.y, b.y);
873 lw = pdf_annot_border(ctx, selected_annot);
874 *rect = fz_expand_rect(*rect, fz_matrix_expansion(view_page_ctm) * lw);
875
876 /* cancel on right click */
877 if (ui.right)
878 state = EL_NONE;
879
880 /* commit on mouse-up */
881 if (!ui.down)
882 {
883 state = EL_NONE;
884 if (points_differ(start_a, a, 1) || points_differ(start_b, b, 1))
885 {
886 a = fz_transform_point(a, view_page_inv_ctm);
887 b = fz_transform_point(b, view_page_inv_ctm);
888 pdf_set_annot_line(ctx, selected_annot, a, b);
889 }
890 }
891 }
892}
893
894static void do_edit_polygon(fz_irect canvas_area, int close)
895{
896 static int drawing = 0;
897 fz_point a, p;
898
899 if (ui_mouse_inside(canvas_area) && ui_mouse_inside(view_page_area))
900 {
901 ui.hot = selected_annot;
902 if (!ui.active || ui.active == selected_annot)
903 ui.cursor = GLUT_CURSOR_CROSSHAIR;
904 if (!ui.active && ui.down)
905 {
906 ui.active = selected_annot;
907 drawing = 1;
908 }
909 }
910
911 if (ui.active == selected_annot && drawing)
912 {
913 int n = pdf_annot_vertex_count(ctx, selected_annot);
914 if (n > 0)
915 {
916 p = pdf_annot_vertex(ctx, selected_annot, n-1);
917 p = fz_transform_point(p, view_page_ctm);
918 if (close)
919 {
920 a = pdf_annot_vertex(ctx, selected_annot, 0);
921 a = fz_transform_point(a, view_page_ctm);
922 }
923 glBegin(GL_LINE_STRIP);
924 glColor4f(1, 0, 0, 1);
925 glVertex2f(p.x, p.y);
926 glVertex2f(ui.x, ui.y);
927 if (close)
928 glVertex2f(a.x, a.y);
929 glEnd();
930 }
931
932 glColor4f(1, 0, 0, 1);
933 glPointSize(4);
934 glBegin(GL_POINTS);
935 glVertex2f(ui.x, ui.y);
936 glEnd();
937
938 /* cancel on right click */
939 if (ui.right)
940 drawing = 0;
941
942 /* commit point on mouse-up */
943 if (!ui.down)
944 {
945 fz_point p = fz_transform_point_xy(ui.x, ui.y, view_page_inv_ctm);
946 pdf_add_annot_vertex(ctx, selected_annot, p);
947 drawing = 0;
948 }
949 }
950}
951
952static void do_edit_ink(fz_irect canvas_area)
953{
954 static int drawing = 0;
955 static fz_point p[1000];
956 static int n, last_x, last_y;
957 int i;
958
959 if (ui_mouse_inside(canvas_area) && ui_mouse_inside(view_page_area))
960 {
961 ui.hot = selected_annot;
962 if (!ui.active || ui.active == selected_annot)
963 ui.cursor = GLUT_CURSOR_CROSSHAIR;
964 if (!ui.active && ui.down)
965 {
966 ui.active = selected_annot;
967 drawing = 1;
968 n = 0;
969 last_x = INT_MIN;
970 last_y = INT_MIN;
971 }
972 }
973
974 if (ui.active == selected_annot && drawing)
975 {
976 if (n < nelem(p) && (ui.x != last_x || ui.y != last_y))
977 {
978 p[n].x = fz_clamp(ui.x, view_page_area.x0, view_page_area.x1);
979 p[n].y = fz_clamp(ui.y, view_page_area.y0, view_page_area.y1);
980 ++n;
981 }
982 last_x = ui.x;
983 last_y = ui.y;
984
985 if (n > 1)
986 {
987 glBegin(GL_LINE_STRIP);
988 glColor4f(1, 0, 0, 1);
989 for (i = 0; i < n; ++i)
990 glVertex2f(p[i].x, p[i].y);
991 glEnd();
992 }
993
994 /* cancel on right click */
995 if (ui.right)
996 {
997 drawing = 0;
998 n = 0;
999 }
1000
1001 /* commit stroke on mouse-up */
1002 if (!ui.down)
1003 {
1004 if (n > 1)
1005 {
1006 for (i = 0; i < n; ++i)
1007 p[i] = fz_transform_point(p[i], view_page_inv_ctm);
1008 pdf_add_annot_ink_list(ctx, selected_annot, n, p);
1009 }
1010 drawing = 0;
1011 n = 0;
1012 }
1013 }
1014}
1015
1016static void do_edit_quad_points(void)
1017{
1018 static fz_point pt = { 0, 0 };
1019 static int marking = 0;
1020 fz_quad hits[1000];
1021 int i, n;
1022
1023 if (ui_mouse_inside(view_page_area))
1024 {
1025 ui.hot = selected_annot;
1026 if (!ui.active || ui.active == selected_annot)
1027 ui.cursor = GLUT_CURSOR_TEXT;
1028 if (!ui.active && ui.down)
1029 {
1030 ui.active = selected_annot;
1031 marking = 1;
1032 pt.x = ui.x;
1033 pt.y = ui.y;
1034 }
1035 }
1036
1037 if (ui.active == selected_annot && marking)
1038 {
1039 fz_point page_a = { pt.x, pt.y };
1040 fz_point page_b = { ui.x, ui.y };
1041
1042 page_a = fz_transform_point(page_a, view_page_inv_ctm);
1043 page_b = fz_transform_point(page_b, view_page_inv_ctm);
1044
1045 n = fz_highlight_selection(ctx, page_text, page_a, page_b, hits, nelem(hits));
1046
1047 glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO); /* invert destination color */
1048 glEnable(GL_BLEND);
1049
1050 glColor4f(1, 1, 1, 1);
1051 glBegin(GL_QUADS);
1052 for (i = 0; i < n; ++i)
1053 {
1054 fz_quad thit = fz_transform_quad(hits[i], view_page_ctm);
1055 glVertex2f(thit.ul.x, thit.ul.y);
1056 glVertex2f(thit.ur.x, thit.ur.y);
1057 glVertex2f(thit.lr.x, thit.lr.y);
1058 glVertex2f(thit.ll.x, thit.ll.y);
1059 }
1060 glEnd();
1061
1062 glDisable(GL_BLEND);
1063
1064 /* cancel on right click */
1065 if (ui.right)
1066 marking = 0;
1067
1068 if (!ui.down)
1069 {
1070 if (n > 0)
1071 {
1072 pdf_clear_annot_quad_points(ctx, selected_annot);
1073 for (i = 0; i < n; ++i)
1074 pdf_add_annot_quad_point(ctx, selected_annot, hits[i]);
1075 }
1076 marking = 0;
1077 }
1078 }
1079}
1080
1081void do_annotate_canvas(fz_irect canvas_area)
1082{
1083 fz_rect bounds;
1084 fz_irect area;
1085 pdf_annot *annot;
1086 const void *nothing = ui.hot;
1087
1088 int was_dirty = pdf->dirty;
1089
1090 for (annot = pdf_first_annot(ctx, page); annot; annot = pdf_next_annot(ctx, annot))
1091 {
1092 enum pdf_annot_type subtype = pdf_annot_type(ctx, annot);
1093
1094 bounds = pdf_bound_annot(ctx, annot);
1095 bounds = fz_transform_rect(bounds, view_page_ctm);
1096 area = fz_irect_from_rect(bounds);
1097
1098 if (ui_mouse_inside(canvas_area) && ui_mouse_inside(area))
1099 {
1100 ui.hot = annot;
1101 if (!ui.active && ui.down)
1102 {
1103 if (selected_annot != annot)
1104 {
1105 if (!selected_annot && !showannotate)
1106 toggle_annotate();
1107 ui.active = annot;
1108 selected_annot = annot;
1109 }
1110 }
1111 }
1112
1113 if (annot == selected_annot)
1114 {
1115 switch (subtype)
1116 {
1117 default:
1118 break;
1119
1120 /* Popup window */
1121 case PDF_ANNOT_POPUP:
1122 do_edit_rect(canvas_area, area, &bounds);
1123 break;
1124
1125 /* Icons */
1126 case PDF_ANNOT_TEXT:
1127 case PDF_ANNOT_CARET:
1128 case PDF_ANNOT_FILE_ATTACHMENT:
1129 case PDF_ANNOT_SOUND:
1130 do_edit_icon(canvas_area, area, &bounds);
1131 break;
1132
1133 case PDF_ANNOT_STAMP:
1134 do_edit_rect(canvas_area, area, &bounds);
1135 break;
1136
1137 case PDF_ANNOT_FREE_TEXT:
1138 do_edit_rect(canvas_area, area, &bounds);
1139 break;
1140
1141 /* Drawings */
1142 case PDF_ANNOT_LINE:
1143 do_edit_line(canvas_area, area, &bounds);
1144 break;
1145 case PDF_ANNOT_CIRCLE:
1146 case PDF_ANNOT_SQUARE:
1147 do_edit_rect(canvas_area, area, &bounds);
1148 break;
1149 case PDF_ANNOT_POLYGON:
1150 if (is_draw_mode)
1151 do_edit_polygon(canvas_area, 1);
1152 break;
1153 case PDF_ANNOT_POLY_LINE:
1154 if (is_draw_mode)
1155 do_edit_polygon(canvas_area, 0);
1156 break;
1157
1158 case PDF_ANNOT_INK:
1159 if (is_draw_mode)
1160 do_edit_ink(canvas_area);
1161 break;
1162
1163 case PDF_ANNOT_HIGHLIGHT:
1164 case PDF_ANNOT_UNDERLINE:
1165 case PDF_ANNOT_STRIKE_OUT:
1166 case PDF_ANNOT_SQUIGGLY:
1167 case PDF_ANNOT_REDACT:
1168 if (is_draw_mode)
1169 do_edit_quad_points();
1170 break;
1171 }
1172
1173 glLineStipple(1, 0xAAAA);
1174 glEnable(GL_LINE_STIPPLE);
1175 glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
1176 glEnable(GL_BLEND);
1177 glColor4f(1, 1, 1, 1);
1178 glBegin(GL_LINE_LOOP);
1179 area = fz_irect_from_rect(bounds);
1180 glVertex2f(area.x0-0.5f, area.y0-0.5f);
1181 glVertex2f(area.x1+0.5f, area.y0-0.5f);
1182 glVertex2f(area.x1+0.5f, area.y1+0.5f);
1183 glVertex2f(area.x0-0.5f, area.y1+0.5f);
1184 glEnd();
1185 glDisable(GL_BLEND);
1186 glDisable(GL_LINE_STIPPLE);
1187
1188 if (annot->needs_new_ap)
1189 {
1190 pdf_update_appearance(ctx, annot);
1191 render_page();
1192 }
1193 }
1194 }
1195
1196 if (ui_mouse_inside(canvas_area) && ui.down)
1197 {
1198 if (!ui.active && ui.hot == nothing)
1199 selected_annot = NULL;
1200 }
1201
1202 if (ui.right)
1203 is_draw_mode = 0;
1204
1205 if (was_dirty != pdf->dirty)
1206 update_title();
1207}
1208