Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/platform/gl/gl-main.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/platform/gl/gl-main.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,3278 @@ +// 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 <limits.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <time.h> +#ifndef _WIN32 +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> +#endif + +#include "mupdf/helpers/pkcs7-openssl.h" + +#if FZ_ENABLE_JS +#include "mujs.h" +#endif + +#ifndef _WIN32 +#include <sys/stat.h> /* for mkdir */ +#include <unistd.h> /* for getcwd */ +#include <spawn.h> /* for posix_spawn */ +extern char **environ; /* see environ (7) */ +#else +#include <direct.h> /* for getcwd */ +#endif + +#ifdef __APPLE__ +static void cleanup(void); +void glutLeaveMainLoop(void) +{ + cleanup(); + exit(0); +} +#endif + +fz_context *ctx = NULL; +fz_colorspace *profile = NULL; +pdf_document *pdf = NULL; +pdf_page *page = NULL; +fz_stext_page *page_text = NULL; +fz_matrix draw_page_ctm, view_page_ctm, view_page_inv_ctm; +fz_rect page_bounds, draw_page_bounds, view_page_bounds; +fz_irect view_page_area; +char filename[PATH_MAX]; + +enum +{ + /* Screen furniture: aggregate size of unusable space from title bars, task bars, window borders, etc */ + SCREEN_FURNITURE_W = 20, + SCREEN_FURNITURE_H = 40, +}; + +static void open_browser(const char *uri) +{ +#ifndef _WIN32 + char *argv[3]; +#endif + char buf[PATH_MAX]; + +#ifndef _WIN32 + pid_t pid; + int err; +#endif + + /* Relative file: URI, make it absolute! */ + if (!strncmp(uri, "file:", 5) && uri[5] != '/') + { + char buf_base[PATH_MAX]; + char buf_cwd[PATH_MAX]; + fz_dirname(buf_base, filename, sizeof buf_base); + if (getcwd(buf_cwd, sizeof buf_cwd)) + { + fz_snprintf(buf, sizeof buf, "file://%s/%s/%s", buf_cwd, buf_base, uri+5); + fz_cleanname(buf+7); + uri = buf; + } + } + + if (strncmp(uri, "file://", 7) && strncmp(uri, "http://", 7) && strncmp(uri, "https://", 8) && strncmp(uri, "mailto:", 7)) + { + fz_warn(ctx, "refusing to open unknown link (%s)", uri); + return; + } + +#ifdef _WIN32 + ShellExecuteA(NULL, "open", uri, 0, 0, SW_SHOWNORMAL); +#else + const char *browser = getenv("BROWSER"); + if (!browser) + { +#ifdef __APPLE__ + browser = "open"; +#else + browser = "xdg-open"; +#endif + } + + argv[0] = (char*) browser; + argv[1] = (char*) uri; + argv[2] = NULL; + err = posix_spawnp(&pid, browser, NULL, NULL, argv, environ); + if (err) + fz_warn(ctx, "cannot spawn browser '%s': %s", browser, strerror(err)); + +#endif +} + +static const int zoom_list[] = { + 6, 12, 24, 36, 48, 60, 72, 84, 96, 108, + 120, 144, 168, 192, 228, 264, + 300, 350, 400, 450, 500, 550, 600 +}; + +static int zoom_in(int oldres) +{ + int i; + for (i = 0; i < (int)nelem(zoom_list) - 1; ++i) + if (zoom_list[i] <= oldres && zoom_list[i+1] > oldres) + return zoom_list[i+1]; + return zoom_list[i]; +} + +static int zoom_out(int oldres) +{ + int i; + for (i = 0; i < (int)nelem(zoom_list) - 1; ++i) + if (zoom_list[i] < oldres && zoom_list[i+1] >= oldres) + return zoom_list[i]; + return zoom_list[0]; +} + +static const char *paper_size_name(int w, int h) +{ + /* ISO A */ + if (w == 2384 && h == 3370) return "A0"; + if (w == 1684 && h == 2384) return "A1"; + if (w == 1191 && h == 1684) return "A2"; + if (w == 842 && h == 1191) return "A3"; + if (w == 595 && h == 842) return "A4"; + if (w == 420 && h == 595) return "A5"; + if (w == 297 && h == 420) return "A6"; + + /* US */ + if (w == 612 && h == 792) return "Letter"; + if (w == 612 && h == 1008) return "Legal"; + if (w == 792 && h == 1224) return "Ledger"; + if (w == 1224 && h == 792) return "Tabloid"; + + return NULL; +} + +#define MINRES (zoom_list[0]) +#define MAXRES (zoom_list[nelem(zoom_list)-1]) +#define DEFRES 96 + +static char *password = ""; +static char *anchor = NULL; +static float layout_w = FZ_DEFAULT_LAYOUT_W; +static float layout_h = FZ_DEFAULT_LAYOUT_H; +static float layout_em = FZ_DEFAULT_LAYOUT_EM; +static char *layout_css = NULL; +static int layout_use_doc_css = 1; +static int enable_js = 1; +static int tint_white = 0xFFFFF0; +static int tint_black = 0x303030; + +static fz_document *doc = NULL; +static fz_page *fzpage = NULL; +static fz_separations *seps = NULL; +static fz_outline *outline = NULL; +static fz_link *links = NULL; + +static int number = 0; + +static fz_pixmap *page_contents = NULL; +static struct texture page_tex = { 0 }; +static int screen_w = 0, screen_h = 0; +static int scroll_x = 0, scroll_y = 0; +static int canvas_x = 0, canvas_w = 100; +static int canvas_y = 0, canvas_h = 100; + +static int outline_w = 14; /* to be scaled by lineheight */ +static int annotate_w = 12; /* to be scaled by lineheight */ + +static int outline_start_x = 0; + +static int oldbox = FZ_CROP_BOX, currentbox = FZ_CROP_BOX; +static int oldtint = 0, currenttint = 0; +static int oldinvert = 0, currentinvert = 0; +static int oldicc = 1, currenticc = 1; +static int oldaa = 8, currentaa = 8; +static int oldseparations = 1, currentseparations = 1; +static fz_location oldpage = {0,0}, currentpage = {0,0}; +static float oldzoom = DEFRES, currentzoom = DEFRES; +static float oldrotate = 0, currentrotate = 0; +int page_contents_changed = 0; +int page_annots_changed = 0; + +static fz_output *trace_file = NULL; +static char *reflow_options = NULL; +static int isfullscreen = 0; +static int showoutline = 0; +static int showundo = 0; +static int showlayers = 0; +static int showlinks = 0; +static int showsearch = 0; +int showannotate = 0; +int showform = 0; + +#if FZ_ENABLE_JS +static int showconsole = 0; +static int console_h = 14; /* to be scaled by lineheight */ +static pdf_js_console gl_js_console; +static int console_start_y = 0; +#endif + +static const char *tooltip = NULL; + +struct mark +{ + fz_location loc; + fz_point scroll; +}; + +static int history_count = 0; +static struct mark history[256]; +static int future_count = 0; +static struct mark future[256]; +static struct mark marks[10]; + +static char *get_history_filename(void) +{ + static char history_path[PATH_MAX]; + static int once = 0; + if (!once) + { + char *home = getenv("MUPDF_HISTORY"); + if (home) + return home; + home = getenv("XDG_CACHE_HOME"); + if (!home) + home = getenv("HOME"); + if (!home) + home = getenv("USERPROFILE"); + if (!home) + home = "/tmp"; + fz_snprintf(history_path, sizeof history_path, "%s/.mupdf.history", home); + fz_cleanname(history_path); + once = 1; + } + return history_path; +} + +static fz_json *read_history_file_as_json(fz_pool *pool) +{ + fz_buffer *buf = NULL; + const char *json = "{}"; + const char *history_file; + fz_json *result = NULL; + + fz_var(buf); + + history_file = get_history_filename(); + if (strlen(history_file) == 0) + return NULL; + + if (fz_file_exists(ctx, history_file)) + { + fz_try(ctx) + { + buf = fz_read_file(ctx, history_file); + json = fz_string_from_buffer(ctx, buf); + } + fz_catch(ctx) + ; + } + + fz_try(ctx) + { + result = fz_parse_json(ctx, pool, json); + } + fz_catch(ctx) + { + fz_report_error(ctx); + fz_warn(ctx, "can't parse history file"); + result = NULL; + } + fz_drop_buffer(ctx, buf); + + if (result == NULL || result->type != FZ_JSON_OBJECT) + result = fz_json_new_object(ctx, pool); + return result; +} + +static fz_location load_location(fz_json *val) +{ + if (fz_json_is_number(ctx, val)) + return fz_make_location(0, fz_json_to_number(ctx, val) - 1); + if (fz_json_is_array(ctx, val)) + return fz_make_location( + fz_json_to_number(ctx, fz_json_array_get(ctx, val, 0)) - 1, + fz_json_to_number(ctx, fz_json_array_get(ctx, val, 1)) - 1 + ); + return fz_make_location(0, 0); +} + +static fz_json *save_location(fz_pool *pool, fz_location loc) +{ + fz_json *arr; + if (loc.chapter == 0) + { + return fz_json_new_number(ctx, pool, loc.page + 1); + } + else + { + arr = fz_json_new_array(ctx, pool); + fz_json_array_push(ctx, pool, arr, fz_json_new_number(ctx, pool, loc.chapter + 1)); + fz_json_array_push(ctx, pool, arr, fz_json_new_number(ctx, pool, loc.page + 1)); + return arr; + } +} + +static void load_history(void) +{ + char absname[PATH_MAX]; + fz_pool *pool = NULL; + fz_json *json, *item, *arr, *val; + int i, n; + + fz_var(pool); + + if (!fz_realpath(filename, absname)) + return; + + fz_try(ctx) + { + pool = fz_new_pool(ctx); + json = read_history_file_as_json(pool); + if (json) + { + item = fz_json_object_get(ctx, json, absname); + if (item) + { + val = fz_json_object_get(ctx, item, "current"); + if (val) + currentpage = load_location(val); + + arr = fz_json_object_get(ctx, item, "history"); + if (fz_json_is_array(ctx, arr)) + { + history_count = fz_clampi(fz_json_array_length(ctx, arr), 0, nelem(history)); + for (i = 0; i < history_count; ++i) + history[i].loc = load_location(fz_json_array_get(ctx, arr, i)); + } + + arr = fz_json_object_get(ctx, item, "future"); + if (fz_json_is_array(ctx, arr)) + { + future_count = fz_clampi(fz_json_array_length(ctx, arr), 0, nelem(future)); + for (i = 0; i < future_count; ++i) + future[i].loc = load_location(fz_json_array_get(ctx, arr, i)); + } + + arr = fz_json_object_get(ctx, item, "marks"); + if (fz_json_is_array(ctx, arr)) + { + n = fz_clampi(fz_json_array_length(ctx, arr), 0, nelem(marks)); + for (i = 0; i < n; ++i) + marks[i].loc = load_location(fz_json_array_get(ctx, arr, i)); + } + } + + } + } + fz_always(ctx) + { + fz_drop_pool(ctx, pool); + } + fz_catch(ctx) + { + fz_report_error(ctx); + fz_warn(ctx, "Can't read history file."); + } +} + +static void save_history(void) +{ + fz_pool *pool; + char absname[PATH_MAX]; + fz_output *out = NULL; + fz_json *json, *item, *arr; + const char *history_file; + int i; + + fz_var(pool); + fz_var(out); + + if (!doc) + return; + + if (!fz_realpath(filename, absname)) + return; + + fz_try(ctx) + { + pool = fz_new_pool(ctx); + json = read_history_file_as_json(pool); + if (json) + { + item = fz_json_new_object(ctx, pool); + fz_json_object_set(ctx, pool, item, "current", save_location(pool, currentpage)); + + arr = fz_json_new_array(ctx, pool); + for (i = 0; i < history_count; ++i) + fz_json_array_push(ctx, pool, arr, save_location(pool, history[i].loc)); + fz_json_object_set(ctx, pool, item, "history", arr); + + arr = fz_json_new_array(ctx, pool); + for (i = 0; i < future_count; ++i) + fz_json_array_push(ctx, pool, arr, save_location(pool, future[i].loc)); + fz_json_object_set(ctx, pool, item, "future", arr); + + arr = fz_json_new_array(ctx, pool); + for (i = 0; i < (int)nelem(marks); ++i) + fz_json_array_push(ctx, pool, arr, save_location(pool, marks[i].loc)); + fz_json_object_set(ctx, pool, item, "marks", arr); + + fz_json_object_set(ctx, pool, json, absname, item); + + history_file = get_history_filename(); + if (strlen(history_file) > 0) { + out = fz_new_output_with_path(ctx, history_file, 0); + fz_write_json(ctx, out, json); + fz_write_byte(ctx, out, '\n'); + fz_close_output(ctx, out); + } + } + } + fz_always(ctx) + { + fz_drop_pool(ctx, pool); + fz_drop_output(ctx, out); + } + fz_catch(ctx) + { + fz_report_error(ctx); + fz_warn(ctx, "Can't write history file."); + } +} + + +static int create_accel_path(char outname[], size_t len, int create, const char *absname, ...) +{ + va_list args; + char *s = outname; + size_t z, remain = len; + char *arg; + + va_start(args, absname); + + while ((arg = va_arg(args, char *)) != NULL) + { + z = fz_snprintf(s, remain, "%s", arg); + if (z+1 > remain) + goto fail; /* won't fit */ + + if (create) + (void) fz_mkdir(outname); + if (!fz_is_directory(ctx, outname)) + goto fail; /* directory creation failed, or that dir doesn't exist! */ +#ifdef _WIN32 + s[z] = '\\'; +#else + s[z] = '/'; +#endif + s[z+1] = 0; + s += z+1; + remain -= z+1; + } + + if (fz_snprintf(s, remain, "%s.accel", absname) >= remain) + goto fail; /* won't fit */ + + va_end(args); + + return 1; + +fail: + va_end(args); + + return 0; +} + +static int convert_to_accel_path(char outname[], char *absname, size_t len, int create) +{ + char *tmpdir; + char *s; + + if (absname[0] == '/' || absname[0] == '\\') + ++absname; + + s = absname; + while (*s) { + if (*s == '/' || *s == '\\' || *s == ':') + *s = '%'; + ++s; + } + +#ifdef _WIN32 + tmpdir = getenv("USERPROFILE"); + if (tmpdir && create_accel_path(outname, len, create, absname, tmpdir, ".config", "mupdf", NULL)) + return 1; /* OK! */ + /* TEMP and TMP are user-specific on modern windows. */ + tmpdir = getenv("TEMP"); + if (tmpdir && create_accel_path(outname, len, create, absname, tmpdir, "mupdf", NULL)) + return 1; /* OK! */ + tmpdir = getenv("TMP"); + if (tmpdir && create_accel_path(outname, len, create, absname, tmpdir, "mupdf", NULL)) + return 1; /* OK! */ +#else + tmpdir = getenv("XDG_CACHE_HOME"); + if (tmpdir && create_accel_path(outname, len, create, absname, tmpdir, "mupdf", NULL)) + return 1; /* OK! */ + tmpdir = getenv("HOME"); + if (tmpdir && create_accel_path(outname, len, create, absname, tmpdir, ".cache", "mupdf", NULL)) + return 1; /* OK! */ +#endif + return 0; /* Fail */ +} + +static int get_accelerator_filename(char outname[], size_t len, int create) +{ + char absname[PATH_MAX]; + if (!fz_realpath(filename, absname)) + return 0; + if (!convert_to_accel_path(outname, absname, len, create)) + return 0; + return 1; +} + +static void save_accelerator(void) +{ + char absname[PATH_MAX]; + + if (!doc) + return; + if (!fz_document_supports_accelerator(ctx, doc)) + return; + if (!get_accelerator_filename(absname, sizeof(absname), 1)) + return; + + fz_save_accelerator(ctx, doc, absname); +} + +static struct input search_input = { { 0 }, 0 }; +static int search_dir = 1; +static fz_location search_page = {-1, -1}; +static fz_location search_hit_page = {-1, -1}; +static int search_active = 0; +char *search_needle = 0; +int search_hit_count = 0; +fz_quad search_hit_quads[5000]; + +static char *help_dialog_text = + "The middle mouse button (scroll wheel button) pans the document view. " + "The right mouse button selects a region and copies the marked text to the clipboard." + "\n" + "\n" + "F1 - show this message\n" + "` F12 - show javascript console\n" + "i - show document information\n" + "o - show document outline\n" + "u - show undo history\n" + "Y - show layer list\n" + "a - show annotation editor\n" + "R - show redaction editor\n" + "L - highlight links\n" + "F - highlight form fields\n" + "r - reload file\n" + "S - save file (only for PDF)\n" + "q - quit\n" + "\n" + "< - decrease E-book font size\n" + "> - increase E-book font size\n" + "B - cycle between MediaBox, CropBox, ArtBox, etc.\n" + "A - toggle anti-aliasing\n" + "I - toggle inverted color mode\n" + "C - toggle tinted color mode\n" + "E - toggle ICC color management\n" + "e - toggle spot color emulation\n" + "\n" + "f - fullscreen window\n" + "w - shrink wrap window\n" + "W - fit to width\n" + "H - fit to height\n" + "Z - fit to page\n" + "z - reset zoom\n" + "[number] z - set zoom resolution in DPI\n" + "plus - zoom in\n" + "minus - zoom out\n" + "[ - rotate counter-clockwise\n" + "] - rotate clockwise\n" + "arrow keys - scroll in small increments\n" + "h, j, k, l - scroll in small increments\n" + "\n" + "b - smart move backward\n" + "space - smart move forward\n" + "comma or page up - go backward\n" + "period or page down - go forward\n" + "g - go to first page\n" + "G - go to last page\n" + "[number] g - go to page number\n" + "\n" + "m - save current location in history\n" + "t - go backward in history\n" + "T - go forward in history\n" + "[number] m - save current location in numbered bookmark\n" + "[number] t - go to numbered bookmark\n" + "\n" + "/ - search for text forward\n" + "? - search for text backward\n" + "n - repeat search\n" + "N - repeat search in reverse direction" + ; + +static void help_dialog(void) +{ + static int scroll; + ui_dialog_begin(ui.gridsize*20, ui.gridsize*40); + ui_layout(T, X, W, ui.padsize, ui.padsize); + ui_label("MuPDF %s", FZ_VERSION); + ui_spacer(); + ui_layout(B, NONE, S, ui.padsize, ui.padsize); + if (ui_button("Okay") || ui.key == KEY_ENTER || ui.key == KEY_ESCAPE) + ui.dialog = NULL; + ui_spacer(); + ui_layout(ALL, BOTH, CENTER, ui.padsize, ui.padsize); + ui_label_with_scrollbar(help_dialog_text, 0, 0, &scroll, NULL); + ui_dialog_end(); +} + +static fz_buffer *format_info_text(); + +static void info_dialog(void) +{ + static int scroll; + fz_buffer *info_text; + + ui_dialog_begin(ui.gridsize*20, ui.gridsize*20); + ui_layout(B, NONE, S, ui.padsize, ui.padsize); + if (ui_button("Okay") || ui.key == KEY_ENTER || ui.key == KEY_ESCAPE) + ui.dialog = NULL; + ui_spacer(); + ui_layout(ALL, BOTH, CENTER, ui.padsize, ui.padsize); + + info_text = format_info_text(); + ui_label_with_scrollbar((char*)fz_string_from_buffer(ctx, info_text), 0, 0, &scroll, NULL); + fz_drop_buffer(ctx, info_text); + + ui_dialog_end(); +} + +static char error_message[256]; +static void error_dialog(void) +{ + ui_dialog_begin(ui.gridsize*20, (ui.gridsize+ui.padsize*2)*4); + ui_layout(T, NONE, NW, ui.padsize, ui.padsize); + ui_label("%C %s", 0x1f4a3, error_message); /* BOMB */ + ui_layout(B, NONE, S, ui.padsize, ui.padsize); + if (ui_button("Quit") || ui.key == KEY_ENTER || ui.key == KEY_ESCAPE || ui.key == 'q') + glutLeaveMainLoop(); + ui_dialog_end(); +} +void ui_show_error_dialog(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fz_vsnprintf(error_message, sizeof error_message, fmt, ap); + va_end(ap); + ui.dialog = error_dialog; +} + +static char warning_message[256]; +static void warning_dialog(void) +{ + ui_dialog_begin(ui.gridsize*20, (ui.gridsize+ui.padsize*2)*4); + ui_layout(T, NONE, NW, ui.padsize, ui.padsize); + ui_label("%C %s", 0x26a0, warning_message); /* WARNING SIGN */ + ui_layout(B, NONE, S, ui.padsize, ui.padsize); + if (ui_button("Okay") || ui.key == KEY_ENTER || ui.key == KEY_ESCAPE) + ui.dialog = NULL; + ui_dialog_end(); +} +void ui_show_warning_dialog(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fz_vsnprintf(warning_message, sizeof warning_message, fmt, ap); + va_end(ap); + ui.dialog = warning_dialog; +} + +static void quit_dialog(void) +{ + ui_dialog_begin(ui.gridsize*20, (ui.gridsize+ui.padsize*2)*3); + ui_layout(T, NONE, NW, ui.padsize, ui.padsize); + ui_label("%C The document has unsaved changes. Are you sure you want to quit?", 0x26a0); /* WARNING SIGN */ + ui_layout(B, X, S, ui.padsize, ui.padsize); + ui_panel_begin(0, ui.gridsize, 0, 0, 0); + { + ui_layout(R, NONE, S, 0, 0); + if (ui_button("Save")) + do_save_pdf_file(); + ui_spacer(); + if (ui_button("Discard") || ui.key == 'q') + glutLeaveMainLoop(); + ui_layout(L, NONE, S, 0, 0); + if (ui_button("Cancel") || ui.key == KEY_ESCAPE) + ui.dialog = NULL; + } + ui_panel_end(); + ui_dialog_end(); +} + +static void quit(void) +{ + if (pdf && pdf_has_unsaved_changes(ctx, pdf)) + ui.dialog = quit_dialog; + else + glutLeaveMainLoop(); +} + +static void reload_dialog(void) +{ + ui_dialog_begin(ui.gridsize*20, (ui.gridsize+ui.padsize*2)*3); + ui_layout(T, NONE, NW, ui.padsize, ui.padsize); + ui_label("%C The document has unsaved changes. Are you sure you want to reload?", 0x26a0); /* WARNING SIGN */ + ui_layout(B, X, S, ui.padsize, ui.padsize); + ui_panel_begin(0, ui.gridsize, 0, 0, 0); + { + ui_layout(R, NONE, S, 0, 0); + if (ui_button("Save")) + do_save_pdf_file(); + ui_spacer(); + if (ui_button("Reload") || ui.key == 'q') + { + ui.dialog = NULL; + reload_document(); + } + ui_layout(L, NONE, S, 0, 0); + if (ui_button("Cancel") || ui.key == KEY_ESCAPE) + ui.dialog = NULL; + } + ui_panel_end(); + ui_dialog_end(); +} + +void reload(void) +{ + if (pdf && pdf_has_unsaved_changes(ctx, pdf)) + ui.dialog = reload_dialog; + else + reload_document(); +} + +void trace_action(const char *fmt, ...) +{ + va_list args; + if (trace_file) + { + va_start(args, fmt); + fz_write_vprintf(ctx, trace_file, fmt, args); + fz_flush_output(ctx, trace_file); + va_end(args); + va_start(args, fmt); + fz_write_vprintf(ctx, fz_stdout(ctx), fmt, args); + fz_flush_output(ctx, fz_stdout(ctx)); + va_end(args); + } +} + +void trace_page_update(void) +{ + trace_action("page.update();\n"); +} + +void trace_save_snapshot(void) +{ + static int trace_idx = 1; + trace_action("page.toPixmap(Matrix.identity, ColorSpace.DeviceRGB).saveAsPNG(\"trace-%03d.png\");\n", trace_idx++); +} + +static int document_shown_as_dirty = 0; + +void update_title(void) +{ + char buf[256]; + const char *title = "MuPDF/GL"; + char *extra = ""; + size_t n; + + int nc = fz_count_chapters(ctx, doc); + + title = fz_basename(filename); + + document_shown_as_dirty = pdf && pdf_has_unsaved_changes(ctx, pdf); + if (document_shown_as_dirty) + extra = "*"; + + n = strlen(title); + if (n > 50) + { + if (nc == 1) + sprintf(buf, "...%s%s - %d/%d", title + n - 50, extra, currentpage.page + 1, fz_count_pages(ctx, doc)); + else + sprintf(buf, "...%s%s - %d/%d - %d/%d", title + n - 50, extra, + currentpage.chapter + 1, nc, + currentpage.page + 1, fz_count_chapter_pages(ctx, doc, currentpage.chapter)); + } + else + { + if (nc == 1) + sprintf(buf, "%s%s - %d/%d", title, extra, currentpage.page + 1, fz_count_pages(ctx, doc)); + else + + sprintf(buf, "%s%s - %d/%d - %d/%d", title, extra, + currentpage.chapter + 1, nc, + currentpage.page + 1, fz_count_chapter_pages(ctx, doc, currentpage.chapter)); + } + glutSetWindowTitle(buf); + glutSetIconTitle(buf); +} + +void transform_page(void) +{ + draw_page_ctm = fz_transform_page(page_bounds, currentzoom, currentrotate); + draw_page_bounds = fz_transform_rect(page_bounds, draw_page_ctm); +} + +static void clear_selected_annot(void) +{ + /* clear all editor selections */ + if (ui.selected_annot && pdf_annot_type(ctx, ui.selected_annot) == PDF_ANNOT_WIDGET) + pdf_annot_event_blur(ctx, ui.selected_annot); + ui_select_annot(NULL); +} + +void load_page(void) +{ + fz_irect area; + + clear_selected_annot(); + + if (trace_file) + trace_action("page = doc.loadPage(%d);\n", fz_page_number_from_location(ctx, doc, currentpage)); + + fz_drop_stext_page(ctx, page_text); + page_text = NULL; + fz_drop_separations(ctx, seps); + seps = NULL; + fz_drop_link(ctx, links); + links = NULL; + fz_drop_page(ctx, fzpage); + fzpage = NULL; + + fzpage = fz_load_chapter_page(ctx, doc, currentpage.chapter, currentpage.page); + if (pdf) + page = (pdf_page*)fzpage; + + if (trace_file) + { + pdf_annot *w; + int i, s; + + for (i = 0, s = 0, w = pdf_first_widget(ctx, page); w != NULL; i++, w = pdf_next_widget(ctx, w)) + if (pdf_widget_type(ctx, w) == PDF_WIDGET_TYPE_SIGNATURE) + { + int is_signed; + + s++; + trace_action("widget = page.getWidgets()[%d];\n", i); + trace_action("widgetstr = 'Signature %d on page %d';\n", + s, fz_page_number_from_location(ctx, doc, currentpage)); + + is_signed = pdf_widget_is_signed(ctx, w); + trace_action("tmp = widget.isSigned();\n"); + trace_action("if (tmp != %d)\n", is_signed); + trace_action(" throw new RegressionError(widgetstr, 'is signed:', tmp|0, 'expected:', %d);\n", is_signed); + + if (is_signed) + { + int valid_until, is_readonly; + char *cert_error, *digest_error; + pdf_pkcs7_distinguished_name *dn; + pdf_pkcs7_verifier *verifier; + char *signatory = NULL; + char buf[500]; + + valid_until = pdf_validate_signature(ctx, w); + is_readonly = pdf_widget_is_readonly(ctx, w); + verifier = pkcs7_openssl_new_verifier(ctx); + cert_error = pdf_signature_error_description(pdf_check_widget_certificate(ctx, verifier, w)); + digest_error = pdf_signature_error_description(pdf_check_widget_digest(ctx, verifier, w)); + dn = pdf_signature_get_widget_signatory(ctx, verifier, w); + if (dn) + { + char *s = pdf_signature_format_distinguished_name(ctx, dn); + fz_strlcpy(buf, s, sizeof buf); + fz_free(ctx, s); + pdf_signature_drop_distinguished_name(ctx, dn); + } + else + { + fz_strlcpy(buf, "Signature information missing.", sizeof buf); + } + signatory = &buf[0]; + pdf_drop_verifier(ctx, verifier); + + trace_action("tmp = widget.validateSignature();\n"); + trace_action("if (tmp != %d)\n", valid_until); + trace_action(" throw new RegressionError(widgetstr, 'valid until:', tmp, 'expected:', %d);\n", valid_until); + trace_action("tmp = widget.isReadOnly();\n"); + trace_action("if (tmp != %d)\n", is_readonly); + trace_action(" throw new RegressionError(widgetstr, 'is read-only:', tmp, 'expected:', %d);\n", is_readonly); + trace_action("tmp = widget.checkCertificate();\n"); + trace_action("if (tmp != '%s')\n", cert_error); + trace_action(" throw new RegressionError(widgetstr, 'certificate error:', tmp, 'expected:', %d);\n", cert_error); + trace_action("tmp = widget.checkDigest();\n"); + trace_action("if (tmp != %q)\n", digest_error); + trace_action(" throw new RegressionError(widgetstr, 'digest error:', tmp, 'expected:', %q);\n", digest_error); + trace_action("tmp = widget.getSignatory();\n"); + trace_action("if (tmp != '%s')\n", signatory); + trace_action(" throw new RegressionError(widgetstr, 'signatory:', '[', tmp, ']', 'expected:', '[', %q, ']');\n", signatory); + } + } + } + + links = fz_load_links(ctx, fzpage); + page_text = fz_new_stext_page_from_page(ctx, fzpage, NULL); + + if (currenticc) + fz_enable_icc(ctx); + else + fz_disable_icc(ctx); + + if (currentseparations) + { + seps = fz_page_separations(ctx, fzpage); + if (seps) + { + int i, n = fz_count_separations(ctx, seps); + for (i = 0; i < n; i++) + fz_set_separation_behavior(ctx, seps, i, FZ_SEPARATION_COMPOSITE); + } + else if (fz_page_uses_overprint(ctx, fzpage)) + seps = fz_new_separations(ctx, 0); + else if (fz_document_output_intent(ctx, doc)) + seps = fz_new_separations(ctx, 0); + } + + /* compute bounds here for initial window size */ + page_bounds = fz_bound_page_box(ctx, fzpage, currentbox); + transform_page(); + + area = fz_irect_from_rect(draw_page_bounds); + page_tex.w = area.x1 - area.x0; + page_tex.h = area.y1 - area.y0; + + page_contents_changed = 1; +} + +static void render_page(void) +{ + fz_irect bbox; + fz_pixmap *pix; + fz_device *dev; + + page_bounds = fz_bound_page_box(ctx, fzpage, currentbox); + transform_page(); + + fz_set_aa_level(ctx, currentaa); + + if (page_contents_changed) + { + fz_drop_pixmap(ctx, page_contents); + page_contents = NULL; + + bbox = fz_round_rect(fz_transform_rect(fz_bound_page_box(ctx, fzpage, currentbox), draw_page_ctm)); + page_contents = fz_new_pixmap_with_bbox(ctx, profile, bbox, seps, 0); + fz_clear_pixmap(ctx, page_contents); + + dev = fz_new_draw_device(ctx, draw_page_ctm, page_contents); + + fz_try(ctx) + { + fz_run_page_contents(ctx, fzpage, dev, fz_identity, NULL); + fz_close_device(ctx, dev); + } + fz_always(ctx) + fz_drop_device(ctx, dev); + fz_catch(ctx) + fz_rethrow(ctx); + } + + pix = fz_clone_pixmap_area_with_different_seps(ctx, page_contents, NULL, profile, NULL, fz_default_color_params, NULL); + { + dev = fz_new_draw_device(ctx, draw_page_ctm, pix); + fz_try(ctx) + { + fz_run_page_annots(ctx, fzpage, dev, fz_identity, NULL); + fz_run_page_widgets(ctx, fzpage, dev, fz_identity, NULL); + fz_close_device(ctx, dev); + } + fz_always(ctx) + fz_drop_device(ctx, dev); + fz_catch(ctx) + fz_rethrow(ctx); + } + + if (currentinvert) + { + fz_invert_pixmap_luminance(ctx, pix); + fz_gamma_pixmap(ctx, pix, 1 / 1.4f); + } + if (currenttint) + { + fz_tint_pixmap(ctx, pix, tint_black, tint_white); + } + + ui_texture_from_pixmap(&page_tex, pix); + + fz_drop_pixmap(ctx, pix); + + FZ_LOG_DUMP_STORE(ctx, "Store state after page render:\n"); +} + +void render_page_if_changed(void) +{ + if (pdf) + { + if (pdf_update_page(ctx, page)) + { + trace_page_update(); + page_annots_changed = 1; + } + } + + if (oldpage.chapter != currentpage.chapter || + oldpage.page != currentpage.page || + oldzoom != currentzoom || + oldrotate != currentrotate || + oldinvert != currentinvert || + oldtint != currenttint || + oldicc != currenticc || + oldseparations != currentseparations || + oldaa != currentaa || + oldbox != currentbox) + { + page_contents_changed = 1; + } + + if (page_contents_changed || page_annots_changed) + { + render_page(); + oldpage = currentpage; + oldzoom = currentzoom; + oldrotate = currentrotate; + oldinvert = currentinvert; + oldtint = currenttint; + oldicc = currenticc; + oldseparations = currentseparations; + oldaa = currentaa; + oldbox = currentbox; + page_contents_changed = 0; + page_annots_changed = 0; + } +} + +static struct mark save_mark() +{ + struct mark mark; + mark.loc = currentpage; + mark.scroll = fz_transform_point_xy(scroll_x, scroll_y, view_page_inv_ctm); + return mark; +} + +static void restore_mark(struct mark mark) +{ + currentpage = mark.loc; + mark.scroll = fz_transform_point(mark.scroll, draw_page_ctm); + scroll_x = mark.scroll.x; + scroll_y = mark.scroll.y; +} + +static int eqloc(fz_location a, fz_location b) +{ + return a.chapter == b.chapter && a.page == b.page; +} + +int search_has_results(void) +{ + return !search_active && eqloc(search_hit_page, currentpage) && search_hit_count > 0; +} + +static int is_first_page(fz_location loc) +{ + return (loc.chapter == 0 && loc.page == 0); +} + +static int is_last_page(fz_location loc) +{ + fz_location last = fz_last_page(ctx, doc); + return (loc.chapter == last.chapter && loc.page == last.page); +} + +static void push_history(void) +{ + if (history_count > 0 && eqloc(history[history_count-1].loc, currentpage)) + return; + if (history_count + 1 >= (int)nelem(history)) + { + memmove(history, history + 1, sizeof *history * (nelem(history) - 1)); + history[history_count] = save_mark(); + } + else + { + history[history_count++] = save_mark(); + } +} + +static void push_future(void) +{ + if (future_count + 1 >= (int)nelem(future)) + { + memmove(future, future + 1, sizeof *future * (nelem(future) - 1)); + future[future_count] = save_mark(); + } + else + { + future[future_count++] = save_mark(); + } +} + +static void clear_future(void) +{ + future_count = 0; +} + +static void jump_to_location(fz_location loc) +{ + clear_future(); + push_history(); + currentpage = fz_clamp_location(ctx, doc, loc); + push_history(); +} + +static void jump_to_location_xy(fz_location loc, float x, float y) +{ + fz_point p = fz_transform_point_xy(x, y, draw_page_ctm); + clear_future(); + push_history(); + currentpage = fz_clamp_location(ctx, doc, loc); + scroll_x = p.x; + scroll_y = p.y; + push_history(); +} + +static void jump_to_page(int newpage) +{ + clear_future(); + push_history(); + currentpage = fz_location_from_page_number(ctx, doc, newpage); + currentpage = fz_clamp_location(ctx, doc, currentpage); + push_history(); +} + +static void jump_to_page_xy(int newpage, float x, float y) +{ + fz_point p = fz_transform_point_xy(x, y, draw_page_ctm); + clear_future(); + push_history(); + currentpage = fz_location_from_page_number(ctx, doc, newpage); + currentpage = fz_clamp_location(ctx, doc, currentpage); + scroll_x = p.x; + scroll_y = p.y; + push_history(); +} + +static void pop_history(void) +{ + fz_location here = currentpage; + push_future(); + while (history_count > 0 && eqloc(currentpage, here)) + restore_mark(history[--history_count]); +} + +static void pop_future(void) +{ + fz_location here = currentpage; + push_history(); + while (future_count > 0 && eqloc(currentpage, here)) + restore_mark(future[--future_count]); + push_history(); +} + +static void relayout(void) +{ + if (layout_em < 6) layout_em = 6; + if (layout_em > 36) layout_em = 36; + if (fz_is_document_reflowable(ctx, doc)) + { + fz_bookmark mark = fz_make_bookmark(ctx, doc, currentpage); + fz_layout_document(ctx, doc, layout_w, layout_h, layout_em); + currentpage = fz_lookup_bookmark(ctx, doc, mark); + history_count = 0; + future_count = 0; + + load_page(); + update_title(); + } +} + +static int count_outline(fz_outline *node, int end) +{ + int is_selected, n, p, np; + int count = 0; + + if (!node) + return 0; + np = fz_page_number_from_location(ctx, doc, node->page); + + do + { + p = np; + count += 1; + n = end; + if (node->next && (np = fz_page_number_from_location(ctx, doc, node->next->page)) >= 0) + n = fz_page_number_from_location(ctx, doc, node->next->page); + is_selected = 0; + if (fz_count_chapters(ctx, doc) == 1) + is_selected = (p>=0) && (currentpage.page == p || (currentpage.page > p && currentpage.page < n)); + if (node->down && (node->is_open || is_selected)) + count += count_outline(node->down, end); + node = node->next; + } + while (node); + + return count; +} + +static void do_outline_imp(struct list *list, int end, fz_outline *node, int depth) +{ + int is_selected, is_open, was_open, n, np; + + if (!node) + return; + + np = fz_page_number_from_location(ctx, doc, node->page); + + do + { + int p = np; + n = end; + if (node->next && (np = fz_page_number_from_location(ctx, doc, node->next->page)) >= 0) + n = np; + + is_open = was_open = node->is_open; + is_selected = 0; + if (fz_count_chapters(ctx, doc) == 1) + is_selected = (p>=0) && (currentpage.page == p || (currentpage.page > p && currentpage.page < n)); + if (ui_tree_item(list, node, node->title, is_selected, depth, !!node->down, &is_open)) + { + node->is_open = is_open; + if (p < 0) + { + currentpage = fz_resolve_link(ctx, doc, node->uri, &node->x, &node->y); + jump_to_location_xy(currentpage, node->x, node->y); + } + else + { + jump_to_page_xy(p, node->x, node->y); + } + } + node->is_open = is_open; + + if (node->down && (was_open || is_selected)) + do_outline_imp(list, n, node->down, depth + 1); + node = node->next; + } + while (node); +} + +static void do_outline(fz_outline *node) +{ + static struct list list; + ui_layout(L, BOTH, NW, 0, 0); + ui_tree_begin(&list, count_outline(node, 65535), outline_w, 0, 1); + do_outline_imp(&list, 65535, node, 0); + ui_tree_end(&list); +} + +static void do_undo(void) +{ + static struct list list; + int count = 0; + int pos; + int i; + int desired = -1; + + if (pdf) + pos = pdf_undoredo_state(ctx, pdf, &count); + else + pos = 0; + ui_layout(L, BOTH, NW, 0, 0); + ui_panel_begin(outline_w, 0, ui.padsize*2, ui.padsize*2, 1); + ui_layout(T, X, NW, ui.padsize, ui.padsize); + ui_label("Undo history:"); + + ui_layout(B, X, NW, ui.padsize, ui.padsize); + if (ui_button_aux("Redo", pos == count)) + desired = pos+1; + if (ui_button_aux("Undo", pos == 0)) + desired = pos-1; + + ui_layout(ALL, BOTH, NW, ui.padsize, ui.padsize); + ui_list_begin(&list, count+1, 0, ui.lineheight * 4 + 4); + + for (i = 0; i < count+1; i++) + { + const char *op; + + if (i == 0) + op = "Original Document"; + else + op = pdf_undoredo_step(ctx, pdf, i-1); + if (ui_list_item(&list, (void *)(intptr_t)(i+1), op, i <= pos)) + { + desired = i; + } + } + + ui_list_end(&list); + + if (desired != -1 && desired != pos) + { + clear_selected_annot(); + page_contents_changed = 1; + while (pos > desired) + { + trace_action("doc.undo();\n"); + pdf_undo(ctx, pdf); + pos--; + } + while (pos < desired) + { + trace_action("doc.redo();\n"); + pdf_redo(ctx, pdf); + pos++; + } + load_page(); + } + + ui_panel_end(); +} + +static void do_layers(void) +{ + const char *name; + int n, i, on; + + ui_layout(L, BOTH, NW, 0, 0); + ui_panel_begin(outline_w, 0, ui.padsize*2, ui.padsize*2, 1); + ui_layout(T, X, NW, ui.padsize, ui.padsize); + ui_label("Layers:"); + ui_layout(T, X, NW, ui.padsize*2, ui.padsize); + + if (pdf) + { + n = pdf_count_layers(ctx, pdf); + for (i = 0; i < n; ++i) + { + name = pdf_layer_name(ctx, pdf, i); + on = pdf_layer_is_enabled(ctx, pdf, i); + if (ui_checkbox(name, &on)) + { + pdf_enable_layer(ctx, pdf, i, on); + page_contents_changed = 1; + } + } + if (n == 0) + ui_label("None"); + } + else + { + ui_label("None"); + } + + ui_panel_end(); +} + +static void do_links(fz_link *link) +{ + fz_rect bounds; + fz_irect area; + float link_x, link_y; + + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + + tooltip = NULL; + + while (link) + { + bounds = link->rect; + bounds = fz_transform_rect(link->rect, view_page_ctm); + area = fz_irect_from_rect(bounds); + + if (ui_mouse_inside(area)) + { + if (!tooltip) + tooltip = link->uri; + ui.hot = link; + if (!ui.active && ui.down) + ui.active = link; + } + + if (ui.hot == link || showlinks) + { + if (ui.active == link && ui.hot == link) + glColor4f(0, 0, 1, 0.4f); + else if (ui.hot == link) + glColor4f(0, 0, 1, 0.2f); + else + glColor4f(0, 0, 1, 0.1f); + glRectf(area.x0, area.y0, area.x1, area.y1); + } + + if (ui.active == link && !ui.down) + { + if (ui.hot == link) + { + if (fz_is_external_link(ctx, link->uri)) + open_browser(link->uri); + else + { + fz_location loc = fz_resolve_link(ctx, doc, link->uri, &link_x, &link_y); + jump_to_location_xy(loc, link_x, link_y); + } + } + } + + link = link->next; + } + + glDisable(GL_BLEND); +} + +static void do_page_selection(void) +{ + static fz_point pt = { 0, 0 }; + static fz_quad hits[1000]; + fz_rect rect; + int i, n; + + if (ui_mouse_inside(view_page_area)) + { + ui.hot = &pt; + if (!ui.active && ui.right) + { + ui.active = &pt; + pt.x = ui.x; + pt.y = ui.y; + } + } + + if (ui.active == &pt) + { + 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, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + glColor4f(0.0, 0.1, 0.4, 0.3f); + + 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); + + if (!ui.right) + { + char *s; +#ifdef _WIN32 + if (ui.mod == GLUT_ACTIVE_SHIFT) + s = fz_copy_rectangle(ctx, page_text, rect, 1); + else + s = fz_copy_selection(ctx, page_text, page_a, page_b, 1); +#else + if (ui.mod == GLUT_ACTIVE_SHIFT) + s = fz_copy_rectangle(ctx, page_text, rect, 0); + else + s = fz_copy_selection(ctx, page_text, page_a, page_b, 0); +#endif + ui_set_clipboard(s); + fz_free(ctx, s); + } + } +} + +static void do_search_hits(void) +{ + int i; + + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + + glColor4f(1, 0, 0, 0.4f); + glBegin(GL_QUADS); + for (i = 0; i < search_hit_count; ++i) + { + fz_quad thit = fz_transform_quad(search_hit_quads[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); +} + +static void toggle_fullscreen(void) +{ + static int win_x = 0, win_y = 0; + static int win_w = 100, win_h = 100; + if (!isfullscreen) + { + win_w = glutGet(GLUT_WINDOW_WIDTH); + win_h = glutGet(GLUT_WINDOW_HEIGHT); + win_x = glutGet(GLUT_WINDOW_X); + win_y = glutGet(GLUT_WINDOW_Y); + glutFullScreen(); + isfullscreen = 1; + } + else + { + glutPositionWindow(win_x, win_y); + glutReshapeWindow(win_w, win_h); + isfullscreen = 0; + } +} + +static void shrinkwrap(void) +{ + int w = page_tex.w; + int h = page_tex.h; + if (showoutline || showundo || showlayers) + w += outline_w + 4; + if (showannotate) + w += annotate_w; +#if FZ_ENABLE_JS + if (showconsole) + h += console_h; +#endif + if (screen_w > 0 && w > screen_w) + w = screen_w; + if (screen_h > 0 && h > screen_h) + h = screen_h; + if (isfullscreen) + toggle_fullscreen(); + glutReshapeWindow(w, h); +} + +static struct input input_password; +static void password_dialog(void) +{ + int is; + ui_dialog_begin(ui.gridsize*16, (ui.gridsize+ui.padsize*2)*3); + { + ui_layout(T, X, NW, ui.padsize, ui.padsize); + ui_label("Password:"); + is = ui_input(&input_password, 200, 1); + + ui_layout(B, X, NW, ui.padsize, ui.padsize); + ui_panel_begin(0, ui.gridsize, 0, 0, 0); + { + ui_layout(R, NONE, S, 0, 0); + if (ui_button("Cancel") || (!ui.focus && ui.key == KEY_ESCAPE)) + glutLeaveMainLoop(); + ui_spacer(); + if (ui_button("Okay") || is == UI_INPUT_ACCEPT) + { + password = input_password.text; + ui.dialog = NULL; + reload_document(); + shrinkwrap(); + } + } + ui_panel_end(); + } + ui_dialog_end(); +} + +/* Parse "chapter:page" from anchor. "chapter:" is also accepted, + * meaning first page. Return 1 if parsing succeeded, 0 if failed. + */ +static int +parse_location(const char *anc, fz_location *loc) +{ + const char *s, *p; + + if (anc == NULL) + return 0; + + s = anc; + while (*s >= '0' && *s <= '9') + s++; + loc->chapter = fz_atoi(anc)-1; + if (*s == 0) + { + *loc = fz_location_from_page_number(ctx, doc, loc->chapter); + return 1; + } + if (*s != ':') + return 0; + p = ++s; + while (*s >= '0' && *s <= '9') + s++; + if (s == p) + loc->page = 0; + else + loc->page = fz_atoi(p)-1; + + return 1; +} + +static void +reload_or_start_journalling(void) +{ + char journal[PATH_MAX]; + + fz_strlcpy(journal, filename, sizeof(journal)); + fz_strlcat(journal, ".journal", sizeof(journal)); + + fz_try(ctx) + { + /* Probe with fz_file_exists to avoid 'can't find' errors. */ + if (fz_file_exists(ctx, journal)) + pdf_load_journal(ctx, pdf, journal); + } + fz_catch(ctx) + { + /* Ignore any failures here. */ + } + trace_action("doc.enableJournal();\n"); + pdf_enable_journal(ctx, pdf); +} + +static void alert_box(const char *fmt, const char *str) +{ +#ifdef _WIN32 + MessageBoxA(NULL, str, "MuPDF Alert", MB_ICONERROR); +#else + fprintf(stderr, "MuPDF Alert: %s\n", str); +#endif +} + + +static void event_cb(fz_context *callback_ctx, pdf_document *callback_doc, pdf_doc_event *evt, void *data) +{ + switch (evt->type) + { + case PDF_DOCUMENT_EVENT_ALERT: + { + pdf_alert_event *alert = pdf_access_alert_event(callback_ctx, evt); + alert_box("%s", alert->message); + } + break; + + default: + fz_throw(callback_ctx, FZ_ERROR_UNSUPPORTED, "event not yet implemented"); + break; + } +} + +static void load_document(void) +{ + char accelpath[PATH_MAX]; + char *accel = NULL; + time_t atime; + time_t dtime; + fz_location location; + + fz_drop_outline(ctx, outline); + outline = NULL; + fz_drop_document(ctx, doc); + doc = NULL; + + if (!strncmp(filename, "file://", 7)) + { + anchor = strchr(filename + 7, '#'); + if (anchor) + { + memmove(anchor + 1, anchor, strlen(anchor) + 1); + *anchor = 0; + anchor++; + } + memmove(filename, filename + 7, strlen(filename)); + } + + /* If there was an accelerator to load, what would it be called? */ + if (get_accelerator_filename(accelpath, sizeof(accelpath), 0)) + { + /* Check whether that file exists, and isn't older than + * the document. */ + atime = fz_stat_mtime(accelpath); + dtime = fz_stat_mtime(filename); + if (atime == 0) + { + /* No accelerator */ + } + else if (atime > dtime) + accel = accelpath; + else + { + /* Accelerator data is out of date */ +#ifdef _WIN32 + fz_remove_utf8(accelpath); +#else + remove(accelpath); +#endif + accel = NULL; /* In case we have jumped up from below */ + } + } + + trace_action("doc = Document.openDocument(%q);\n", filename); + + doc = fz_open_accelerated_document(ctx, filename, accel); + pdf = pdf_specifics(ctx, doc); + + if (pdf && trace_file) + { + int needspass = pdf_needs_password(ctx, pdf); + trace_action( + "tmp = doc.needsPassword();\n" + "if (tmp != %s)\n" + " throw new RegressionError('Document password needed:', tmp, 'expected:', %s);\n", + needspass ? "true" : "false", + needspass ? "true" : "false"); + } + + if (fz_needs_password(ctx, doc)) + { + int result = fz_authenticate_password(ctx, doc, password); + + if (pdf && trace_file) + { + trace_action( + "tmp = doc.authenticatePassword(%q);\n" + "if (tmp != %s)\n" + " throw new RegressionError('Open document with password %q result: %s', 'expected:', '%s');\n", + password, + result ? "true" : "false", + password, + !result ? "pass" : "fail", + result ? "pass" : "fail"); + } + + if (!result) + { + fz_drop_document(ctx, doc); + doc = NULL; + ui_input_init(&input_password, ""); + ui.focus = &input_password; + ui.dialog = password_dialog; + return; + } + } + + fz_layout_document(ctx, doc, layout_w, layout_h, layout_em); + + fz_try(ctx) + outline = fz_load_outline(ctx, doc); + fz_catch(ctx) + { + fz_report_error(ctx); + outline = NULL; + } + + load_history(); + + if (pdf) + { +#if FZ_ENABLE_JS + if (enable_js) + { + trace_action("doc.enableJS();\n"); + pdf_enable_js(ctx, pdf); + pdf_js_set_console(ctx, pdf, &gl_js_console, NULL); + } +#endif + + reload_or_start_journalling(); + + if (trace_file) + { + int vsns = pdf_count_versions(ctx, pdf); + trace_action( + "tmp = doc.countVersions();\n" + "if (tmp != %d)\n" + " throw new RegressionError('Document versions:', tmp, 'expected:', %d);\n", + vsns, vsns); + if (vsns > 1) + { + int valid = pdf_validate_change_history(ctx, pdf); + trace_action("tmp = doc.validateChangeHistory();\n"); + trace_action("if (tmp != %d)\n", valid); + trace_action(" throw new RegressionError('History validation:', tmp, 'expected:', %d);\n", valid); + } + } + } + + if (anchor) + { + if (parse_location(anchor, &location)) + jump_to_location(location); + else + { + location = fz_resolve_link(ctx, doc, anchor, NULL, NULL); + if (location.page < 0) + fz_warn(ctx, "cannot find location: %s", anchor); + else + jump_to_location(location); + } + } + anchor = NULL; + + oldpage = currentpage = fz_clamp_location(ctx, doc, currentpage); + + if (pdf) + pdf_set_doc_event_callback(ctx, pdf, event_cb, NULL, NULL); +} + +static void reflow_document(void) +{ + char buf[256]; + fz_document *new_doc; + fz_stext_options opts; + + if (fz_is_document_reflowable(ctx, doc)) + return; + + fz_drop_outline(ctx, outline); + outline = NULL; + + fz_parse_stext_options(ctx, &opts, reflow_options); + + new_doc = fz_open_reflowed_document(ctx, doc, &opts); + fz_drop_document(ctx, doc); + doc = new_doc; + pdf = NULL; + page = NULL; + + fz_layout_document(ctx, doc, layout_w, layout_h, layout_em); + + fz_try(ctx) + outline = fz_load_outline(ctx, doc); + fz_catch(ctx) + outline = NULL; + + fz_strlcpy(buf, filename, sizeof buf); + fz_snprintf(filename, sizeof filename, "%s.xhtml", buf); + + load_history(); + + if (anchor) + jump_to_page(fz_atoi(anchor) - 1); + anchor = NULL; + + currentpage = fz_clamp_location(ctx, doc, currentpage); +} + +void reload_document(void) +{ + save_history(); + save_accelerator(); + load_document(); + if (doc) + { + if (reflow_options) + reflow_document(); + load_page(); + update_title(); + } +} + +static void toggle_outline(void) +{ + if (outline) + { + showoutline = !showoutline; + showundo = showlayers = 0; + if (canvas_w == page_tex.w && canvas_h == page_tex.h) + shrinkwrap(); + } +} + +static void toggle_undo(void) +{ + showundo = !showundo; + showoutline = showlayers = 0; + if (canvas_w == page_tex.w && canvas_h == page_tex.h) + shrinkwrap(); +} + +static void toggle_layers(void) +{ + showlayers = !showlayers; + showoutline = showundo = 0; + if (canvas_w == page_tex.w && canvas_h == page_tex.h) + shrinkwrap(); +} + +void toggle_annotate(int mode) +{ + if (pdf) + { + if (showannotate != mode) + showannotate = mode; + else + showannotate = ANNOTATE_MODE_NONE; + if (canvas_w == page_tex.w && canvas_h == page_tex.h) + shrinkwrap(); + } +} + +static void set_zoom(int z, int cx, int cy) +{ + z = fz_clamp(z, MINRES, MAXRES); + scroll_x = (scroll_x + cx - canvas_x) * z / currentzoom - cx + canvas_x; + scroll_y = (scroll_y + cy - canvas_y) * z / currentzoom - cy + canvas_y; + currentzoom = z; +} + +static void auto_zoom_w(void) +{ + currentzoom = fz_clamp(currentzoom * canvas_w / page_tex.w, MINRES, MAXRES); +} + +static void auto_zoom_h(void) +{ + currentzoom = fz_clamp(currentzoom * canvas_h / page_tex.h, MINRES, MAXRES); +} + +static void auto_zoom(void) +{ + float page_a = (float) page_tex.w / page_tex.h; + float screen_a = (float) canvas_w / canvas_h; + if (page_a > screen_a) + auto_zoom_w(); + else + auto_zoom_h(); +} + +static void smart_move_backward(void) +{ + int slop_x = page_tex.w / 20; + int slop_y = page_tex.h / 20; + if (scroll_y <= slop_y) + { + if (scroll_x <= slop_x) + { + fz_location prev = fz_previous_page(ctx, doc, currentpage); + if (!eqloc(currentpage, prev)) + { + scroll_x = (page_tex.w <= canvas_w) ? 0 : page_tex.w - canvas_w; + scroll_y = (page_tex.h <= canvas_h) ? 0 : page_tex.h - canvas_h; + currentpage = prev; + } + } + else + { + scroll_y = page_tex.h; + scroll_x -= canvas_w * 9 / 10; + } + } + else + { + scroll_y -= canvas_h * 9 / 10; + } +} + +static void smart_move_forward(void) +{ + int slop_x = page_tex.w / 20; + int slop_y = page_tex.h / 20; + if (scroll_y + canvas_h >= page_tex.h - slop_y) + { + if (scroll_x + canvas_w >= page_tex.w - slop_x) + { + fz_location next = fz_next_page(ctx, doc, currentpage); + if (!eqloc(currentpage, next)) + { + scroll_x = 0; + scroll_y = 0; + currentpage = next; + } + } + else + { + scroll_y = 0; + scroll_x += canvas_w * 9 / 10; + } + } + else + { + scroll_y += canvas_h * 9 / 10; + } +} + +static void clear_search(void) +{ + showsearch = 0; + search_page = currentpage; + search_hit_page = fz_make_location(-1, -1); + search_hit_count = 0; +} + +#if FZ_ENABLE_JS + +#define MAX_CONSOLE_LINES 500 + +static fz_buffer *console_buffer; +static int console_scroll = 0; +static int console_sticky = 1; +static int console_lines = 0; +static struct readline console_readline; +static void (*warning_callback)(void *, const char *) = NULL; +static void (*error_callback)(void *, const char *) = NULL; +static void *warning_user = NULL; +static void *error_user = NULL; + +static void +remove_oldest_console_line() +{ + unsigned char *s; + size_t size = fz_buffer_storage(ctx, console_buffer, &s); + unsigned char *p = s; + unsigned char *e = s + size; + + while (p < e && *p != '\n') + p++; + + if (p < e && *p == '\n') + { + p++; + memmove(s, p, e - p); + fz_resize_buffer(ctx, console_buffer, e - p); + console_lines--; + } +} + +static void +gl_js_console_write(void *user, const char *message) +{ + const char *p = NULL; + + if (message == NULL) + return; + + p = message; + while (*p) + { + if (*p == '\n') + console_lines++; + if (console_lines >= MAX_CONSOLE_LINES) + remove_oldest_console_line(); + if (*p) + fz_append_byte(ctx, console_buffer, *p); + p++; + } +} + +static void +gl_js_console_show(void *user) +{ + if (showconsole) + return; + + showconsole = 1; + if (canvas_w == page_tex.w && canvas_h == page_tex.h) + shrinkwrap(); + ui.focus = &console_readline; +} + +static void +gl_js_console_hide(void *user) +{ + if (!showconsole) + return; + + showconsole = 0; + if (canvas_w == page_tex.w && canvas_h == page_tex.h) + shrinkwrap(); + ui.focus = NULL; +} + +static void +gl_js_console_clear(void *user) +{ + fz_resize_buffer(ctx, console_buffer, 0); + console_lines = 0; +} + +static void console_warn(void *user, const char *message) +{ + gl_js_console_write(ctx, "\nwarning: "); + gl_js_console_write(ctx, message); + if (warning_callback) + warning_callback(warning_user, message); +} + +static void console_err(void *user, const char *message) +{ + gl_js_console_write(ctx, "\nerror: "); + gl_js_console_write(ctx, message); + if (error_callback) + error_callback(error_user, message); +} + +static void console_init(void) +{ + ui_readline_init(&console_readline, NULL); + + console_buffer = fz_new_buffer(ctx, 0); + fz_append_printf(ctx, console_buffer, "Welcome to MuPDF %s with MuJS %d.%d.%d", + FZ_VERSION, + JS_VERSION_MAJOR, JS_VERSION_MINOR, JS_VERSION_PATCH); + + warning_callback = fz_warning_callback(ctx, &warning_user); + fz_set_warning_callback(ctx, console_warn, NULL); + error_callback = fz_error_callback(ctx, &error_user); + fz_set_error_callback(ctx, console_err, NULL); +} + +static void console_fin(void) +{ + fz_set_warning_callback(ctx, warning_callback, warning_user); + fz_set_error_callback(ctx, error_callback, error_user); + fz_drop_buffer(ctx, console_buffer); + console_buffer = NULL; +} + +static pdf_js_console gl_js_console = { + NULL, + gl_js_console_show, + gl_js_console_hide, + gl_js_console_clear, + gl_js_console_write, +}; + +static void toggle_console(void) +{ + showconsole = !showconsole; + if (showconsole) + ui.focus = &console_readline; + if (canvas_w == page_tex.w && canvas_h == page_tex.h) + shrinkwrap(); +} + +void do_console(void) +{ + pdf_js_console *console = pdf_js_get_console(ctx, pdf); + char *result = NULL; + const char *accepted = NULL; + + fz_var(result); + + ui_layout(B, BOTH, NW, 0, 0); + ui_panel_begin(canvas_w, console_h, ui.padsize, ui.padsize, 1); + + ui_layout(B, X, NW, 0, 0); + + accepted = ui_readline(&console_readline, 0); + if (accepted != NULL) + { + ui.focus = &console_readline; + if (console_readline.input.text[0]) + { + fz_try(ctx) + { + if (console && console->write) + { + console->write(ctx, "\n> "); + console->write(ctx, console_readline.input.text); + } + pdf_js_execute(pdf ? pdf->js : NULL, "console", console_readline.input.text, &result); + if (result && console && console->write) + { + console->write(ctx, "\n"); + console->write(ctx, result); + } + } + fz_always(ctx) + fz_free(ctx, result); + fz_catch(ctx) + { + if (console) + { + console->write(ctx, "\nError: "); + console->write(ctx, fz_caught_message(ctx)); + fz_report_error(ctx); + } + } + fz_flush_warnings(ctx); + ui_input_init(&console_readline.input, ""); + } + } + + ui_layout(ALL, BOTH, NW, ui.padsize, ui.padsize); + + // White background! + glColorHex(0xF5F5F5); + glRectf(ui.cavity->x0, ui.cavity->y0, ui.cavity->x1, ui.cavity->y1); + + char *console_string = (char *) fz_string_from_buffer(ctx, console_buffer); + ui_label_with_scrollbar(console_string, 0, 10, &console_scroll, &console_sticky); + + ui_panel_end(); +} + +#endif + +static void do_app(void) +{ + if (ui.mod == GLUT_ACTIVE_ALT) + { + if (ui.key == KEY_F4) + quit(); + + if (ui.key == KEY_LEFT) + ui.key = 't', ui.mod = 0, ui.plain = 1; + if (ui.key == KEY_RIGHT) + ui.key = 'T', ui.mod = 0, ui.plain = 1; + } + + if (trace_file && ui.key == KEY_CTL_P) + trace_save_snapshot(); + + if (!ui.focus && ui.key && ui.plain) + { + switch (ui.key) + { + case KEY_ESCAPE: clear_search(); ui_select_annot(NULL); break; + case KEY_F1: ui.dialog = help_dialog; break; + case 'a': toggle_annotate(ANNOTATE_MODE_NORMAL); break; + case 'R': toggle_annotate(ANNOTATE_MODE_REDACT); break; + case 'o': toggle_outline(); break; + case 'u': toggle_undo(); break; + case 'Y': toggle_layers(); break; + case 'L': showlinks = !showlinks; break; + case 'F': showform = !showform; break; + case 'i': ui.dialog = info_dialog; break; +#if FZ_ENABLE_JS + case '`': case KEY_F12: toggle_console(); break; +#endif + case 'r': reload(); break; + case 'q': quit(); break; + case 'S': do_save_pdf_file(); break; + + case '>': layout_em = number > 0 ? number : layout_em + 1; relayout(); break; + case '<': layout_em = number > 0 ? number : layout_em - 1; relayout(); break; + + case 'C': currenttint = !currenttint; break; + case 'I': currentinvert = !currentinvert; break; + case 'e': currentseparations = !currentseparations; break; + case 'E': currenticc = !currenticc; break; + case 'f': toggle_fullscreen(); break; + case 'w': shrinkwrap(); break; + case 'W': auto_zoom_w(); break; + case 'H': auto_zoom_h(); break; + case 'Z': auto_zoom(); break; + case 'z': set_zoom(number > 0 ? number : DEFRES, canvas_w/2, canvas_h/2); break; + case '+': set_zoom(zoom_in(currentzoom), ui.x, ui.y); break; + case '-': set_zoom(zoom_out(currentzoom), ui.x, ui.y); break; + case '[': currentrotate -= 90; break; + case ']': currentrotate += 90; break; + case 'k': case KEY_UP: scroll_y -= canvas_h/10; break; + case 'j': case KEY_DOWN: scroll_y += canvas_h/10; break; + case 'h': case KEY_LEFT: scroll_x -= canvas_w/10; break; + case 'l': case KEY_RIGHT: scroll_x += canvas_w/10; break; + + case 'b': number = fz_maxi(number, 1); while (number--) smart_move_backward(); break; + case ' ': number = fz_maxi(number, 1); while (number--) smart_move_forward(); break; + case 'g': jump_to_page(number - 1); break; + case 'G': jump_to_location(fz_last_page(ctx, doc)); break; + + case ',': case KEY_PAGE_UP: + number = fz_maxi(number, 1); + while (number--) + currentpage = fz_previous_page(ctx, doc, currentpage); + break; + case '.': case KEY_PAGE_DOWN: + number = fz_maxi(number, 1); + while (number--) + currentpage = fz_next_page(ctx, doc, currentpage); + break; + + case 'A': + if (number == 0) + currentaa = (currentaa == 8 ? 0 : 8); + else + currentaa = number; + break; + + case 'B': + currentbox += 1; + if (currentbox >= FZ_UNKNOWN_BOX) + currentbox = FZ_MEDIA_BOX; + break; + + case 'm': + if (number == 0) + push_history(); + else if (number > 0 && number < (int)nelem(marks)) + marks[number] = save_mark(); + break; + case 't': + if (number == 0) + { + if (history_count > 0) + pop_history(); + } + else if (number > 0 && number < (int)nelem(marks)) + { + struct mark mark = marks[number]; + restore_mark(mark); + jump_to_location(mark.loc); + } + break; + case 'T': + if (number == 0) + { + if (future_count > 0) + pop_future(); + } + break; + + case '/': + clear_search(); + search_dir = 1; + showsearch = 1; + ui.focus = &search_input; + search_input.p = search_input.text; + search_input.q = search_input.end; + break; + case '?': + clear_search(); + search_dir = -1; + showsearch = 1; + ui.focus = &search_input; + search_input.p = search_input.text; + search_input.q = search_input.end; + break; + case 'N': + search_dir = -1; + search_active = !!search_needle; + if (eqloc(search_hit_page, currentpage)) + { + if (is_first_page(search_page)) + search_active = 0; + else + search_page = fz_previous_page(ctx, doc, currentpage); + } + else + { + search_page = currentpage; + } + search_hit_page = fz_make_location(-1, -1); + break; + case 'n': + search_dir = 1; + search_active = !!search_needle; + if (eqloc(search_hit_page, currentpage)) + { + if (is_last_page(search_page)) + search_active = 0; + else + search_page = fz_next_page(ctx, doc, currentpage); + } + else + { + search_page = currentpage; + } + search_hit_page = fz_make_location(-1, -1); + break; + default: + if (ui.key < '0' || ui.key > '9') + { + number = 0; + return; /* unrecognized key, pass it through */ + } + } + + if (ui.key >= '0' && ui.key <= '9') + number = number * 10 + ui.key - '0'; + else + number = 0; + + currentpage = fz_clamp_location(ctx, doc, currentpage); + while (currentrotate < 0) currentrotate += 360; + while (currentrotate >= 360) currentrotate -= 360; + + if (!eqloc(search_hit_page, currentpage)) + search_hit_page = fz_make_location(-1, -1); /* clear highlights when navigating */ + + ui.key = 0; /* we ate the key event, so zap it */ + } +} + +typedef struct +{ + int max; + int len; + pdf_obj **sig; +} sigs_list; + +static void +process_sigs(fz_context *ctx_, pdf_obj *field, void *arg, pdf_obj **ft) +{ + sigs_list *sigs = (sigs_list *)arg; + + if (!pdf_name_eq(ctx, pdf_dict_get(ctx, field, PDF_NAME(Type)), PDF_NAME(Annot)) || + !pdf_name_eq(ctx, pdf_dict_get(ctx, field, PDF_NAME(Subtype)), PDF_NAME(Widget)) || + !pdf_name_eq(ctx, *ft, PDF_NAME(Sig))) + return; + + if (sigs->len == sigs->max) + { + int newsize = sigs->max * 2; + if (newsize == 0) + newsize = 4; + sigs->sig = fz_realloc_array(ctx, sigs->sig, newsize, pdf_obj *); + sigs->max = newsize; + } + + sigs->sig[sigs->len++] = field; +} + +static char *short_signature_error_desc(pdf_signature_error err) +{ + switch (err) + { + case PDF_SIGNATURE_ERROR_OKAY: + return "OK"; + case PDF_SIGNATURE_ERROR_NO_SIGNATURES: + return "No signatures"; + case PDF_SIGNATURE_ERROR_NO_CERTIFICATE: + return "No certificate"; + case PDF_SIGNATURE_ERROR_DIGEST_FAILURE: + return "Invalid"; + case PDF_SIGNATURE_ERROR_SELF_SIGNED: + return "Self-signed"; + case PDF_SIGNATURE_ERROR_SELF_SIGNED_IN_CHAIN: + return "Self-signed in chain"; + case PDF_SIGNATURE_ERROR_NOT_TRUSTED: + return "Untrusted"; + case PDF_SIGNATURE_ERROR_NOT_SIGNED: + return "Not signed"; + default: + case PDF_SIGNATURE_ERROR_UNKNOWN: + return "Unknown error"; + } +} + +const char *format_date(int64_t secs64) +{ + static char buf[100]; +#ifdef _POSIX_SOURCE + struct tm tmbuf, *tm; +#else + struct tm *tm; +#endif + time_t secs = (time_t)secs64; + + if (secs <= 0) + return NULL; + +#ifdef _POSIX_SOURCE + tm = gmtime_r(&secs, &tmbuf); +#else + tm = gmtime(&secs); +#endif + if (!tm) + return NULL; + + strftime(buf, sizeof buf, "%Y-%m-%d %H:%M UTC", tm); + return buf; +} + +static fz_buffer *format_info_text() +{ + fz_buffer *out = fz_new_buffer(ctx, 4096); + pdf_document *pdoc = pdf_specifics(ctx, doc); + sigs_list list = { 0, 0, NULL }; + char buf[100]; + + if (pdoc) + { + static pdf_obj *ft_list[2] = { PDF_NAME(FT), NULL }; + pdf_obj *ft = NULL; + pdf_obj *form_fields = pdf_dict_getp(ctx, pdf_trailer(ctx, pdoc), "Root/AcroForm/Fields"); + pdf_walk_tree(ctx, form_fields, PDF_NAME(Kids), process_sigs, NULL, &list, &ft_list[0], &ft); + } + + fz_append_printf(ctx, out, "File: %s\n\n", filename); + + if (fz_lookup_metadata(ctx, doc, FZ_META_INFO_TITLE, buf, sizeof buf) > 0) + fz_append_printf(ctx, out, "Title: %s\n", buf); + if (fz_lookup_metadata(ctx, doc, FZ_META_INFO_AUTHOR, buf, sizeof buf) > 0) + fz_append_printf(ctx, out, "Author: %s\n", buf); + if (fz_lookup_metadata(ctx, doc, FZ_META_FORMAT, buf, sizeof buf) > 0) + fz_append_printf(ctx, out, "Format: %s\n", buf); + if (fz_lookup_metadata(ctx, doc, FZ_META_ENCRYPTION, buf, sizeof buf) > 0) + fz_append_printf(ctx, out, "Encryption: %s\n", buf); + + fz_append_string(ctx, out, "\n"); + + if (pdoc) + { + int updates = pdf_count_versions(ctx, pdoc); + + if (fz_lookup_metadata(ctx, doc, FZ_META_INFO_CREATOR, buf, sizeof buf) > 0) + fz_append_printf(ctx, out, "PDF Creator: %s\n", buf); + if (fz_lookup_metadata(ctx, doc, FZ_META_INFO_PRODUCER, buf, sizeof buf) > 0) + fz_append_printf(ctx, out, "PDF Producer: %s\n", buf); + if (fz_lookup_metadata(ctx, doc, FZ_META_INFO_SUBJECT, buf, sizeof buf) > 0) + fz_append_printf(ctx, out, "Subject: %s\n", buf); + if (fz_lookup_metadata(ctx, doc, FZ_META_INFO_KEYWORDS, buf, sizeof buf) > 0) + fz_append_printf(ctx, out, "Keywords: %s\n", buf); + if (fz_lookup_metadata(ctx, doc, FZ_META_INFO_CREATIONDATE, buf, sizeof buf) > 0) + { + const char *s = format_date(pdf_parse_date(ctx, buf)); + if (s) + fz_append_printf(ctx, out, "Creation date: %s\n", s); + } + if (fz_lookup_metadata(ctx, doc, FZ_META_INFO_MODIFICATIONDATE, buf, sizeof buf) > 0) + { + const char *s = format_date(pdf_parse_date(ctx, buf)); + if (s) + fz_append_printf(ctx, out, "Modification date: %s\n", s); + } + + buf[0] = 0; + if (fz_has_permission(ctx, doc, FZ_PERMISSION_PRINT)) + fz_strlcat(buf, "print, ", sizeof buf); + if (fz_has_permission(ctx, doc, FZ_PERMISSION_COPY)) + fz_strlcat(buf, "copy, ", sizeof buf); + if (fz_has_permission(ctx, doc, FZ_PERMISSION_EDIT)) + fz_strlcat(buf, "edit, ", sizeof buf); + if (fz_has_permission(ctx, doc, FZ_PERMISSION_ANNOTATE)) + fz_strlcat(buf, "annotate, ", sizeof buf); + if (fz_has_permission(ctx, doc, FZ_PERMISSION_FORM)) + fz_strlcat(buf, "form, ", sizeof buf); + if (fz_has_permission(ctx, doc, FZ_PERMISSION_ACCESSIBILITY)) + fz_strlcat(buf, "accessibility, ", sizeof buf); + if (fz_has_permission(ctx, doc, FZ_PERMISSION_ASSEMBLE)) + fz_strlcat(buf, "assemble, ", sizeof buf); + if (fz_has_permission(ctx, doc, FZ_PERMISSION_PRINT_HQ)) + fz_strlcat(buf, "print-hq, ", sizeof buf); + if (strlen(buf) > 2) + buf[strlen(buf)-2] = 0; + else + fz_strlcat(buf, "none", sizeof buf); + fz_append_printf(ctx, out, "Permissions: %s\n", buf); + + fz_append_printf(ctx, out, "PDF %sdocument with %d update%s\n", + pdf_doc_was_linearized(ctx, pdoc) ? "linearized " : "", + updates, updates > 1 ? "s" : ""); + if (updates > 0) + { + int n = pdf_validate_change_history(ctx, pdoc); + if (n == 0) + fz_append_printf(ctx, out, "Change history seems valid.\n"); + else if (n == 1) + fz_append_printf(ctx, out, "Invalid changes made to the document in the last update.\n"); + else if (n == 2) + fz_append_printf(ctx, out, "Invalid changes made to the document in the penultimate update.\n"); + else + fz_append_printf(ctx, out, "Invalid changes made to the document %d updates ago.\n", n); + } + + if (list.len) + { + int i; + for (i = 0; i < list.len; i++) + { + pdf_obj *field = list.sig[i]; + fz_try(ctx) + { + if (pdf_signature_is_signed(ctx, pdf, field)) + { + pdf_pkcs7_verifier *verifier = pkcs7_openssl_new_verifier(ctx); + pdf_signature_error sig_cert_error = pdf_check_certificate(ctx, verifier, pdf, field); + pdf_signature_error sig_digest_error = pdf_check_digest(ctx, verifier, pdf, field); + fz_append_printf(ctx, out, "Signature %d: CERT: %s, DIGEST: %s%s\n", i+1, + short_signature_error_desc(sig_cert_error), + short_signature_error_desc(sig_digest_error), + pdf_signature_incremental_change_since_signing(ctx, pdf, field) ? ", Changed since": ""); + pdf_drop_verifier(ctx, verifier); + } + else + fz_append_printf(ctx, out, "Signature %d: Unsigned\n", i+1); + } + fz_catch(ctx) + fz_append_printf(ctx, out, "Signature %d: Error\n", i+1); + } + fz_free(ctx, list.sig); + + if (updates == 0) + fz_append_printf(ctx, out, "No updates since document creation\n"); + else + { + int n = pdf_validate_change_history(ctx, pdf); + if (n == 0) + fz_append_printf(ctx, out, "Document changes conform to permissions\n"); + else + fz_append_printf(ctx, out, "Document permissions violated %d updates ago\n", n); + } + } + + fz_append_string(ctx, out, "\n"); + } + + fz_append_printf(ctx, out, "Page: %d / %d\n", fz_page_number_from_location(ctx, doc, currentpage)+1, fz_count_pages(ctx, doc)); + fz_append_printf(ctx, out, "Page Label: %s\n", fz_page_label(ctx, fzpage, buf, sizeof buf)); + { + int w = (int)(page_bounds.x1 - page_bounds.x0 + 0.5f); + int h = (int)(page_bounds.y1 - page_bounds.y0 + 0.5f); + const char *size = paper_size_name(w, h); + if (!size) + size = paper_size_name(h, w); + if (size) + fz_append_printf(ctx, out, "Size: %d x %d (%s - %s)\n", w, h, fz_string_from_box_type(currentbox), size); + else + fz_append_printf(ctx, out, "Size: %d x %d (%s)\n", w, h, fz_string_from_box_type(currentbox)); + } + fz_append_printf(ctx, out, "ICC rendering: %s.\n", currenticc ? "on" : "off"); + fz_append_printf(ctx, out, "Spot rendering: %s.\n", currentseparations ? "on" : "off"); + + if (fz_is_document_reflowable(ctx, doc)) + fz_append_printf(ctx, out, "Em size: %g\n", layout_em); + + return out; +} + +static void do_canvas(void) +{ + static int saved_scroll_x = 0; + static int saved_scroll_y = 0; + static int saved_ui_x = 0; + static int saved_ui_y = 0; + fz_irect area; + int page_x, page_y; + + tooltip = NULL; + + ui_layout(ALL, BOTH, NW, 0, 0); + ui_pack_push(area = ui_pack(0, 0)); + glScissor(area.x0, ui.window_h-area.y1, area.x1-area.x0, area.y1-area.y0); + glEnable(GL_SCISSOR_TEST); + + canvas_x = area.x0; + canvas_y = area.y0; + canvas_w = area.x1 - area.x0; + canvas_h = area.y1 - area.y0; + + if (ui_mouse_inside(area)) + { + ui.hot = doc; + if (!ui.active && ui.middle) + { + ui.active = doc; + saved_scroll_x = scroll_x; + saved_scroll_y = scroll_y; + saved_ui_x = ui.x; + saved_ui_y = ui.y; + } + } + + if (ui.hot == doc) + { + if (ui.mod == 0) + { + scroll_x -= ui.scroll_x * ui.lineheight * 3; + scroll_y -= ui.scroll_y * ui.lineheight * 3; + } + else if (ui.mod == GLUT_ACTIVE_CTRL) + { + if (ui.scroll_y > 0) set_zoom(zoom_in(currentzoom), ui.x, ui.y); + if (ui.scroll_y < 0) set_zoom(zoom_out(currentzoom), ui.x, ui.y); + } + } + + render_page_if_changed(); + + if (ui.active == doc) + { + scroll_x = saved_scroll_x + saved_ui_x - ui.x; + scroll_y = saved_scroll_y + saved_ui_y - ui.y; + } + + if (page_tex.w <= canvas_w) + { + scroll_x = 0; + page_x = canvas_x + (canvas_w - page_tex.w) / 2; + } + else + { + scroll_x = fz_clamp(scroll_x, 0, page_tex.w - canvas_w); + page_x = canvas_x - scroll_x; + } + + if (page_tex.h <= canvas_h) + { + scroll_y = 0; + page_y = canvas_y + (canvas_h - page_tex.h) / 2; + } + else + { + scroll_y = fz_clamp(scroll_y, 0, page_tex.h - canvas_h); + page_y = canvas_y - scroll_y; + } + + view_page_ctm = draw_page_ctm; + view_page_ctm.e += page_x; + view_page_ctm.f += page_y; + view_page_inv_ctm = fz_invert_matrix(view_page_ctm); + view_page_bounds = fz_transform_rect(page_bounds, view_page_ctm); + view_page_area = fz_irect_from_rect(view_page_bounds); + + ui_draw_image(&page_tex, page_x, page_y); + + if (search_active) + { + int chapters = fz_count_chapters(ctx, doc); + ui_layout(T, X, NW, 0, 0); + ui_panel_begin(0, ui.gridsize + ui.padsize*4, ui.padsize*2, ui.padsize*2, 1); + ui_layout(L, NONE, W, ui.padsize, 0); + if (chapters == 1 && search_page.chapter == 0) + ui_label("Searching page %d...", search_page.page); + else + ui_label("Searching chapter %d page %d...", search_page.chapter, search_page.page); + ui_panel_end(); + } + else + { + if (pdf) + { + do_annotate_canvas(area); + do_widget_canvas(area); + } + do_links(links); + do_page_selection(); + + if (eqloc(search_hit_page, currentpage) && search_hit_count > 0) + do_search_hits(); + } + + if (showsearch) + { + ui_layout(T, X, NW, 0, 0); + ui_panel_begin(0, ui.gridsize + ui.padsize*4, ui.padsize*2, ui.padsize*2, 1); + ui_layout(L, NONE, W, ui.padsize, 0); + ui_label("Search:"); + ui_layout(ALL, X, E, ui.padsize, 0); + if (ui_input(&search_input, 0, 1) == UI_INPUT_ACCEPT) + { + showsearch = 0; + search_page = fz_make_location(-1, -1); + if (search_needle) + { + fz_free(ctx, search_needle); + search_needle = NULL; + } + if (search_input.end > search_input.text) + { + search_needle = fz_strdup(ctx, search_input.text); + search_active = 1; + search_page = currentpage; + } + } + if (ui.focus != &search_input) + showsearch = 0; + ui_panel_end(); + } + + if (tooltip) + { + ui_layout(B, X, N, 0, 0); + ui_panel_begin(0, ui.gridsize, ui.padsize*2, ui.padsize*2, 1); + ui_layout(L, NONE, W, ui.padsize, 0); + ui_label("%s", tooltip); + ui_panel_end(); + } + + ui_pack_pop(); + glDisable(GL_SCISSOR_TEST); +} + +void do_main(void) +{ + if (search_active) + { + int start_time = glutGet(GLUT_ELAPSED_TIME); + + if (ui.key == KEY_ESCAPE) + search_active = 0; + + /* ignore events during search */ + ui.key = ui.mod = ui.plain = 0; + ui.down = ui.middle = ui.right = 0; + + while (search_active && glutGet(GLUT_ELAPSED_TIME) < start_time + 200) + { + search_hit_count = fz_search_chapter_page_number(ctx, doc, + search_page.chapter, search_page.page, + search_needle, + NULL, search_hit_quads, nelem(search_hit_quads)); + trace_action("hits = doc.loadPage(%d).search(%q);\n", fz_page_number_from_location(ctx, doc, search_page), search_needle); + trace_action("print('Search page %d:', repr(%q), hits.length, repr(hits));\n", fz_page_number_from_location(ctx, doc, search_page), search_needle); + if (search_hit_count) + { + float search_hit_x = search_hit_quads[0].ul.x; + float search_hit_y = search_hit_quads[0].ul.y; + search_active = 0; + search_hit_page = search_page; + jump_to_location_xy(search_hit_page, search_hit_x, search_hit_y); + } + else + { + if (search_dir > 0) + { + if (is_last_page(search_page)) + search_active = 0; + else + search_page = fz_next_page(ctx, doc, search_page); + } + else + { + if (is_first_page(search_page)) + search_active = 0; + else + search_page = fz_previous_page(ctx, doc, search_page); + } + } + } + + /* keep searching later */ + if (search_active) + glutPostRedisplay(); + } + + do_app(); + + if (showoutline) + do_outline(outline); + else if (showundo) + do_undo(); + else if (showlayers) + do_layers(); + if (showoutline || showundo || showlayers) + ui_splitter(&outline_start_x, &outline_w, 6*ui.gridsize, 20*ui.gridsize, R); + + if (!eqloc(oldpage, currentpage) || oldseparations != currentseparations || oldicc != currenticc) + { + load_page(); + update_title(); + } + + if (showannotate) + { + ui_layout(R, BOTH, NW, 0, 0); + ui_panel_begin(annotate_w, 0, ui.padsize*2, ui.padsize*2, 1); + if (showannotate == ANNOTATE_MODE_NORMAL) + do_annotate_panel(); + else + do_redact_panel(); + ui_panel_end(); + } + +#if FZ_ENABLE_JS + if (showconsole) + { + do_console(); + ui_splitter(&console_start_y, &console_h, 6*ui.lineheight, 25*ui.lineheight, T); + } +#endif + + do_canvas(); + + if (pdf) + { + if (document_shown_as_dirty != pdf_has_unsaved_changes(ctx, pdf)) + update_title(); + } +} + +void run_main_loop(void) +{ + if (currentinvert) + glClearColor(0, 0, 0, 1); + else + glClearColor(0.3f, 0.3f, 0.3f, 1); + ui_begin(); + fz_try(ctx) + { + if (ui.dialog) + ui.dialog(); + else + do_main(); + } + fz_catch(ctx) + { + ui_show_error_dialog("%s", fz_caught_message(ctx)); + fz_report_error(ctx); + } + ui_end(); +} + +static void usage(const char *argv0) +{ + fprintf(stderr, "mupdf-gl version %s\n", FZ_VERSION); + fprintf(stderr, "usage: %s [options] document [page]\n", argv0); + fprintf(stderr, "\t-p -\tpassword\n"); + fprintf(stderr, "\t-r -\tresolution\n"); + fprintf(stderr, "\t-c -\tdisplay ICC profile\n"); + fprintf(stderr, "\t-b -\tuse named page box (MediaBox, CropBox, BleedBox, TrimBox, or ArtBox)\n"); + fprintf(stderr, "\t-I\tinvert colors\n"); + fprintf(stderr, "\t-W -\tpage width for EPUB layout\n"); + fprintf(stderr, "\t-H -\tpage height for EPUB layout\n"); + fprintf(stderr, "\t-S -\tfont size for EPUB layout\n"); + fprintf(stderr, "\t-U -\tuser style sheet for EPUB layout\n"); + fprintf(stderr, "\t-X\tdisable document styles for EPUB layout\n"); + fprintf(stderr, "\t-J\tdisable javascript in PDF forms\n"); + fprintf(stderr, "\t-A -\tset anti-aliasing level (0-8,9,10)\n"); + fprintf(stderr, "\t-B -\tset black tint color (default: 303030)\n"); + fprintf(stderr, "\t-C -\tset white tint color (default: FFFFF0)\n"); + fprintf(stderr, "\t-Y -\tset the UI scaling factor\n"); + fprintf(stderr, "\t-R -\tenable reflow and set the text extraction options\n"); + fprintf(stderr, "\t\t\texample: -R dehyphenate,preserve-images\n"); + exit(1); +} + +static int document_filter(const char *fname) +{ + return !!fz_recognize_document(ctx, fname); +} + +static void do_open_document_dialog(void) +{ + if (ui_open_file(filename, "Select a document to open:")) + { + ui.dialog = NULL; + if (filename[0] == 0) + glutLeaveMainLoop(); + else + { + load_document(); + if (doc) + { + if (reflow_options) + reflow_document(); + load_page(); + shrinkwrap(); + update_title(); + } + } + } +} + +static void cleanup(void) +{ + save_history(); + fz_try(ctx) + save_accelerator(); + fz_catch(ctx) + fz_warn(ctx, "cannot save accelerator file"); + + ui_finish(); + + fz_drop_pixmap(ctx, page_contents); + page_contents = NULL; +#ifndef NDEBUG + if (fz_atoi(getenv("FZ_DEBUG_STORE"))) + fz_debug_store(ctx, fz_stdout(ctx)); +#endif + + trace_action("quit(0);\n"); + + fz_flush_warnings(ctx); + +#if FZ_ENABLE_JS + console_fin(); +#endif + + fz_drop_output(ctx, trace_file); + fz_drop_stext_page(ctx, page_text); + fz_drop_separations(ctx, seps); + fz_drop_link(ctx, links); + fz_drop_page(ctx, fzpage); + fz_drop_outline(ctx, outline); + fz_drop_document(ctx, doc); + fz_drop_context(ctx); +} + +int reloadrequested = 0; + +#ifndef _WIN32 +static void signal_handler(int signal) +{ + if (signal == SIGHUP) + reloadrequested = 1; +} +#endif + +#ifdef _MSC_VER +int main_utf8(int argc, char **argv) +#else +int main(int argc, char **argv) +#endif +{ + const char *trace_file_name = NULL; + const char *profile_name = NULL; + float scale = 0; + int c; + +#ifndef _WIN32 + + /* Never wait for termination of child processes. */ + struct sigaction arg = { + .sa_handler=SIG_IGN, + .sa_flags=SA_NOCLDWAIT + }; + sigaction(SIGCHLD, &arg, NULL); + + signal(SIGHUP, signal_handler); +#endif + + glutInit(&argc, argv); + + while ((c = fz_getopt(argc, argv, "p:r:IW:H:S:U:XJb:A:B:C:T:Y:R:c:")) != -1) + { + switch (c) + { + default: usage(argv[0]); break; + case 'p': password = fz_optarg; break; + case 'r': currentzoom = fz_atof(fz_optarg); break; + case 'c': profile_name = fz_optarg; break; + case 'I': currentinvert = !currentinvert; break; + case 'b': currentbox = fz_box_type_from_string(fz_optarg); break; + case 'W': layout_w = fz_atof(fz_optarg); break; + case 'H': layout_h = fz_atof(fz_optarg); break; + case 'S': layout_em = fz_atof(fz_optarg); break; + case 'U': layout_css = fz_optarg; break; + case 'X': layout_use_doc_css = 0; break; + case 'J': enable_js = !enable_js; break; + case 'A': currentaa = fz_atoi(fz_optarg); break; + case 'C': currenttint = 1; tint_white = strtol(fz_optarg, NULL, 16); break; + case 'B': currenttint = 1; tint_black = strtol(fz_optarg, NULL, 16); break; + case 'R': reflow_options = fz_optarg; break; + case 'T': trace_file_name = fz_optpath(fz_optarg); break; + case 'Y': scale = fz_atof(fz_optarg); break; + } + } + + screen_w = glutGet(GLUT_SCREEN_WIDTH) - SCREEN_FURNITURE_W; + screen_h = glutGet(GLUT_SCREEN_HEIGHT) - SCREEN_FURNITURE_H; + + ui_init_dpi(scale); + + oldzoom = currentzoom = currentzoom * ui.scale; + + ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT); + +#ifdef _WIN32 + /* stderr goes nowhere. Get us a debug stream we have a chance + * of seeing. */ + fz_set_stddbg(ctx, fz_stdods(ctx)); +#endif + +#if FZ_ENABLE_JS + console_init(); +#endif + + fz_register_document_handlers(ctx); + + if (trace_file_name) + { + trace_file = fz_new_output_with_path(ctx, trace_file_name, 0); + trace_action("var doc, page, annot, widget, widgetstr, hits, tmp;\n"); + trace_action("function RegressionError() {\n"); + trace_action(" var err = new Error(Array.prototype.join.call(arguments, ' '));\n"); + trace_action(" err.name = 'RegressionError';\n"); + trace_action(" return err;\n"); + trace_action("}\n"); + } + + if (profile_name) + { + fz_buffer *profile_data = fz_read_file(ctx, profile_name); + profile = fz_new_icc_colorspace(ctx, FZ_COLORSPACE_RGB, 0, NULL, profile_data); + fz_drop_buffer(ctx, profile_data); + } + else + { + profile = fz_device_rgb(ctx); + } + + if (layout_css) + fz_load_user_css(ctx, layout_css); + fz_set_use_document_css(ctx, layout_use_doc_css); + + if (fz_optind < argc) + { + fz_strlcpy(filename, argv[fz_optind++], sizeof filename); + if (fz_optind < argc) + anchor = argv[fz_optind++]; + if (fz_optind < argc) + usage(argv[0]); + + fz_try(ctx) + { + page_tex.w = 600; + page_tex.h = 700; + load_document(); + if (doc) + { + if (reflow_options) + reflow_document(); + load_page(); + } + } + fz_always(ctx) + { + float sx = 1, sy = 1; + if (screen_w > 0 && page_tex.w > screen_w) + sx = (float)screen_w / page_tex.w; + if (screen_h > 0 && page_tex.h > screen_h) + sy = (float)screen_h / page_tex.h; + if (sy < sx) + sx = sy; + if (sx < 1) + { + fz_irect area; + + currentzoom *= sx; + oldzoom = currentzoom; + + /* compute bounds here for initial window size */ + page_bounds = fz_bound_page_box(ctx, fzpage, currentbox); + transform_page(); + + area = fz_irect_from_rect(draw_page_bounds); + page_tex.w = area.x1 - area.x0; + page_tex.h = area.y1 - area.y0; + } + + ui_init(page_tex.w, page_tex.h, "MuPDF: Loading..."); + ui_input_init(&search_input, ""); + } + fz_catch(ctx) + { + ui_show_error_dialog("%s", fz_caught_message(ctx)); + fz_report_error(ctx); + } + + fz_try(ctx) + { + if (doc) + update_title(); + } + fz_catch(ctx) + { + ui_show_error_dialog("%s", fz_caught_message(ctx)); + fz_report_error(ctx); + } + } + else + { +#ifdef _WIN32 + win_install(); +#endif + ui_init(ui.gridsize * 26, ui.gridsize * 26, "MuPDF: Open document"); + ui_input_init(&search_input, ""); + ui_init_open_file(".", document_filter); + ui.dialog = do_open_document_dialog; + } + + annotate_w *= ui.lineheight; + outline_w *= ui.lineheight; +#if FZ_ENABLE_JS + console_h *= ui.lineheight; +#endif + + glutMainLoop(); + + cleanup(); + + return 0; +} + +#ifdef _WIN32 +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) +{ + int argc; + LPWSTR *wargv = CommandLineToArgvW(GetCommandLineW(), &argc); + char **argv = fz_argv_from_wargv(argc, wargv); + int ret = main_utf8(argc, argv); + fz_free_argv(argc, argv); + return ret; +} +#endif
