Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/platform/gl/gl-annotate.c @ 3:2c135c81b16c
MERGE: upstream PyMuPDF 1.26.4 with MuPDF 1.26.7
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:44:09 +0200 |
| parents | b50eed0cc0ef |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/platform/gl/gl-annotate.c Mon Sep 15 11:44:09 2025 +0200 @@ -0,0 +1,2308 @@ +// Copyright (C) 2004-2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html> +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see <https://www.artifex.com/> or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +#include "gl-app.h" + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <time.h> +#include <limits.h> + +static int is_draw_mode = 0; +static int new_contents = 0; + +static char save_filename[PATH_MAX]; +static pdf_write_options save_opts; +static struct input opwinput; +static struct input upwinput; +static int do_high_security; +static int hs_resolution = 200; +static struct input ocr_language_input; +static int ocr_language_input_initialised = 0; +static pdf_document *pdf_has_redactions_doc = NULL; +static int pdf_has_redactions; +static int do_snapshot; + +static int label_slider(const char *label, int *value, int min, int max) +{ + int changed; + ui_panel_begin(0, ui.gridsize, 0, 0, 0); + ui_layout(R, NONE, CENTER, 0, 0); + changed = ui_slider(value, min, max, 100); + ui_layout(L, X, CENTER, 0, 0); + ui_label("%s: %d", label, *value); + ui_panel_end(); + return changed; +} + +static int label_select(const char *label, const char *id, const char *current, const char *options[], int n) +{ + int changed; + ui_panel_begin(0, ui.gridsize, 0, 0, 0); + ui_layout(L, NONE, CENTER, 0, 0); + ui_label("%s: ", label); + ui_layout(T, X, CENTER, 0, 0); + changed = ui_select(id, current, options, n); + ui_panel_end(); + return changed; +} + +static int pdf_filter(const char *fn) +{ + const char *extension = strrchr(fn, '.'); + if (extension && !fz_strcasecmp(extension, ".pdf")) + return 1; + return 0; +} + +static void init_save_pdf_options(void) +{ + save_opts = pdf_default_write_options; + if (pdf->redacted) + save_opts.do_garbage = 1; + if (pdf_can_be_saved_incrementally(ctx, pdf)) + save_opts.do_incremental = 1; + save_opts.do_compress = 1; + save_opts.do_compress_images = 1; + save_opts.do_compress_fonts = 1; + do_high_security = 0; + ui_input_init(&opwinput, ""); + ui_input_init(&upwinput, ""); + if (!ocr_language_input_initialised) + { + ui_input_init(&ocr_language_input, "eng"); + ocr_language_input_initialised = 1; + } +} + +static const char *cryptalgo_names[] = { + "Keep", + "None", + "RC4, 40 bit", + "RC4, 128 bit", + "AES, 128 bit", + "AES, 256 bit", +}; + +static void save_pdf_options(void) +{ + const char *cryptalgo = cryptalgo_names[save_opts.do_encrypt]; + int choice; + int can_be_incremental; + + ui_layout(T, X, NW, ui.padsize, ui.padsize); + ui_label("PDF write options:"); + ui_layout(T, X, NW, ui.padsize*2, ui.padsize); + + can_be_incremental = pdf_can_be_saved_incrementally(ctx, pdf); + + ui_checkbox("Snapshot", &do_snapshot); + if (do_snapshot) + return; /* ignore normal PDF options */ + + ui_checkbox("High Security", &do_high_security); + if (do_high_security) + { + int res200 = (hs_resolution == 200); + int res300 = (hs_resolution == 300); + int res600 = (hs_resolution == 600); + int res1200 = (hs_resolution == 1200); + ui_label("WARNING: High Security saving is a lossy procedure! Keep your original file safe."); + ui_label("Resolution:"); + ui_checkbox("200", &res200); + ui_checkbox("300", &res300); + ui_checkbox("600", &res600); + ui_checkbox("1200", &res1200); + if (res200 && hs_resolution != 200) + hs_resolution = 200; + else if (res300 && hs_resolution != 300) + hs_resolution = 300; + else if (res600 && hs_resolution != 600) + hs_resolution = 600; + else if (res1200 && hs_resolution != 1200) + hs_resolution = 1200; + else if (!res200 && !res300 && !res600 && !res1200) + hs_resolution = 200; + ui_label("OCR Language:"); + ui_input(&ocr_language_input, 32, 1); + + return; /* ignore normal PDF options */ + } + + ui_checkbox_aux("Incremental", &save_opts.do_incremental, !can_be_incremental); + + fz_try(ctx) + { + if (pdf_count_signatures(ctx, pdf) && !save_opts.do_incremental) + { + if (can_be_incremental) + ui_label("WARNING: Saving non-incrementally will break existing signatures"); + else + ui_label("WARNING: Saving will break existing signatures"); + } + } + fz_catch(ctx) + { + /* Ignore the error. */ + } + + ui_spacer(); + ui_checkbox("Pretty-print", &save_opts.do_pretty); + ui_checkbox("Ascii", &save_opts.do_ascii); + ui_checkbox("Decompress", &save_opts.do_decompress); + 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("Object streams", &save_opts.do_use_objstms); + ui_checkbox("Preserve metadata", &save_opts.do_preserve_metadata); + + if (save_opts.do_incremental) + { + save_opts.do_garbage = 0; + save_opts.do_linear = 0; + save_opts.do_clean = 0; + save_opts.do_sanitize = 0; + save_opts.do_encrypt = PDF_ENCRYPT_KEEP; + } + else + { + ui_spacer(); + ui_checkbox("Linearize", &save_opts.do_linear); + ui_checkbox_aux("Garbage collect", &save_opts.do_garbage, pdf->redacted); + ui_checkbox("Clean syntax", &save_opts.do_clean); + ui_checkbox("Sanitize syntax", &save_opts.do_sanitize); + + ui_spacer(); + ui_label("Encryption:"); + choice = ui_select("Encryption", cryptalgo, cryptalgo_names, nelem(cryptalgo_names)); + if (choice != -1) + save_opts.do_encrypt = choice; + } + + if (save_opts.do_encrypt >= PDF_ENCRYPT_RC4_40) + { + ui_spacer(); + ui_label("User password:"); + if (ui_input(&upwinput, 32, 1) >= UI_INPUT_EDIT) + fz_strlcpy(save_opts.upwd_utf8, upwinput.text, nelem(save_opts.upwd_utf8)); + ui_label("Owner password:"); + if (ui_input(&opwinput, 32, 1) >= UI_INPUT_EDIT) + fz_strlcpy(save_opts.opwd_utf8, opwinput.text, nelem(save_opts.opwd_utf8)); + } +} + +struct { + int n; + int i; + char *operation_text; + char *progress_text; + int (*step)(int cancel); + int display; +} ui_slow_operation_state; + +static int run_slow_operation_step(int cancel) +{ + fz_try(ctx) + { + int i = ui_slow_operation_state.step(cancel); + if (ui_slow_operation_state.i == 0) + { + ui_slow_operation_state.i = 1; + ui_slow_operation_state.n = i; + } + else + { + ui_slow_operation_state.i = i; + } + } + fz_catch(ctx) + { + ui_slow_operation_state.i = -1; + ui_show_warning_dialog("%s failed: %s", + ui_slow_operation_state.operation_text, + fz_caught_message(ctx)); + fz_report_error(ctx); + + /* Call to cancel. */ + fz_try(ctx) + ui_slow_operation_state.step(1); + fz_catch(ctx) + { + /* Ignore any error from cancelling */ + } + return 1; + } + + return 0; +} + +static void slow_operation_dialog(void) +{ + int start_time; + int errored = 0; + + ui_dialog_begin(16 * ui.gridsize, 4 * ui.gridsize); + ui_layout(T, X, NW, ui.padsize, ui.padsize); + + ui_label("%s", ui_slow_operation_state.operation_text); + + ui_spacer(); + + if (ui_slow_operation_state.i == 0) + ui_label("Initializing."); + else if (ui_slow_operation_state.i > ui_slow_operation_state.n) + ui_label("Finalizing."); + else + ui_label("%s: %d / %d", + ui_slow_operation_state.progress_text, + ui_slow_operation_state.i, + ui_slow_operation_state.n); + + ui_spacer(); + + ui_panel_begin(0, ui.gridsize, 0, 0, 0); + { + ui_layout(R, NONE, S, 0, 0); + if (ui_button("Cancel")) + { + ui.dialog = NULL; + run_slow_operation_step(1); + return; + } + } + ui_panel_end(); + + /* Only run the operations every other time. This ensures we + * actually see the update for page i before page i is + * processed. */ + ui_slow_operation_state.display = !ui_slow_operation_state.display; + if (ui_slow_operation_state.display == 0) + { + /* Run steps for 200ms or until we're done. */ + start_time = glutGet(GLUT_ELAPSED_TIME); + while (!errored && ui_slow_operation_state.i >= 0 && + glutGet(GLUT_ELAPSED_TIME) < start_time + 200) + { + errored = run_slow_operation_step(0); + } + } + + if (!errored && ui_slow_operation_state.i == -1) + ui.dialog = NULL; + + /* ... and trigger a redisplay */ + glutPostRedisplay(); +} + +static void +ui_start_slow_operation(char *operation, char *progress, int (*step)(int)) +{ + ui.dialog = slow_operation_dialog; + ui_slow_operation_state.operation_text = operation; + ui_slow_operation_state.progress_text = progress; + ui_slow_operation_state.i = 0; + ui_slow_operation_state.step = step; +} + +struct { + int i; + int n; + fz_document_writer *writer; +} hss_state; + +static int step_high_security_save(int cancel) +{ + fz_page *page = NULL; + fz_device *dev; + + /* Called with i=0 for init. i=1...n for pages 1 to n inclusive. + * i=n+1 for finalisation. */ + + /* If cancelling, tidy up state. */ + if (cancel) + { + fz_drop_document_writer(ctx, hss_state.writer); + hss_state.writer = NULL; + return -1; + } + + /* If initing, open the file, count the pages, return the number + * of pages ("number of steps in the operation"). */ + if (hss_state.i == 0) + { + char options[1024]; + + fz_snprintf(options, sizeof(options), + "compression=flate,resolution=%d,ocr-language=%s", + hs_resolution, ocr_language_input.text); + + hss_state.writer = fz_new_pdfocr_writer(ctx, save_filename, options); + hss_state.i = 1; + hss_state.n = fz_count_pages(ctx, (fz_document *)pdf); + return hss_state.n; + } + /* If we've done all the pages, finish the write. */ + if (hss_state.i > hss_state.n) + { + fz_close_document_writer(ctx, hss_state.writer); + fz_drop_document_writer(ctx, hss_state.writer); + hss_state.writer = NULL; + fz_strlcpy(filename, save_filename, PATH_MAX); + reload_document(); + return -1; + } + /* Otherwise, do the next page. */ + page = fz_load_page(ctx, (fz_document *)pdf, hss_state.i-1); + + fz_try(ctx) + { + dev = fz_begin_page(ctx, hss_state.writer, fz_bound_page(ctx, page)); + fz_run_page(ctx, page, dev, fz_identity, NULL); + fz_drop_page(ctx, page); + page = NULL; + fz_end_page(ctx, hss_state.writer); + } + fz_catch(ctx) + { + fz_drop_page(ctx, page); + fz_rethrow(ctx); + } + + return ++hss_state.i; +} + +static void save_high_security(void) +{ + /* FIXME */ + trace_action("//doc.hsredact(%q);\n", save_filename); + memset(&hss_state, 0, sizeof(hss_state)); + ui_start_slow_operation("High Security Save", "Page", step_high_security_save); +} + +static void do_save_pdf_dialog(int for_signing) +{ + if (ui_save_file(save_filename, save_pdf_options, + do_snapshot ? + "Select where to save the snapshot:" : + do_high_security ? + "Select where to save the redacted document:" : + for_signing ? + "Select where to save the signed document:" : + "Select where to save the document:")) + { + ui.dialog = NULL; + if (save_filename[0] != 0) + { + if (do_high_security) + { + save_high_security(); + return; + } + if (for_signing && !do_sign()) + return; + if (save_opts.do_garbage) + save_opts.do_garbage = 2; + fz_try(ctx) + { + static char opts_string[4096]; + pdf_format_write_options(ctx, opts_string, sizeof(opts_string), &save_opts); + trace_action("doc.save(%q,%q);\n", save_filename, opts_string); + if (do_snapshot) + { + pdf_save_snapshot(ctx, pdf, save_filename); + fz_strlcat(save_filename, ".journal", PATH_MAX); + pdf_save_journal(ctx, pdf, save_filename); + } + else + { + pdf_save_document(ctx, pdf, save_filename, &save_opts); + fz_strlcpy(filename, save_filename, PATH_MAX); + fz_strlcat(save_filename, ".journal", PATH_MAX); +#ifdef _WIN32 + fz_remove_utf8(save_filename); +#else + remove(save_filename); +#endif + reload_document(); + } + } + fz_catch(ctx) + { + ui_show_warning_dialog("%s", fz_caught_message(ctx)); + fz_report_error(ctx); + } + } + } +} + +static void save_pdf_dialog(void) +{ + do_save_pdf_dialog(0); +} + +static void save_signed_pdf_dialog(void) +{ + do_save_pdf_dialog(1); +} + +void do_save_signed_pdf_file(void) +{ + init_save_pdf_options(); + ui_init_save_file(filename, pdf_filter); + ui.dialog = save_signed_pdf_dialog; +} + +void do_save_pdf_file(void) +{ + if (pdf) + { + init_save_pdf_options(); + ui_init_save_file(filename, pdf_filter); + ui.dialog = save_pdf_dialog; + } +} + +static char attach_filename[PATH_MAX]; + +static void save_attachment_dialog(void) +{ + if (ui_save_file(attach_filename, NULL, "Save attachment as:")) + { + ui.dialog = NULL; + if (attach_filename[0] != 0) + { + fz_try(ctx) + { + pdf_obj *fs = pdf_annot_filespec(ctx, ui.selected_annot); + fz_buffer *buf = pdf_load_embedded_file_contents(ctx, fs); + fz_save_buffer(ctx, buf, attach_filename); + fz_drop_buffer(ctx, buf); + trace_action("tmp = annot.getFilespec()\n"); + trace_action("doc.getEmbeddedFileContents(tmp).save(\"%s\");\n", attach_filename); + trace_action("tmp = doc.verifyEmbeddedFileChecksum(tmp);\n"); + trace_action("if (tmp != true)\n"); + trace_action(" throw new RegressionError('Embedded file checksum:', tmp, 'expected:', true);\n"); + } + fz_catch(ctx) + { + ui_show_warning_dialog("%s", fz_caught_message(ctx)); + fz_report_error(ctx); + } + } + } +} + +static void open_attachment_dialog(void) +{ + if (ui_open_file(attach_filename, "Select file to attach:")) + { + ui.dialog = NULL; + if (attach_filename[0] != 0) + { + pdf_obj *fs = NULL; + pdf_begin_operation(ctx, pdf, "Embed file attachment"); + fz_var(fs); + fz_try(ctx) + { + int64_t created, modified; + fz_buffer *contents; + const char *filename; + + filename = fz_basename(attach_filename); + contents = fz_read_file(ctx, attach_filename); + created = fz_stat_ctime(attach_filename); + modified = fz_stat_mtime(attach_filename); + + fs = pdf_add_embedded_file(ctx, pdf, filename, NULL, contents, + created, modified, 0); + pdf_set_annot_filespec(ctx, ui.selected_annot, fs); + fz_drop_buffer(ctx, contents); + 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); + } + fz_always(ctx) + { + pdf_drop_obj(ctx, fs); + pdf_end_operation(ctx, pdf); + } + fz_catch(ctx) + { + ui_show_warning_dialog("%s", fz_caught_message(ctx)); + fz_report_error(ctx); + } + } + } +} + +static char stamp_image_filename[PATH_MAX]; + +static void open_stamp_image_dialog(void) +{ + if (ui_open_file(stamp_image_filename, "Select file for customized stamp:")) + { + ui.dialog = NULL; + if (stamp_image_filename[0] != 0) + { + fz_image *img = NULL; + fz_var(img); + fz_try(ctx) + { + fz_rect rect = pdf_annot_rect(ctx, ui.selected_annot); + trace_action("tmp = new Image(%q);\n", stamp_image_filename); + img = fz_new_image_from_file(ctx, stamp_image_filename); + trace_action("annot.setAppearance(tmp);\n"); + pdf_set_annot_stamp_image(ctx, ui.selected_annot, img); + pdf_set_annot_rect(ctx, ui.selected_annot, fz_make_rect( + rect.x0, rect.y0, + rect.x0 + img->w * 72 / img->xres, + rect.y0 + img->h * 72 / img->yres) + ); + trace_action("annot.setIcon(%q);\n", fz_basename(stamp_image_filename)); + pdf_set_annot_icon_name(ctx, ui.selected_annot, fz_basename(stamp_image_filename)); + } + fz_always(ctx) + { + fz_drop_image(ctx, img); + } + fz_catch(ctx) + { + ui_show_warning_dialog("%s", fz_caught_message(ctx)); + fz_report_error(ctx); + } + } + } +} + +static int rects_differ(fz_rect a, 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(fz_point a, 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) +{ + char msg[100]; + + trace_action("annot = page.createAnnotation(%q);\n", pdf_string_from_annot_type(ctx, type)); + + fz_snprintf(msg, sizeof msg, "Create %s Annotation", pdf_string_from_annot_type(ctx, type)); + pdf_begin_operation(ctx, pdf, msg); + + ui_select_annot(pdf_create_annot(ctx, page, type)); + + pdf_set_annot_modification_date(ctx, ui.selected_annot, time(NULL)); + if (pdf_annot_has_author(ctx, ui.selected_annot)) + pdf_set_annot_author(ctx, ui.selected_annot, getuser()); + + pdf_end_operation(ctx, pdf); + + switch (type) + { + case PDF_ANNOT_REDACT: + pdf_has_redactions_doc = pdf; + pdf_has_redactions = 1; + /* fallthrough */ + case PDF_ANNOT_INK: + case PDF_ANNOT_POLYGON: + case PDF_ANNOT_POLY_LINE: + case PDF_ANNOT_HIGHLIGHT: + case PDF_ANNOT_UNDERLINE: + case PDF_ANNOT_STRIKE_OUT: + case PDF_ANNOT_SQUIGGLY: + is_draw_mode = 1; + break; + } +} + +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 0x00000000; + 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, fz_device_cmyk(ctx), color, fz_device_rgb(ctx), rgb, NULL, fz_default_color_params); + 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 < (int)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, ui.selected_annot, &n, color); + choice = label_select(label, label, name_from_hex(hex_from_color(n, color)), color_names, nelem(color_names)); + if (choice != -1) + { + hex = color_values[choice]; + if (hex == 0) + { + trace_action("annot.set%s([]);\n", label); + set_color(ctx, ui.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; + trace_action("annot.set%s([%g, %g, %g]);\n", label, color[0], color[1], color[2]); + set_color(ctx, ui.selected_annot, 3, color); + } + } +} + +static void do_annotate_author(void) +{ + if (pdf_annot_has_author(ctx, ui.selected_annot)) + { + const char *author = pdf_annot_author(ctx, ui.selected_annot); + if (strlen(author) > 0) + ui_label("Author: %s", author); + } +} + +static void do_annotate_date(void) +{ + const char *s = format_date(pdf_annot_modification_date(ctx, ui.selected_annot)); + if (s) + ui_label("Date: %s", s); +} + +static const char *intent_names[] = { + "Default", // all + "FreeTextCallout", // freetext + "FreeTextTypewriter", // freetext + "LineArrow", // line + "LineDimension", // line + "PolyLineDimension", // polyline + "PolygonCloud", // polygon + "PolygonDimension", // polygon + "StampImage", // stamp + "StampSnapshot", // stamp +}; + +static enum pdf_intent do_annotate_intent(void) +{ + enum pdf_intent intent; + const char *intent_name; + int choice; + + if (!pdf_annot_has_intent(ctx, ui.selected_annot)) + return PDF_ANNOT_IT_DEFAULT; + + intent = pdf_annot_intent(ctx, ui.selected_annot); + + if (intent == PDF_ANNOT_IT_UNKNOWN) + intent_name = "Unknown"; + else + intent_name = intent_names[intent]; + + choice = label_select("Intent", "IT", intent_name, intent_names, nelem(intent_names)); + if (choice != -1) + { + trace_action("annot.setIntent(%d);\n", choice); + pdf_set_annot_intent(ctx, ui.selected_annot, choice); + intent = choice; + + // Changed intent! + if (intent == PDF_ANNOT_IT_FREETEXT_CALLOUT) + { + pdf_set_annot_callout_point(ctx, ui.selected_annot, fz_make_point(0, 0)); + pdf_set_annot_callout_style(ctx, ui.selected_annot, PDF_ANNOT_LE_OPEN_ARROW); + } + } + + // Press 'c' to move Callout line to current cursor position. + if (intent == PDF_ANNOT_IT_FREETEXT_CALLOUT) + { + if (!ui.focus && ui.key && ui.plain) + { + if (ui.key == 'c') + { + fz_point p = fz_transform_point(fz_make_point(ui.x, ui.y), view_page_inv_ctm); + pdf_set_annot_callout_point(ctx, ui.selected_annot, p); + } + } + } + + return intent; +} + +static int do_annotate_contents(void) +{ + static int is_same_edit_operation = 1; + static pdf_annot *last_annot = NULL; + static struct input input; + const char *contents; + + if (ui.focus != &input) + is_same_edit_operation = 0; + + if (ui.selected_annot != last_annot || new_contents) + { + is_same_edit_operation = 0; + last_annot = ui.selected_annot; + contents = pdf_annot_contents(ctx, ui.selected_annot); + ui_input_init(&input, contents); + new_contents = 0; + } + + ui_label("Contents:"); + if (ui_input(&input, 0, 5) >= UI_INPUT_EDIT) + { + trace_action("annot.setContents(%q);\n", input.text); + if (is_same_edit_operation) + { + pdf_begin_implicit_operation(ctx, pdf); + pdf_set_annot_contents(ctx, ui.selected_annot, input.text); + pdf_end_operation(ctx, pdf); + } + else + { + pdf_set_annot_contents(ctx, ui.selected_annot, input.text); + is_same_edit_operation = 1; + } + } + + return input.text[0] != 0; +} + +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 const char *font_names[] = { "Cour", "Helv", "TiRo" }; +static const char *lang_names[] = { "", "ja", "ko", "zh-Hans", "zh-Hant" }; +static const char *im_redact_names[] = { "Keep images", "Remove images", "Erase pixels" }; +static const char *la_redact_names[] = { "Keep line art", "Remove covered line art", "Remove touched line art" }; +static const char *tx_redact_names[] = { "Remove text", "Keep text" }; +static const char *border_styles[] = { "Solid", "Dashed", "Dotted" }; +static const char *border_intensities[] = { "None", "Small clouds", "Large clouds", "Enormous clouds" }; + +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_POLYGON: + case PDF_ANNOT_POLY_LINE: + case PDF_ANNOT_SQUARE: + case PDF_ANNOT_CIRCLE: + return 1; + } +} + +struct { + int n; + int i; + pdf_redact_options opts; +} rap_state; + +static int +step_redact_all_pages(int cancel) +{ + pdf_page *pg; + + /* Called with i=0 for init. i=1...n for pages 1 to n inclusive. + * i=n+1 for finalisation. */ + + if (cancel) + return -1; + + if (rap_state.i == 0) + { + rap_state.i = 1; + rap_state.n = pdf_count_pages(ctx, pdf); + return rap_state.n; + } + else if (rap_state.i > rap_state.n) + { + trace_action("page = tmp;\n"); + trace_page_update(); + pdf_has_redactions = 0; + load_page(); + return -1; + } + + trace_action("page = doc.loadPage(%d);\n", rap_state.i-1); + trace_action("page.applyRedactions(%s, %d);\n", + rap_state.opts.black_boxes ? "true" : "false", + rap_state.opts.image_method); + pg = pdf_load_page(ctx, pdf, rap_state.i-1); + fz_try(ctx) + pdf_redact_page(ctx, pdf, pg, &rap_state.opts); + fz_always(ctx) + fz_drop_page(ctx, (fz_page *)pg); + fz_catch(ctx) + fz_rethrow(ctx); + + return ++rap_state.i; +} + +static void redact_all_pages(pdf_redact_options *opts) +{ + trace_action("tmp = page;\n"); + memset(&rap_state, 0, sizeof(rap_state)); + rap_state.opts = *opts; + ui_start_slow_operation("Redacting all pages in document.", "Page", step_redact_all_pages); +} + +static int +document_has_redactions(void) +{ + int i, n; + pdf_page *page = NULL; + pdf_annot *annot; + int has_redact = 0; + + fz_var(page); + fz_var(has_redact); + + fz_try(ctx) + { + n = pdf_count_pages(ctx, pdf); + for (i = 0; i < n && !has_redact; i++) + { + page = pdf_load_page(ctx, pdf, i); + for (annot = pdf_first_annot(ctx, page); + annot != NULL; + annot = pdf_next_annot(ctx, annot)) + { + if (pdf_annot_type(ctx, annot) == PDF_ANNOT_REDACT) + { + has_redact = 1; + break; + } + } + fz_drop_page(ctx, (fz_page *)page); + page = NULL; + } + } + fz_catch(ctx) + { + /* Ignore the error, and assume no redactions */ + fz_drop_page(ctx, (fz_page *)page); + } + return has_redact; +} + +static int detect_border_style(enum pdf_border_style style, float width) +{ + if (style == PDF_BORDER_STYLE_DASHED) + { + int count = pdf_annot_border_dash_count(ctx, ui.selected_annot); + float dashlen = pdf_annot_border_dash_item(ctx, ui.selected_annot, 0); + if ((count == 1 || count == 2) && dashlen < 2 * width) + return 2; + return 1; + } + return 0; +} + +static void do_border(void) +{ + static int width; + static int choice; + enum pdf_border_style style; + + ui_spacer(); + + width = pdf_annot_border_width(ctx, ui.selected_annot); + style = pdf_annot_border_style(ctx, ui.selected_annot); + + width = fz_clampi(width, 0, 12); + if (label_slider("Border", &width, 0, 12)) + { + pdf_set_annot_border_width(ctx, ui.selected_annot, width); + trace_action("annot.setBorderWidth(%d);\n", width); + } + + width = fz_max(width, 1); + + choice = detect_border_style(style, width); + choice = label_select("Style", "BorderStyle", border_styles[choice], border_styles, nelem(border_styles)); + if (choice != -1) + { + pdf_clear_annot_border_dash(ctx, ui.selected_annot); + trace_action("annot.clearBorderDash();\n"); + if (choice == 0) + { + pdf_set_annot_border_style(ctx, ui.selected_annot, PDF_BORDER_STYLE_SOLID); + trace_action("annot.setBorderType('Solid');\n"); + } + else if (choice == 1) + { + pdf_set_annot_border_style(ctx, ui.selected_annot, PDF_BORDER_STYLE_DASHED); + pdf_add_annot_border_dash_item(ctx, ui.selected_annot, 3.0f * width); + trace_action("annot.setBorderType('Dashed');\n"); + trace_action("annot.addBorderDashItem(%g);\n", 3.0f * width); + } + else if (choice == 2) + { + pdf_set_annot_border_style(ctx, ui.selected_annot, PDF_BORDER_STYLE_DASHED); + pdf_add_annot_border_dash_item(ctx, ui.selected_annot, 1.0f * width); + trace_action("annot.setBorderType('Dashed');\n"); + trace_action("annot.addBorderDashItem(%g);\n", 1.0f * width); + } + } + + if (pdf_annot_has_border_effect(ctx, ui.selected_annot)) + { + static int intensity; + intensity = fz_clampi(pdf_annot_border_effect_intensity(ctx, ui.selected_annot), 0, 3); + if (pdf_annot_border_effect(ctx, ui.selected_annot) == PDF_BORDER_EFFECT_NONE) + intensity = 0; + + intensity = label_select("Effect", "BorderEffect", border_intensities[intensity], border_intensities, nelem(border_intensities)); + if (intensity != -1) + { + enum pdf_border_effect effect = intensity ? PDF_BORDER_EFFECT_CLOUDY : PDF_BORDER_EFFECT_NONE; + pdf_set_annot_border_effect(ctx, ui.selected_annot, effect); + pdf_set_annot_border_effect_intensity(ctx, ui.selected_annot, intensity); + trace_action("annot.setBorderEffect('%s');\n", effect ? "Cloudy" : "None"); + trace_action("annot.setBorderEffectIntensity(%d);\n", intensity); + } + } +} + +static int image_file_filter(const char *fn) +{ + return !!fz_strstrcase(fn, ".jpg") || !!fz_strstrcase(fn, ".jpeg") || !!fz_strstrcase(fn, ".png"); +} + +void do_annotate_panel(void) +{ + static struct list annot_list; + enum pdf_annot_type subtype; + enum pdf_intent intent; + pdf_annot *annot; + int idx; + int n; + + ui_layout(T, X, NW, ui.padsize, ui.padsize); + + if (ui_popup("CreateAnnotPopup", "Create...", 1, 16)) + { + 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); + if (ui_popup_item("FileAttachment")) new_annot(PDF_ANNOT_FILE_ATTACHMENT); + if (ui_popup_item("Redact")) new_annot(PDF_ANNOT_REDACT); + 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 * 6 + 4); + for (idx=0, annot = pdf_first_annot(ctx, page); annot; ++idx, annot = pdf_next_annot(ctx, annot)) + { + char buf[256]; + int num = pdf_to_num(ctx, pdf_annot_obj(ctx, annot)); + subtype = pdf_annot_type(ctx, annot); + fz_snprintf(buf, sizeof buf, "%d: %s", num, pdf_string_from_annot_type(ctx, subtype)); + if (ui_list_item(&annot_list, pdf_annot_obj(ctx, annot), buf, ui.selected_annot == annot)) + { + trace_action("annot = page.getAnnotations()[%d];\n", idx); + ui_select_annot(pdf_keep_annot(ctx, annot)); + } + } + ui_list_end(&annot_list); + + if (ui.selected_annot && (subtype = pdf_annot_type(ctx, ui.selected_annot)) != PDF_ANNOT_WIDGET) + { + int n, choice, has_content; + pdf_obj *obj; + + /* common annotation properties */ + + ui_spacer(); + + do_annotate_author(); + do_annotate_date(); + + obj = pdf_dict_get(ctx, pdf_annot_obj(ctx, ui.selected_annot), PDF_NAME(Popup)); + if (obj) + ui_label("Popup: %d 0 R", pdf_to_num(ctx, obj)); + + has_content = do_annotate_contents(); + + intent = do_annotate_intent(); + if (subtype == PDF_ANNOT_FREE_TEXT && intent == PDF_ANNOT_IT_FREETEXT_CALLOUT) + { + enum pdf_line_ending s; + int s_choice; + + s = pdf_annot_callout_style(ctx, ui.selected_annot); + + s_choice = label_select("Callout", "CL", line_ending_styles[s], line_ending_styles, nelem(line_ending_styles)); + if (s_choice != -1) + { + s = s_choice; + trace_action("annot.setCalloutStyle(%q);\n", line_ending_styles[s]); + pdf_set_annot_callout_style(ctx, ui.selected_annot, s); + } + } + + if (subtype == PDF_ANNOT_FREE_TEXT) + { + int lang_choice, font_choice, color_choice, size_changed; + int q; + const char *text_lang; + const char *text_font; + char text_font_buf[20]; + char lang_buf[8]; + static float text_size_f, text_color[4]; + static int text_size; + + ui_spacer(); + + text_lang = fz_string_from_text_language(lang_buf, pdf_annot_language(ctx, ui.selected_annot)); + lang_choice = label_select("Language", "DA/Lang", text_lang, lang_names, nelem(lang_names)); + if (lang_choice != -1) + { + text_lang = lang_names[lang_choice]; + trace_action("annot.setLanguage(%q);\n", text_lang); + pdf_set_annot_language(ctx, ui.selected_annot, fz_text_language_from_string(text_lang)); + } + + q = pdf_annot_quadding(ctx, ui.selected_annot); + choice = label_select("Text Align", "Q", quadding_names[q], quadding_names, nelem(quadding_names)); + if (choice != -1) + { + trace_action("annot.setQuadding(%d);\n", choice); + pdf_set_annot_quadding(ctx, ui.selected_annot, choice); + } + + pdf_annot_default_appearance_unmapped(ctx, ui.selected_annot, text_font_buf, sizeof text_font_buf, &text_size_f, &n, text_color); + text_size = text_size_f; + font_choice = label_select("Text Font", "DA/Font", text_font_buf, font_names, nelem(font_names)); + size_changed = label_slider("Text Size", &text_size, 8, 36); + color_choice = label_select("Text Color", "DA/Color", name_from_hex(hex_from_color(n, text_color)), color_names+1, nelem(color_names)-1); + if (font_choice != -1 || color_choice != -1 || size_changed) + { + if (font_choice != -1) + text_font = font_names[font_choice]; + else + text_font = text_font_buf; + if (color_choice != -1) + { + n = 3; + text_color[0] = ((color_values[color_choice+1]>>16) & 0xff) / 255.0f; + text_color[1] = ((color_values[color_choice+1]>>8) & 0xff) / 255.0f; + text_color[2] = ((color_values[color_choice+1]) & 0xff) / 255.0f; + + if (text_color[0] == text_color[1] && text_color[1] == text_color[2]) + n = 1; + } + if (n == 1) + trace_action("annot.setDefaultAppearance(%q, %d, [%g]);\n", + text_font, text_size, text_color[0]); + else if (n == 3) + trace_action("annot.setDefaultAppearance(%q, %d, [%g, %g, %g]);\n", + text_font, text_size, text_color[0], text_color[1], text_color[2]); + else if (n == 4) + trace_action("annot.setDefaultAppearance(%q, %d, [%g, %g, %g, %g]);\n", + text_font, text_size, text_color[0], text_color[1], text_color[2], text_color[3]); + else + trace_action("annot.setDefaultAppearance(%q, %d, []);\n", + text_font, text_size); + pdf_set_annot_default_appearance(ctx, ui.selected_annot, text_font, text_size, n, text_color); + } + } + + if (subtype == PDF_ANNOT_LINE || subtype == PDF_ANNOT_POLY_LINE) + { + enum pdf_line_ending s, e; + int s_choice, e_choice; + + ui_spacer(); + + pdf_annot_line_ending_styles(ctx, ui.selected_annot, &s, &e); + + s_choice = label_select("Line End 1", "LE0", line_ending_styles[s], line_ending_styles, nelem(line_ending_styles)); + e_choice = label_select("Line End 2", "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; + trace_action("annot.setLineEndingStyles(%q, %q);\n", line_ending_styles[s], line_ending_styles[e]); + pdf_set_annot_line_ending_styles(ctx, ui.selected_annot, s, e); + } + } + + if (subtype == PDF_ANNOT_LINE) + { + static int ll, lle, llo; + static int cap; + + ll = pdf_annot_line_leader(ctx, ui.selected_annot); + if (label_slider("Leader", &ll, -20, 20)) + { + pdf_set_annot_line_leader(ctx, ui.selected_annot, ll); + trace_action("annot.setLineLeader(%d);\n", ll); + } + + if (ll) + { + lle = pdf_annot_line_leader_extension(ctx, ui.selected_annot); + if (label_slider(" LLE", &lle, 0, 20)) + { + pdf_set_annot_line_leader_extension(ctx, ui.selected_annot, lle); + trace_action("annot.setLineLeaderExtension(%d);\n", ll); + } + + llo = pdf_annot_line_leader_offset(ctx, ui.selected_annot); + if (label_slider(" LLO", &llo, 0, 20)) + { + pdf_set_annot_line_leader_offset(ctx, ui.selected_annot, llo); + trace_action("annot.setLineLeaderOffset(%d);\n", ll); + } + } + + if (has_content) + { + cap = pdf_annot_line_caption(ctx, ui.selected_annot); + if (ui_checkbox("Caption", &cap)) + { + pdf_set_annot_line_caption(ctx, ui.selected_annot, cap); + trace_action("annot.setLineCaption(%s);\n", cap ? "true" : "false"); + } + } + } + + if (pdf_annot_has_icon_name(ctx, ui.selected_annot)) + { + const char *name = pdf_annot_icon_name(ctx, ui.selected_annot); + + switch (pdf_annot_type(ctx, ui.selected_annot)) + { + default: + break; + case PDF_ANNOT_TEXT: + ui_spacer(); + choice = label_select("Icon", "Icon", name, text_icons, nelem(text_icons)); + if (choice != -1) + { + trace_action("annot.setIcon(%q);\n", text_icons[choice]); + pdf_set_annot_icon_name(ctx, ui.selected_annot, text_icons[choice]); + } + break; + case PDF_ANNOT_FILE_ATTACHMENT: + ui_spacer(); + choice = label_select("Icon", "Icon", name, file_attachment_icons, nelem(file_attachment_icons)); + if (choice != -1) + { + trace_action("annot.setIcon(%q);\n", file_attachment_icons[choice]); + pdf_set_annot_icon_name(ctx, ui.selected_annot, file_attachment_icons[choice]); + } + break; + case PDF_ANNOT_SOUND: + ui_spacer(); + choice = label_select("Icon", "Icon", name, sound_icons, nelem(sound_icons)); + if (choice != -1) + { + trace_action("annot.setIcon(%q);\n", sound_icons[choice]); + pdf_set_annot_icon_name(ctx, ui.selected_annot, sound_icons[choice]); + } + break; + case PDF_ANNOT_STAMP: + ui_spacer(); + choice = label_select("Icon", "Icon", name, stamp_icons, nelem(stamp_icons)); + if (choice != -1) + { + trace_action("annot.setIcon(%q);\n", stamp_icons[choice]); + pdf_set_annot_icon_name(ctx, ui.selected_annot, stamp_icons[choice]); + } + break; + } + } + + if (pdf_annot_has_border(ctx, ui.selected_annot)) + do_border(); + + ui_spacer(); + + if (should_edit_color(subtype)) + do_annotate_color("Color", pdf_annot_color, pdf_set_annot_color); + if (should_edit_icolor(subtype)) + do_annotate_color("InteriorColor", pdf_annot_interior_color, pdf_set_annot_interior_color); + + { + static int opacity; + opacity = pdf_annot_opacity(ctx, ui.selected_annot) * 100; + if (label_slider("Opacity", &opacity, 0, 100)) + { + trace_action("annot.setOpacity(%g);\n", opacity / 100.0f); + pdf_set_annot_opacity(ctx, ui.selected_annot, opacity / 100.0f); + } + } + + if (pdf_annot_has_quad_points(ctx, ui.selected_annot)) + { + ui_spacer(); + if (is_draw_mode) + { + n = pdf_annot_quad_point_count(ctx, ui.selected_annot); + ui_label("QuadPoints: %d", n); + if (ui_button("Clear")) + { + trace_action("annot.clearQuadPoints();\n"); + pdf_clear_annot_quad_points(ctx, ui.selected_annot); + } + if (ui_button("Done")) + is_draw_mode = 0; + } + else + { + if (ui_button("Edit")) + is_draw_mode = 1; + } + } + + if (pdf_annot_has_vertices(ctx, ui.selected_annot)) + { + ui_spacer(); + if (is_draw_mode) + { + n = pdf_annot_vertex_count(ctx, ui.selected_annot); + ui_label("Vertices: %d", n); + if (ui_button("Clear")) + { + trace_action("annot.clearVertices();\n"); + pdf_clear_annot_vertices(ctx, ui.selected_annot); + } + if (ui_button("Done")) + is_draw_mode = 0; + } + else + { + if (ui_button("Edit")) + is_draw_mode = 1; + } + } + + if (pdf_annot_has_ink_list(ctx, ui.selected_annot)) + { + ui_spacer(); + if (is_draw_mode) + { + n = pdf_annot_ink_list_count(ctx, ui.selected_annot); + ui_label("InkList: %d strokes", n); + if (ui_button("Clear")) + { + trace_action("annot.clearInkList();\n"); + pdf_clear_annot_ink_list(ctx, ui.selected_annot); + } + if (ui_button("Done")) + is_draw_mode = 0; + } + else + { + if (ui_button("Edit")) + is_draw_mode = 1; + } + } + + if (pdf_annot_type(ctx, ui.selected_annot) == PDF_ANNOT_STAMP) + { + char attname[PATH_MAX]; + ui_spacer(); + if (ui_button("Image...")) + { + fz_dirname(attname, filename, sizeof attname); + ui_init_open_file(attname, image_file_filter); + ui.dialog = open_stamp_image_dialog; + } + } + + if (pdf_annot_type(ctx, ui.selected_annot) == PDF_ANNOT_FILE_ATTACHMENT) + { + pdf_filespec_params params; + char attname[PATH_MAX]; + pdf_obj *fs = pdf_annot_filespec(ctx, ui.selected_annot); + ui_spacer(); + if (pdf_is_embedded_file(ctx, fs)) + { + if (ui_button("Save...")) + { + fz_dirname(attname, filename, sizeof attname); + fz_strlcat(attname, "/", sizeof attname); + pdf_get_filespec_params(ctx, fs, ¶ms); + fz_strlcat(attname, params.filename, sizeof attname); + ui_init_save_file(attname, NULL); + ui.dialog = save_attachment_dialog; + } + } + if (ui_button("Embed...")) + { + fz_dirname(attname, filename, sizeof attname); + ui_init_open_file(attname, NULL); + ui.dialog = open_attachment_dialog; + } + } + + ui_spacer(); + if (ui_button("Delete")) + { + trace_action("page.deleteAnnotation(annot);\n"); + pdf_delete_annot(ctx, page, ui.selected_annot); + page_annots_changed = 1; + ui_select_annot(NULL); + return; + } + } + + ui_layout(B, X, NW, ui.padsize, ui.padsize); + + if (ui_button("Save PDF...")) + do_save_pdf_file(); +} + +static void new_redaction(pdf_page *page, fz_quad q) +{ + pdf_annot *annot; + + pdf_begin_operation(ctx, pdf, "Create Redaction"); + + annot = pdf_create_annot(ctx, page, PDF_ANNOT_REDACT); + + fz_try(ctx) + { + pdf_set_annot_modification_date(ctx, annot, time(NULL)); + if (pdf_annot_has_author(ctx, annot)) + pdf_set_annot_author(ctx, annot, getuser()); + pdf_add_annot_quad_point(ctx, annot, q); + pdf_set_annot_contents(ctx, annot, search_needle); + + trace_action("annot = page.createAnnotation(%q);\n", "Redact"); + trace_action("annot.addQuadPoint([%g, %g, %g, %g, %g, %g, %g, %g]);\n", + q.ul.x, q.ul.y, + q.ur.x, q.ur.y, + q.ll.x, q.ll.y, + q.lr.x, q.lr.y); + trace_action("annot.setContents(%q);\n", search_needle); + } + fz_always(ctx) + pdf_drop_annot(ctx, annot); + fz_catch(ctx) + fz_rethrow(ctx); + + pdf_has_redactions_doc = pdf; + pdf_has_redactions = 1; + + pdf_end_operation(ctx, pdf); +} + +static struct { int i, n; } rds_state; + +static int mark_search_step(int cancel) +{ + fz_quad quads[500]; + int i, count; + + if (rds_state.i == 0) + return ++rds_state.i, rds_state.n; + + if (cancel || rds_state.i > rds_state.n) + { + trace_action("page = tmp;\n"); + load_page(); + return -1; + } + + count = fz_search_page_number(ctx, (fz_document*)pdf, rds_state.i-1, search_needle, NULL, quads, nelem(quads)); + if (count > 0) + { + pdf_page *page = pdf_load_page(ctx, pdf, rds_state.i-1); + trace_action("page = doc.loadPage(%d);\n", rds_state.i-1); + for (i = 0; i < count; i++) + new_redaction(page, quads[i]); + fz_drop_page(ctx, (fz_page*)page); + } + + return ++rds_state.i; +} + +void mark_all_search_results(void) +{ + rds_state.i = 0; + rds_state.n = pdf_count_pages(ctx, pdf); + trace_action("tmp = page;\n"); + ui_start_slow_operation("Marking all search results for redaction.", "Page", mark_search_step); +} + +void do_redact_panel(void) +{ + static struct list annot_list; + enum pdf_annot_type subtype; + pdf_annot *annot; + int idx; + int im_choice; + int la_choice; + int tx_choice; + int i; + + int num_redact = 0; + static pdf_redact_options redact_opts = { 1, PDF_REDACT_IMAGE_PIXELS, PDF_REDACT_LINE_ART_REMOVE_IF_TOUCHED }; + int search_valid; + + if (pdf_has_redactions_doc != pdf) + { + pdf_has_redactions_doc = pdf; + pdf_has_redactions = document_has_redactions(); + } + + num_redact = 0; + for (annot = pdf_first_annot(ctx, page); annot; annot = pdf_next_annot(ctx, annot)) + if (pdf_annot_type(ctx, annot) == PDF_ANNOT_REDACT) + ++num_redact; + + ui_layout(T, X, NW, ui.padsize, ui.padsize); + + if (ui_button("Add Redaction")) + new_annot(PDF_ANNOT_REDACT); + + search_valid = search_has_results(); + if (ui_button_aux("Mark search in page", !search_valid)) + { + for (i = 0; i < search_hit_count; i++) + new_redaction(page, search_hit_quads[i]); + search_hit_count = 0; + ui_select_annot(NULL); + } + if (ui_button_aux("Mark search in document", search_needle == NULL)) + { + mark_all_search_results(); + search_hit_count = 0; + ui_select_annot(NULL); + } + + ui_spacer(); + + ui_label("When Redacting:"); + ui_checkbox("Draw black boxes", &redact_opts.black_boxes); + im_choice = ui_select("Redact/IM", im_redact_names[redact_opts.image_method], im_redact_names, nelem(im_redact_names)); + if (im_choice != -1) + redact_opts.image_method = im_choice; + + la_choice = ui_select("Redact/LA", la_redact_names[redact_opts.line_art], la_redact_names, nelem(la_redact_names)); + if (la_choice != -1) + redact_opts.line_art = la_choice; + + tx_choice = ui_select("Redact/TX", tx_redact_names[redact_opts.text], tx_redact_names, nelem(tx_redact_names)); + if (tx_choice != -1) + redact_opts.text = tx_choice; + + ui_spacer(); + + if (ui_button_aux("Redact Page", num_redact == 0)) + { + ui_select_annot(NULL); + trace_action("page.applyRedactions(%s, %d, %d);\n", + redact_opts.black_boxes ? "true" : "false", + redact_opts.image_method, + redact_opts.line_art); + pdf_redact_page(ctx, pdf, page, &redact_opts); + trace_page_update(); + load_page(); + } + + if (ui_button_aux("Redact Document", !pdf_has_redactions)) + { + ui_select_annot(NULL); + redact_all_pages(&redact_opts); + } + + ui_spacer(); + + ui_list_begin(&annot_list, num_redact, 0, ui.lineheight * 6 + 4); + for (idx=0, annot = pdf_first_annot(ctx, page); annot; ++idx, annot = pdf_next_annot(ctx, annot)) + { + char buf[50]; + int num = pdf_to_num(ctx, pdf_annot_obj(ctx, annot)); + subtype = pdf_annot_type(ctx, annot); + if (subtype == PDF_ANNOT_REDACT) + { + const char *contents = pdf_annot_contents(ctx, annot); + fz_snprintf(buf, sizeof buf, "%d: %s", num, contents[0] ? contents : "Redact"); + if (ui_list_item(&annot_list, pdf_annot_obj(ctx, annot), buf, ui.selected_annot == annot)) + { + trace_action("annot = page.getAnnotations()[%d];\n", idx); + ui_select_annot(pdf_keep_annot(ctx, annot)); + } + } + } + ui_list_end(&annot_list); + + ui_spacer(); + + if (ui.selected_annot && (subtype = pdf_annot_type(ctx, ui.selected_annot)) == PDF_ANNOT_REDACT) + { + int n; + + do_annotate_author(); + do_annotate_date(); + + ui_spacer(); + + if (is_draw_mode) + { + n = pdf_annot_quad_point_count(ctx, ui.selected_annot); + ui_label("QuadPoints: %d", n); + if (ui_button("Clear")) + { + trace_action("annot.clearQuadPoints();\n"); + pdf_clear_annot_quad_points(ctx, ui.selected_annot); + } + if (ui_button("Done")) + is_draw_mode = 0; + } + else + { + if (ui_button("Edit")) + is_draw_mode = 1; + } + + ui_spacer(); + + if (ui_button("Delete")) + { + trace_action("page.deleteAnnotation(annot);\n"); + pdf_delete_annot(ctx, page, ui.selected_annot); + page_annots_changed = 1; + ui_select_annot(NULL); + return; + } + } + + ui_layout(B, X, NW, ui.padsize, ui.padsize); + + if (ui_button("Save PDF...")) + do_save_pdf_file(); +} + +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 = ui.selected_annot; + if (!ui.active && ui.down) + { + ui.active = ui.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 == ui.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) + { + fz_point dp = { rect->x0 - start_pt.x, rect->y0 - start_pt.y }; + moving = 0; + if (fz_abs(dp.x) > 0.1f || fz_abs(dp.y) > 0.1f) + { + fz_rect trect = pdf_annot_rect(ctx, ui.selected_annot); + dp = fz_transform_vector(dp, view_page_inv_ctm); + trect.x0 += dp.x; trect.x1 += dp.x; + trect.y0 += dp.y; trect.y1 += dp.y; + trace_action("annot.setRect([%g, %g, %g, %g]);\n", trect.x0, trect.y0, trect.x1, trect.y1); + pdf_set_annot_rect(ctx, ui.selected_annot, trect); + } + } + } +} + +static void do_edit_rect(fz_irect canvas_area, fz_irect area, fz_rect *rect, int lock_aspect) +{ + 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; + + area = fz_expand_irect(area, 5); + if (ui_mouse_inside(canvas_area) && ui_mouse_inside(area)) + { + ui.hot = ui.selected_annot; + if (!ui.active && ui.down) + { + ui.active = ui.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 == ui.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; + + if (lock_aspect) + { + float aspect = (start_rect.x1 - start_rect.x0) / (start_rect.y1 - start_rect.y0); + switch (state) + { + case ER_SW: + case ER_NW: + rect->x0 = rect->x1 - (rect->y1 - rect->y0) * aspect; + break; + case ER_NE: + case ER_SE: + case ER_N: + case ER_S: + rect->x1 = rect->x0 + (rect->y1 - rect->y0) * aspect; + break; + case ER_E: + case ER_W: + rect->y1 = rect->y0 + (rect->x1 - rect->x0) / aspect; + break; + } + } + + /* 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 = fz_transform_rect(*rect, view_page_inv_ctm); + trace_action("annot.setRect([%g, %g, %g, %g]);\n", trect.x0, trect.y0, trect.x1, trect.y1); + pdf_set_annot_rect(ctx, ui.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; + + area = fz_expand_irect(area, 5); + if (ui_mouse_inside(canvas_area) && ui_mouse_inside(area)) + { + ui.hot = ui.selected_annot; + if (!ui.active && ui.down) + { + ui.active = ui.selected_annot; + pdf_annot_line(ctx, ui.selected_annot, &start_a, &start_b); + start_a = fz_transform_point(start_a, view_page_ctm); + start_b = 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); + a_grab = fz_expand_irect(a_grab, 10); + b_grab = 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 == ui.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_width(ctx, ui.selected_annot); + *rect = 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)) + { + a = fz_transform_point(a, view_page_inv_ctm); + b = fz_transform_point(b, view_page_inv_ctm); + trace_action("annot.setLine([%g, %g], [%g, %g]);\n", a.x, a.y, b.x, b.y); + pdf_set_annot_line(ctx, ui.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 = ui.selected_annot; + if (!ui.active || ui.active == ui.selected_annot) + ui.cursor = GLUT_CURSOR_CROSSHAIR; + if (!ui.active && ui.down) + { + ui.active = ui.selected_annot; + drawing = 1; + } + } + + if (ui.active == ui.selected_annot && drawing) + { + int n = pdf_annot_vertex_count(ctx, ui.selected_annot); + if (n > 0) + { + p = pdf_annot_vertex(ctx, ui.selected_annot, n-1); + p = fz_transform_point(p, view_page_ctm); + if (close) + { + a = pdf_annot_vertex(ctx, ui.selected_annot, 0); + a = 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 = fz_transform_point_xy(ui.x, ui.y, view_page_inv_ctm); + trace_action("annot.addVertex(%g, %g);\n", p.x, p.y); + pdf_add_annot_vertex(ctx, ui.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 = ui.selected_annot; + if (!ui.active || ui.active == ui.selected_annot) + ui.cursor = GLUT_CURSOR_CROSSHAIR; + if (!ui.active && ui.down) + { + ui.active = ui.selected_annot; + drawing = 1; + n = 0; + last_x = INT_MIN; + last_y = INT_MIN; + } + } + + if (ui.active == ui.selected_annot && drawing) + { + if (n < (int)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) + { + trace_action("annot.addInkList(["); + for (i = 0; i < n; ++i) + { + p[i] = fz_transform_point(p[i], view_page_inv_ctm); + trace_action("%s[%g, %g]", (i > 0 ? ", " : ""), p[i].x, p[i].y); + } + trace_action("]);\n"); + pdf_add_annot_ink_list(ctx, ui.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; + static fz_quad hits[1000]; + fz_rect rect; + char *text; + int i, n; + + if (ui_mouse_inside(view_page_area)) + { + ui.hot = ui.selected_annot; + if (!ui.active || ui.active == ui.selected_annot) + ui.cursor = GLUT_CURSOR_TEXT; + if (!ui.active && ui.down) + { + ui.active = ui.selected_annot; + marking = 1; + pt.x = ui.x; + pt.y = ui.y; + } + } + + if (ui.active == ui.selected_annot && marking) + { + fz_point page_a = { pt.x, pt.y }; + fz_point page_b = { ui.x, ui.y }; + + page_a = fz_transform_point(page_a, view_page_inv_ctm); + page_b = fz_transform_point(page_b, view_page_inv_ctm); + + if (ui.mod == GLUT_ACTIVE_CTRL) + fz_snap_selection(ctx, page_text, &page_a, &page_b, FZ_SELECT_WORDS); + else if (ui.mod == GLUT_ACTIVE_CTRL + GLUT_ACTIVE_SHIFT) + fz_snap_selection(ctx, page_text, &page_a, &page_b, FZ_SELECT_LINES); + + if (ui.mod == GLUT_ACTIVE_SHIFT) + { + rect = fz_make_rect( + fz_min(page_a.x, page_b.x), + fz_min(page_a.y, page_b.y), + fz_max(page_a.x, page_b.x), + fz_max(page_a.y, page_b.y)); + n = 1; + hits[0] = fz_quad_from_rect(rect); + } + else + { + 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); + glBegin(GL_QUADS); + for (i = 0; i < n; ++i) + { + fz_quad thit = fz_transform_quad(hits[i], view_page_ctm); + glVertex2f(thit.ul.x, thit.ul.y); + glVertex2f(thit.ur.x, thit.ur.y); + glVertex2f(thit.lr.x, thit.lr.y); + glVertex2f(thit.ll.x, thit.ll.y); + } + glEnd(); + + glDisable(GL_BLEND); + + /* cancel on right click */ + if (ui.right) + marking = 0; + + if (!ui.down) + { + if (n > 0) + { + pdf_begin_operation(ctx, pdf, "Edit quad points"); + + trace_action("annot.clearQuadPoints();\n"); + pdf_clear_annot_quad_points(ctx, ui.selected_annot); + for (i = 0; i < n; ++i) + { + trace_action("annot.addQuadPoint([%g, %g, %g, %g, %g, %g, %g, %g]);\n", + hits[i].ul.x, hits[i].ul.y, + hits[i].ur.x, hits[i].ur.y, + hits[i].ll.x, hits[i].ll.y, + hits[i].lr.x, hits[i].lr.y); + pdf_add_annot_quad_point(ctx, ui.selected_annot, hits[i]); + } + + if (ui.mod == GLUT_ACTIVE_SHIFT) + text = fz_copy_rectangle(ctx, page_text, rect, 0); + else + text = fz_copy_selection(ctx, page_text, page_a, page_b, 0); + + trace_action("annot.setContents(%q);\n", text); + pdf_set_annot_contents(ctx, ui.selected_annot, text); + new_contents = 1; + fz_free(ctx, text); + + pdf_end_operation(ctx, pdf); + } + marking = 0; + } + } +} + +void do_annotate_canvas(fz_irect canvas_area) +{ + fz_rect bounds; + fz_irect area; + pdf_annot *annot; + const void *nothing = ui.hot; + int idx; + + for (idx=0, annot = pdf_first_annot(ctx, page); annot; ++idx, annot = pdf_next_annot(ctx, annot)) + { + enum pdf_annot_type subtype = pdf_annot_type(ctx, annot); + + if (pdf_annot_has_rect(ctx, annot)) + bounds = pdf_annot_rect(ctx, annot); + else + bounds = pdf_bound_annot(ctx, annot); + + bounds = fz_transform_rect(bounds, view_page_ctm); + area = fz_irect_from_rect(bounds); + + if (ui_mouse_inside(canvas_area) && ui_mouse_inside(area)) + { + pdf_set_annot_hot(ctx, annot, 1); + + ui.hot = annot; + if (!ui.active && ui.down) + { + if (ui.selected_annot != annot) + { + trace_action("annot = page.getAnnotations()[%d];\n", idx); + if (!ui.selected_annot && !showannotate) + toggle_annotate(ANNOTATE_MODE_NORMAL); + ui.active = annot; + ui_select_annot(pdf_keep_annot(ctx, annot)); + } + } + } + else + { + pdf_set_annot_hot(ctx, annot, 0); + } + + if (annot == ui.selected_annot) + { + switch (subtype) + { + default: + break; + + /* Popup window */ + case PDF_ANNOT_POPUP: + do_edit_rect(canvas_area, area, &bounds, 0); + 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, 1); + break; + + case PDF_ANNOT_FREE_TEXT: + do_edit_rect(canvas_area, area, &bounds, 0); + 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, 0); + break; + case PDF_ANNOT_POLYGON: + if (is_draw_mode) + do_edit_polygon(canvas_area, 1); + break; + case PDF_ANNOT_POLY_LINE: + if (is_draw_mode) + do_edit_polygon(canvas_area, 0); + break; + + case PDF_ANNOT_INK: + if (is_draw_mode) + do_edit_ink(canvas_area); + break; + + case PDF_ANNOT_HIGHLIGHT: + case PDF_ANNOT_UNDERLINE: + case PDF_ANNOT_STRIKE_OUT: + case PDF_ANNOT_SQUIGGLY: + case PDF_ANNOT_REDACT: + if (is_draw_mode) + 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); + area = fz_irect_from_rect(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 (ui_mouse_inside(canvas_area) && ui.down) + { + if (!ui.active && ui.hot == nothing) + ui_select_annot(NULL); + } + + if (ui.right) + is_draw_mode = 0; +}
