comparison 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
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 // Copyright (C) 2004-2025 Artifex Software, Inc.
2 //
3 // This file is part of MuPDF.
4 //
5 // MuPDF is free software: you can redistribute it and/or modify it under the
6 // terms of the GNU Affero General Public License as published by the Free
7 // Software Foundation, either version 3 of the License, or (at your option)
8 // any later version.
9 //
10 // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
11 // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13 // details.
14 //
15 // You should have received a copy of the GNU Affero General Public License
16 // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
17 //
18 // Alternative licensing terms are available from the licensor.
19 // For commercial licensing, see <https://www.artifex.com/> or contact
20 // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
21 // CA 94129, USA, for further information.
22
23 #include "gl-app.h"
24
25 #include <limits.h>
26 #include <string.h>
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <time.h>
30 #ifndef _WIN32
31 #include <sys/types.h>
32 #include <sys/wait.h>
33 #include <signal.h>
34 #endif
35
36 #include "mupdf/helpers/pkcs7-openssl.h"
37
38 #if FZ_ENABLE_JS
39 #include "mujs.h"
40 #endif
41
42 #ifndef _WIN32
43 #include <sys/stat.h> /* for mkdir */
44 #include <unistd.h> /* for getcwd */
45 #include <spawn.h> /* for posix_spawn */
46 extern char **environ; /* see environ (7) */
47 #else
48 #include <direct.h> /* for getcwd */
49 #endif
50
51 #ifdef __APPLE__
52 static void cleanup(void);
53 void glutLeaveMainLoop(void)
54 {
55 cleanup();
56 exit(0);
57 }
58 #endif
59
60 fz_context *ctx = NULL;
61 fz_colorspace *profile = NULL;
62 pdf_document *pdf = NULL;
63 pdf_page *page = NULL;
64 fz_stext_page *page_text = NULL;
65 fz_matrix draw_page_ctm, view_page_ctm, view_page_inv_ctm;
66 fz_rect page_bounds, draw_page_bounds, view_page_bounds;
67 fz_irect view_page_area;
68 char filename[PATH_MAX];
69
70 enum
71 {
72 /* Screen furniture: aggregate size of unusable space from title bars, task bars, window borders, etc */
73 SCREEN_FURNITURE_W = 20,
74 SCREEN_FURNITURE_H = 40,
75 };
76
77 static void open_browser(const char *uri)
78 {
79 #ifndef _WIN32
80 char *argv[3];
81 #endif
82 char buf[PATH_MAX];
83
84 #ifndef _WIN32
85 pid_t pid;
86 int err;
87 #endif
88
89 /* Relative file: URI, make it absolute! */
90 if (!strncmp(uri, "file:", 5) && uri[5] != '/')
91 {
92 char buf_base[PATH_MAX];
93 char buf_cwd[PATH_MAX];
94 fz_dirname(buf_base, filename, sizeof buf_base);
95 if (getcwd(buf_cwd, sizeof buf_cwd))
96 {
97 fz_snprintf(buf, sizeof buf, "file://%s/%s/%s", buf_cwd, buf_base, uri+5);
98 fz_cleanname(buf+7);
99 uri = buf;
100 }
101 }
102
103 if (strncmp(uri, "file://", 7) && strncmp(uri, "http://", 7) && strncmp(uri, "https://", 8) && strncmp(uri, "mailto:", 7))
104 {
105 fz_warn(ctx, "refusing to open unknown link (%s)", uri);
106 return;
107 }
108
109 #ifdef _WIN32
110 ShellExecuteA(NULL, "open", uri, 0, 0, SW_SHOWNORMAL);
111 #else
112 const char *browser = getenv("BROWSER");
113 if (!browser)
114 {
115 #ifdef __APPLE__
116 browser = "open";
117 #else
118 browser = "xdg-open";
119 #endif
120 }
121
122 argv[0] = (char*) browser;
123 argv[1] = (char*) uri;
124 argv[2] = NULL;
125 err = posix_spawnp(&pid, browser, NULL, NULL, argv, environ);
126 if (err)
127 fz_warn(ctx, "cannot spawn browser '%s': %s", browser, strerror(err));
128
129 #endif
130 }
131
132 static const int zoom_list[] = {
133 6, 12, 24, 36, 48, 60, 72, 84, 96, 108,
134 120, 144, 168, 192, 228, 264,
135 300, 350, 400, 450, 500, 550, 600
136 };
137
138 static int zoom_in(int oldres)
139 {
140 int i;
141 for (i = 0; i < (int)nelem(zoom_list) - 1; ++i)
142 if (zoom_list[i] <= oldres && zoom_list[i+1] > oldres)
143 return zoom_list[i+1];
144 return zoom_list[i];
145 }
146
147 static int zoom_out(int oldres)
148 {
149 int i;
150 for (i = 0; i < (int)nelem(zoom_list) - 1; ++i)
151 if (zoom_list[i] < oldres && zoom_list[i+1] >= oldres)
152 return zoom_list[i];
153 return zoom_list[0];
154 }
155
156 static const char *paper_size_name(int w, int h)
157 {
158 /* ISO A */
159 if (w == 2384 && h == 3370) return "A0";
160 if (w == 1684 && h == 2384) return "A1";
161 if (w == 1191 && h == 1684) return "A2";
162 if (w == 842 && h == 1191) return "A3";
163 if (w == 595 && h == 842) return "A4";
164 if (w == 420 && h == 595) return "A5";
165 if (w == 297 && h == 420) return "A6";
166
167 /* US */
168 if (w == 612 && h == 792) return "Letter";
169 if (w == 612 && h == 1008) return "Legal";
170 if (w == 792 && h == 1224) return "Ledger";
171 if (w == 1224 && h == 792) return "Tabloid";
172
173 return NULL;
174 }
175
176 #define MINRES (zoom_list[0])
177 #define MAXRES (zoom_list[nelem(zoom_list)-1])
178 #define DEFRES 96
179
180 static char *password = "";
181 static char *anchor = NULL;
182 static float layout_w = FZ_DEFAULT_LAYOUT_W;
183 static float layout_h = FZ_DEFAULT_LAYOUT_H;
184 static float layout_em = FZ_DEFAULT_LAYOUT_EM;
185 static char *layout_css = NULL;
186 static int layout_use_doc_css = 1;
187 static int enable_js = 1;
188 static int tint_white = 0xFFFFF0;
189 static int tint_black = 0x303030;
190
191 static fz_document *doc = NULL;
192 static fz_page *fzpage = NULL;
193 static fz_separations *seps = NULL;
194 static fz_outline *outline = NULL;
195 static fz_link *links = NULL;
196
197 static int number = 0;
198
199 static fz_pixmap *page_contents = NULL;
200 static struct texture page_tex = { 0 };
201 static int screen_w = 0, screen_h = 0;
202 static int scroll_x = 0, scroll_y = 0;
203 static int canvas_x = 0, canvas_w = 100;
204 static int canvas_y = 0, canvas_h = 100;
205
206 static int outline_w = 14; /* to be scaled by lineheight */
207 static int annotate_w = 12; /* to be scaled by lineheight */
208
209 static int outline_start_x = 0;
210
211 static int oldbox = FZ_CROP_BOX, currentbox = FZ_CROP_BOX;
212 static int oldtint = 0, currenttint = 0;
213 static int oldinvert = 0, currentinvert = 0;
214 static int oldicc = 1, currenticc = 1;
215 static int oldaa = 8, currentaa = 8;
216 static int oldseparations = 1, currentseparations = 1;
217 static fz_location oldpage = {0,0}, currentpage = {0,0};
218 static float oldzoom = DEFRES, currentzoom = DEFRES;
219 static float oldrotate = 0, currentrotate = 0;
220 int page_contents_changed = 0;
221 int page_annots_changed = 0;
222
223 static fz_output *trace_file = NULL;
224 static char *reflow_options = NULL;
225 static int isfullscreen = 0;
226 static int showoutline = 0;
227 static int showundo = 0;
228 static int showlayers = 0;
229 static int showlinks = 0;
230 static int showsearch = 0;
231 int showannotate = 0;
232 int showform = 0;
233
234 #if FZ_ENABLE_JS
235 static int showconsole = 0;
236 static int console_h = 14; /* to be scaled by lineheight */
237 static pdf_js_console gl_js_console;
238 static int console_start_y = 0;
239 #endif
240
241 static const char *tooltip = NULL;
242
243 struct mark
244 {
245 fz_location loc;
246 fz_point scroll;
247 };
248
249 static int history_count = 0;
250 static struct mark history[256];
251 static int future_count = 0;
252 static struct mark future[256];
253 static struct mark marks[10];
254
255 static char *get_history_filename(void)
256 {
257 static char history_path[PATH_MAX];
258 static int once = 0;
259 if (!once)
260 {
261 char *home = getenv("MUPDF_HISTORY");
262 if (home)
263 return home;
264 home = getenv("XDG_CACHE_HOME");
265 if (!home)
266 home = getenv("HOME");
267 if (!home)
268 home = getenv("USERPROFILE");
269 if (!home)
270 home = "/tmp";
271 fz_snprintf(history_path, sizeof history_path, "%s/.mupdf.history", home);
272 fz_cleanname(history_path);
273 once = 1;
274 }
275 return history_path;
276 }
277
278 static fz_json *read_history_file_as_json(fz_pool *pool)
279 {
280 fz_buffer *buf = NULL;
281 const char *json = "{}";
282 const char *history_file;
283 fz_json *result = NULL;
284
285 fz_var(buf);
286
287 history_file = get_history_filename();
288 if (strlen(history_file) == 0)
289 return NULL;
290
291 if (fz_file_exists(ctx, history_file))
292 {
293 fz_try(ctx)
294 {
295 buf = fz_read_file(ctx, history_file);
296 json = fz_string_from_buffer(ctx, buf);
297 }
298 fz_catch(ctx)
299 ;
300 }
301
302 fz_try(ctx)
303 {
304 result = fz_parse_json(ctx, pool, json);
305 }
306 fz_catch(ctx)
307 {
308 fz_report_error(ctx);
309 fz_warn(ctx, "can't parse history file");
310 result = NULL;
311 }
312 fz_drop_buffer(ctx, buf);
313
314 if (result == NULL || result->type != FZ_JSON_OBJECT)
315 result = fz_json_new_object(ctx, pool);
316 return result;
317 }
318
319 static fz_location load_location(fz_json *val)
320 {
321 if (fz_json_is_number(ctx, val))
322 return fz_make_location(0, fz_json_to_number(ctx, val) - 1);
323 if (fz_json_is_array(ctx, val))
324 return fz_make_location(
325 fz_json_to_number(ctx, fz_json_array_get(ctx, val, 0)) - 1,
326 fz_json_to_number(ctx, fz_json_array_get(ctx, val, 1)) - 1
327 );
328 return fz_make_location(0, 0);
329 }
330
331 static fz_json *save_location(fz_pool *pool, fz_location loc)
332 {
333 fz_json *arr;
334 if (loc.chapter == 0)
335 {
336 return fz_json_new_number(ctx, pool, loc.page + 1);
337 }
338 else
339 {
340 arr = fz_json_new_array(ctx, pool);
341 fz_json_array_push(ctx, pool, arr, fz_json_new_number(ctx, pool, loc.chapter + 1));
342 fz_json_array_push(ctx, pool, arr, fz_json_new_number(ctx, pool, loc.page + 1));
343 return arr;
344 }
345 }
346
347 static void load_history(void)
348 {
349 char absname[PATH_MAX];
350 fz_pool *pool = NULL;
351 fz_json *json, *item, *arr, *val;
352 int i, n;
353
354 fz_var(pool);
355
356 if (!fz_realpath(filename, absname))
357 return;
358
359 fz_try(ctx)
360 {
361 pool = fz_new_pool(ctx);
362 json = read_history_file_as_json(pool);
363 if (json)
364 {
365 item = fz_json_object_get(ctx, json, absname);
366 if (item)
367 {
368 val = fz_json_object_get(ctx, item, "current");
369 if (val)
370 currentpage = load_location(val);
371
372 arr = fz_json_object_get(ctx, item, "history");
373 if (fz_json_is_array(ctx, arr))
374 {
375 history_count = fz_clampi(fz_json_array_length(ctx, arr), 0, nelem(history));
376 for (i = 0; i < history_count; ++i)
377 history[i].loc = load_location(fz_json_array_get(ctx, arr, i));
378 }
379
380 arr = fz_json_object_get(ctx, item, "future");
381 if (fz_json_is_array(ctx, arr))
382 {
383 future_count = fz_clampi(fz_json_array_length(ctx, arr), 0, nelem(future));
384 for (i = 0; i < future_count; ++i)
385 future[i].loc = load_location(fz_json_array_get(ctx, arr, i));
386 }
387
388 arr = fz_json_object_get(ctx, item, "marks");
389 if (fz_json_is_array(ctx, arr))
390 {
391 n = fz_clampi(fz_json_array_length(ctx, arr), 0, nelem(marks));
392 for (i = 0; i < n; ++i)
393 marks[i].loc = load_location(fz_json_array_get(ctx, arr, i));
394 }
395 }
396
397 }
398 }
399 fz_always(ctx)
400 {
401 fz_drop_pool(ctx, pool);
402 }
403 fz_catch(ctx)
404 {
405 fz_report_error(ctx);
406 fz_warn(ctx, "Can't read history file.");
407 }
408 }
409
410 static void save_history(void)
411 {
412 fz_pool *pool;
413 char absname[PATH_MAX];
414 fz_output *out = NULL;
415 fz_json *json, *item, *arr;
416 const char *history_file;
417 int i;
418
419 fz_var(pool);
420 fz_var(out);
421
422 if (!doc)
423 return;
424
425 if (!fz_realpath(filename, absname))
426 return;
427
428 fz_try(ctx)
429 {
430 pool = fz_new_pool(ctx);
431 json = read_history_file_as_json(pool);
432 if (json)
433 {
434 item = fz_json_new_object(ctx, pool);
435 fz_json_object_set(ctx, pool, item, "current", save_location(pool, currentpage));
436
437 arr = fz_json_new_array(ctx, pool);
438 for (i = 0; i < history_count; ++i)
439 fz_json_array_push(ctx, pool, arr, save_location(pool, history[i].loc));
440 fz_json_object_set(ctx, pool, item, "history", arr);
441
442 arr = fz_json_new_array(ctx, pool);
443 for (i = 0; i < future_count; ++i)
444 fz_json_array_push(ctx, pool, arr, save_location(pool, future[i].loc));
445 fz_json_object_set(ctx, pool, item, "future", arr);
446
447 arr = fz_json_new_array(ctx, pool);
448 for (i = 0; i < (int)nelem(marks); ++i)
449 fz_json_array_push(ctx, pool, arr, save_location(pool, marks[i].loc));
450 fz_json_object_set(ctx, pool, item, "marks", arr);
451
452 fz_json_object_set(ctx, pool, json, absname, item);
453
454 history_file = get_history_filename();
455 if (strlen(history_file) > 0) {
456 out = fz_new_output_with_path(ctx, history_file, 0);
457 fz_write_json(ctx, out, json);
458 fz_write_byte(ctx, out, '\n');
459 fz_close_output(ctx, out);
460 }
461 }
462 }
463 fz_always(ctx)
464 {
465 fz_drop_pool(ctx, pool);
466 fz_drop_output(ctx, out);
467 }
468 fz_catch(ctx)
469 {
470 fz_report_error(ctx);
471 fz_warn(ctx, "Can't write history file.");
472 }
473 }
474
475
476 static int create_accel_path(char outname[], size_t len, int create, const char *absname, ...)
477 {
478 va_list args;
479 char *s = outname;
480 size_t z, remain = len;
481 char *arg;
482
483 va_start(args, absname);
484
485 while ((arg = va_arg(args, char *)) != NULL)
486 {
487 z = fz_snprintf(s, remain, "%s", arg);
488 if (z+1 > remain)
489 goto fail; /* won't fit */
490
491 if (create)
492 (void) fz_mkdir(outname);
493 if (!fz_is_directory(ctx, outname))
494 goto fail; /* directory creation failed, or that dir doesn't exist! */
495 #ifdef _WIN32
496 s[z] = '\\';
497 #else
498 s[z] = '/';
499 #endif
500 s[z+1] = 0;
501 s += z+1;
502 remain -= z+1;
503 }
504
505 if (fz_snprintf(s, remain, "%s.accel", absname) >= remain)
506 goto fail; /* won't fit */
507
508 va_end(args);
509
510 return 1;
511
512 fail:
513 va_end(args);
514
515 return 0;
516 }
517
518 static int convert_to_accel_path(char outname[], char *absname, size_t len, int create)
519 {
520 char *tmpdir;
521 char *s;
522
523 if (absname[0] == '/' || absname[0] == '\\')
524 ++absname;
525
526 s = absname;
527 while (*s) {
528 if (*s == '/' || *s == '\\' || *s == ':')
529 *s = '%';
530 ++s;
531 }
532
533 #ifdef _WIN32
534 tmpdir = getenv("USERPROFILE");
535 if (tmpdir && create_accel_path(outname, len, create, absname, tmpdir, ".config", "mupdf", NULL))
536 return 1; /* OK! */
537 /* TEMP and TMP are user-specific on modern windows. */
538 tmpdir = getenv("TEMP");
539 if (tmpdir && create_accel_path(outname, len, create, absname, tmpdir, "mupdf", NULL))
540 return 1; /* OK! */
541 tmpdir = getenv("TMP");
542 if (tmpdir && create_accel_path(outname, len, create, absname, tmpdir, "mupdf", NULL))
543 return 1; /* OK! */
544 #else
545 tmpdir = getenv("XDG_CACHE_HOME");
546 if (tmpdir && create_accel_path(outname, len, create, absname, tmpdir, "mupdf", NULL))
547 return 1; /* OK! */
548 tmpdir = getenv("HOME");
549 if (tmpdir && create_accel_path(outname, len, create, absname, tmpdir, ".cache", "mupdf", NULL))
550 return 1; /* OK! */
551 #endif
552 return 0; /* Fail */
553 }
554
555 static int get_accelerator_filename(char outname[], size_t len, int create)
556 {
557 char absname[PATH_MAX];
558 if (!fz_realpath(filename, absname))
559 return 0;
560 if (!convert_to_accel_path(outname, absname, len, create))
561 return 0;
562 return 1;
563 }
564
565 static void save_accelerator(void)
566 {
567 char absname[PATH_MAX];
568
569 if (!doc)
570 return;
571 if (!fz_document_supports_accelerator(ctx, doc))
572 return;
573 if (!get_accelerator_filename(absname, sizeof(absname), 1))
574 return;
575
576 fz_save_accelerator(ctx, doc, absname);
577 }
578
579 static struct input search_input = { { 0 }, 0 };
580 static int search_dir = 1;
581 static fz_location search_page = {-1, -1};
582 static fz_location search_hit_page = {-1, -1};
583 static int search_active = 0;
584 char *search_needle = 0;
585 int search_hit_count = 0;
586 fz_quad search_hit_quads[5000];
587
588 static char *help_dialog_text =
589 "The middle mouse button (scroll wheel button) pans the document view. "
590 "The right mouse button selects a region and copies the marked text to the clipboard."
591 "\n"
592 "\n"
593 "F1 - show this message\n"
594 "` F12 - show javascript console\n"
595 "i - show document information\n"
596 "o - show document outline\n"
597 "u - show undo history\n"
598 "Y - show layer list\n"
599 "a - show annotation editor\n"
600 "R - show redaction editor\n"
601 "L - highlight links\n"
602 "F - highlight form fields\n"
603 "r - reload file\n"
604 "S - save file (only for PDF)\n"
605 "q - quit\n"
606 "\n"
607 "< - decrease E-book font size\n"
608 "> - increase E-book font size\n"
609 "B - cycle between MediaBox, CropBox, ArtBox, etc.\n"
610 "A - toggle anti-aliasing\n"
611 "I - toggle inverted color mode\n"
612 "C - toggle tinted color mode\n"
613 "E - toggle ICC color management\n"
614 "e - toggle spot color emulation\n"
615 "\n"
616 "f - fullscreen window\n"
617 "w - shrink wrap window\n"
618 "W - fit to width\n"
619 "H - fit to height\n"
620 "Z - fit to page\n"
621 "z - reset zoom\n"
622 "[number] z - set zoom resolution in DPI\n"
623 "plus - zoom in\n"
624 "minus - zoom out\n"
625 "[ - rotate counter-clockwise\n"
626 "] - rotate clockwise\n"
627 "arrow keys - scroll in small increments\n"
628 "h, j, k, l - scroll in small increments\n"
629 "\n"
630 "b - smart move backward\n"
631 "space - smart move forward\n"
632 "comma or page up - go backward\n"
633 "period or page down - go forward\n"
634 "g - go to first page\n"
635 "G - go to last page\n"
636 "[number] g - go to page number\n"
637 "\n"
638 "m - save current location in history\n"
639 "t - go backward in history\n"
640 "T - go forward in history\n"
641 "[number] m - save current location in numbered bookmark\n"
642 "[number] t - go to numbered bookmark\n"
643 "\n"
644 "/ - search for text forward\n"
645 "? - search for text backward\n"
646 "n - repeat search\n"
647 "N - repeat search in reverse direction"
648 ;
649
650 static void help_dialog(void)
651 {
652 static int scroll;
653 ui_dialog_begin(ui.gridsize*20, ui.gridsize*40);
654 ui_layout(T, X, W, ui.padsize, ui.padsize);
655 ui_label("MuPDF %s", FZ_VERSION);
656 ui_spacer();
657 ui_layout(B, NONE, S, ui.padsize, ui.padsize);
658 if (ui_button("Okay") || ui.key == KEY_ENTER || ui.key == KEY_ESCAPE)
659 ui.dialog = NULL;
660 ui_spacer();
661 ui_layout(ALL, BOTH, CENTER, ui.padsize, ui.padsize);
662 ui_label_with_scrollbar(help_dialog_text, 0, 0, &scroll, NULL);
663 ui_dialog_end();
664 }
665
666 static fz_buffer *format_info_text();
667
668 static void info_dialog(void)
669 {
670 static int scroll;
671 fz_buffer *info_text;
672
673 ui_dialog_begin(ui.gridsize*20, ui.gridsize*20);
674 ui_layout(B, NONE, S, ui.padsize, ui.padsize);
675 if (ui_button("Okay") || ui.key == KEY_ENTER || ui.key == KEY_ESCAPE)
676 ui.dialog = NULL;
677 ui_spacer();
678 ui_layout(ALL, BOTH, CENTER, ui.padsize, ui.padsize);
679
680 info_text = format_info_text();
681 ui_label_with_scrollbar((char*)fz_string_from_buffer(ctx, info_text), 0, 0, &scroll, NULL);
682 fz_drop_buffer(ctx, info_text);
683
684 ui_dialog_end();
685 }
686
687 static char error_message[256];
688 static void error_dialog(void)
689 {
690 ui_dialog_begin(ui.gridsize*20, (ui.gridsize+ui.padsize*2)*4);
691 ui_layout(T, NONE, NW, ui.padsize, ui.padsize);
692 ui_label("%C %s", 0x1f4a3, error_message); /* BOMB */
693 ui_layout(B, NONE, S, ui.padsize, ui.padsize);
694 if (ui_button("Quit") || ui.key == KEY_ENTER || ui.key == KEY_ESCAPE || ui.key == 'q')
695 glutLeaveMainLoop();
696 ui_dialog_end();
697 }
698 void ui_show_error_dialog(const char *fmt, ...)
699 {
700 va_list ap;
701 va_start(ap, fmt);
702 fz_vsnprintf(error_message, sizeof error_message, fmt, ap);
703 va_end(ap);
704 ui.dialog = error_dialog;
705 }
706
707 static char warning_message[256];
708 static void warning_dialog(void)
709 {
710 ui_dialog_begin(ui.gridsize*20, (ui.gridsize+ui.padsize*2)*4);
711 ui_layout(T, NONE, NW, ui.padsize, ui.padsize);
712 ui_label("%C %s", 0x26a0, warning_message); /* WARNING SIGN */
713 ui_layout(B, NONE, S, ui.padsize, ui.padsize);
714 if (ui_button("Okay") || ui.key == KEY_ENTER || ui.key == KEY_ESCAPE)
715 ui.dialog = NULL;
716 ui_dialog_end();
717 }
718 void ui_show_warning_dialog(const char *fmt, ...)
719 {
720 va_list ap;
721 va_start(ap, fmt);
722 fz_vsnprintf(warning_message, sizeof warning_message, fmt, ap);
723 va_end(ap);
724 ui.dialog = warning_dialog;
725 }
726
727 static void quit_dialog(void)
728 {
729 ui_dialog_begin(ui.gridsize*20, (ui.gridsize+ui.padsize*2)*3);
730 ui_layout(T, NONE, NW, ui.padsize, ui.padsize);
731 ui_label("%C The document has unsaved changes. Are you sure you want to quit?", 0x26a0); /* WARNING SIGN */
732 ui_layout(B, X, S, ui.padsize, ui.padsize);
733 ui_panel_begin(0, ui.gridsize, 0, 0, 0);
734 {
735 ui_layout(R, NONE, S, 0, 0);
736 if (ui_button("Save"))
737 do_save_pdf_file();
738 ui_spacer();
739 if (ui_button("Discard") || ui.key == 'q')
740 glutLeaveMainLoop();
741 ui_layout(L, NONE, S, 0, 0);
742 if (ui_button("Cancel") || ui.key == KEY_ESCAPE)
743 ui.dialog = NULL;
744 }
745 ui_panel_end();
746 ui_dialog_end();
747 }
748
749 static void quit(void)
750 {
751 if (pdf && pdf_has_unsaved_changes(ctx, pdf))
752 ui.dialog = quit_dialog;
753 else
754 glutLeaveMainLoop();
755 }
756
757 static void reload_dialog(void)
758 {
759 ui_dialog_begin(ui.gridsize*20, (ui.gridsize+ui.padsize*2)*3);
760 ui_layout(T, NONE, NW, ui.padsize, ui.padsize);
761 ui_label("%C The document has unsaved changes. Are you sure you want to reload?", 0x26a0); /* WARNING SIGN */
762 ui_layout(B, X, S, ui.padsize, ui.padsize);
763 ui_panel_begin(0, ui.gridsize, 0, 0, 0);
764 {
765 ui_layout(R, NONE, S, 0, 0);
766 if (ui_button("Save"))
767 do_save_pdf_file();
768 ui_spacer();
769 if (ui_button("Reload") || ui.key == 'q')
770 {
771 ui.dialog = NULL;
772 reload_document();
773 }
774 ui_layout(L, NONE, S, 0, 0);
775 if (ui_button("Cancel") || ui.key == KEY_ESCAPE)
776 ui.dialog = NULL;
777 }
778 ui_panel_end();
779 ui_dialog_end();
780 }
781
782 void reload(void)
783 {
784 if (pdf && pdf_has_unsaved_changes(ctx, pdf))
785 ui.dialog = reload_dialog;
786 else
787 reload_document();
788 }
789
790 void trace_action(const char *fmt, ...)
791 {
792 va_list args;
793 if (trace_file)
794 {
795 va_start(args, fmt);
796 fz_write_vprintf(ctx, trace_file, fmt, args);
797 fz_flush_output(ctx, trace_file);
798 va_end(args);
799 va_start(args, fmt);
800 fz_write_vprintf(ctx, fz_stdout(ctx), fmt, args);
801 fz_flush_output(ctx, fz_stdout(ctx));
802 va_end(args);
803 }
804 }
805
806 void trace_page_update(void)
807 {
808 trace_action("page.update();\n");
809 }
810
811 void trace_save_snapshot(void)
812 {
813 static int trace_idx = 1;
814 trace_action("page.toPixmap(Matrix.identity, ColorSpace.DeviceRGB).saveAsPNG(\"trace-%03d.png\");\n", trace_idx++);
815 }
816
817 static int document_shown_as_dirty = 0;
818
819 void update_title(void)
820 {
821 char buf[256];
822 const char *title = "MuPDF/GL";
823 char *extra = "";
824 size_t n;
825
826 int nc = fz_count_chapters(ctx, doc);
827
828 title = fz_basename(filename);
829
830 document_shown_as_dirty = pdf && pdf_has_unsaved_changes(ctx, pdf);
831 if (document_shown_as_dirty)
832 extra = "*";
833
834 n = strlen(title);
835 if (n > 50)
836 {
837 if (nc == 1)
838 sprintf(buf, "...%s%s - %d/%d", title + n - 50, extra, currentpage.page + 1, fz_count_pages(ctx, doc));
839 else
840 sprintf(buf, "...%s%s - %d/%d - %d/%d", title + n - 50, extra,
841 currentpage.chapter + 1, nc,
842 currentpage.page + 1, fz_count_chapter_pages(ctx, doc, currentpage.chapter));
843 }
844 else
845 {
846 if (nc == 1)
847 sprintf(buf, "%s%s - %d/%d", title, extra, currentpage.page + 1, fz_count_pages(ctx, doc));
848 else
849
850 sprintf(buf, "%s%s - %d/%d - %d/%d", title, extra,
851 currentpage.chapter + 1, nc,
852 currentpage.page + 1, fz_count_chapter_pages(ctx, doc, currentpage.chapter));
853 }
854 glutSetWindowTitle(buf);
855 glutSetIconTitle(buf);
856 }
857
858 void transform_page(void)
859 {
860 draw_page_ctm = fz_transform_page(page_bounds, currentzoom, currentrotate);
861 draw_page_bounds = fz_transform_rect(page_bounds, draw_page_ctm);
862 }
863
864 static void clear_selected_annot(void)
865 {
866 /* clear all editor selections */
867 if (ui.selected_annot && pdf_annot_type(ctx, ui.selected_annot) == PDF_ANNOT_WIDGET)
868 pdf_annot_event_blur(ctx, ui.selected_annot);
869 ui_select_annot(NULL);
870 }
871
872 void load_page(void)
873 {
874 fz_irect area;
875
876 clear_selected_annot();
877
878 if (trace_file)
879 trace_action("page = doc.loadPage(%d);\n", fz_page_number_from_location(ctx, doc, currentpage));
880
881 fz_drop_stext_page(ctx, page_text);
882 page_text = NULL;
883 fz_drop_separations(ctx, seps);
884 seps = NULL;
885 fz_drop_link(ctx, links);
886 links = NULL;
887 fz_drop_page(ctx, fzpage);
888 fzpage = NULL;
889
890 fzpage = fz_load_chapter_page(ctx, doc, currentpage.chapter, currentpage.page);
891 if (pdf)
892 page = (pdf_page*)fzpage;
893
894 if (trace_file)
895 {
896 pdf_annot *w;
897 int i, s;
898
899 for (i = 0, s = 0, w = pdf_first_widget(ctx, page); w != NULL; i++, w = pdf_next_widget(ctx, w))
900 if (pdf_widget_type(ctx, w) == PDF_WIDGET_TYPE_SIGNATURE)
901 {
902 int is_signed;
903
904 s++;
905 trace_action("widget = page.getWidgets()[%d];\n", i);
906 trace_action("widgetstr = 'Signature %d on page %d';\n",
907 s, fz_page_number_from_location(ctx, doc, currentpage));
908
909 is_signed = pdf_widget_is_signed(ctx, w);
910 trace_action("tmp = widget.isSigned();\n");
911 trace_action("if (tmp != %d)\n", is_signed);
912 trace_action(" throw new RegressionError(widgetstr, 'is signed:', tmp|0, 'expected:', %d);\n", is_signed);
913
914 if (is_signed)
915 {
916 int valid_until, is_readonly;
917 char *cert_error, *digest_error;
918 pdf_pkcs7_distinguished_name *dn;
919 pdf_pkcs7_verifier *verifier;
920 char *signatory = NULL;
921 char buf[500];
922
923 valid_until = pdf_validate_signature(ctx, w);
924 is_readonly = pdf_widget_is_readonly(ctx, w);
925 verifier = pkcs7_openssl_new_verifier(ctx);
926 cert_error = pdf_signature_error_description(pdf_check_widget_certificate(ctx, verifier, w));
927 digest_error = pdf_signature_error_description(pdf_check_widget_digest(ctx, verifier, w));
928 dn = pdf_signature_get_widget_signatory(ctx, verifier, w);
929 if (dn)
930 {
931 char *s = pdf_signature_format_distinguished_name(ctx, dn);
932 fz_strlcpy(buf, s, sizeof buf);
933 fz_free(ctx, s);
934 pdf_signature_drop_distinguished_name(ctx, dn);
935 }
936 else
937 {
938 fz_strlcpy(buf, "Signature information missing.", sizeof buf);
939 }
940 signatory = &buf[0];
941 pdf_drop_verifier(ctx, verifier);
942
943 trace_action("tmp = widget.validateSignature();\n");
944 trace_action("if (tmp != %d)\n", valid_until);
945 trace_action(" throw new RegressionError(widgetstr, 'valid until:', tmp, 'expected:', %d);\n", valid_until);
946 trace_action("tmp = widget.isReadOnly();\n");
947 trace_action("if (tmp != %d)\n", is_readonly);
948 trace_action(" throw new RegressionError(widgetstr, 'is read-only:', tmp, 'expected:', %d);\n", is_readonly);
949 trace_action("tmp = widget.checkCertificate();\n");
950 trace_action("if (tmp != '%s')\n", cert_error);
951 trace_action(" throw new RegressionError(widgetstr, 'certificate error:', tmp, 'expected:', %d);\n", cert_error);
952 trace_action("tmp = widget.checkDigest();\n");
953 trace_action("if (tmp != %q)\n", digest_error);
954 trace_action(" throw new RegressionError(widgetstr, 'digest error:', tmp, 'expected:', %q);\n", digest_error);
955 trace_action("tmp = widget.getSignatory();\n");
956 trace_action("if (tmp != '%s')\n", signatory);
957 trace_action(" throw new RegressionError(widgetstr, 'signatory:', '[', tmp, ']', 'expected:', '[', %q, ']');\n", signatory);
958 }
959 }
960 }
961
962 links = fz_load_links(ctx, fzpage);
963 page_text = fz_new_stext_page_from_page(ctx, fzpage, NULL);
964
965 if (currenticc)
966 fz_enable_icc(ctx);
967 else
968 fz_disable_icc(ctx);
969
970 if (currentseparations)
971 {
972 seps = fz_page_separations(ctx, fzpage);
973 if (seps)
974 {
975 int i, n = fz_count_separations(ctx, seps);
976 for (i = 0; i < n; i++)
977 fz_set_separation_behavior(ctx, seps, i, FZ_SEPARATION_COMPOSITE);
978 }
979 else if (fz_page_uses_overprint(ctx, fzpage))
980 seps = fz_new_separations(ctx, 0);
981 else if (fz_document_output_intent(ctx, doc))
982 seps = fz_new_separations(ctx, 0);
983 }
984
985 /* compute bounds here for initial window size */
986 page_bounds = fz_bound_page_box(ctx, fzpage, currentbox);
987 transform_page();
988
989 area = fz_irect_from_rect(draw_page_bounds);
990 page_tex.w = area.x1 - area.x0;
991 page_tex.h = area.y1 - area.y0;
992
993 page_contents_changed = 1;
994 }
995
996 static void render_page(void)
997 {
998 fz_irect bbox;
999 fz_pixmap *pix;
1000 fz_device *dev;
1001
1002 page_bounds = fz_bound_page_box(ctx, fzpage, currentbox);
1003 transform_page();
1004
1005 fz_set_aa_level(ctx, currentaa);
1006
1007 if (page_contents_changed)
1008 {
1009 fz_drop_pixmap(ctx, page_contents);
1010 page_contents = NULL;
1011
1012 bbox = fz_round_rect(fz_transform_rect(fz_bound_page_box(ctx, fzpage, currentbox), draw_page_ctm));
1013 page_contents = fz_new_pixmap_with_bbox(ctx, profile, bbox, seps, 0);
1014 fz_clear_pixmap(ctx, page_contents);
1015
1016 dev = fz_new_draw_device(ctx, draw_page_ctm, page_contents);
1017
1018 fz_try(ctx)
1019 {
1020 fz_run_page_contents(ctx, fzpage, dev, fz_identity, NULL);
1021 fz_close_device(ctx, dev);
1022 }
1023 fz_always(ctx)
1024 fz_drop_device(ctx, dev);
1025 fz_catch(ctx)
1026 fz_rethrow(ctx);
1027 }
1028
1029 pix = fz_clone_pixmap_area_with_different_seps(ctx, page_contents, NULL, profile, NULL, fz_default_color_params, NULL);
1030 {
1031 dev = fz_new_draw_device(ctx, draw_page_ctm, pix);
1032 fz_try(ctx)
1033 {
1034 fz_run_page_annots(ctx, fzpage, dev, fz_identity, NULL);
1035 fz_run_page_widgets(ctx, fzpage, dev, fz_identity, NULL);
1036 fz_close_device(ctx, dev);
1037 }
1038 fz_always(ctx)
1039 fz_drop_device(ctx, dev);
1040 fz_catch(ctx)
1041 fz_rethrow(ctx);
1042 }
1043
1044 if (currentinvert)
1045 {
1046 fz_invert_pixmap_luminance(ctx, pix);
1047 fz_gamma_pixmap(ctx, pix, 1 / 1.4f);
1048 }
1049 if (currenttint)
1050 {
1051 fz_tint_pixmap(ctx, pix, tint_black, tint_white);
1052 }
1053
1054 ui_texture_from_pixmap(&page_tex, pix);
1055
1056 fz_drop_pixmap(ctx, pix);
1057
1058 FZ_LOG_DUMP_STORE(ctx, "Store state after page render:\n");
1059 }
1060
1061 void render_page_if_changed(void)
1062 {
1063 if (pdf)
1064 {
1065 if (pdf_update_page(ctx, page))
1066 {
1067 trace_page_update();
1068 page_annots_changed = 1;
1069 }
1070 }
1071
1072 if (oldpage.chapter != currentpage.chapter ||
1073 oldpage.page != currentpage.page ||
1074 oldzoom != currentzoom ||
1075 oldrotate != currentrotate ||
1076 oldinvert != currentinvert ||
1077 oldtint != currenttint ||
1078 oldicc != currenticc ||
1079 oldseparations != currentseparations ||
1080 oldaa != currentaa ||
1081 oldbox != currentbox)
1082 {
1083 page_contents_changed = 1;
1084 }
1085
1086 if (page_contents_changed || page_annots_changed)
1087 {
1088 render_page();
1089 oldpage = currentpage;
1090 oldzoom = currentzoom;
1091 oldrotate = currentrotate;
1092 oldinvert = currentinvert;
1093 oldtint = currenttint;
1094 oldicc = currenticc;
1095 oldseparations = currentseparations;
1096 oldaa = currentaa;
1097 oldbox = currentbox;
1098 page_contents_changed = 0;
1099 page_annots_changed = 0;
1100 }
1101 }
1102
1103 static struct mark save_mark()
1104 {
1105 struct mark mark;
1106 mark.loc = currentpage;
1107 mark.scroll = fz_transform_point_xy(scroll_x, scroll_y, view_page_inv_ctm);
1108 return mark;
1109 }
1110
1111 static void restore_mark(struct mark mark)
1112 {
1113 currentpage = mark.loc;
1114 mark.scroll = fz_transform_point(mark.scroll, draw_page_ctm);
1115 scroll_x = mark.scroll.x;
1116 scroll_y = mark.scroll.y;
1117 }
1118
1119 static int eqloc(fz_location a, fz_location b)
1120 {
1121 return a.chapter == b.chapter && a.page == b.page;
1122 }
1123
1124 int search_has_results(void)
1125 {
1126 return !search_active && eqloc(search_hit_page, currentpage) && search_hit_count > 0;
1127 }
1128
1129 static int is_first_page(fz_location loc)
1130 {
1131 return (loc.chapter == 0 && loc.page == 0);
1132 }
1133
1134 static int is_last_page(fz_location loc)
1135 {
1136 fz_location last = fz_last_page(ctx, doc);
1137 return (loc.chapter == last.chapter && loc.page == last.page);
1138 }
1139
1140 static void push_history(void)
1141 {
1142 if (history_count > 0 && eqloc(history[history_count-1].loc, currentpage))
1143 return;
1144 if (history_count + 1 >= (int)nelem(history))
1145 {
1146 memmove(history, history + 1, sizeof *history * (nelem(history) - 1));
1147 history[history_count] = save_mark();
1148 }
1149 else
1150 {
1151 history[history_count++] = save_mark();
1152 }
1153 }
1154
1155 static void push_future(void)
1156 {
1157 if (future_count + 1 >= (int)nelem(future))
1158 {
1159 memmove(future, future + 1, sizeof *future * (nelem(future) - 1));
1160 future[future_count] = save_mark();
1161 }
1162 else
1163 {
1164 future[future_count++] = save_mark();
1165 }
1166 }
1167
1168 static void clear_future(void)
1169 {
1170 future_count = 0;
1171 }
1172
1173 static void jump_to_location(fz_location loc)
1174 {
1175 clear_future();
1176 push_history();
1177 currentpage = fz_clamp_location(ctx, doc, loc);
1178 push_history();
1179 }
1180
1181 static void jump_to_location_xy(fz_location loc, float x, float y)
1182 {
1183 fz_point p = fz_transform_point_xy(x, y, draw_page_ctm);
1184 clear_future();
1185 push_history();
1186 currentpage = fz_clamp_location(ctx, doc, loc);
1187 scroll_x = p.x;
1188 scroll_y = p.y;
1189 push_history();
1190 }
1191
1192 static void jump_to_page(int newpage)
1193 {
1194 clear_future();
1195 push_history();
1196 currentpage = fz_location_from_page_number(ctx, doc, newpage);
1197 currentpage = fz_clamp_location(ctx, doc, currentpage);
1198 push_history();
1199 }
1200
1201 static void jump_to_page_xy(int newpage, float x, float y)
1202 {
1203 fz_point p = fz_transform_point_xy(x, y, draw_page_ctm);
1204 clear_future();
1205 push_history();
1206 currentpage = fz_location_from_page_number(ctx, doc, newpage);
1207 currentpage = fz_clamp_location(ctx, doc, currentpage);
1208 scroll_x = p.x;
1209 scroll_y = p.y;
1210 push_history();
1211 }
1212
1213 static void pop_history(void)
1214 {
1215 fz_location here = currentpage;
1216 push_future();
1217 while (history_count > 0 && eqloc(currentpage, here))
1218 restore_mark(history[--history_count]);
1219 }
1220
1221 static void pop_future(void)
1222 {
1223 fz_location here = currentpage;
1224 push_history();
1225 while (future_count > 0 && eqloc(currentpage, here))
1226 restore_mark(future[--future_count]);
1227 push_history();
1228 }
1229
1230 static void relayout(void)
1231 {
1232 if (layout_em < 6) layout_em = 6;
1233 if (layout_em > 36) layout_em = 36;
1234 if (fz_is_document_reflowable(ctx, doc))
1235 {
1236 fz_bookmark mark = fz_make_bookmark(ctx, doc, currentpage);
1237 fz_layout_document(ctx, doc, layout_w, layout_h, layout_em);
1238 currentpage = fz_lookup_bookmark(ctx, doc, mark);
1239 history_count = 0;
1240 future_count = 0;
1241
1242 load_page();
1243 update_title();
1244 }
1245 }
1246
1247 static int count_outline(fz_outline *node, int end)
1248 {
1249 int is_selected, n, p, np;
1250 int count = 0;
1251
1252 if (!node)
1253 return 0;
1254 np = fz_page_number_from_location(ctx, doc, node->page);
1255
1256 do
1257 {
1258 p = np;
1259 count += 1;
1260 n = end;
1261 if (node->next && (np = fz_page_number_from_location(ctx, doc, node->next->page)) >= 0)
1262 n = fz_page_number_from_location(ctx, doc, node->next->page);
1263 is_selected = 0;
1264 if (fz_count_chapters(ctx, doc) == 1)
1265 is_selected = (p>=0) && (currentpage.page == p || (currentpage.page > p && currentpage.page < n));
1266 if (node->down && (node->is_open || is_selected))
1267 count += count_outline(node->down, end);
1268 node = node->next;
1269 }
1270 while (node);
1271
1272 return count;
1273 }
1274
1275 static void do_outline_imp(struct list *list, int end, fz_outline *node, int depth)
1276 {
1277 int is_selected, is_open, was_open, n, np;
1278
1279 if (!node)
1280 return;
1281
1282 np = fz_page_number_from_location(ctx, doc, node->page);
1283
1284 do
1285 {
1286 int p = np;
1287 n = end;
1288 if (node->next && (np = fz_page_number_from_location(ctx, doc, node->next->page)) >= 0)
1289 n = np;
1290
1291 is_open = was_open = node->is_open;
1292 is_selected = 0;
1293 if (fz_count_chapters(ctx, doc) == 1)
1294 is_selected = (p>=0) && (currentpage.page == p || (currentpage.page > p && currentpage.page < n));
1295 if (ui_tree_item(list, node, node->title, is_selected, depth, !!node->down, &is_open))
1296 {
1297 node->is_open = is_open;
1298 if (p < 0)
1299 {
1300 currentpage = fz_resolve_link(ctx, doc, node->uri, &node->x, &node->y);
1301 jump_to_location_xy(currentpage, node->x, node->y);
1302 }
1303 else
1304 {
1305 jump_to_page_xy(p, node->x, node->y);
1306 }
1307 }
1308 node->is_open = is_open;
1309
1310 if (node->down && (was_open || is_selected))
1311 do_outline_imp(list, n, node->down, depth + 1);
1312 node = node->next;
1313 }
1314 while (node);
1315 }
1316
1317 static void do_outline(fz_outline *node)
1318 {
1319 static struct list list;
1320 ui_layout(L, BOTH, NW, 0, 0);
1321 ui_tree_begin(&list, count_outline(node, 65535), outline_w, 0, 1);
1322 do_outline_imp(&list, 65535, node, 0);
1323 ui_tree_end(&list);
1324 }
1325
1326 static void do_undo(void)
1327 {
1328 static struct list list;
1329 int count = 0;
1330 int pos;
1331 int i;
1332 int desired = -1;
1333
1334 if (pdf)
1335 pos = pdf_undoredo_state(ctx, pdf, &count);
1336 else
1337 pos = 0;
1338 ui_layout(L, BOTH, NW, 0, 0);
1339 ui_panel_begin(outline_w, 0, ui.padsize*2, ui.padsize*2, 1);
1340 ui_layout(T, X, NW, ui.padsize, ui.padsize);
1341 ui_label("Undo history:");
1342
1343 ui_layout(B, X, NW, ui.padsize, ui.padsize);
1344 if (ui_button_aux("Redo", pos == count))
1345 desired = pos+1;
1346 if (ui_button_aux("Undo", pos == 0))
1347 desired = pos-1;
1348
1349 ui_layout(ALL, BOTH, NW, ui.padsize, ui.padsize);
1350 ui_list_begin(&list, count+1, 0, ui.lineheight * 4 + 4);
1351
1352 for (i = 0; i < count+1; i++)
1353 {
1354 const char *op;
1355
1356 if (i == 0)
1357 op = "Original Document";
1358 else
1359 op = pdf_undoredo_step(ctx, pdf, i-1);
1360 if (ui_list_item(&list, (void *)(intptr_t)(i+1), op, i <= pos))
1361 {
1362 desired = i;
1363 }
1364 }
1365
1366 ui_list_end(&list);
1367
1368 if (desired != -1 && desired != pos)
1369 {
1370 clear_selected_annot();
1371 page_contents_changed = 1;
1372 while (pos > desired)
1373 {
1374 trace_action("doc.undo();\n");
1375 pdf_undo(ctx, pdf);
1376 pos--;
1377 }
1378 while (pos < desired)
1379 {
1380 trace_action("doc.redo();\n");
1381 pdf_redo(ctx, pdf);
1382 pos++;
1383 }
1384 load_page();
1385 }
1386
1387 ui_panel_end();
1388 }
1389
1390 static void do_layers(void)
1391 {
1392 const char *name;
1393 int n, i, on;
1394
1395 ui_layout(L, BOTH, NW, 0, 0);
1396 ui_panel_begin(outline_w, 0, ui.padsize*2, ui.padsize*2, 1);
1397 ui_layout(T, X, NW, ui.padsize, ui.padsize);
1398 ui_label("Layers:");
1399 ui_layout(T, X, NW, ui.padsize*2, ui.padsize);
1400
1401 if (pdf)
1402 {
1403 n = pdf_count_layers(ctx, pdf);
1404 for (i = 0; i < n; ++i)
1405 {
1406 name = pdf_layer_name(ctx, pdf, i);
1407 on = pdf_layer_is_enabled(ctx, pdf, i);
1408 if (ui_checkbox(name, &on))
1409 {
1410 pdf_enable_layer(ctx, pdf, i, on);
1411 page_contents_changed = 1;
1412 }
1413 }
1414 if (n == 0)
1415 ui_label("None");
1416 }
1417 else
1418 {
1419 ui_label("None");
1420 }
1421
1422 ui_panel_end();
1423 }
1424
1425 static void do_links(fz_link *link)
1426 {
1427 fz_rect bounds;
1428 fz_irect area;
1429 float link_x, link_y;
1430
1431 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
1432 glEnable(GL_BLEND);
1433
1434 tooltip = NULL;
1435
1436 while (link)
1437 {
1438 bounds = link->rect;
1439 bounds = fz_transform_rect(link->rect, view_page_ctm);
1440 area = fz_irect_from_rect(bounds);
1441
1442 if (ui_mouse_inside(area))
1443 {
1444 if (!tooltip)
1445 tooltip = link->uri;
1446 ui.hot = link;
1447 if (!ui.active && ui.down)
1448 ui.active = link;
1449 }
1450
1451 if (ui.hot == link || showlinks)
1452 {
1453 if (ui.active == link && ui.hot == link)
1454 glColor4f(0, 0, 1, 0.4f);
1455 else if (ui.hot == link)
1456 glColor4f(0, 0, 1, 0.2f);
1457 else
1458 glColor4f(0, 0, 1, 0.1f);
1459 glRectf(area.x0, area.y0, area.x1, area.y1);
1460 }
1461
1462 if (ui.active == link && !ui.down)
1463 {
1464 if (ui.hot == link)
1465 {
1466 if (fz_is_external_link(ctx, link->uri))
1467 open_browser(link->uri);
1468 else
1469 {
1470 fz_location loc = fz_resolve_link(ctx, doc, link->uri, &link_x, &link_y);
1471 jump_to_location_xy(loc, link_x, link_y);
1472 }
1473 }
1474 }
1475
1476 link = link->next;
1477 }
1478
1479 glDisable(GL_BLEND);
1480 }
1481
1482 static void do_page_selection(void)
1483 {
1484 static fz_point pt = { 0, 0 };
1485 static fz_quad hits[1000];
1486 fz_rect rect;
1487 int i, n;
1488
1489 if (ui_mouse_inside(view_page_area))
1490 {
1491 ui.hot = &pt;
1492 if (!ui.active && ui.right)
1493 {
1494 ui.active = &pt;
1495 pt.x = ui.x;
1496 pt.y = ui.y;
1497 }
1498 }
1499
1500 if (ui.active == &pt)
1501 {
1502 fz_point page_a = { pt.x, pt.y };
1503 fz_point page_b = { ui.x, ui.y };
1504
1505 page_a = fz_transform_point(page_a, view_page_inv_ctm);
1506 page_b = fz_transform_point(page_b, view_page_inv_ctm);
1507
1508 if (ui.mod == GLUT_ACTIVE_CTRL)
1509 fz_snap_selection(ctx, page_text, &page_a, &page_b, FZ_SELECT_WORDS);
1510 else if (ui.mod == GLUT_ACTIVE_CTRL + GLUT_ACTIVE_SHIFT)
1511 fz_snap_selection(ctx, page_text, &page_a, &page_b, FZ_SELECT_LINES);
1512
1513 if (ui.mod == GLUT_ACTIVE_SHIFT)
1514 {
1515 rect = fz_make_rect(
1516 fz_min(page_a.x, page_b.x),
1517 fz_min(page_a.y, page_b.y),
1518 fz_max(page_a.x, page_b.x),
1519 fz_max(page_a.y, page_b.y));
1520 n = 1;
1521 hits[0] = fz_quad_from_rect(rect);
1522 }
1523 else
1524 {
1525 n = fz_highlight_selection(ctx, page_text, page_a, page_b, hits, nelem(hits));
1526 }
1527
1528 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
1529 glEnable(GL_BLEND);
1530 glColor4f(0.0, 0.1, 0.4, 0.3f);
1531
1532 glBegin(GL_QUADS);
1533 for (i = 0; i < n; ++i)
1534 {
1535 fz_quad thit = fz_transform_quad(hits[i], view_page_ctm);
1536 glVertex2f(thit.ul.x, thit.ul.y);
1537 glVertex2f(thit.ur.x, thit.ur.y);
1538 glVertex2f(thit.lr.x, thit.lr.y);
1539 glVertex2f(thit.ll.x, thit.ll.y);
1540 }
1541 glEnd();
1542
1543 glDisable(GL_BLEND);
1544
1545 if (!ui.right)
1546 {
1547 char *s;
1548 #ifdef _WIN32
1549 if (ui.mod == GLUT_ACTIVE_SHIFT)
1550 s = fz_copy_rectangle(ctx, page_text, rect, 1);
1551 else
1552 s = fz_copy_selection(ctx, page_text, page_a, page_b, 1);
1553 #else
1554 if (ui.mod == GLUT_ACTIVE_SHIFT)
1555 s = fz_copy_rectangle(ctx, page_text, rect, 0);
1556 else
1557 s = fz_copy_selection(ctx, page_text, page_a, page_b, 0);
1558 #endif
1559 ui_set_clipboard(s);
1560 fz_free(ctx, s);
1561 }
1562 }
1563 }
1564
1565 static void do_search_hits(void)
1566 {
1567 int i;
1568
1569 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
1570 glEnable(GL_BLEND);
1571
1572 glColor4f(1, 0, 0, 0.4f);
1573 glBegin(GL_QUADS);
1574 for (i = 0; i < search_hit_count; ++i)
1575 {
1576 fz_quad thit = fz_transform_quad(search_hit_quads[i], view_page_ctm);
1577 glVertex2f(thit.ul.x, thit.ul.y);
1578 glVertex2f(thit.ur.x, thit.ur.y);
1579 glVertex2f(thit.lr.x, thit.lr.y);
1580 glVertex2f(thit.ll.x, thit.ll.y);
1581 }
1582
1583 glEnd();
1584 glDisable(GL_BLEND);
1585 }
1586
1587 static void toggle_fullscreen(void)
1588 {
1589 static int win_x = 0, win_y = 0;
1590 static int win_w = 100, win_h = 100;
1591 if (!isfullscreen)
1592 {
1593 win_w = glutGet(GLUT_WINDOW_WIDTH);
1594 win_h = glutGet(GLUT_WINDOW_HEIGHT);
1595 win_x = glutGet(GLUT_WINDOW_X);
1596 win_y = glutGet(GLUT_WINDOW_Y);
1597 glutFullScreen();
1598 isfullscreen = 1;
1599 }
1600 else
1601 {
1602 glutPositionWindow(win_x, win_y);
1603 glutReshapeWindow(win_w, win_h);
1604 isfullscreen = 0;
1605 }
1606 }
1607
1608 static void shrinkwrap(void)
1609 {
1610 int w = page_tex.w;
1611 int h = page_tex.h;
1612 if (showoutline || showundo || showlayers)
1613 w += outline_w + 4;
1614 if (showannotate)
1615 w += annotate_w;
1616 #if FZ_ENABLE_JS
1617 if (showconsole)
1618 h += console_h;
1619 #endif
1620 if (screen_w > 0 && w > screen_w)
1621 w = screen_w;
1622 if (screen_h > 0 && h > screen_h)
1623 h = screen_h;
1624 if (isfullscreen)
1625 toggle_fullscreen();
1626 glutReshapeWindow(w, h);
1627 }
1628
1629 static struct input input_password;
1630 static void password_dialog(void)
1631 {
1632 int is;
1633 ui_dialog_begin(ui.gridsize*16, (ui.gridsize+ui.padsize*2)*3);
1634 {
1635 ui_layout(T, X, NW, ui.padsize, ui.padsize);
1636 ui_label("Password:");
1637 is = ui_input(&input_password, 200, 1);
1638
1639 ui_layout(B, X, NW, ui.padsize, ui.padsize);
1640 ui_panel_begin(0, ui.gridsize, 0, 0, 0);
1641 {
1642 ui_layout(R, NONE, S, 0, 0);
1643 if (ui_button("Cancel") || (!ui.focus && ui.key == KEY_ESCAPE))
1644 glutLeaveMainLoop();
1645 ui_spacer();
1646 if (ui_button("Okay") || is == UI_INPUT_ACCEPT)
1647 {
1648 password = input_password.text;
1649 ui.dialog = NULL;
1650 reload_document();
1651 shrinkwrap();
1652 }
1653 }
1654 ui_panel_end();
1655 }
1656 ui_dialog_end();
1657 }
1658
1659 /* Parse "chapter:page" from anchor. "chapter:" is also accepted,
1660 * meaning first page. Return 1 if parsing succeeded, 0 if failed.
1661 */
1662 static int
1663 parse_location(const char *anc, fz_location *loc)
1664 {
1665 const char *s, *p;
1666
1667 if (anc == NULL)
1668 return 0;
1669
1670 s = anc;
1671 while (*s >= '0' && *s <= '9')
1672 s++;
1673 loc->chapter = fz_atoi(anc)-1;
1674 if (*s == 0)
1675 {
1676 *loc = fz_location_from_page_number(ctx, doc, loc->chapter);
1677 return 1;
1678 }
1679 if (*s != ':')
1680 return 0;
1681 p = ++s;
1682 while (*s >= '0' && *s <= '9')
1683 s++;
1684 if (s == p)
1685 loc->page = 0;
1686 else
1687 loc->page = fz_atoi(p)-1;
1688
1689 return 1;
1690 }
1691
1692 static void
1693 reload_or_start_journalling(void)
1694 {
1695 char journal[PATH_MAX];
1696
1697 fz_strlcpy(journal, filename, sizeof(journal));
1698 fz_strlcat(journal, ".journal", sizeof(journal));
1699
1700 fz_try(ctx)
1701 {
1702 /* Probe with fz_file_exists to avoid 'can't find' errors. */
1703 if (fz_file_exists(ctx, journal))
1704 pdf_load_journal(ctx, pdf, journal);
1705 }
1706 fz_catch(ctx)
1707 {
1708 /* Ignore any failures here. */
1709 }
1710 trace_action("doc.enableJournal();\n");
1711 pdf_enable_journal(ctx, pdf);
1712 }
1713
1714 static void alert_box(const char *fmt, const char *str)
1715 {
1716 #ifdef _WIN32
1717 MessageBoxA(NULL, str, "MuPDF Alert", MB_ICONERROR);
1718 #else
1719 fprintf(stderr, "MuPDF Alert: %s\n", str);
1720 #endif
1721 }
1722
1723
1724 static void event_cb(fz_context *callback_ctx, pdf_document *callback_doc, pdf_doc_event *evt, void *data)
1725 {
1726 switch (evt->type)
1727 {
1728 case PDF_DOCUMENT_EVENT_ALERT:
1729 {
1730 pdf_alert_event *alert = pdf_access_alert_event(callback_ctx, evt);
1731 alert_box("%s", alert->message);
1732 }
1733 break;
1734
1735 default:
1736 fz_throw(callback_ctx, FZ_ERROR_UNSUPPORTED, "event not yet implemented");
1737 break;
1738 }
1739 }
1740
1741 static void load_document(void)
1742 {
1743 char accelpath[PATH_MAX];
1744 char *accel = NULL;
1745 time_t atime;
1746 time_t dtime;
1747 fz_location location;
1748
1749 fz_drop_outline(ctx, outline);
1750 outline = NULL;
1751 fz_drop_document(ctx, doc);
1752 doc = NULL;
1753
1754 if (!strncmp(filename, "file://", 7))
1755 {
1756 anchor = strchr(filename + 7, '#');
1757 if (anchor)
1758 {
1759 memmove(anchor + 1, anchor, strlen(anchor) + 1);
1760 *anchor = 0;
1761 anchor++;
1762 }
1763 memmove(filename, filename + 7, strlen(filename));
1764 }
1765
1766 /* If there was an accelerator to load, what would it be called? */
1767 if (get_accelerator_filename(accelpath, sizeof(accelpath), 0))
1768 {
1769 /* Check whether that file exists, and isn't older than
1770 * the document. */
1771 atime = fz_stat_mtime(accelpath);
1772 dtime = fz_stat_mtime(filename);
1773 if (atime == 0)
1774 {
1775 /* No accelerator */
1776 }
1777 else if (atime > dtime)
1778 accel = accelpath;
1779 else
1780 {
1781 /* Accelerator data is out of date */
1782 #ifdef _WIN32
1783 fz_remove_utf8(accelpath);
1784 #else
1785 remove(accelpath);
1786 #endif
1787 accel = NULL; /* In case we have jumped up from below */
1788 }
1789 }
1790
1791 trace_action("doc = Document.openDocument(%q);\n", filename);
1792
1793 doc = fz_open_accelerated_document(ctx, filename, accel);
1794 pdf = pdf_specifics(ctx, doc);
1795
1796 if (pdf && trace_file)
1797 {
1798 int needspass = pdf_needs_password(ctx, pdf);
1799 trace_action(
1800 "tmp = doc.needsPassword();\n"
1801 "if (tmp != %s)\n"
1802 " throw new RegressionError('Document password needed:', tmp, 'expected:', %s);\n",
1803 needspass ? "true" : "false",
1804 needspass ? "true" : "false");
1805 }
1806
1807 if (fz_needs_password(ctx, doc))
1808 {
1809 int result = fz_authenticate_password(ctx, doc, password);
1810
1811 if (pdf && trace_file)
1812 {
1813 trace_action(
1814 "tmp = doc.authenticatePassword(%q);\n"
1815 "if (tmp != %s)\n"
1816 " throw new RegressionError('Open document with password %q result: %s', 'expected:', '%s');\n",
1817 password,
1818 result ? "true" : "false",
1819 password,
1820 !result ? "pass" : "fail",
1821 result ? "pass" : "fail");
1822 }
1823
1824 if (!result)
1825 {
1826 fz_drop_document(ctx, doc);
1827 doc = NULL;
1828 ui_input_init(&input_password, "");
1829 ui.focus = &input_password;
1830 ui.dialog = password_dialog;
1831 return;
1832 }
1833 }
1834
1835 fz_layout_document(ctx, doc, layout_w, layout_h, layout_em);
1836
1837 fz_try(ctx)
1838 outline = fz_load_outline(ctx, doc);
1839 fz_catch(ctx)
1840 {
1841 fz_report_error(ctx);
1842 outline = NULL;
1843 }
1844
1845 load_history();
1846
1847 if (pdf)
1848 {
1849 #if FZ_ENABLE_JS
1850 if (enable_js)
1851 {
1852 trace_action("doc.enableJS();\n");
1853 pdf_enable_js(ctx, pdf);
1854 pdf_js_set_console(ctx, pdf, &gl_js_console, NULL);
1855 }
1856 #endif
1857
1858 reload_or_start_journalling();
1859
1860 if (trace_file)
1861 {
1862 int vsns = pdf_count_versions(ctx, pdf);
1863 trace_action(
1864 "tmp = doc.countVersions();\n"
1865 "if (tmp != %d)\n"
1866 " throw new RegressionError('Document versions:', tmp, 'expected:', %d);\n",
1867 vsns, vsns);
1868 if (vsns > 1)
1869 {
1870 int valid = pdf_validate_change_history(ctx, pdf);
1871 trace_action("tmp = doc.validateChangeHistory();\n");
1872 trace_action("if (tmp != %d)\n", valid);
1873 trace_action(" throw new RegressionError('History validation:', tmp, 'expected:', %d);\n", valid);
1874 }
1875 }
1876 }
1877
1878 if (anchor)
1879 {
1880 if (parse_location(anchor, &location))
1881 jump_to_location(location);
1882 else
1883 {
1884 location = fz_resolve_link(ctx, doc, anchor, NULL, NULL);
1885 if (location.page < 0)
1886 fz_warn(ctx, "cannot find location: %s", anchor);
1887 else
1888 jump_to_location(location);
1889 }
1890 }
1891 anchor = NULL;
1892
1893 oldpage = currentpage = fz_clamp_location(ctx, doc, currentpage);
1894
1895 if (pdf)
1896 pdf_set_doc_event_callback(ctx, pdf, event_cb, NULL, NULL);
1897 }
1898
1899 static void reflow_document(void)
1900 {
1901 char buf[256];
1902 fz_document *new_doc;
1903 fz_stext_options opts;
1904
1905 if (fz_is_document_reflowable(ctx, doc))
1906 return;
1907
1908 fz_drop_outline(ctx, outline);
1909 outline = NULL;
1910
1911 fz_parse_stext_options(ctx, &opts, reflow_options);
1912
1913 new_doc = fz_open_reflowed_document(ctx, doc, &opts);
1914 fz_drop_document(ctx, doc);
1915 doc = new_doc;
1916 pdf = NULL;
1917 page = NULL;
1918
1919 fz_layout_document(ctx, doc, layout_w, layout_h, layout_em);
1920
1921 fz_try(ctx)
1922 outline = fz_load_outline(ctx, doc);
1923 fz_catch(ctx)
1924 outline = NULL;
1925
1926 fz_strlcpy(buf, filename, sizeof buf);
1927 fz_snprintf(filename, sizeof filename, "%s.xhtml", buf);
1928
1929 load_history();
1930
1931 if (anchor)
1932 jump_to_page(fz_atoi(anchor) - 1);
1933 anchor = NULL;
1934
1935 currentpage = fz_clamp_location(ctx, doc, currentpage);
1936 }
1937
1938 void reload_document(void)
1939 {
1940 save_history();
1941 save_accelerator();
1942 load_document();
1943 if (doc)
1944 {
1945 if (reflow_options)
1946 reflow_document();
1947 load_page();
1948 update_title();
1949 }
1950 }
1951
1952 static void toggle_outline(void)
1953 {
1954 if (outline)
1955 {
1956 showoutline = !showoutline;
1957 showundo = showlayers = 0;
1958 if (canvas_w == page_tex.w && canvas_h == page_tex.h)
1959 shrinkwrap();
1960 }
1961 }
1962
1963 static void toggle_undo(void)
1964 {
1965 showundo = !showundo;
1966 showoutline = showlayers = 0;
1967 if (canvas_w == page_tex.w && canvas_h == page_tex.h)
1968 shrinkwrap();
1969 }
1970
1971 static void toggle_layers(void)
1972 {
1973 showlayers = !showlayers;
1974 showoutline = showundo = 0;
1975 if (canvas_w == page_tex.w && canvas_h == page_tex.h)
1976 shrinkwrap();
1977 }
1978
1979 void toggle_annotate(int mode)
1980 {
1981 if (pdf)
1982 {
1983 if (showannotate != mode)
1984 showannotate = mode;
1985 else
1986 showannotate = ANNOTATE_MODE_NONE;
1987 if (canvas_w == page_tex.w && canvas_h == page_tex.h)
1988 shrinkwrap();
1989 }
1990 }
1991
1992 static void set_zoom(int z, int cx, int cy)
1993 {
1994 z = fz_clamp(z, MINRES, MAXRES);
1995 scroll_x = (scroll_x + cx - canvas_x) * z / currentzoom - cx + canvas_x;
1996 scroll_y = (scroll_y + cy - canvas_y) * z / currentzoom - cy + canvas_y;
1997 currentzoom = z;
1998 }
1999
2000 static void auto_zoom_w(void)
2001 {
2002 currentzoom = fz_clamp(currentzoom * canvas_w / page_tex.w, MINRES, MAXRES);
2003 }
2004
2005 static void auto_zoom_h(void)
2006 {
2007 currentzoom = fz_clamp(currentzoom * canvas_h / page_tex.h, MINRES, MAXRES);
2008 }
2009
2010 static void auto_zoom(void)
2011 {
2012 float page_a = (float) page_tex.w / page_tex.h;
2013 float screen_a = (float) canvas_w / canvas_h;
2014 if (page_a > screen_a)
2015 auto_zoom_w();
2016 else
2017 auto_zoom_h();
2018 }
2019
2020 static void smart_move_backward(void)
2021 {
2022 int slop_x = page_tex.w / 20;
2023 int slop_y = page_tex.h / 20;
2024 if (scroll_y <= slop_y)
2025 {
2026 if (scroll_x <= slop_x)
2027 {
2028 fz_location prev = fz_previous_page(ctx, doc, currentpage);
2029 if (!eqloc(currentpage, prev))
2030 {
2031 scroll_x = (page_tex.w <= canvas_w) ? 0 : page_tex.w - canvas_w;
2032 scroll_y = (page_tex.h <= canvas_h) ? 0 : page_tex.h - canvas_h;
2033 currentpage = prev;
2034 }
2035 }
2036 else
2037 {
2038 scroll_y = page_tex.h;
2039 scroll_x -= canvas_w * 9 / 10;
2040 }
2041 }
2042 else
2043 {
2044 scroll_y -= canvas_h * 9 / 10;
2045 }
2046 }
2047
2048 static void smart_move_forward(void)
2049 {
2050 int slop_x = page_tex.w / 20;
2051 int slop_y = page_tex.h / 20;
2052 if (scroll_y + canvas_h >= page_tex.h - slop_y)
2053 {
2054 if (scroll_x + canvas_w >= page_tex.w - slop_x)
2055 {
2056 fz_location next = fz_next_page(ctx, doc, currentpage);
2057 if (!eqloc(currentpage, next))
2058 {
2059 scroll_x = 0;
2060 scroll_y = 0;
2061 currentpage = next;
2062 }
2063 }
2064 else
2065 {
2066 scroll_y = 0;
2067 scroll_x += canvas_w * 9 / 10;
2068 }
2069 }
2070 else
2071 {
2072 scroll_y += canvas_h * 9 / 10;
2073 }
2074 }
2075
2076 static void clear_search(void)
2077 {
2078 showsearch = 0;
2079 search_page = currentpage;
2080 search_hit_page = fz_make_location(-1, -1);
2081 search_hit_count = 0;
2082 }
2083
2084 #if FZ_ENABLE_JS
2085
2086 #define MAX_CONSOLE_LINES 500
2087
2088 static fz_buffer *console_buffer;
2089 static int console_scroll = 0;
2090 static int console_sticky = 1;
2091 static int console_lines = 0;
2092 static struct readline console_readline;
2093 static void (*warning_callback)(void *, const char *) = NULL;
2094 static void (*error_callback)(void *, const char *) = NULL;
2095 static void *warning_user = NULL;
2096 static void *error_user = NULL;
2097
2098 static void
2099 remove_oldest_console_line()
2100 {
2101 unsigned char *s;
2102 size_t size = fz_buffer_storage(ctx, console_buffer, &s);
2103 unsigned char *p = s;
2104 unsigned char *e = s + size;
2105
2106 while (p < e && *p != '\n')
2107 p++;
2108
2109 if (p < e && *p == '\n')
2110 {
2111 p++;
2112 memmove(s, p, e - p);
2113 fz_resize_buffer(ctx, console_buffer, e - p);
2114 console_lines--;
2115 }
2116 }
2117
2118 static void
2119 gl_js_console_write(void *user, const char *message)
2120 {
2121 const char *p = NULL;
2122
2123 if (message == NULL)
2124 return;
2125
2126 p = message;
2127 while (*p)
2128 {
2129 if (*p == '\n')
2130 console_lines++;
2131 if (console_lines >= MAX_CONSOLE_LINES)
2132 remove_oldest_console_line();
2133 if (*p)
2134 fz_append_byte(ctx, console_buffer, *p);
2135 p++;
2136 }
2137 }
2138
2139 static void
2140 gl_js_console_show(void *user)
2141 {
2142 if (showconsole)
2143 return;
2144
2145 showconsole = 1;
2146 if (canvas_w == page_tex.w && canvas_h == page_tex.h)
2147 shrinkwrap();
2148 ui.focus = &console_readline;
2149 }
2150
2151 static void
2152 gl_js_console_hide(void *user)
2153 {
2154 if (!showconsole)
2155 return;
2156
2157 showconsole = 0;
2158 if (canvas_w == page_tex.w && canvas_h == page_tex.h)
2159 shrinkwrap();
2160 ui.focus = NULL;
2161 }
2162
2163 static void
2164 gl_js_console_clear(void *user)
2165 {
2166 fz_resize_buffer(ctx, console_buffer, 0);
2167 console_lines = 0;
2168 }
2169
2170 static void console_warn(void *user, const char *message)
2171 {
2172 gl_js_console_write(ctx, "\nwarning: ");
2173 gl_js_console_write(ctx, message);
2174 if (warning_callback)
2175 warning_callback(warning_user, message);
2176 }
2177
2178 static void console_err(void *user, const char *message)
2179 {
2180 gl_js_console_write(ctx, "\nerror: ");
2181 gl_js_console_write(ctx, message);
2182 if (error_callback)
2183 error_callback(error_user, message);
2184 }
2185
2186 static void console_init(void)
2187 {
2188 ui_readline_init(&console_readline, NULL);
2189
2190 console_buffer = fz_new_buffer(ctx, 0);
2191 fz_append_printf(ctx, console_buffer, "Welcome to MuPDF %s with MuJS %d.%d.%d",
2192 FZ_VERSION,
2193 JS_VERSION_MAJOR, JS_VERSION_MINOR, JS_VERSION_PATCH);
2194
2195 warning_callback = fz_warning_callback(ctx, &warning_user);
2196 fz_set_warning_callback(ctx, console_warn, NULL);
2197 error_callback = fz_error_callback(ctx, &error_user);
2198 fz_set_error_callback(ctx, console_err, NULL);
2199 }
2200
2201 static void console_fin(void)
2202 {
2203 fz_set_warning_callback(ctx, warning_callback, warning_user);
2204 fz_set_error_callback(ctx, error_callback, error_user);
2205 fz_drop_buffer(ctx, console_buffer);
2206 console_buffer = NULL;
2207 }
2208
2209 static pdf_js_console gl_js_console = {
2210 NULL,
2211 gl_js_console_show,
2212 gl_js_console_hide,
2213 gl_js_console_clear,
2214 gl_js_console_write,
2215 };
2216
2217 static void toggle_console(void)
2218 {
2219 showconsole = !showconsole;
2220 if (showconsole)
2221 ui.focus = &console_readline;
2222 if (canvas_w == page_tex.w && canvas_h == page_tex.h)
2223 shrinkwrap();
2224 }
2225
2226 void do_console(void)
2227 {
2228 pdf_js_console *console = pdf_js_get_console(ctx, pdf);
2229 char *result = NULL;
2230 const char *accepted = NULL;
2231
2232 fz_var(result);
2233
2234 ui_layout(B, BOTH, NW, 0, 0);
2235 ui_panel_begin(canvas_w, console_h, ui.padsize, ui.padsize, 1);
2236
2237 ui_layout(B, X, NW, 0, 0);
2238
2239 accepted = ui_readline(&console_readline, 0);
2240 if (accepted != NULL)
2241 {
2242 ui.focus = &console_readline;
2243 if (console_readline.input.text[0])
2244 {
2245 fz_try(ctx)
2246 {
2247 if (console && console->write)
2248 {
2249 console->write(ctx, "\n> ");
2250 console->write(ctx, console_readline.input.text);
2251 }
2252 pdf_js_execute(pdf ? pdf->js : NULL, "console", console_readline.input.text, &result);
2253 if (result && console && console->write)
2254 {
2255 console->write(ctx, "\n");
2256 console->write(ctx, result);
2257 }
2258 }
2259 fz_always(ctx)
2260 fz_free(ctx, result);
2261 fz_catch(ctx)
2262 {
2263 if (console)
2264 {
2265 console->write(ctx, "\nError: ");
2266 console->write(ctx, fz_caught_message(ctx));
2267 fz_report_error(ctx);
2268 }
2269 }
2270 fz_flush_warnings(ctx);
2271 ui_input_init(&console_readline.input, "");
2272 }
2273 }
2274
2275 ui_layout(ALL, BOTH, NW, ui.padsize, ui.padsize);
2276
2277 // White background!
2278 glColorHex(0xF5F5F5);
2279 glRectf(ui.cavity->x0, ui.cavity->y0, ui.cavity->x1, ui.cavity->y1);
2280
2281 char *console_string = (char *) fz_string_from_buffer(ctx, console_buffer);
2282 ui_label_with_scrollbar(console_string, 0, 10, &console_scroll, &console_sticky);
2283
2284 ui_panel_end();
2285 }
2286
2287 #endif
2288
2289 static void do_app(void)
2290 {
2291 if (ui.mod == GLUT_ACTIVE_ALT)
2292 {
2293 if (ui.key == KEY_F4)
2294 quit();
2295
2296 if (ui.key == KEY_LEFT)
2297 ui.key = 't', ui.mod = 0, ui.plain = 1;
2298 if (ui.key == KEY_RIGHT)
2299 ui.key = 'T', ui.mod = 0, ui.plain = 1;
2300 }
2301
2302 if (trace_file && ui.key == KEY_CTL_P)
2303 trace_save_snapshot();
2304
2305 if (!ui.focus && ui.key && ui.plain)
2306 {
2307 switch (ui.key)
2308 {
2309 case KEY_ESCAPE: clear_search(); ui_select_annot(NULL); break;
2310 case KEY_F1: ui.dialog = help_dialog; break;
2311 case 'a': toggle_annotate(ANNOTATE_MODE_NORMAL); break;
2312 case 'R': toggle_annotate(ANNOTATE_MODE_REDACT); break;
2313 case 'o': toggle_outline(); break;
2314 case 'u': toggle_undo(); break;
2315 case 'Y': toggle_layers(); break;
2316 case 'L': showlinks = !showlinks; break;
2317 case 'F': showform = !showform; break;
2318 case 'i': ui.dialog = info_dialog; break;
2319 #if FZ_ENABLE_JS
2320 case '`': case KEY_F12: toggle_console(); break;
2321 #endif
2322 case 'r': reload(); break;
2323 case 'q': quit(); break;
2324 case 'S': do_save_pdf_file(); break;
2325
2326 case '>': layout_em = number > 0 ? number : layout_em + 1; relayout(); break;
2327 case '<': layout_em = number > 0 ? number : layout_em - 1; relayout(); break;
2328
2329 case 'C': currenttint = !currenttint; break;
2330 case 'I': currentinvert = !currentinvert; break;
2331 case 'e': currentseparations = !currentseparations; break;
2332 case 'E': currenticc = !currenticc; break;
2333 case 'f': toggle_fullscreen(); break;
2334 case 'w': shrinkwrap(); break;
2335 case 'W': auto_zoom_w(); break;
2336 case 'H': auto_zoom_h(); break;
2337 case 'Z': auto_zoom(); break;
2338 case 'z': set_zoom(number > 0 ? number : DEFRES, canvas_w/2, canvas_h/2); break;
2339 case '+': set_zoom(zoom_in(currentzoom), ui.x, ui.y); break;
2340 case '-': set_zoom(zoom_out(currentzoom), ui.x, ui.y); break;
2341 case '[': currentrotate -= 90; break;
2342 case ']': currentrotate += 90; break;
2343 case 'k': case KEY_UP: scroll_y -= canvas_h/10; break;
2344 case 'j': case KEY_DOWN: scroll_y += canvas_h/10; break;
2345 case 'h': case KEY_LEFT: scroll_x -= canvas_w/10; break;
2346 case 'l': case KEY_RIGHT: scroll_x += canvas_w/10; break;
2347
2348 case 'b': number = fz_maxi(number, 1); while (number--) smart_move_backward(); break;
2349 case ' ': number = fz_maxi(number, 1); while (number--) smart_move_forward(); break;
2350 case 'g': jump_to_page(number - 1); break;
2351 case 'G': jump_to_location(fz_last_page(ctx, doc)); break;
2352
2353 case ',': case KEY_PAGE_UP:
2354 number = fz_maxi(number, 1);
2355 while (number--)
2356 currentpage = fz_previous_page(ctx, doc, currentpage);
2357 break;
2358 case '.': case KEY_PAGE_DOWN:
2359 number = fz_maxi(number, 1);
2360 while (number--)
2361 currentpage = fz_next_page(ctx, doc, currentpage);
2362 break;
2363
2364 case 'A':
2365 if (number == 0)
2366 currentaa = (currentaa == 8 ? 0 : 8);
2367 else
2368 currentaa = number;
2369 break;
2370
2371 case 'B':
2372 currentbox += 1;
2373 if (currentbox >= FZ_UNKNOWN_BOX)
2374 currentbox = FZ_MEDIA_BOX;
2375 break;
2376
2377 case 'm':
2378 if (number == 0)
2379 push_history();
2380 else if (number > 0 && number < (int)nelem(marks))
2381 marks[number] = save_mark();
2382 break;
2383 case 't':
2384 if (number == 0)
2385 {
2386 if (history_count > 0)
2387 pop_history();
2388 }
2389 else if (number > 0 && number < (int)nelem(marks))
2390 {
2391 struct mark mark = marks[number];
2392 restore_mark(mark);
2393 jump_to_location(mark.loc);
2394 }
2395 break;
2396 case 'T':
2397 if (number == 0)
2398 {
2399 if (future_count > 0)
2400 pop_future();
2401 }
2402 break;
2403
2404 case '/':
2405 clear_search();
2406 search_dir = 1;
2407 showsearch = 1;
2408 ui.focus = &search_input;
2409 search_input.p = search_input.text;
2410 search_input.q = search_input.end;
2411 break;
2412 case '?':
2413 clear_search();
2414 search_dir = -1;
2415 showsearch = 1;
2416 ui.focus = &search_input;
2417 search_input.p = search_input.text;
2418 search_input.q = search_input.end;
2419 break;
2420 case 'N':
2421 search_dir = -1;
2422 search_active = !!search_needle;
2423 if (eqloc(search_hit_page, currentpage))
2424 {
2425 if (is_first_page(search_page))
2426 search_active = 0;
2427 else
2428 search_page = fz_previous_page(ctx, doc, currentpage);
2429 }
2430 else
2431 {
2432 search_page = currentpage;
2433 }
2434 search_hit_page = fz_make_location(-1, -1);
2435 break;
2436 case 'n':
2437 search_dir = 1;
2438 search_active = !!search_needle;
2439 if (eqloc(search_hit_page, currentpage))
2440 {
2441 if (is_last_page(search_page))
2442 search_active = 0;
2443 else
2444 search_page = fz_next_page(ctx, doc, currentpage);
2445 }
2446 else
2447 {
2448 search_page = currentpage;
2449 }
2450 search_hit_page = fz_make_location(-1, -1);
2451 break;
2452 default:
2453 if (ui.key < '0' || ui.key > '9')
2454 {
2455 number = 0;
2456 return; /* unrecognized key, pass it through */
2457 }
2458 }
2459
2460 if (ui.key >= '0' && ui.key <= '9')
2461 number = number * 10 + ui.key - '0';
2462 else
2463 number = 0;
2464
2465 currentpage = fz_clamp_location(ctx, doc, currentpage);
2466 while (currentrotate < 0) currentrotate += 360;
2467 while (currentrotate >= 360) currentrotate -= 360;
2468
2469 if (!eqloc(search_hit_page, currentpage))
2470 search_hit_page = fz_make_location(-1, -1); /* clear highlights when navigating */
2471
2472 ui.key = 0; /* we ate the key event, so zap it */
2473 }
2474 }
2475
2476 typedef struct
2477 {
2478 int max;
2479 int len;
2480 pdf_obj **sig;
2481 } sigs_list;
2482
2483 static void
2484 process_sigs(fz_context *ctx_, pdf_obj *field, void *arg, pdf_obj **ft)
2485 {
2486 sigs_list *sigs = (sigs_list *)arg;
2487
2488 if (!pdf_name_eq(ctx, pdf_dict_get(ctx, field, PDF_NAME(Type)), PDF_NAME(Annot)) ||
2489 !pdf_name_eq(ctx, pdf_dict_get(ctx, field, PDF_NAME(Subtype)), PDF_NAME(Widget)) ||
2490 !pdf_name_eq(ctx, *ft, PDF_NAME(Sig)))
2491 return;
2492
2493 if (sigs->len == sigs->max)
2494 {
2495 int newsize = sigs->max * 2;
2496 if (newsize == 0)
2497 newsize = 4;
2498 sigs->sig = fz_realloc_array(ctx, sigs->sig, newsize, pdf_obj *);
2499 sigs->max = newsize;
2500 }
2501
2502 sigs->sig[sigs->len++] = field;
2503 }
2504
2505 static char *short_signature_error_desc(pdf_signature_error err)
2506 {
2507 switch (err)
2508 {
2509 case PDF_SIGNATURE_ERROR_OKAY:
2510 return "OK";
2511 case PDF_SIGNATURE_ERROR_NO_SIGNATURES:
2512 return "No signatures";
2513 case PDF_SIGNATURE_ERROR_NO_CERTIFICATE:
2514 return "No certificate";
2515 case PDF_SIGNATURE_ERROR_DIGEST_FAILURE:
2516 return "Invalid";
2517 case PDF_SIGNATURE_ERROR_SELF_SIGNED:
2518 return "Self-signed";
2519 case PDF_SIGNATURE_ERROR_SELF_SIGNED_IN_CHAIN:
2520 return "Self-signed in chain";
2521 case PDF_SIGNATURE_ERROR_NOT_TRUSTED:
2522 return "Untrusted";
2523 case PDF_SIGNATURE_ERROR_NOT_SIGNED:
2524 return "Not signed";
2525 default:
2526 case PDF_SIGNATURE_ERROR_UNKNOWN:
2527 return "Unknown error";
2528 }
2529 }
2530
2531 const char *format_date(int64_t secs64)
2532 {
2533 static char buf[100];
2534 #ifdef _POSIX_SOURCE
2535 struct tm tmbuf, *tm;
2536 #else
2537 struct tm *tm;
2538 #endif
2539 time_t secs = (time_t)secs64;
2540
2541 if (secs <= 0)
2542 return NULL;
2543
2544 #ifdef _POSIX_SOURCE
2545 tm = gmtime_r(&secs, &tmbuf);
2546 #else
2547 tm = gmtime(&secs);
2548 #endif
2549 if (!tm)
2550 return NULL;
2551
2552 strftime(buf, sizeof buf, "%Y-%m-%d %H:%M UTC", tm);
2553 return buf;
2554 }
2555
2556 static fz_buffer *format_info_text()
2557 {
2558 fz_buffer *out = fz_new_buffer(ctx, 4096);
2559 pdf_document *pdoc = pdf_specifics(ctx, doc);
2560 sigs_list list = { 0, 0, NULL };
2561 char buf[100];
2562
2563 if (pdoc)
2564 {
2565 static pdf_obj *ft_list[2] = { PDF_NAME(FT), NULL };
2566 pdf_obj *ft = NULL;
2567 pdf_obj *form_fields = pdf_dict_getp(ctx, pdf_trailer(ctx, pdoc), "Root/AcroForm/Fields");
2568 pdf_walk_tree(ctx, form_fields, PDF_NAME(Kids), process_sigs, NULL, &list, &ft_list[0], &ft);
2569 }
2570
2571 fz_append_printf(ctx, out, "File: %s\n\n", filename);
2572
2573 if (fz_lookup_metadata(ctx, doc, FZ_META_INFO_TITLE, buf, sizeof buf) > 0)
2574 fz_append_printf(ctx, out, "Title: %s\n", buf);
2575 if (fz_lookup_metadata(ctx, doc, FZ_META_INFO_AUTHOR, buf, sizeof buf) > 0)
2576 fz_append_printf(ctx, out, "Author: %s\n", buf);
2577 if (fz_lookup_metadata(ctx, doc, FZ_META_FORMAT, buf, sizeof buf) > 0)
2578 fz_append_printf(ctx, out, "Format: %s\n", buf);
2579 if (fz_lookup_metadata(ctx, doc, FZ_META_ENCRYPTION, buf, sizeof buf) > 0)
2580 fz_append_printf(ctx, out, "Encryption: %s\n", buf);
2581
2582 fz_append_string(ctx, out, "\n");
2583
2584 if (pdoc)
2585 {
2586 int updates = pdf_count_versions(ctx, pdoc);
2587
2588 if (fz_lookup_metadata(ctx, doc, FZ_META_INFO_CREATOR, buf, sizeof buf) > 0)
2589 fz_append_printf(ctx, out, "PDF Creator: %s\n", buf);
2590 if (fz_lookup_metadata(ctx, doc, FZ_META_INFO_PRODUCER, buf, sizeof buf) > 0)
2591 fz_append_printf(ctx, out, "PDF Producer: %s\n", buf);
2592 if (fz_lookup_metadata(ctx, doc, FZ_META_INFO_SUBJECT, buf, sizeof buf) > 0)
2593 fz_append_printf(ctx, out, "Subject: %s\n", buf);
2594 if (fz_lookup_metadata(ctx, doc, FZ_META_INFO_KEYWORDS, buf, sizeof buf) > 0)
2595 fz_append_printf(ctx, out, "Keywords: %s\n", buf);
2596 if (fz_lookup_metadata(ctx, doc, FZ_META_INFO_CREATIONDATE, buf, sizeof buf) > 0)
2597 {
2598 const char *s = format_date(pdf_parse_date(ctx, buf));
2599 if (s)
2600 fz_append_printf(ctx, out, "Creation date: %s\n", s);
2601 }
2602 if (fz_lookup_metadata(ctx, doc, FZ_META_INFO_MODIFICATIONDATE, buf, sizeof buf) > 0)
2603 {
2604 const char *s = format_date(pdf_parse_date(ctx, buf));
2605 if (s)
2606 fz_append_printf(ctx, out, "Modification date: %s\n", s);
2607 }
2608
2609 buf[0] = 0;
2610 if (fz_has_permission(ctx, doc, FZ_PERMISSION_PRINT))
2611 fz_strlcat(buf, "print, ", sizeof buf);
2612 if (fz_has_permission(ctx, doc, FZ_PERMISSION_COPY))
2613 fz_strlcat(buf, "copy, ", sizeof buf);
2614 if (fz_has_permission(ctx, doc, FZ_PERMISSION_EDIT))
2615 fz_strlcat(buf, "edit, ", sizeof buf);
2616 if (fz_has_permission(ctx, doc, FZ_PERMISSION_ANNOTATE))
2617 fz_strlcat(buf, "annotate, ", sizeof buf);
2618 if (fz_has_permission(ctx, doc, FZ_PERMISSION_FORM))
2619 fz_strlcat(buf, "form, ", sizeof buf);
2620 if (fz_has_permission(ctx, doc, FZ_PERMISSION_ACCESSIBILITY))
2621 fz_strlcat(buf, "accessibility, ", sizeof buf);
2622 if (fz_has_permission(ctx, doc, FZ_PERMISSION_ASSEMBLE))
2623 fz_strlcat(buf, "assemble, ", sizeof buf);
2624 if (fz_has_permission(ctx, doc, FZ_PERMISSION_PRINT_HQ))
2625 fz_strlcat(buf, "print-hq, ", sizeof buf);
2626 if (strlen(buf) > 2)
2627 buf[strlen(buf)-2] = 0;
2628 else
2629 fz_strlcat(buf, "none", sizeof buf);
2630 fz_append_printf(ctx, out, "Permissions: %s\n", buf);
2631
2632 fz_append_printf(ctx, out, "PDF %sdocument with %d update%s\n",
2633 pdf_doc_was_linearized(ctx, pdoc) ? "linearized " : "",
2634 updates, updates > 1 ? "s" : "");
2635 if (updates > 0)
2636 {
2637 int n = pdf_validate_change_history(ctx, pdoc);
2638 if (n == 0)
2639 fz_append_printf(ctx, out, "Change history seems valid.\n");
2640 else if (n == 1)
2641 fz_append_printf(ctx, out, "Invalid changes made to the document in the last update.\n");
2642 else if (n == 2)
2643 fz_append_printf(ctx, out, "Invalid changes made to the document in the penultimate update.\n");
2644 else
2645 fz_append_printf(ctx, out, "Invalid changes made to the document %d updates ago.\n", n);
2646 }
2647
2648 if (list.len)
2649 {
2650 int i;
2651 for (i = 0; i < list.len; i++)
2652 {
2653 pdf_obj *field = list.sig[i];
2654 fz_try(ctx)
2655 {
2656 if (pdf_signature_is_signed(ctx, pdf, field))
2657 {
2658 pdf_pkcs7_verifier *verifier = pkcs7_openssl_new_verifier(ctx);
2659 pdf_signature_error sig_cert_error = pdf_check_certificate(ctx, verifier, pdf, field);
2660 pdf_signature_error sig_digest_error = pdf_check_digest(ctx, verifier, pdf, field);
2661 fz_append_printf(ctx, out, "Signature %d: CERT: %s, DIGEST: %s%s\n", i+1,
2662 short_signature_error_desc(sig_cert_error),
2663 short_signature_error_desc(sig_digest_error),
2664 pdf_signature_incremental_change_since_signing(ctx, pdf, field) ? ", Changed since": "");
2665 pdf_drop_verifier(ctx, verifier);
2666 }
2667 else
2668 fz_append_printf(ctx, out, "Signature %d: Unsigned\n", i+1);
2669 }
2670 fz_catch(ctx)
2671 fz_append_printf(ctx, out, "Signature %d: Error\n", i+1);
2672 }
2673 fz_free(ctx, list.sig);
2674
2675 if (updates == 0)
2676 fz_append_printf(ctx, out, "No updates since document creation\n");
2677 else
2678 {
2679 int n = pdf_validate_change_history(ctx, pdf);
2680 if (n == 0)
2681 fz_append_printf(ctx, out, "Document changes conform to permissions\n");
2682 else
2683 fz_append_printf(ctx, out, "Document permissions violated %d updates ago\n", n);
2684 }
2685 }
2686
2687 fz_append_string(ctx, out, "\n");
2688 }
2689
2690 fz_append_printf(ctx, out, "Page: %d / %d\n", fz_page_number_from_location(ctx, doc, currentpage)+1, fz_count_pages(ctx, doc));
2691 fz_append_printf(ctx, out, "Page Label: %s\n", fz_page_label(ctx, fzpage, buf, sizeof buf));
2692 {
2693 int w = (int)(page_bounds.x1 - page_bounds.x0 + 0.5f);
2694 int h = (int)(page_bounds.y1 - page_bounds.y0 + 0.5f);
2695 const char *size = paper_size_name(w, h);
2696 if (!size)
2697 size = paper_size_name(h, w);
2698 if (size)
2699 fz_append_printf(ctx, out, "Size: %d x %d (%s - %s)\n", w, h, fz_string_from_box_type(currentbox), size);
2700 else
2701 fz_append_printf(ctx, out, "Size: %d x %d (%s)\n", w, h, fz_string_from_box_type(currentbox));
2702 }
2703 fz_append_printf(ctx, out, "ICC rendering: %s.\n", currenticc ? "on" : "off");
2704 fz_append_printf(ctx, out, "Spot rendering: %s.\n", currentseparations ? "on" : "off");
2705
2706 if (fz_is_document_reflowable(ctx, doc))
2707 fz_append_printf(ctx, out, "Em size: %g\n", layout_em);
2708
2709 return out;
2710 }
2711
2712 static void do_canvas(void)
2713 {
2714 static int saved_scroll_x = 0;
2715 static int saved_scroll_y = 0;
2716 static int saved_ui_x = 0;
2717 static int saved_ui_y = 0;
2718 fz_irect area;
2719 int page_x, page_y;
2720
2721 tooltip = NULL;
2722
2723 ui_layout(ALL, BOTH, NW, 0, 0);
2724 ui_pack_push(area = ui_pack(0, 0));
2725 glScissor(area.x0, ui.window_h-area.y1, area.x1-area.x0, area.y1-area.y0);
2726 glEnable(GL_SCISSOR_TEST);
2727
2728 canvas_x = area.x0;
2729 canvas_y = area.y0;
2730 canvas_w = area.x1 - area.x0;
2731 canvas_h = area.y1 - area.y0;
2732
2733 if (ui_mouse_inside(area))
2734 {
2735 ui.hot = doc;
2736 if (!ui.active && ui.middle)
2737 {
2738 ui.active = doc;
2739 saved_scroll_x = scroll_x;
2740 saved_scroll_y = scroll_y;
2741 saved_ui_x = ui.x;
2742 saved_ui_y = ui.y;
2743 }
2744 }
2745
2746 if (ui.hot == doc)
2747 {
2748 if (ui.mod == 0)
2749 {
2750 scroll_x -= ui.scroll_x * ui.lineheight * 3;
2751 scroll_y -= ui.scroll_y * ui.lineheight * 3;
2752 }
2753 else if (ui.mod == GLUT_ACTIVE_CTRL)
2754 {
2755 if (ui.scroll_y > 0) set_zoom(zoom_in(currentzoom), ui.x, ui.y);
2756 if (ui.scroll_y < 0) set_zoom(zoom_out(currentzoom), ui.x, ui.y);
2757 }
2758 }
2759
2760 render_page_if_changed();
2761
2762 if (ui.active == doc)
2763 {
2764 scroll_x = saved_scroll_x + saved_ui_x - ui.x;
2765 scroll_y = saved_scroll_y + saved_ui_y - ui.y;
2766 }
2767
2768 if (page_tex.w <= canvas_w)
2769 {
2770 scroll_x = 0;
2771 page_x = canvas_x + (canvas_w - page_tex.w) / 2;
2772 }
2773 else
2774 {
2775 scroll_x = fz_clamp(scroll_x, 0, page_tex.w - canvas_w);
2776 page_x = canvas_x - scroll_x;
2777 }
2778
2779 if (page_tex.h <= canvas_h)
2780 {
2781 scroll_y = 0;
2782 page_y = canvas_y + (canvas_h - page_tex.h) / 2;
2783 }
2784 else
2785 {
2786 scroll_y = fz_clamp(scroll_y, 0, page_tex.h - canvas_h);
2787 page_y = canvas_y - scroll_y;
2788 }
2789
2790 view_page_ctm = draw_page_ctm;
2791 view_page_ctm.e += page_x;
2792 view_page_ctm.f += page_y;
2793 view_page_inv_ctm = fz_invert_matrix(view_page_ctm);
2794 view_page_bounds = fz_transform_rect(page_bounds, view_page_ctm);
2795 view_page_area = fz_irect_from_rect(view_page_bounds);
2796
2797 ui_draw_image(&page_tex, page_x, page_y);
2798
2799 if (search_active)
2800 {
2801 int chapters = fz_count_chapters(ctx, doc);
2802 ui_layout(T, X, NW, 0, 0);
2803 ui_panel_begin(0, ui.gridsize + ui.padsize*4, ui.padsize*2, ui.padsize*2, 1);
2804 ui_layout(L, NONE, W, ui.padsize, 0);
2805 if (chapters == 1 && search_page.chapter == 0)
2806 ui_label("Searching page %d...", search_page.page);
2807 else
2808 ui_label("Searching chapter %d page %d...", search_page.chapter, search_page.page);
2809 ui_panel_end();
2810 }
2811 else
2812 {
2813 if (pdf)
2814 {
2815 do_annotate_canvas(area);
2816 do_widget_canvas(area);
2817 }
2818 do_links(links);
2819 do_page_selection();
2820
2821 if (eqloc(search_hit_page, currentpage) && search_hit_count > 0)
2822 do_search_hits();
2823 }
2824
2825 if (showsearch)
2826 {
2827 ui_layout(T, X, NW, 0, 0);
2828 ui_panel_begin(0, ui.gridsize + ui.padsize*4, ui.padsize*2, ui.padsize*2, 1);
2829 ui_layout(L, NONE, W, ui.padsize, 0);
2830 ui_label("Search:");
2831 ui_layout(ALL, X, E, ui.padsize, 0);
2832 if (ui_input(&search_input, 0, 1) == UI_INPUT_ACCEPT)
2833 {
2834 showsearch = 0;
2835 search_page = fz_make_location(-1, -1);
2836 if (search_needle)
2837 {
2838 fz_free(ctx, search_needle);
2839 search_needle = NULL;
2840 }
2841 if (search_input.end > search_input.text)
2842 {
2843 search_needle = fz_strdup(ctx, search_input.text);
2844 search_active = 1;
2845 search_page = currentpage;
2846 }
2847 }
2848 if (ui.focus != &search_input)
2849 showsearch = 0;
2850 ui_panel_end();
2851 }
2852
2853 if (tooltip)
2854 {
2855 ui_layout(B, X, N, 0, 0);
2856 ui_panel_begin(0, ui.gridsize, ui.padsize*2, ui.padsize*2, 1);
2857 ui_layout(L, NONE, W, ui.padsize, 0);
2858 ui_label("%s", tooltip);
2859 ui_panel_end();
2860 }
2861
2862 ui_pack_pop();
2863 glDisable(GL_SCISSOR_TEST);
2864 }
2865
2866 void do_main(void)
2867 {
2868 if (search_active)
2869 {
2870 int start_time = glutGet(GLUT_ELAPSED_TIME);
2871
2872 if (ui.key == KEY_ESCAPE)
2873 search_active = 0;
2874
2875 /* ignore events during search */
2876 ui.key = ui.mod = ui.plain = 0;
2877 ui.down = ui.middle = ui.right = 0;
2878
2879 while (search_active && glutGet(GLUT_ELAPSED_TIME) < start_time + 200)
2880 {
2881 search_hit_count = fz_search_chapter_page_number(ctx, doc,
2882 search_page.chapter, search_page.page,
2883 search_needle,
2884 NULL, search_hit_quads, nelem(search_hit_quads));
2885 trace_action("hits = doc.loadPage(%d).search(%q);\n", fz_page_number_from_location(ctx, doc, search_page), search_needle);
2886 trace_action("print('Search page %d:', repr(%q), hits.length, repr(hits));\n", fz_page_number_from_location(ctx, doc, search_page), search_needle);
2887 if (search_hit_count)
2888 {
2889 float search_hit_x = search_hit_quads[0].ul.x;
2890 float search_hit_y = search_hit_quads[0].ul.y;
2891 search_active = 0;
2892 search_hit_page = search_page;
2893 jump_to_location_xy(search_hit_page, search_hit_x, search_hit_y);
2894 }
2895 else
2896 {
2897 if (search_dir > 0)
2898 {
2899 if (is_last_page(search_page))
2900 search_active = 0;
2901 else
2902 search_page = fz_next_page(ctx, doc, search_page);
2903 }
2904 else
2905 {
2906 if (is_first_page(search_page))
2907 search_active = 0;
2908 else
2909 search_page = fz_previous_page(ctx, doc, search_page);
2910 }
2911 }
2912 }
2913
2914 /* keep searching later */
2915 if (search_active)
2916 glutPostRedisplay();
2917 }
2918
2919 do_app();
2920
2921 if (showoutline)
2922 do_outline(outline);
2923 else if (showundo)
2924 do_undo();
2925 else if (showlayers)
2926 do_layers();
2927 if (showoutline || showundo || showlayers)
2928 ui_splitter(&outline_start_x, &outline_w, 6*ui.gridsize, 20*ui.gridsize, R);
2929
2930 if (!eqloc(oldpage, currentpage) || oldseparations != currentseparations || oldicc != currenticc)
2931 {
2932 load_page();
2933 update_title();
2934 }
2935
2936 if (showannotate)
2937 {
2938 ui_layout(R, BOTH, NW, 0, 0);
2939 ui_panel_begin(annotate_w, 0, ui.padsize*2, ui.padsize*2, 1);
2940 if (showannotate == ANNOTATE_MODE_NORMAL)
2941 do_annotate_panel();
2942 else
2943 do_redact_panel();
2944 ui_panel_end();
2945 }
2946
2947 #if FZ_ENABLE_JS
2948 if (showconsole)
2949 {
2950 do_console();
2951 ui_splitter(&console_start_y, &console_h, 6*ui.lineheight, 25*ui.lineheight, T);
2952 }
2953 #endif
2954
2955 do_canvas();
2956
2957 if (pdf)
2958 {
2959 if (document_shown_as_dirty != pdf_has_unsaved_changes(ctx, pdf))
2960 update_title();
2961 }
2962 }
2963
2964 void run_main_loop(void)
2965 {
2966 if (currentinvert)
2967 glClearColor(0, 0, 0, 1);
2968 else
2969 glClearColor(0.3f, 0.3f, 0.3f, 1);
2970 ui_begin();
2971 fz_try(ctx)
2972 {
2973 if (ui.dialog)
2974 ui.dialog();
2975 else
2976 do_main();
2977 }
2978 fz_catch(ctx)
2979 {
2980 ui_show_error_dialog("%s", fz_caught_message(ctx));
2981 fz_report_error(ctx);
2982 }
2983 ui_end();
2984 }
2985
2986 static void usage(const char *argv0)
2987 {
2988 fprintf(stderr, "mupdf-gl version %s\n", FZ_VERSION);
2989 fprintf(stderr, "usage: %s [options] document [page]\n", argv0);
2990 fprintf(stderr, "\t-p -\tpassword\n");
2991 fprintf(stderr, "\t-r -\tresolution\n");
2992 fprintf(stderr, "\t-c -\tdisplay ICC profile\n");
2993 fprintf(stderr, "\t-b -\tuse named page box (MediaBox, CropBox, BleedBox, TrimBox, or ArtBox)\n");
2994 fprintf(stderr, "\t-I\tinvert colors\n");
2995 fprintf(stderr, "\t-W -\tpage width for EPUB layout\n");
2996 fprintf(stderr, "\t-H -\tpage height for EPUB layout\n");
2997 fprintf(stderr, "\t-S -\tfont size for EPUB layout\n");
2998 fprintf(stderr, "\t-U -\tuser style sheet for EPUB layout\n");
2999 fprintf(stderr, "\t-X\tdisable document styles for EPUB layout\n");
3000 fprintf(stderr, "\t-J\tdisable javascript in PDF forms\n");
3001 fprintf(stderr, "\t-A -\tset anti-aliasing level (0-8,9,10)\n");
3002 fprintf(stderr, "\t-B -\tset black tint color (default: 303030)\n");
3003 fprintf(stderr, "\t-C -\tset white tint color (default: FFFFF0)\n");
3004 fprintf(stderr, "\t-Y -\tset the UI scaling factor\n");
3005 fprintf(stderr, "\t-R -\tenable reflow and set the text extraction options\n");
3006 fprintf(stderr, "\t\t\texample: -R dehyphenate,preserve-images\n");
3007 exit(1);
3008 }
3009
3010 static int document_filter(const char *fname)
3011 {
3012 return !!fz_recognize_document(ctx, fname);
3013 }
3014
3015 static void do_open_document_dialog(void)
3016 {
3017 if (ui_open_file(filename, "Select a document to open:"))
3018 {
3019 ui.dialog = NULL;
3020 if (filename[0] == 0)
3021 glutLeaveMainLoop();
3022 else
3023 {
3024 load_document();
3025 if (doc)
3026 {
3027 if (reflow_options)
3028 reflow_document();
3029 load_page();
3030 shrinkwrap();
3031 update_title();
3032 }
3033 }
3034 }
3035 }
3036
3037 static void cleanup(void)
3038 {
3039 save_history();
3040 fz_try(ctx)
3041 save_accelerator();
3042 fz_catch(ctx)
3043 fz_warn(ctx, "cannot save accelerator file");
3044
3045 ui_finish();
3046
3047 fz_drop_pixmap(ctx, page_contents);
3048 page_contents = NULL;
3049 #ifndef NDEBUG
3050 if (fz_atoi(getenv("FZ_DEBUG_STORE")))
3051 fz_debug_store(ctx, fz_stdout(ctx));
3052 #endif
3053
3054 trace_action("quit(0);\n");
3055
3056 fz_flush_warnings(ctx);
3057
3058 #if FZ_ENABLE_JS
3059 console_fin();
3060 #endif
3061
3062 fz_drop_output(ctx, trace_file);
3063 fz_drop_stext_page(ctx, page_text);
3064 fz_drop_separations(ctx, seps);
3065 fz_drop_link(ctx, links);
3066 fz_drop_page(ctx, fzpage);
3067 fz_drop_outline(ctx, outline);
3068 fz_drop_document(ctx, doc);
3069 fz_drop_context(ctx);
3070 }
3071
3072 int reloadrequested = 0;
3073
3074 #ifndef _WIN32
3075 static void signal_handler(int signal)
3076 {
3077 if (signal == SIGHUP)
3078 reloadrequested = 1;
3079 }
3080 #endif
3081
3082 #ifdef _MSC_VER
3083 int main_utf8(int argc, char **argv)
3084 #else
3085 int main(int argc, char **argv)
3086 #endif
3087 {
3088 const char *trace_file_name = NULL;
3089 const char *profile_name = NULL;
3090 float scale = 0;
3091 int c;
3092
3093 #ifndef _WIN32
3094
3095 /* Never wait for termination of child processes. */
3096 struct sigaction arg = {
3097 .sa_handler=SIG_IGN,
3098 .sa_flags=SA_NOCLDWAIT
3099 };
3100 sigaction(SIGCHLD, &arg, NULL);
3101
3102 signal(SIGHUP, signal_handler);
3103 #endif
3104
3105 glutInit(&argc, argv);
3106
3107 while ((c = fz_getopt(argc, argv, "p:r:IW:H:S:U:XJb:A:B:C:T:Y:R:c:")) != -1)
3108 {
3109 switch (c)
3110 {
3111 default: usage(argv[0]); break;
3112 case 'p': password = fz_optarg; break;
3113 case 'r': currentzoom = fz_atof(fz_optarg); break;
3114 case 'c': profile_name = fz_optarg; break;
3115 case 'I': currentinvert = !currentinvert; break;
3116 case 'b': currentbox = fz_box_type_from_string(fz_optarg); break;
3117 case 'W': layout_w = fz_atof(fz_optarg); break;
3118 case 'H': layout_h = fz_atof(fz_optarg); break;
3119 case 'S': layout_em = fz_atof(fz_optarg); break;
3120 case 'U': layout_css = fz_optarg; break;
3121 case 'X': layout_use_doc_css = 0; break;
3122 case 'J': enable_js = !enable_js; break;
3123 case 'A': currentaa = fz_atoi(fz_optarg); break;
3124 case 'C': currenttint = 1; tint_white = strtol(fz_optarg, NULL, 16); break;
3125 case 'B': currenttint = 1; tint_black = strtol(fz_optarg, NULL, 16); break;
3126 case 'R': reflow_options = fz_optarg; break;
3127 case 'T': trace_file_name = fz_optpath(fz_optarg); break;
3128 case 'Y': scale = fz_atof(fz_optarg); break;
3129 }
3130 }
3131
3132 screen_w = glutGet(GLUT_SCREEN_WIDTH) - SCREEN_FURNITURE_W;
3133 screen_h = glutGet(GLUT_SCREEN_HEIGHT) - SCREEN_FURNITURE_H;
3134
3135 ui_init_dpi(scale);
3136
3137 oldzoom = currentzoom = currentzoom * ui.scale;
3138
3139 ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT);
3140
3141 #ifdef _WIN32
3142 /* stderr goes nowhere. Get us a debug stream we have a chance
3143 * of seeing. */
3144 fz_set_stddbg(ctx, fz_stdods(ctx));
3145 #endif
3146
3147 #if FZ_ENABLE_JS
3148 console_init();
3149 #endif
3150
3151 fz_register_document_handlers(ctx);
3152
3153 if (trace_file_name)
3154 {
3155 trace_file = fz_new_output_with_path(ctx, trace_file_name, 0);
3156 trace_action("var doc, page, annot, widget, widgetstr, hits, tmp;\n");
3157 trace_action("function RegressionError() {\n");
3158 trace_action(" var err = new Error(Array.prototype.join.call(arguments, ' '));\n");
3159 trace_action(" err.name = 'RegressionError';\n");
3160 trace_action(" return err;\n");
3161 trace_action("}\n");
3162 }
3163
3164 if (profile_name)
3165 {
3166 fz_buffer *profile_data = fz_read_file(ctx, profile_name);
3167 profile = fz_new_icc_colorspace(ctx, FZ_COLORSPACE_RGB, 0, NULL, profile_data);
3168 fz_drop_buffer(ctx, profile_data);
3169 }
3170 else
3171 {
3172 profile = fz_device_rgb(ctx);
3173 }
3174
3175 if (layout_css)
3176 fz_load_user_css(ctx, layout_css);
3177 fz_set_use_document_css(ctx, layout_use_doc_css);
3178
3179 if (fz_optind < argc)
3180 {
3181 fz_strlcpy(filename, argv[fz_optind++], sizeof filename);
3182 if (fz_optind < argc)
3183 anchor = argv[fz_optind++];
3184 if (fz_optind < argc)
3185 usage(argv[0]);
3186
3187 fz_try(ctx)
3188 {
3189 page_tex.w = 600;
3190 page_tex.h = 700;
3191 load_document();
3192 if (doc)
3193 {
3194 if (reflow_options)
3195 reflow_document();
3196 load_page();
3197 }
3198 }
3199 fz_always(ctx)
3200 {
3201 float sx = 1, sy = 1;
3202 if (screen_w > 0 && page_tex.w > screen_w)
3203 sx = (float)screen_w / page_tex.w;
3204 if (screen_h > 0 && page_tex.h > screen_h)
3205 sy = (float)screen_h / page_tex.h;
3206 if (sy < sx)
3207 sx = sy;
3208 if (sx < 1)
3209 {
3210 fz_irect area;
3211
3212 currentzoom *= sx;
3213 oldzoom = currentzoom;
3214
3215 /* compute bounds here for initial window size */
3216 page_bounds = fz_bound_page_box(ctx, fzpage, currentbox);
3217 transform_page();
3218
3219 area = fz_irect_from_rect(draw_page_bounds);
3220 page_tex.w = area.x1 - area.x0;
3221 page_tex.h = area.y1 - area.y0;
3222 }
3223
3224 ui_init(page_tex.w, page_tex.h, "MuPDF: Loading...");
3225 ui_input_init(&search_input, "");
3226 }
3227 fz_catch(ctx)
3228 {
3229 ui_show_error_dialog("%s", fz_caught_message(ctx));
3230 fz_report_error(ctx);
3231 }
3232
3233 fz_try(ctx)
3234 {
3235 if (doc)
3236 update_title();
3237 }
3238 fz_catch(ctx)
3239 {
3240 ui_show_error_dialog("%s", fz_caught_message(ctx));
3241 fz_report_error(ctx);
3242 }
3243 }
3244 else
3245 {
3246 #ifdef _WIN32
3247 win_install();
3248 #endif
3249 ui_init(ui.gridsize * 26, ui.gridsize * 26, "MuPDF: Open document");
3250 ui_input_init(&search_input, "");
3251 ui_init_open_file(".", document_filter);
3252 ui.dialog = do_open_document_dialog;
3253 }
3254
3255 annotate_w *= ui.lineheight;
3256 outline_w *= ui.lineheight;
3257 #if FZ_ENABLE_JS
3258 console_h *= ui.lineheight;
3259 #endif
3260
3261 glutMainLoop();
3262
3263 cleanup();
3264
3265 return 0;
3266 }
3267
3268 #ifdef _WIN32
3269 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
3270 {
3271 int argc;
3272 LPWSTR *wargv = CommandLineToArgvW(GetCommandLineW(), &argc);
3273 char **argv = fz_argv_from_wargv(argc, wargv);
3274 int ret = main_utf8(argc, argv);
3275 fz_free_argv(argc, argv);
3276 return ret;
3277 }
3278 #endif