summaryrefslogtreecommitdiff
path: root/platform/gl/gl-annotate.c
diff options
context:
space:
mode:
authorTor Andersson <tor.andersson@artifex.com>2018-02-21 22:00:10 +0100
committerRobin Watts <robin.watts@artifex.com>2018-06-22 16:48:46 +0100
commit41050d282409f86f425ac4a157fc07d889e4b54d (patch)
treeff0611853ff17214a3c6e3e472b7ee2dfb425107 /platform/gl/gl-annotate.c
parentbde703f83b83232f830426b45957b10e4e44043d (diff)
downloadmupdf-41050d282409f86f425ac4a157fc07d889e4b54d.tar.xz
gl: Add annotation editor.
* Select with right mouse, edit with left mouse. * Clamp movement to page area. * Integrate page scroll offset into matrices. * Edit Ink annotations. * Initialise important annotation properties when creating them. * Deselect annotation when pressing ESC. * Add polygon/polyline annotation editing. * Tweak polygon editing. * Edit square/circle annotations. * Cancel canvas edits on right click. * Expand selectable areas and grab zones for rects. * Edit line annotations. * Edit caret annotations. * Return true when value has changed (checkbox and slider). * Add popup menu widget: Menu items are drawn at ui_end to overlay other widgets properly. * Use popup menu to set icons. * Fix text selection translation offset. * Edit quad point annotations. * Set user and modification date when creating new annotations. * Add select widget. * Edit line endings. * Edit highlight opacity. * Create properly sized stamp annotations. * Edit FreeText annotations. * Trigger list selection on mouse-up instead of mouse-down. * Use index in ui_select. * Edit Freetext quadding. * Fix windows build issues. * Use 'const void *' for ids. * Add file dialog to choose file when none given on command line. * Add save file dialog. * Add pdfwrite options to save dialog. * Add error dialog instead of dying silently on exceptions. * Add password dialog. * Add warning dialog that does not exit the program. * Show in title bar when document is modified. * Separate motion and passive motion callbacks. * Add /Volumes 'disk' for MacOS X mount points. * Tweak input focus/blur handling. * Use popup menu to create annotations instead of big list of buttons. * Update appearance after canvas edits too. * Release old grab before checking for new grab and taking focus. * Set cursor shape depending on hot item. * Draw prettier widgets. * Use integers for slider to allow snapping to values. * Add 'ui.gridsize' to ease layout of buttons and text fields. * Tweak file dialog layout. * Bevels around lists and scroll bars. * Only add new points to the ink list when drawing. * Use named color constants instead of hardcoding color values. * Adjust layout and which properties to edit for each annotation. * Use a panel for search field. * Add splitter, dialogs, and panel padding. * Popup menus above the button if they don't fit below it. * Use triangle strip to draw check mark.
Diffstat (limited to 'platform/gl/gl-annotate.c')
-rw-r--r--platform/gl/gl-annotate.c1123
1 files changed, 1123 insertions, 0 deletions
diff --git a/platform/gl/gl-annotate.c b/platform/gl/gl-annotate.c
new file mode 100644
index 00000000..f23de5ba
--- /dev/null
+++ b/platform/gl/gl-annotate.c
@@ -0,0 +1,1123 @@
+#include "gl-app.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <limits.h>
+
+#ifndef PATH_MAX
+#define PATH_MAX 2048
+#endif
+
+static char save_filename[PATH_MAX];
+static pdf_write_options save_opts;
+
+static int pdf_filter(const char *fn)
+{
+ const char *suffix = strrchr(fn, '.');
+ if (suffix && !fz_strcasecmp(suffix, ".pdf"))
+ return 1;
+ return 0;
+}
+
+static void init_save_pdf_options(void)
+{
+ memset(&save_opts, 0, sizeof save_opts);
+ save_opts.do_garbage = 1;
+}
+
+static void save_pdf_options(void)
+{
+ ui_layout(T, X, NW, 2, 2);
+ ui_label("PDF write options:");
+ ui_layout(T, X, NW, 4, 2);
+ ui_checkbox("Incremental", &save_opts.do_incremental);
+ ui_checkbox("Pretty-print", &save_opts.do_pretty);
+ ui_checkbox("Ascii", &save_opts.do_ascii);
+ ui_checkbox("Compress", &save_opts.do_compress);
+ ui_checkbox("Compress images", &save_opts.do_compress_images);
+ ui_checkbox("Compress fonts", &save_opts.do_compress_fonts);
+ ui_checkbox("Decompress", &save_opts.do_decompress);
+ ui_checkbox("Garbage collect", &save_opts.do_garbage);
+ ui_checkbox("Linearize", &save_opts.do_linear);
+ ui_checkbox("Clean syntax", &save_opts.do_clean);
+ ui_checkbox("Sanitize syntax", &save_opts.do_sanitize);
+}
+
+static void save_pdf_dialog(void)
+{
+ if (ui_save_file(save_filename, save_pdf_options))
+ {
+ ui.dialog = NULL;
+ if (save_filename[0] != 0)
+ {
+ if (save_opts.do_garbage)
+ save_opts.do_garbage = 2;
+ fz_try(ctx)
+ {
+ pdf_save_document(ctx, pdf, save_filename, &save_opts);
+ fz_strlcpy(filename, save_filename, PATH_MAX);
+ update_title();
+ }
+ fz_catch(ctx)
+ {
+ ui_show_warning_dialog(fz_caught_message(ctx));
+ }
+ }
+ }
+}
+
+static int rects_differ(const fz_rect *a, const fz_rect *b, float threshold)
+{
+ if (fz_abs(a->x0 - b->x0) > threshold) return 1;
+ if (fz_abs(a->y0 - b->y0) > threshold) return 1;
+ if (fz_abs(a->x1 - b->x1) > threshold) return 1;
+ if (fz_abs(a->y1 - b->y1) > threshold) return 1;
+ return 0;
+}
+
+static int points_differ(const fz_point *a, const fz_point *b, float threshold)
+{
+ if (fz_abs(a->x - b->x) > threshold) return 1;
+ if (fz_abs(a->y - b->y) > threshold) return 1;
+ return 0;
+}
+
+static const char *getuser(void)
+{
+ const char *u;
+ u = getenv("USER");
+ if (!u) u = getenv("USERNAME");
+ if (!u) u = "user";
+ return u;
+}
+
+static void new_annot(int type)
+{
+ static const float black[3] = { 0, 0, 0 };
+ static const float red[3] = { 1, 0, 0 };
+ static const float green[3] = { 0, 1, 0 };
+ static const float blue[3] = { 0, 0, 1 };
+ static const float yellow[3] = { 1, 1, 0 };
+ static const float magenta[3] = { 1, 0, 1 };
+
+ selected_annot = pdf_create_annot(ctx, page, type);
+
+ pdf_set_annot_modification_date(ctx, selected_annot, time(NULL));
+ if (pdf_annot_has_author(ctx, selected_annot))
+ pdf_set_annot_author(ctx, selected_annot, getuser());
+
+ switch (type)
+ {
+ case PDF_ANNOT_TEXT:
+ case PDF_ANNOT_FILE_ATTACHMENT:
+ case PDF_ANNOT_SOUND:
+ {
+ fz_rect icon_rect = { 12, 12, 12+20, 12+20 };
+ pdf_set_annot_rect(ctx, selected_annot, &icon_rect);
+ pdf_set_annot_color(ctx, selected_annot, 3, yellow);
+ }
+ break;
+
+ case PDF_ANNOT_FREE_TEXT:
+ {
+ fz_rect text_rect = { 12, 12, 12+200, 12+100 };
+ pdf_set_annot_rect(ctx, selected_annot, &text_rect);
+ pdf_set_annot_border(ctx, selected_annot, 0);
+ pdf_set_annot_default_appearance(ctx, selected_annot, "Helv", 12, black);
+ }
+ break;
+
+ case PDF_ANNOT_STAMP:
+ {
+ fz_rect stamp_rect = { 12, 12, 12+190, 12+50 };
+ pdf_set_annot_rect(ctx, selected_annot, &stamp_rect);
+ pdf_set_annot_color(ctx, selected_annot, 3, red);
+ }
+ break;
+
+ case PDF_ANNOT_CARET:
+ {
+ fz_rect caret_rect = { 12, 12, 12+18, 12+15 };
+ pdf_set_annot_rect(ctx, selected_annot, &caret_rect);
+ pdf_set_annot_color(ctx, selected_annot, 3, blue);
+ }
+ break;
+
+ case PDF_ANNOT_LINE:
+ {
+ fz_point a = { 12, 12 }, b = { 12 + 100, 12 + 50 };
+ pdf_set_annot_line(ctx, selected_annot, a, b);
+ pdf_set_annot_border(ctx, selected_annot, 1);
+ pdf_set_annot_color(ctx, selected_annot, 3, red);
+ }
+ break;
+
+ case PDF_ANNOT_SQUARE:
+ case PDF_ANNOT_CIRCLE:
+ {
+ fz_rect shape_rect = { 12, 12, 12+100, 12+50 };
+ pdf_set_annot_rect(ctx, selected_annot, &shape_rect);
+ pdf_set_annot_border(ctx, selected_annot, 1);
+ pdf_set_annot_color(ctx, selected_annot, 3, red);
+ }
+ break;
+
+ case PDF_ANNOT_POLYGON:
+ case PDF_ANNOT_POLY_LINE:
+ case PDF_ANNOT_INK:
+ pdf_set_annot_border(ctx, selected_annot, 1);
+ pdf_set_annot_color(ctx, selected_annot, 3, red);
+ break;
+
+ case PDF_ANNOT_HIGHLIGHT:
+ pdf_set_annot_color(ctx, selected_annot, 3, yellow);
+ break;
+ case PDF_ANNOT_UNDERLINE:
+ pdf_set_annot_color(ctx, selected_annot, 3, green);
+ break;
+ case PDF_ANNOT_STRIKE_OUT:
+ pdf_set_annot_color(ctx, selected_annot, 3, red);
+ break;
+ case PDF_ANNOT_SQUIGGLY:
+ pdf_set_annot_color(ctx, selected_annot, 3, magenta);
+ break;
+ }
+
+ pdf_update_appearance(ctx, selected_annot);
+ render_page();
+}
+
+static void do_annotate_flags(void)
+{
+ char buf[4096];
+ int f = pdf_annot_flags(ctx, selected_annot);
+ fz_strlcpy(buf, "Flags:", sizeof buf);
+ if (f & PDF_ANNOT_IS_INVISIBLE) fz_strlcat(buf, " inv", sizeof buf);
+ if (f & PDF_ANNOT_IS_HIDDEN) fz_strlcat(buf, " hidden", sizeof buf);
+ if (f & PDF_ANNOT_IS_PRINT) fz_strlcat(buf, " print", sizeof buf);
+ if (f & PDF_ANNOT_IS_NO_ZOOM) fz_strlcat(buf, " nz", sizeof buf);
+ if (f & PDF_ANNOT_IS_NO_ROTATE) fz_strlcat(buf, " nr", sizeof buf);
+ if (f & PDF_ANNOT_IS_NO_VIEW) fz_strlcat(buf, " nv", sizeof buf);
+ if (f & PDF_ANNOT_IS_READ_ONLY) fz_strlcat(buf, " ro", sizeof buf);
+ if (f & PDF_ANNOT_IS_LOCKED) fz_strlcat(buf, " lock", sizeof buf);
+ if (f & PDF_ANNOT_IS_TOGGLE_NO_VIEW) fz_strlcat(buf, " tnv", sizeof buf);
+ if (f & PDF_ANNOT_IS_LOCKED_CONTENTS) fz_strlcat(buf, " lc", sizeof buf);
+ if (!f) fz_strlcat(buf, " none", sizeof buf);
+ ui_label("%s", buf);
+}
+
+static const char *color_names[] = {
+ "None",
+ "Aqua",
+ "Black",
+ "Blue",
+ "Fuchsia",
+ "Gray",
+ "Green",
+ "Lime",
+ "Maroon",
+ "Navy",
+ "Olive",
+ "Orange",
+ "Purple",
+ "Red",
+ "Silver",
+ "Teal",
+ "White",
+ "Yellow",
+};
+
+static unsigned int color_values[] = {
+ 0x00000000, /* transparent */
+ 0xff00ffff, /* aqua */
+ 0xff000000, /* black */
+ 0xff0000ff, /* blue */
+ 0xffff00ff, /* fuchsia */
+ 0xff808080, /* gray */
+ 0xff008000, /* green */
+ 0xff00ff00, /* lime */
+ 0xff800000, /* maroon */
+ 0xff000080, /* navy */
+ 0xff808000, /* olive */
+ 0xffffa500, /* orange */
+ 0xff800080, /* purple */
+ 0xffff0000, /* red */
+ 0xffc0c0c0, /* silver */
+ 0xff008080, /* teal */
+ 0xffffffff, /* white */
+ 0xffffff00, /* yellow */
+};
+
+static unsigned int hex_from_color(int n, float color[4])
+{
+ float rgb[4];
+ int r, g, b;
+ switch (n)
+ {
+ default:
+ return 0;
+ case 1:
+ r = color[0] * 255;
+ return 0xff000000 | (r<<16) | (r<<8) | r;
+ case 3:
+ r = color[0] * 255;
+ g = color[1] * 255;
+ b = color[2] * 255;
+ return 0xff000000 | (r<<16) | (g<<8) | b;
+ case 4:
+ fz_convert_color(ctx, NULL, NULL, fz_device_rgb(ctx), rgb, fz_device_cmyk(ctx), color);
+ r = rgb[0] * 255;
+ g = rgb[1] * 255;
+ b = rgb[2] * 255;
+ return 0xff000000 | (r<<16) | (g<<8) | b;
+ }
+}
+
+static const char *name_from_hex(unsigned int hex)
+{
+ static char buf[10];
+ int i;
+ for (i = 0; i < nelem(color_names); ++i)
+ if (color_values[i] == hex)
+ return color_names[i];
+ fz_snprintf(buf, sizeof buf, "#%06x", hex & 0xffffff);
+ return buf;
+}
+
+static void do_annotate_color(char *label,
+ void (*get_color)(fz_context *ctx, pdf_annot *annot, int *n, float color[4]),
+ void (*set_color)(fz_context *ctx, pdf_annot *annot, int n, const float color[4]))
+{
+ float color[4];
+ int hex, choice, n;
+ get_color(ctx, selected_annot, &n, color);
+ ui_label("%s:", label);
+ choice = ui_select(label, name_from_hex(hex_from_color(n, color)), color_names, nelem(color_names));
+ if (choice != -1)
+ {
+ hex = color_values[choice];
+ if (hex == 0)
+ set_color(ctx, selected_annot, 0, color);
+ else
+ {
+ color[0] = ((hex>>16)&0xff) / 255.0f;
+ color[1] = ((hex>>8)&0xff) / 255.0f;
+ color[2] = ((hex)&0xff) / 255.0f;
+ set_color(ctx, selected_annot, 3, color);
+ }
+ }
+}
+
+static void do_annotate_author(void)
+{
+ if (pdf_annot_has_author(ctx, selected_annot))
+ {
+ char *author = pdf_copy_annot_author(ctx, selected_annot);
+ if (author && strlen(author) > 0)
+ ui_label("Author: %s", author);
+ fz_free(ctx, author);
+ }
+}
+
+static void do_annotate_date(void)
+{
+ time_t secs = pdf_annot_modification_date(ctx, selected_annot);
+ if (secs > 0)
+ {
+#ifdef _POSIX_SOURCE
+ struct tm tmbuf, *tm = gmtime_r(&secs, &tmbuf);
+#else
+ struct tm *tm = gmtime(&secs);
+#endif
+ char buf[100];
+ if (tm)
+ {
+ strftime(buf, sizeof buf, "%Y-%m-%d %H:%M UTC", tm);
+ ui_label("Date: %s", buf);
+ }
+ }
+}
+
+static void do_annotate_contents(void)
+{
+ static pdf_annot *last_annot = NULL;
+ static struct input input;
+ char *contents;
+
+ if (selected_annot != last_annot)
+ {
+ last_annot = selected_annot;
+ contents = pdf_copy_annot_contents(ctx, selected_annot);
+ ui_input_init(&input, contents);
+ fz_free(ctx, contents);
+ }
+
+ ui_label("Contents:");
+ if (ui_input(&input, 0) >= UI_INPUT_EDIT)
+ pdf_set_annot_contents(ctx, selected_annot, input.text);
+}
+
+static const char *file_attachment_icons[] = { "Graph", "Paperclip", "PushPin", "Tag" };
+static const char *sound_icons[] = { "Speaker", "Mic" };
+static const char *stamp_icons[] = {
+ "Approved", "AsIs", "Confidential", "Departmental", "Draft",
+ "Experimental", "Expired", "Final", "ForComment", "ForPublicRelease",
+ "NotApproved", "NotForPublicRelease", "Sold", "TopSecret" };
+static const char *text_icons[] = {
+ "Comment", "Help", "Insert", "Key", "NewParagraph", "Note", "Paragraph" };
+static const char *line_ending_styles[] = {
+ "None", "Square", "Circle", "Diamond", "OpenArrow", "ClosedArrow", "Butt",
+ "ROpenArrow", "RClosedArrow", "Slash" };
+static const char *quadding_names[] = { "Left", "Center", "Right" };
+
+static int should_edit_border(enum pdf_annot_type subtype)
+{
+ switch (subtype) {
+ default:
+ return 0;
+ case PDF_ANNOT_FREE_TEXT:
+ return 1;
+ case PDF_ANNOT_INK:
+ case PDF_ANNOT_LINE:
+ case PDF_ANNOT_SQUARE:
+ case PDF_ANNOT_CIRCLE:
+ case PDF_ANNOT_POLYGON:
+ case PDF_ANNOT_POLY_LINE:
+ return 1;
+ }
+}
+
+static int should_edit_color(enum pdf_annot_type subtype)
+{
+ switch (subtype) {
+ default:
+ return 0;
+ case PDF_ANNOT_STAMP:
+ case PDF_ANNOT_TEXT:
+ case PDF_ANNOT_FILE_ATTACHMENT:
+ case PDF_ANNOT_SOUND:
+ case PDF_ANNOT_CARET:
+ return 1;
+ case PDF_ANNOT_FREE_TEXT:
+ return 1;
+ case PDF_ANNOT_INK:
+ case PDF_ANNOT_LINE:
+ case PDF_ANNOT_SQUARE:
+ case PDF_ANNOT_CIRCLE:
+ case PDF_ANNOT_POLYGON:
+ case PDF_ANNOT_POLY_LINE:
+ return 1;
+ case PDF_ANNOT_HIGHLIGHT:
+ case PDF_ANNOT_UNDERLINE:
+ case PDF_ANNOT_STRIKE_OUT:
+ case PDF_ANNOT_SQUIGGLY:
+ return 1;
+ }
+}
+
+static int should_edit_icolor(enum pdf_annot_type subtype)
+{
+ switch (subtype) {
+ default:
+ return 0;
+ case PDF_ANNOT_LINE:
+ case PDF_ANNOT_SQUARE:
+ case PDF_ANNOT_CIRCLE:
+ return 1;
+ }
+}
+
+void do_annotate_panel(void)
+{
+ static struct list annot_list;
+ pdf_annot *annot;
+ int n;
+
+ int was_dirty = pdf->dirty;
+
+ ui_layout(T, X, NW, 2, 2);
+
+ if (ui_popup("CreateAnnotPopup", "Create...", 1, 14))
+ {
+ if (ui_popup_item("Text")) new_annot(PDF_ANNOT_TEXT);
+ if (ui_popup_item("FreeText")) new_annot(PDF_ANNOT_FREE_TEXT);
+ if (ui_popup_item("Stamp")) new_annot(PDF_ANNOT_STAMP);
+ if (ui_popup_item("Caret")) new_annot(PDF_ANNOT_CARET);
+ if (ui_popup_item("Ink")) new_annot(PDF_ANNOT_INK);
+ if (ui_popup_item("Square")) new_annot(PDF_ANNOT_SQUARE);
+ if (ui_popup_item("Circle")) new_annot(PDF_ANNOT_CIRCLE);
+ if (ui_popup_item("Line")) new_annot(PDF_ANNOT_LINE);
+ if (ui_popup_item("Polygon")) new_annot(PDF_ANNOT_POLYGON);
+ if (ui_popup_item("PolyLine")) new_annot(PDF_ANNOT_POLY_LINE);
+ if (ui_popup_item("Highlight")) new_annot(PDF_ANNOT_HIGHLIGHT);
+ if (ui_popup_item("Underline")) new_annot(PDF_ANNOT_UNDERLINE);
+ if (ui_popup_item("StrikeOut")) new_annot(PDF_ANNOT_STRIKE_OUT);
+ if (ui_popup_item("Squiggly")) new_annot(PDF_ANNOT_SQUIGGLY);
+ ui_popup_end();
+ }
+
+ n = 0;
+ for (annot = pdf_first_annot(ctx, page); annot; annot = pdf_next_annot(ctx, annot))
+ ++n;
+
+ ui_list_begin(&annot_list, n, 0, ui.lineheight * 10 + 4);
+ for (annot = pdf_first_annot(ctx, page); annot; annot = pdf_next_annot(ctx, annot))
+ {
+ char buf[256];
+ int num = pdf_to_num(ctx, annot->obj);
+ const char *type = pdf_string_from_annot_type(ctx, pdf_annot_type(ctx, annot));
+ fz_snprintf(buf, sizeof buf, "%d: %s", num, type);
+ if (ui_list_item(&annot_list, annot->obj, buf, selected_annot == annot))
+ selected_annot = annot;
+ }
+ ui_list_end(&annot_list);
+
+ if (selected_annot)
+ {
+ enum pdf_annot_type subtype = pdf_annot_type(ctx, selected_annot);
+ fz_rect rect;
+ fz_irect irect;
+ int n, choice;
+ pdf_obj *obj;
+
+ if (ui_button("Delete"))
+ {
+ pdf_delete_annot(ctx, page, selected_annot);
+ selected_annot = NULL;
+ render_page();
+ return;
+ }
+
+ ui_spacer();
+
+ /* common annotation properties */
+
+ pdf_annot_rect(ctx, selected_annot, &rect);
+ fz_irect_from_rect(&irect, &rect);
+ ui_label("Rect: %d %d %d %d", irect.x0, irect.y0, irect.x1, irect.y1);
+
+ do_annotate_flags();
+ do_annotate_author();
+ do_annotate_date();
+
+ obj = pdf_dict_get(ctx, selected_annot->obj, PDF_NAME(Popup));
+ if (obj)
+ ui_label("Popup: %d 0 R", pdf_to_num(ctx, obj));
+
+ ui_spacer();
+
+ do_annotate_contents();
+
+ ui_spacer();
+
+ if (subtype == PDF_ANNOT_FREE_TEXT)
+ {
+ int q = pdf_annot_quadding(ctx, selected_annot);
+ ui_label("Text Alignment:");
+ choice = ui_select("Q", quadding_names[q], quadding_names, nelem(quadding_names));
+ if (choice != -1)
+ pdf_set_annot_quadding(ctx, selected_annot, choice);
+ }
+
+ if (subtype == PDF_ANNOT_LINE)
+ {
+ enum pdf_line_ending s, e;
+ int s_choice, e_choice;
+
+ pdf_annot_line_ending_styles(ctx, selected_annot, &s, &e);
+
+ ui_label("Line Start:");
+ s_choice = ui_select("LE0", line_ending_styles[s], line_ending_styles, nelem(line_ending_styles));
+
+ ui_label("Line End:");
+ e_choice = ui_select("LE1", line_ending_styles[e], line_ending_styles, nelem(line_ending_styles));
+
+ if (s_choice != -1 || e_choice != -1)
+ {
+ if (s_choice != -1) s = s_choice;
+ if (e_choice != -1) e = e_choice;
+ pdf_set_annot_line_ending_styles(ctx, selected_annot, s, e);
+ }
+ }
+
+ if (pdf_annot_has_icon_name(ctx, selected_annot))
+ {
+ const char *name = pdf_annot_icon_name(ctx, selected_annot);
+ ui_label("Icon:");
+ switch (pdf_annot_type(ctx, selected_annot))
+ {
+ case PDF_ANNOT_TEXT:
+ choice = ui_select("Icon", name, text_icons, nelem(text_icons));
+ if (choice != -1)
+ pdf_set_annot_icon_name(ctx, selected_annot, text_icons[choice]);
+ break;
+ case PDF_ANNOT_FILE_ATTACHMENT:
+ choice = ui_select("Icon", name, file_attachment_icons, nelem(file_attachment_icons));
+ if (choice != -1)
+ pdf_set_annot_icon_name(ctx, selected_annot, file_attachment_icons[choice]);
+ break;
+ case PDF_ANNOT_SOUND:
+ choice = ui_select("Icon", name, sound_icons, nelem(sound_icons));
+ if (choice != -1)
+ pdf_set_annot_icon_name(ctx, selected_annot, sound_icons[choice]);
+ break;
+ case PDF_ANNOT_STAMP:
+ choice = ui_select("Icon", name, stamp_icons, nelem(stamp_icons));
+ if (choice != -1)
+ pdf_set_annot_icon_name(ctx, selected_annot, stamp_icons[choice]);
+ break;
+ }
+ }
+
+ if (should_edit_border(subtype))
+ {
+ static int border;
+ border = pdf_annot_border(ctx, selected_annot);
+ ui_label("Border: %d", border);
+ if (ui_slider(&border, 0, 10, 100))
+ pdf_set_annot_border(ctx, selected_annot, border);
+ }
+
+ if (should_edit_color(subtype))
+ do_annotate_color("Color", pdf_annot_color, pdf_set_annot_color);
+ if (should_edit_icolor(subtype))
+ do_annotate_color("Interior Color", pdf_annot_interior_color, pdf_set_annot_interior_color);
+
+ if (subtype == PDF_ANNOT_HIGHLIGHT)
+ {
+ static int opacity;
+ opacity = pdf_annot_opacity(ctx, selected_annot) * 255;
+ ui_label("Opacity:");
+ if (ui_slider(&opacity, 0, 255, 256))
+ pdf_set_annot_opacity(ctx, selected_annot, opacity / 255.0f);
+ }
+
+ if (pdf_annot_has_open(ctx, selected_annot))
+ {
+ int is_open = pdf_annot_is_open(ctx, selected_annot);
+ int start_is_open = is_open;
+ ui_checkbox("Open", &is_open);
+ if (start_is_open != is_open)
+ pdf_set_annot_is_open(ctx, selected_annot, is_open);
+ }
+
+ ui_spacer();
+
+ if (pdf_annot_has_quad_points(ctx, selected_annot))
+ {
+ n = pdf_annot_quad_point_count(ctx, selected_annot);
+ ui_label("QuadPoints: %d", n);
+ if (ui_button("Clear"))
+ pdf_clear_annot_quad_points(ctx, selected_annot);
+ }
+
+ if (pdf_annot_has_vertices(ctx, selected_annot))
+ {
+ n = pdf_annot_vertex_count(ctx, selected_annot);
+ ui_label("Vertices: %d", n);
+ if (ui_button("Clear"))
+ pdf_clear_annot_vertices(ctx, selected_annot);
+ }
+
+ if (pdf_annot_has_ink_list(ctx, selected_annot))
+ {
+ n = pdf_annot_ink_list_count(ctx, selected_annot);
+ ui_label("InkList: %d strokes", n);
+ if (ui_button("Clear"))
+ pdf_clear_annot_ink_list(ctx, selected_annot);
+ }
+
+ if (selected_annot && selected_annot->needs_new_ap)
+ {
+ pdf_update_appearance(ctx, selected_annot);
+ render_page();
+ }
+ }
+
+ ui_layout(B, X, NW, 2, 2);
+ if (ui_button("Save PDF..."))
+ {
+ init_save_pdf_options();
+ ui_init_save_file(filename, pdf_filter);
+ ui.dialog = save_pdf_dialog;
+ }
+
+ if (was_dirty != pdf->dirty)
+ update_title();
+}
+
+static void do_edit_icon(fz_irect canvas_area, fz_irect area, fz_rect *rect)
+{
+ static fz_point start_pt;
+ static float w, h;
+ static int moving = 0;
+
+ if (ui_mouse_inside(&canvas_area) && ui_mouse_inside(&area))
+ {
+ ui.hot = selected_annot;
+ if (!ui.active && ui.down)
+ {
+ ui.active = selected_annot;
+ start_pt.x = rect->x0;
+ start_pt.y = rect->y0;
+ w = rect->x1 - rect->x0;
+ h = rect->y1 - rect->y0;
+ moving = 1;
+ }
+ }
+
+ if (ui.active == selected_annot && moving)
+ {
+ rect->x0 = start_pt.x + (ui.x - ui.down_x);
+ rect->y0 = start_pt.y + (ui.y - ui.down_y);
+
+ /* Clamp to fit on page */
+ rect->x0 = fz_clamp(rect->x0, view_page_area.x0, view_page_area.x1-w);
+ rect->y0 = fz_clamp(rect->y0, view_page_area.y0, view_page_area.y1-h);
+ rect->x1 = rect->x0 + w;
+ rect->y1 = rect->y0 + h;
+
+ /* cancel on right click */
+ if (ui.right)
+ moving = 0;
+
+ /* Commit movement on mouse-up */
+ if (!ui.down)
+ {
+ moving = 0;
+ if (fz_abs(start_pt.x - rect->x0) > 0.1f || fz_abs(start_pt.x - rect->y0) > 0.1f)
+ {
+ fz_rect trect = *rect;
+ fz_transform_rect(&trect, &view_page_inv_ctm);
+ pdf_set_annot_rect(ctx, selected_annot, &trect);
+ }
+ }
+ }
+}
+
+static void do_edit_rect(fz_irect canvas_area, fz_irect area, fz_rect *rect)
+{
+ enum {
+ ER_N=1, ER_E=2, ER_S=4, ER_W=8,
+ ER_NONE = 0,
+ ER_NW = ER_N|ER_W,
+ ER_NE = ER_N|ER_E,
+ ER_SW = ER_S|ER_W,
+ ER_SE = ER_S|ER_E,
+ ER_MOVE = ER_N|ER_E|ER_S|ER_W,
+ };
+ static fz_rect start_rect;
+ static int state = ER_NONE;
+
+ fz_expand_irect(&area, 5);
+ if (ui_mouse_inside(&canvas_area) && ui_mouse_inside(&area))
+ {
+ ui.hot = selected_annot;
+ if (!ui.active && ui.down)
+ {
+ ui.active = selected_annot;
+ start_rect = *rect;
+ state = ER_NONE;
+ if (ui.x <= area.x0 + 10) state |= ER_W;
+ if (ui.x >= area.x1 - 10) state |= ER_E;
+ if (ui.y <= area.y0 + 10) state |= ER_N;
+ if (ui.y >= area.y1 - 10) state |= ER_S;
+ if (!state) state = ER_MOVE;
+ }
+ }
+
+ if (ui.active == selected_annot && state != ER_NONE)
+ {
+ *rect = start_rect;
+ if (state & ER_W) rect->x0 += (ui.x - ui.down_x);
+ if (state & ER_E) rect->x1 += (ui.x - ui.down_x);
+ if (state & ER_N) rect->y0 += (ui.y - ui.down_y);
+ if (state & ER_S) rect->y1 += (ui.y - ui.down_y);
+ if (rect->x1 < rect->x0) { float t = rect->x1; rect->x1 = rect->x0; rect->x0 = t; }
+ if (rect->y1 < rect->y0) { float t = rect->y1; rect->y1 = rect->y0; rect->y0 = t; }
+ if (rect->x1 < rect->x0 + 10) rect->x1 = rect->x0 + 10;
+ if (rect->y1 < rect->y0 + 10) rect->y1 = rect->y0 + 10;
+
+ /* cancel on right click */
+ if (ui.right)
+ state = ER_NONE;
+
+ /* commit on mouse-up */
+ if (!ui.down)
+ {
+ state = ER_NONE;
+ if (rects_differ(&start_rect, rect, 1))
+ {
+ fz_rect trect = *rect;
+ fz_transform_rect(&trect, &view_page_inv_ctm);
+ pdf_set_annot_rect(ctx, selected_annot, &trect);
+ }
+ }
+ }
+}
+
+static void do_edit_line(fz_irect canvas_area, fz_irect area, fz_rect *rect)
+{
+ enum { EL_NONE, EL_A=1, EL_B=2, EL_MOVE=EL_A|EL_B };
+ static fz_point start_a, start_b;
+ static int state = EL_NONE;
+ fz_irect a_grab, b_grab;
+ fz_point a, b;
+ float lw;
+
+ fz_expand_irect(&area, 5);
+ if (ui_mouse_inside(&canvas_area) && ui_mouse_inside(&area))
+ {
+ ui.hot = selected_annot;
+ if (!ui.active && ui.down)
+ {
+ ui.active = selected_annot;
+ pdf_annot_line(ctx, selected_annot, &start_a, &start_b);
+ fz_transform_point(&start_a, &view_page_ctm);
+ fz_transform_point(&start_b, &view_page_ctm);
+ a_grab = fz_make_irect(start_a.x, start_a.y, start_a.x, start_a.y);
+ b_grab = fz_make_irect(start_b.x, start_b.y, start_b.x, start_b.y);
+ fz_expand_irect(&a_grab, 10);
+ fz_expand_irect(&b_grab, 10);
+ state = EL_NONE;
+ if (ui_mouse_inside(&a_grab)) state |= EL_A;
+ if (ui_mouse_inside(&b_grab)) state |= EL_B;
+ if (!state) state = EL_MOVE;
+ }
+ }
+
+ if (ui.active == selected_annot && state != 0)
+ {
+ a = start_a;
+ b = start_b;
+ if (state & EL_A) { a.x += (ui.x - ui.down_x); a.y += (ui.y - ui.down_y); }
+ if (state & EL_B) { b.x += (ui.x - ui.down_x); b.y += (ui.y - ui.down_y); }
+
+ glBegin(GL_LINES);
+ glColor4f(1, 0, 0, 1);
+ glVertex2f(a.x, a.y);
+ glVertex2f(b.x, b.y);
+ glEnd();
+
+ rect->x0 = fz_min(a.x, b.x);
+ rect->y0 = fz_min(a.y, b.y);
+ rect->x1 = fz_max(a.x, b.x);
+ rect->y1 = fz_max(a.y, b.y);
+ lw = pdf_annot_border(ctx, selected_annot);
+ fz_expand_rect(rect, fz_matrix_expansion(&view_page_ctm) * lw);
+
+ /* cancel on right click */
+ if (ui.right)
+ state = EL_NONE;
+
+ /* commit on mouse-up */
+ if (!ui.down)
+ {
+ state = EL_NONE;
+ if (points_differ(&start_a, &a, 1) || points_differ(&start_b, &b, 1))
+ {
+ fz_transform_point(&a, &view_page_inv_ctm);
+ fz_transform_point(&b, &view_page_inv_ctm);
+ pdf_set_annot_line(ctx, selected_annot, a, b);
+ }
+ }
+ }
+}
+
+static void do_edit_polygon(fz_irect canvas_area, int close)
+{
+ static int drawing = 0;
+ fz_point a, p;
+
+ if (ui_mouse_inside(&canvas_area) && ui_mouse_inside(&view_page_area))
+ {
+ ui.hot = selected_annot;
+ if (!ui.active || ui.active == selected_annot)
+ ui.cursor = GLUT_CURSOR_CROSSHAIR;
+ if (!ui.active && ui.down)
+ {
+ ui.active = selected_annot;
+ drawing = 1;
+ }
+ }
+
+ if (ui.active == selected_annot && drawing)
+ {
+ int n = pdf_annot_vertex_count(ctx, selected_annot);
+ if (n > 0)
+ {
+ p = pdf_annot_vertex(ctx, selected_annot, n-1);
+ fz_transform_point(&p, &view_page_ctm);
+ if (close)
+ {
+ a = pdf_annot_vertex(ctx, selected_annot, 0);
+ fz_transform_point(&a, &view_page_ctm);
+ }
+ glBegin(GL_LINE_STRIP);
+ glColor4f(1, 0, 0, 1);
+ glVertex2f(p.x, p.y);
+ glVertex2f(ui.x, ui.y);
+ if (close)
+ glVertex2f(a.x, a.y);
+ glEnd();
+ }
+
+ glColor4f(1, 0, 0, 1);
+ glPointSize(4);
+ glBegin(GL_POINTS);
+ glVertex2f(ui.x, ui.y);
+ glEnd();
+
+ /* cancel on right click */
+ if (ui.right)
+ drawing = 0;
+
+ /* commit point on mouse-up */
+ if (!ui.down)
+ {
+ fz_point p = { ui.x, ui.y };
+ fz_transform_point(&p, &view_page_inv_ctm);
+ pdf_add_annot_vertex(ctx, selected_annot, p);
+ drawing = 0;
+ }
+ }
+}
+
+static void do_edit_ink(fz_irect canvas_area)
+{
+ static int drawing = 0;
+ static fz_point p[1000];
+ static int n, last_x, last_y;
+ int i;
+
+ if (ui_mouse_inside(&canvas_area) && ui_mouse_inside(&view_page_area))
+ {
+ ui.hot = selected_annot;
+ if (!ui.active || ui.active == selected_annot)
+ ui.cursor = GLUT_CURSOR_CROSSHAIR;
+ if (!ui.active && ui.down)
+ {
+ ui.active = selected_annot;
+ drawing = 1;
+ n = 0;
+ last_x = INT_MIN;
+ last_y = INT_MIN;
+ }
+ }
+
+ if (ui.active == selected_annot && drawing)
+ {
+ if (n < nelem(p) && (ui.x != last_x || ui.y != last_y))
+ {
+ p[n].x = fz_clamp(ui.x, view_page_area.x0, view_page_area.x1);
+ p[n].y = fz_clamp(ui.y, view_page_area.y0, view_page_area.y1);
+ ++n;
+ }
+ last_x = ui.x;
+ last_y = ui.y;
+
+ if (n > 1)
+ {
+ glBegin(GL_LINE_STRIP);
+ glColor4f(1, 0, 0, 1);
+ for (i = 0; i < n; ++i)
+ glVertex2f(p[i].x, p[i].y);
+ glEnd();
+ }
+
+ /* cancel on right click */
+ if (ui.right)
+ {
+ drawing = 0;
+ n = 0;
+ }
+
+ /* commit stroke on mouse-up */
+ if (!ui.down)
+ {
+ if (n > 1)
+ {
+ for (i = 0; i < n; ++i)
+ fz_transform_point(&p[i], &view_page_inv_ctm);
+ pdf_add_annot_ink_list(ctx, selected_annot, n, p);
+ }
+ drawing = 0;
+ n = 0;
+ }
+ }
+}
+
+static void do_edit_quad_points(void)
+{
+ static fz_point pt = { 0, 0 };
+ static int marking = 0;
+ fz_rect hits[1000];
+ int i, n;
+
+ if (ui_mouse_inside(&view_page_area))
+ {
+ ui.hot = selected_annot;
+ if (!ui.active || ui.active == selected_annot)
+ ui.cursor = GLUT_CURSOR_TEXT;
+ if (!ui.active && ui.down)
+ {
+ ui.active = selected_annot;
+ marking = 1;
+ pt.x = ui.x;
+ pt.y = ui.y;
+ }
+ }
+
+ if (ui.active == selected_annot && marking)
+ {
+ fz_point page_a = { pt.x, pt.y };
+ fz_point page_b = { ui.x, ui.y };
+
+ fz_transform_point(&page_a, &view_page_inv_ctm);
+ fz_transform_point(&page_b, &view_page_inv_ctm);
+
+ n = fz_highlight_selection(ctx, page_text, page_a, page_b, hits, nelem(hits));
+
+ glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO); /* invert destination color */
+ glEnable(GL_BLEND);
+
+ glColor4f(1, 1, 1, 1);
+ for (i = 0; i < n; ++i)
+ {
+ fz_rect thit = hits[i];
+ fz_transform_rect(&thit, &view_page_ctm);
+ glRectf(thit.x0, thit.y0, thit.x1 + 1, thit.y1 + 1);
+ }
+
+ glDisable(GL_BLEND);
+
+ /* cancel on right click */
+ if (ui.right)
+ marking = 0;
+
+ if (!ui.down)
+ {
+ if (n > 0)
+ {
+ pdf_clear_annot_quad_points(ctx, selected_annot);
+ for (i = 0; i < n; ++i)
+ pdf_add_annot_quad_point(ctx, selected_annot, hits[i]);
+ }
+ marking = 0;
+ }
+ }
+}
+
+void do_annotate_canvas(fz_irect canvas_area)
+{
+ fz_rect bounds;
+ fz_irect area;
+ pdf_annot *annot;
+
+ int was_dirty = pdf->dirty;
+
+ for (annot = pdf_first_annot(ctx, page); annot; annot = pdf_next_annot(ctx, annot))
+ {
+ pdf_bound_annot(ctx, annot, &bounds);
+ fz_transform_rect(&bounds, &view_page_ctm);
+ fz_irect_from_rect(&area, &bounds);
+
+ if (ui_mouse_inside(&canvas_area) && ui_mouse_inside(&area))
+ {
+ ui.hot = annot;
+ if (!ui.active && ui.right)
+ {
+ ui.active = annot;
+ selected_annot = annot;
+ }
+ }
+
+ if (annot == selected_annot)
+ {
+ switch (pdf_annot_type(ctx, selected_annot))
+ {
+ default:
+ break;
+
+ /* Popup window */
+ case PDF_ANNOT_POPUP:
+ do_edit_rect(canvas_area, area, &bounds);
+ break;
+
+ /* Icons */
+ case PDF_ANNOT_TEXT:
+ case PDF_ANNOT_CARET:
+ case PDF_ANNOT_FILE_ATTACHMENT:
+ case PDF_ANNOT_SOUND:
+ do_edit_icon(canvas_area, area, &bounds);
+ break;
+
+ case PDF_ANNOT_STAMP:
+ do_edit_rect(canvas_area, area, &bounds);
+ break;
+
+ case PDF_ANNOT_FREE_TEXT:
+ do_edit_rect(canvas_area, area, &bounds);
+ break;
+
+ /* Drawings */
+ case PDF_ANNOT_LINE:
+ do_edit_line(canvas_area, area, &bounds);
+ break;
+ case PDF_ANNOT_CIRCLE:
+ case PDF_ANNOT_SQUARE:
+ do_edit_rect(canvas_area, area, &bounds);
+ break;
+ case PDF_ANNOT_POLYGON:
+ do_edit_polygon(canvas_area, 1);
+ break;
+ case PDF_ANNOT_POLY_LINE:
+ do_edit_polygon(canvas_area, 0);
+ break;
+
+ case PDF_ANNOT_INK:
+ do_edit_ink(canvas_area);
+ break;
+
+ case PDF_ANNOT_HIGHLIGHT:
+ case PDF_ANNOT_UNDERLINE:
+ case PDF_ANNOT_STRIKE_OUT:
+ case PDF_ANNOT_SQUIGGLY:
+ do_edit_quad_points();
+ break;
+ }
+
+ glLineStipple(1, 0xAAAA);
+ glEnable(GL_LINE_STIPPLE);
+ glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
+ glEnable(GL_BLEND);
+ glColor4f(1, 1, 1, 1);
+ glBegin(GL_LINE_LOOP);
+ fz_irect_from_rect(&area, &bounds);
+ glVertex2f(area.x0-0.5f, area.y0-0.5f);
+ glVertex2f(area.x1+0.5f, area.y0-0.5f);
+ glVertex2f(area.x1+0.5f, area.y1+0.5f);
+ glVertex2f(area.x0-0.5f, area.y1+0.5f);
+ glEnd();
+ glDisable(GL_BLEND);
+ glDisable(GL_LINE_STIPPLE);
+
+ if (annot->needs_new_ap)
+ {
+ pdf_update_appearance(ctx, annot);
+ render_page();
+ }
+ }
+ }
+
+ if (ui_mouse_inside(&canvas_area) && ui.right)
+ {
+ if (!ui.active)
+ selected_annot = NULL;
+ }
+
+ if (was_dirty != pdf->dirty)
+ update_title();
+}