1#include "gl-app.h"
2
3#include <limits.h>
4#include <string.h>
5#include <stdlib.h>
6#include <stdio.h>
7#ifndef _WIN32
8#include <signal.h>
9#endif
10
11#include "mujs.h"
12
13#ifndef PATH_MAX
14#define PATH_MAX 2048
15#endif
16
17#ifndef _WIN32
18#include <unistd.h> /* for fork, exec, and getcwd */
19#else
20char *realpath(const char *path, char *resolved_path); /* in gl-file.c */
21#endif
22
23#ifdef __APPLE__
24static void cleanup(void);
25void glutLeaveMainLoop(void)
26{
27 cleanup();
28 exit(0);
29}
30#endif
31
32fz_context *ctx = NULL;
33pdf_document *pdf = NULL;
34pdf_page *page = NULL;
35pdf_annot *selected_annot = NULL;
36fz_stext_page *page_text = NULL;
37fz_matrix draw_page_ctm, view_page_ctm, view_page_inv_ctm;
38fz_rect page_bounds, draw_page_bounds, view_page_bounds;
39fz_irect view_page_area;
40char filename[PATH_MAX];
41
42enum
43{
44 /* Screen furniture: aggregate size of unusable space from title bars, task bars, window borders, etc */
45 SCREEN_FURNITURE_W = 20,
46 SCREEN_FURNITURE_H = 40,
47};
48
49static void open_browser(const char *uri)
50{
51 char buf[PATH_MAX];
52
53 /* Relative file:// URI, make it absolute! */
54 if (!strncmp(uri, "file://", 7) && uri[7] != '/')
55 {
56 char buf_base[PATH_MAX];
57 char buf_cwd[PATH_MAX];
58 fz_dirname(buf_base, filename, sizeof buf_base);
59 getcwd(buf_cwd, sizeof buf_cwd);
60 fz_snprintf(buf, sizeof buf, "file://%s/%s/%s", buf_cwd, buf_base, uri+7);
61 fz_cleanname(buf+7);
62 uri = buf;
63 }
64
65#ifdef _WIN32
66 ShellExecuteA(NULL, "open", uri, 0, 0, SW_SHOWNORMAL);
67#else
68 const char *browser = getenv("BROWSER");
69 if (!browser)
70 {
71#ifdef __APPLE__
72 browser = "open";
73#else
74 browser = "xdg-open";
75#endif
76 }
77 if (fork() == 0)
78 {
79 execlp(browser, browser, uri, (char*)0);
80 fprintf(stderr, "cannot exec '%s'\n", browser);
81 exit(0);
82 }
83#endif
84}
85
86static const int zoom_list[] = {
87 24, 36, 48, 60, 72, 84, 96, 108,
88 120, 144, 168, 192, 228, 264,
89 300, 350, 400, 450, 500, 550, 600
90};
91
92static int zoom_in(int oldres)
93{
94 int i;
95 for (i = 0; i < nelem(zoom_list) - 1; ++i)
96 if (zoom_list[i] <= oldres && zoom_list[i+1] > oldres)
97 return zoom_list[i+1];
98 return zoom_list[i];
99}
100
101static int zoom_out(int oldres)
102{
103 int i;
104 for (i = 0; i < nelem(zoom_list) - 1; ++i)
105 if (zoom_list[i] < oldres && zoom_list[i+1] >= oldres)
106 return zoom_list[i];
107 return zoom_list[0];
108}
109
110static const char *paper_size_name(int w, int h)
111{
112 /* ISO A */
113 if (w == 2384 && h == 3370) return "A0";
114 if (w == 1684 && h == 2384) return "A1";
115 if (w == 1191 && h == 1684) return "A2";
116 if (w == 842 && h == 1191) return "A3";
117 if (w == 595 && h == 842) return "A4";
118 if (w == 420 && h == 595) return "A5";
119 if (w == 297 && h == 420) return "A6";
120
121 /* US */
122 if (w == 612 && h == 792) return "Letter";
123 if (w == 612 && h == 1008) return "Legal";
124 if (w == 792 && h == 1224) return "Ledger";
125 if (w == 1224 && h == 792) return "Tabloid";
126
127 return NULL;
128}
129
130#define MINRES (zoom_list[0])
131#define MAXRES (zoom_list[nelem(zoom_list)-1])
132#define DEFRES 96
133
134static char *password = "";
135static char *anchor = NULL;
136static float layout_w = FZ_DEFAULT_LAYOUT_W;
137static float layout_h = FZ_DEFAULT_LAYOUT_H;
138static float layout_em = FZ_DEFAULT_LAYOUT_EM;
139static char *layout_css = NULL;
140static int layout_use_doc_css = 1;
141static int enable_js = 1;
142static int tint_white = 0xFFFFF0;
143static int tint_black = 0x303030;
144
145static fz_document *doc = NULL;
146static fz_page *fzpage = NULL;
147static fz_separations *seps = NULL;
148static fz_outline *outline = NULL;
149static fz_link *links = NULL;
150
151static int number = 0;
152
153static struct texture page_tex = { 0 };
154static int screen_w = 0, screen_h = 0;
155static int scroll_x = 0, scroll_y = 0;
156static int canvas_x = 0, canvas_w = 100;
157static int canvas_y = 0, canvas_h = 100;
158
159static int outline_w = 14; /* to be scaled by lineheight */
160static int annotate_w = 12; /* to be scaled by lineheight */
161
162static int oldtint = 0, currenttint = 0;
163static int oldinvert = 0, currentinvert = 0;
164static int oldicc = 1, currenticc = 1;
165static int oldaa = 8, currentaa = 8;
166static int oldseparations = 0, currentseparations = 0;
167static int oldpage = 0, currentpage = 0;
168static float oldzoom = DEFRES, currentzoom = DEFRES;
169static float oldrotate = 0, currentrotate = 0;
170
171static int isfullscreen = 0;
172static int showoutline = 0;
173static int showlinks = 0;
174static int showsearch = 0;
175static int showinfo = 0;
176int showannotate = 0;
177int showform = 0;
178
179static const char *tooltip = NULL;
180
181struct mark
182{
183 int page;
184 fz_point scroll;
185};
186
187static int history_count = 0;
188static struct mark history[256];
189static int future_count = 0;
190static struct mark future[256];
191static struct mark marks[10];
192
193static char *get_history_filename(void)
194{
195 static char history_path[PATH_MAX];
196 static int once = 0;
197 if (!once)
198 {
199 char *home = getenv("HOME");
200 if (!home)
201 home = getenv("USERPROFILE");
202 if (!home)
203 home = "/tmp";
204 fz_snprintf(history_path, sizeof history_path, "%s/.mupdf.history", home);
205 fz_cleanname(history_path);
206 once = 1;
207 }
208 return history_path;
209}
210
211static void read_history_file_as_json(js_State *J)
212{
213 fz_buffer *buf = NULL;
214 const char *json = "{}";
215
216 fz_var(buf);
217
218 if (fz_file_exists(ctx, get_history_filename()))
219 {
220 fz_try(ctx)
221 {
222 buf = fz_read_file(ctx, get_history_filename());
223 json = fz_string_from_buffer(ctx, buf);
224 }
225 fz_catch(ctx)
226 ;
227 }
228
229 js_getglobal(J, "JSON");
230 js_getproperty(J, -1, "parse");
231 js_pushnull(J);
232 js_pushstring(J, json);
233 if (js_pcall(J, 1))
234 {
235 fz_warn(ctx, "Can't parse history file: %s", js_trystring(J, -1, "error"));
236 js_pop(J, 1);
237 js_newobject(J);
238 }
239 else
240 {
241 js_rot2pop1(J);
242 }
243
244 fz_drop_buffer(ctx, buf);
245}
246
247static void load_history(void)
248{
249 js_State *J;
250 char absname[PATH_MAX];
251 int i, n;
252
253 if (!realpath(filename, absname))
254 return;
255
256 J = js_newstate(NULL, NULL, 0);
257
258 read_history_file_as_json(J);
259
260 if (js_hasproperty(J, -1, absname))
261 {
262 if (js_hasproperty(J, -1, "current"))
263 {
264 currentpage = js_tryinteger(J, -1, 1) - 1;
265 js_pop(J, 1);
266 }
267
268 if (js_hasproperty(J, -1, "history"))
269 {
270 if (js_isarray(J, -1))
271 {
272 history_count = fz_clampi(js_getlength(J, -1), 0, nelem(history));
273 for (i = 0; i < history_count; ++i)
274 {
275 js_getindex(J, -1, i);
276 history[i].page = js_tryinteger(J, -1, 1) - 1;
277 js_pop(J, 1);
278 }
279 }
280 js_pop(J, 1);
281 }
282
283 if (js_hasproperty(J, -1, "future"))
284 {
285 if (js_isarray(J, -1))
286 {
287 future_count = fz_clampi(js_getlength(J, -1), 0, nelem(future));
288 for (i = 0; i < future_count; ++i)
289 {
290 js_getindex(J, -1, i);
291 future[i].page = js_tryinteger(J, -1, 1) - 1;
292 js_pop(J, 1);
293 }
294 }
295 js_pop(J, 1);
296 }
297
298 if (js_hasproperty(J, -1, "marks"))
299 {
300 if (js_isarray(J, -1))
301 {
302 n = fz_clampi(js_getlength(J, -1), 0, nelem(marks));
303 for (i = 0; i < n; ++i)
304 {
305 js_getindex(J, -1, i);
306 marks[i].page = js_tryinteger(J, -1, 1) - 1;
307 js_pop(J, 1);
308 }
309 }
310 js_pop(J, 1);
311 }
312 }
313
314 js_freestate(J);
315}
316
317static void save_history(void)
318{
319 js_State *J;
320 char absname[PATH_MAX];
321 fz_output *out = NULL;
322 const char *json;
323 int i;
324
325 fz_var(out);
326
327 if (!doc)
328 return;
329
330 if (!realpath(filename, absname))
331 return;
332
333 J = js_newstate(NULL, NULL, 0);
334
335 read_history_file_as_json(J);
336
337 js_newobject(J);
338 {
339 js_pushnumber(J, currentpage+1);
340 js_setproperty(J, -2, "current");
341
342 js_newarray(J);
343 for (i = 0; i < history_count; ++i)
344 {
345 js_pushnumber(J, history[i].page+1);
346 js_setindex(J, -2, i);
347 }
348 js_setproperty(J, -2, "history");
349
350 js_newarray(J);
351 for (i = 0; i < future_count; ++i)
352 {
353 js_pushnumber(J, future[i].page+1);
354 js_setindex(J, -2, i);
355 }
356 js_setproperty(J, -2, "future");
357
358 js_newarray(J);
359 for (i = 0; i < nelem(marks); ++i)
360 {
361 js_pushnumber(J, marks[i].page+1);
362 js_setindex(J, -2, i);
363 }
364 js_setproperty(J, -2, "marks");
365 }
366 js_setproperty(J, -2, absname);
367
368 js_getglobal(J, "JSON");
369 js_getproperty(J, -1, "stringify");
370 js_pushnull(J);
371 js_copy(J, -4);
372 js_pushnull(J);
373 js_pushnumber(J, 0);
374 js_call(J, 3);
375 js_rot2pop1(J);
376 json = js_tostring(J, -1);
377
378 fz_try(ctx)
379 {
380 out = fz_new_output_with_path(ctx, get_history_filename(), 0);
381 fz_write_string(ctx, out, json);
382 fz_write_byte(ctx, out, '\n');
383 fz_close_output(ctx, out);
384 }
385 fz_always(ctx)
386 fz_drop_output(ctx, out);
387 fz_catch(ctx)
388 fz_warn(ctx, "Can't write history file.");
389
390 js_freestate(J);
391}
392
393static int search_active = 0;
394static struct input search_input = { { 0 }, 0 };
395static char *search_needle = 0;
396static int search_dir = 1;
397static int search_page = -1;
398static int search_hit_page = -1;
399static int search_hit_count = 0;
400static fz_quad search_hit_quads[5000];
401
402static char *help_dialog_text =
403 "The middle mouse button (scroll wheel button) pans the document view. "
404 "The right mouse button selects a region and copies the marked text to the clipboard."
405 "\n"
406 "\n"
407 "F1 - show this message\n"
408 "i - show document information\n"
409 "o - show document outline\n"
410 "a - show annotation editor\n"
411 "L - highlight links\n"
412 "F - highlight form fields\n"
413 "r - reload file\n"
414 "S - save file (only for PDF)\n"
415 "q - quit\n"
416 "\n"
417 "< - decrease E-book font size\n"
418 "> - increase E-book font size\n"
419 "A - toggle anti-aliasing\n"
420 "I - toggle inverted color mode\n"
421 "C - toggle tinted color mode\n"
422 "E - toggle ICC color management\n"
423 "e - toggle spot color emulation\n"
424 "\n"
425 "f - fullscreen window\n"
426 "w - shrink wrap window\n"
427 "W - fit to width\n"
428 "H - fit to height\n"
429 "Z - fit to page\n"
430 "z - reset zoom\n"
431 "[number] z - set zoom resolution in DPI\n"
432 "plus - zoom in\n"
433 "minus - zoom out\n"
434 "[ - rotate counter-clockwise\n"
435 "] - rotate clockwise\n"
436 "arrow keys - scroll in small increments\n"
437 "h, j, k, l - scroll in small increments\n"
438 "\n"
439 "b - smart move backward\n"
440 "space - smart move forward\n"
441 "comma or page up - go backward\n"
442 "period or page down - go forward\n"
443 "g - go to first page\n"
444 "G - go to last page\n"
445 "[number] g - go to page number\n"
446 "\n"
447 "m - save current location in history\n"
448 "t - go backward in history\n"
449 "T - go forward in history\n"
450 "[number] m - save current location in numbered bookmark\n"
451 "[number] t - go to numbered bookmark\n"
452 "\n"
453 "/ - search for text forward\n"
454 "? - search for text backward\n"
455 "n - repeat search\n"
456 "N - repeat search in reverse direction"
457 ;
458
459static void help_dialog(void)
460{
461 static int scroll;
462 ui_dialog_begin(500, 1000);
463 ui_layout(T, X, W, 2, 2);
464 ui_label("MuPDF %s", FZ_VERSION);
465 ui_spacer();
466 ui_layout(B, NONE, S, 2, 2);
467 if (ui_button("Okay") || ui.key == KEY_ENTER || ui.key == KEY_ESCAPE)
468 ui.dialog = NULL;
469 ui_spacer();
470 ui_layout(ALL, BOTH, CENTER, 2, 2);
471 ui_label_with_scrollbar(help_dialog_text, 0, 0, &scroll);
472 ui_dialog_end();
473}
474
475static char error_message[256];
476static void error_dialog(void)
477{
478 ui_dialog_begin(500, (ui.gridsize+4)*4);
479 ui_layout(T, NONE, NW, 2, 2);
480 ui_label("%C %s", 0x1f4a3, error_message); /* BOMB */
481 ui_layout(B, NONE, S, 2, 2);
482 if (ui_button("Quit") || ui.key == KEY_ENTER || ui.key == KEY_ESCAPE || ui.key == 'q')
483 glutLeaveMainLoop();
484 ui_dialog_end();
485}
486void ui_show_error_dialog(const char *fmt, ...)
487{
488 va_list ap;
489 va_start(ap, fmt);
490 fz_vsnprintf(error_message, sizeof error_message, fmt, ap);
491 va_end(ap);
492 ui.dialog = error_dialog;
493}
494
495static char warning_message[256];
496static void warning_dialog(void)
497{
498 ui_dialog_begin(500, (ui.gridsize+4)*4);
499 ui_layout(T, NONE, NW, 2, 2);
500 ui_label("%C %s", 0x26a0, warning_message); /* WARNING SIGN */
501 ui_layout(B, NONE, S, 2, 2);
502 if (ui_button("Okay") || ui.key == KEY_ENTER || ui.key == KEY_ESCAPE)
503 ui.dialog = NULL;
504 ui_dialog_end();
505}
506void ui_show_warning_dialog(const char *fmt, ...)
507{
508 va_list ap;
509 va_start(ap, fmt);
510 fz_vsnprintf(warning_message, sizeof warning_message, fmt, ap);
511 va_end(ap);
512 ui.dialog = warning_dialog;
513}
514
515void update_title(void)
516{
517 char buf[256];
518 char *title = "MuPDF/GL";
519 char *extra = "";
520 size_t n;
521
522 title = strrchr(filename, '/');
523 if (!title)
524 title = strrchr(filename, '\\');
525 if (title)
526 ++title;
527 else
528 title = filename;
529
530 if (pdf && pdf->dirty)
531 extra = "*";
532
533 n = strlen(title);
534 if (n > 50)
535 sprintf(buf, "...%s%s - %d / %d", title + n - 50, extra, currentpage + 1, fz_count_pages(ctx, doc));
536 else
537 sprintf(buf, "%s%s - %d / %d", title, extra, currentpage + 1, fz_count_pages(ctx, doc));
538 glutSetWindowTitle(buf);
539 glutSetIconTitle(buf);
540}
541
542void transform_page(void)
543{
544 draw_page_ctm = fz_transform_page(page_bounds, currentzoom, currentrotate);
545 draw_page_bounds = fz_transform_rect(page_bounds, draw_page_ctm);
546}
547
548void load_page(void)
549{
550 fz_irect area;
551
552 /* clear all editor selections */
553 if (selected_annot && pdf_annot_type(ctx, selected_annot) == PDF_ANNOT_WIDGET)
554 pdf_annot_event_blur(ctx, selected_annot);
555 selected_annot = NULL;
556
557 fz_drop_stext_page(ctx, page_text);
558 page_text = NULL;
559 fz_drop_separations(ctx, seps);
560 seps = NULL;
561 fz_drop_link(ctx, links);
562 links = NULL;
563 fz_drop_page(ctx, fzpage);
564 fzpage = NULL;
565
566 fzpage = fz_load_page(ctx, doc, currentpage);
567 if (pdf)
568 page = (pdf_page*)fzpage;
569
570 links = fz_load_links(ctx, fzpage);
571 page_text = fz_new_stext_page_from_page(ctx, fzpage, NULL);
572
573 if (currenticc)
574 fz_enable_icc(ctx);
575 else
576 fz_disable_icc(ctx);
577
578 if (currentseparations)
579 {
580 seps = fz_page_separations(ctx, &page->super);
581 if (seps)
582 {
583 int i, n = fz_count_separations(ctx, seps);
584 for (i = 0; i < n; i++)
585 fz_set_separation_behavior(ctx, seps, i, FZ_SEPARATION_COMPOSITE);
586 }
587 else if (fz_page_uses_overprint(ctx, &page->super))
588 seps = fz_new_separations(ctx, 0);
589 else if (fz_document_output_intent(ctx, doc))
590 seps = fz_new_separations(ctx, 0);
591 }
592
593 /* compute bounds here for initial window size */
594 page_bounds = fz_bound_page(ctx, fzpage);
595 transform_page();
596
597 area = fz_irect_from_rect(draw_page_bounds);
598 page_tex.w = area.x1 - area.x0;
599 page_tex.h = area.y1 - area.y0;
600}
601
602void render_page(void)
603{
604 fz_pixmap *pix;
605
606 transform_page();
607
608 fz_set_aa_level(ctx, currentaa);
609
610 pix = fz_new_pixmap_from_page_with_separations(ctx, fzpage, draw_page_ctm, fz_device_rgb(ctx), seps, 0);
611 if (currentinvert)
612 {
613 fz_invert_pixmap_luminance(ctx, pix);
614 fz_gamma_pixmap(ctx, pix, 1 / 1.4f);
615 }
616 if (currenttint)
617 {
618 fz_tint_pixmap(ctx, pix, tint_black, tint_white);
619 }
620
621 ui_texture_from_pixmap(&page_tex, pix);
622 fz_drop_pixmap(ctx, pix);
623}
624
625void render_page_if_changed(void)
626{
627 if (oldpage != currentpage ||
628 oldzoom != currentzoom ||
629 oldrotate != currentrotate ||
630 oldinvert != currentinvert ||
631 oldtint != currenttint ||
632 oldicc != currenticc ||
633 oldseparations != currentseparations ||
634 oldaa != currentaa)
635 {
636 render_page();
637 oldpage = currentpage;
638 oldzoom = currentzoom;
639 oldrotate = currentrotate;
640 oldinvert = currentinvert;
641 oldtint = currenttint;
642 oldicc = currenticc;
643 oldseparations = currentseparations;
644 oldaa = currentaa;
645 }
646}
647
648static struct mark save_mark()
649{
650 struct mark mark;
651 mark.page = currentpage;
652 mark.scroll = fz_transform_point_xy(scroll_x, scroll_y, view_page_inv_ctm);
653 return mark;
654}
655
656static void restore_mark(struct mark mark)
657{
658 currentpage = mark.page;
659 mark.scroll = fz_transform_point(mark.scroll, draw_page_ctm);
660 scroll_x = mark.scroll.x;
661 scroll_y = mark.scroll.y;
662}
663
664static void push_history(void)
665{
666 if (history_count > 0 && history[history_count-1].page == currentpage)
667 return;
668 if (history_count + 1 >= nelem(history))
669 {
670 memmove(history, history + 1, sizeof *history * (nelem(history) - 1));
671 history[history_count] = save_mark();
672 }
673 else
674 {
675 history[history_count++] = save_mark();
676 }
677}
678
679static void push_future(void)
680{
681 if (future_count + 1 >= nelem(future))
682 {
683 memmove(future, future + 1, sizeof *future * (nelem(future) - 1));
684 future[future_count] = save_mark();
685 }
686 else
687 {
688 future[future_count++] = save_mark();
689 }
690}
691
692static void clear_future(void)
693{
694 future_count = 0;
695}
696
697static void jump_to_page(int newpage)
698{
699 newpage = fz_clampi(newpage, 0, fz_count_pages(ctx, doc) - 1);
700 clear_future();
701 push_history();
702 currentpage = newpage;
703 push_history();
704}
705
706static void jump_to_page_xy(int newpage, float x, float y)
707{
708 fz_point p = fz_transform_point_xy(x, y, draw_page_ctm);
709 newpage = fz_clampi(newpage, 0, fz_count_pages(ctx, doc) - 1);
710 clear_future();
711 push_history();
712 currentpage = newpage;
713 scroll_x = p.x;
714 scroll_y = p.y;
715 push_history();
716}
717
718static void pop_history(void)
719{
720 int here = currentpage;
721 push_future();
722 while (history_count > 0 && currentpage == here)
723 restore_mark(history[--history_count]);
724}
725
726static void pop_future(void)
727{
728 int here = currentpage;
729 push_history();
730 while (future_count > 0 && currentpage == here)
731 restore_mark(future[--future_count]);
732 push_history();
733}
734
735static void relayout(void)
736{
737 if (layout_em < 6) layout_em = 6;
738 if (layout_em > 36) layout_em = 36;
739 if (fz_is_document_reflowable(ctx, doc))
740 {
741 fz_bookmark mark = fz_make_bookmark(ctx, doc, currentpage);
742 fz_layout_document(ctx, doc, layout_w, layout_h, layout_em);
743 currentpage = fz_lookup_bookmark(ctx, doc, mark);
744 history_count = 0;
745 future_count = 0;
746
747 load_page();
748 render_page();
749 update_title();
750 }
751}
752
753static int count_outline(fz_outline *node, int end)
754{
755 int is_selected, n, p;
756 int count = 0;
757 while (node)
758 {
759 p = node->page;
760 if (p >= 0)
761 {
762 count += 1;
763 n = end;
764 if (node->next && node->next->page >= 0)
765 n = node->next->page;
766 is_selected = (currentpage == p || (currentpage > p && currentpage < n));
767 if (node->down && (node->is_open || is_selected))
768 count += count_outline(node->down, end);
769 }
770 node = node->next;
771 }
772 return count;
773}
774
775static void do_outline_imp(struct list *list, int end, fz_outline *node, int depth)
776{
777 int selected, was_open, n;
778
779 while (node)
780 {
781 int p = node->page;
782 if (p >= 0)
783 {
784 n = end;
785 if (node->next && node->next->page >= 0)
786 n = node->next->page;
787
788 was_open = node->is_open;
789 selected = (currentpage == p || (currentpage > p && currentpage < n));
790 if (ui_tree_item(list, node, node->title, selected, depth, !!node->down, &node->is_open))
791 jump_to_page_xy(p, node->x, node->y);
792
793 if (node->down && (was_open || selected))
794 do_outline_imp(list, n, node->down, depth + 1);
795 }
796 node = node->next;
797 }
798}
799
800static void do_outline(fz_outline *node)
801{
802 static struct list list;
803 ui_layout(L, BOTH, NW, 0, 0);
804 ui_tree_begin(&list, count_outline(node, fz_count_pages(ctx, doc)), outline_w, 0, 1);
805 do_outline_imp(&list, fz_count_pages(ctx, doc), node, 0);
806 ui_tree_end(&list);
807 ui_splitter(&outline_w, 150, 500, R);
808}
809
810static void do_links(fz_link *link)
811{
812 fz_rect bounds;
813 fz_irect area;
814 float link_x, link_y;
815
816 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
817 glEnable(GL_BLEND);
818
819 while (link)
820 {
821 bounds = link->rect;
822 bounds = fz_transform_rect(link->rect, view_page_ctm);
823 area = fz_irect_from_rect(bounds);
824
825 if (ui_mouse_inside(area))
826 {
827 tooltip = link->uri;
828 ui.hot = link;
829 if (!ui.active && ui.down)
830 ui.active = link;
831 }
832
833 if (ui.hot == link || showlinks)
834 {
835 if (ui.active == link && ui.hot == link)
836 glColor4f(0, 0, 1, 0.4f);
837 else if (ui.hot == link)
838 glColor4f(0, 0, 1, 0.2f);
839 else
840 glColor4f(0, 0, 1, 0.1f);
841 glRectf(area.x0, area.y0, area.x1, area.y1);
842 }
843
844 if (ui.active == link && !ui.down)
845 {
846 if (ui.hot == link)
847 {
848 if (fz_is_external_link(ctx, link->uri))
849 open_browser(link->uri);
850 else
851 {
852 int p = fz_resolve_link(ctx, doc, link->uri, &link_x, &link_y);
853 if (p >= 0)
854 jump_to_page_xy(p, link_x, link_y);
855 else
856 fz_warn(ctx, "cannot find link destination '%s'", link->uri);
857 }
858 }
859 }
860
861 link = link->next;
862 }
863
864 glDisable(GL_BLEND);
865}
866
867static void do_page_selection(void)
868{
869 static fz_point pt = { 0, 0 };
870 fz_quad hits[1000];
871 int i, n;
872
873 if (ui_mouse_inside(view_page_area))
874 {
875 ui.hot = &pt;
876 if (!ui.active && ui.right)
877 {
878 ui.active = &pt;
879 pt.x = ui.x;
880 pt.y = ui.y;
881 }
882 }
883
884 if (ui.active == &pt)
885 {
886 fz_point page_a = { pt.x, pt.y };
887 fz_point page_b = { ui.x, ui.y };
888
889 page_a = fz_transform_point(page_a, view_page_inv_ctm);
890 page_b = fz_transform_point(page_b, view_page_inv_ctm);
891
892 if (ui.mod == GLUT_ACTIVE_CTRL)
893 fz_snap_selection(ctx, page_text, &page_a, &page_b, FZ_SELECT_WORDS);
894 else if (ui.mod == GLUT_ACTIVE_CTRL + GLUT_ACTIVE_SHIFT)
895 fz_snap_selection(ctx, page_text, &page_a, &page_b, FZ_SELECT_LINES);
896
897 n = fz_highlight_selection(ctx, page_text, page_a, page_b, hits, nelem(hits));
898
899 glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO); /* invert destination color */
900 glEnable(GL_BLEND);
901
902 glColor4f(1, 1, 1, 1);
903 glBegin(GL_QUADS);
904 for (i = 0; i < n; ++i)
905 {
906 fz_quad thit = fz_transform_quad(hits[i], view_page_ctm);
907 glVertex2f(thit.ul.x, thit.ul.y);
908 glVertex2f(thit.ur.x, thit.ur.y);
909 glVertex2f(thit.lr.x, thit.lr.y);
910 glVertex2f(thit.ll.x, thit.ll.y);
911 }
912 glEnd();
913
914 glDisable(GL_BLEND);
915
916 if (!ui.right)
917 {
918 char *s;
919#ifdef _WIN32
920 s = fz_copy_selection(ctx, page_text, page_a, page_b, 1);
921#else
922 s = fz_copy_selection(ctx, page_text, page_a, page_b, 0);
923#endif
924 ui_set_clipboard(s);
925 fz_free(ctx, s);
926 }
927 }
928}
929
930static void do_search_hits(void)
931{
932 int i;
933
934 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
935 glEnable(GL_BLEND);
936
937 glColor4f(1, 0, 0, 0.4f);
938 glBegin(GL_QUADS);
939 for (i = 0; i < search_hit_count; ++i)
940 {
941 fz_quad thit = fz_transform_quad(search_hit_quads[i], view_page_ctm);
942 glVertex2f(thit.ul.x, thit.ul.y);
943 glVertex2f(thit.ur.x, thit.ur.y);
944 glVertex2f(thit.lr.x, thit.lr.y);
945 glVertex2f(thit.ll.x, thit.ll.y);
946 }
947
948 glEnd();
949 glDisable(GL_BLEND);
950}
951
952static void toggle_fullscreen(void)
953{
954 static int win_x = 0, win_y = 0;
955 static int win_w = 100, win_h = 100;
956 if (!isfullscreen)
957 {
958 win_w = glutGet(GLUT_WINDOW_WIDTH);
959 win_h = glutGet(GLUT_WINDOW_HEIGHT);
960 win_x = glutGet(GLUT_WINDOW_X);
961 win_y = glutGet(GLUT_WINDOW_Y);
962 glutFullScreen();
963 isfullscreen = 1;
964 }
965 else
966 {
967 glutPositionWindow(win_x, win_y);
968 glutReshapeWindow(win_w, win_h);
969 isfullscreen = 0;
970 }
971}
972
973static void shrinkwrap(void)
974{
975 int w = page_tex.w + (showoutline ? outline_w + 4 : 0) + (showannotate ? annotate_w : 0);
976 int h = page_tex.h;
977 if (screen_w > 0 && w > screen_w)
978 w = screen_w;
979 if (screen_h > 0 && h > screen_h)
980 h = screen_h;
981 if (isfullscreen)
982 toggle_fullscreen();
983 glutReshapeWindow(w, h);
984}
985
986static struct input input_password;
987static void password_dialog(void)
988{
989 int is;
990 ui_dialog_begin(400, (ui.gridsize+4)*3);
991 {
992 ui_layout(T, X, NW, 2, 2);
993 ui_label("Password:");
994 is = ui_input(&input_password, 200, 1);
995
996 ui_layout(B, X, NW, 2, 2);
997 ui_panel_begin(0, ui.gridsize, 0, 0, 0);
998 {
999 ui_layout(R, NONE, S, 0, 0);
1000 if (ui_button("Cancel") || (!ui.focus && ui.key == KEY_ESCAPE))
1001 glutLeaveMainLoop();
1002 ui_spacer();
1003 if (ui_button("Okay") || is == UI_INPUT_ACCEPT)
1004 {
1005 password = input_password.text;
1006 ui.dialog = NULL;
1007 reload();
1008 shrinkwrap();
1009 }
1010 }
1011 ui_panel_end();
1012 }
1013 ui_dialog_end();
1014}
1015
1016static void load_document(void)
1017{
1018 fz_drop_outline(ctx, outline);
1019 fz_drop_document(ctx, doc);
1020
1021 doc = fz_open_document(ctx, filename);
1022 if (fz_needs_password(ctx, doc))
1023 {
1024 if (!fz_authenticate_password(ctx, doc, password))
1025 {
1026 fz_drop_document(ctx, doc);
1027 doc = NULL;
1028 ui_input_init(&input_password, "");
1029 ui.focus = &input_password;
1030 ui.dialog = password_dialog;
1031 return;
1032 }
1033 }
1034
1035 fz_layout_document(ctx, doc, layout_w, layout_h, layout_em);
1036
1037 fz_try(ctx)
1038 outline = fz_load_outline(ctx, doc);
1039 fz_catch(ctx)
1040 outline = NULL;
1041
1042 load_history();
1043
1044 pdf = pdf_specifics(ctx, doc);
1045 if (pdf)
1046 {
1047 if (enable_js)
1048 pdf_enable_js(ctx, pdf);
1049 if (anchor)
1050 jump_to_page(pdf_lookup_anchor(ctx, pdf, anchor, NULL, NULL));
1051 }
1052 else
1053 {
1054 if (anchor)
1055 jump_to_page(fz_atoi(anchor) - 1);
1056 }
1057 anchor = NULL;
1058
1059 currentpage = fz_clampi(currentpage, 0, fz_count_pages(ctx, doc) - 1);
1060}
1061
1062void reload(void)
1063{
1064 save_history();
1065 load_document();
1066 if (doc)
1067 {
1068 load_page();
1069 render_page();
1070 update_title();
1071 }
1072}
1073
1074static void toggle_outline(void)
1075{
1076 if (outline)
1077 {
1078 showoutline = !showoutline;
1079 if (canvas_w == page_tex.w && canvas_h == page_tex.h)
1080 shrinkwrap();
1081 }
1082}
1083
1084void toggle_annotate(void)
1085{
1086 if (pdf)
1087 {
1088 showannotate = !showannotate;
1089 if (canvas_w == page_tex.w && canvas_h == page_tex.h)
1090 shrinkwrap();
1091 }
1092}
1093
1094static void set_zoom(int z, int cx, int cy)
1095{
1096 z = fz_clamp(z, MINRES, MAXRES);
1097 scroll_x = (scroll_x + cx - canvas_x) * z / currentzoom - cx + canvas_x;
1098 scroll_y = (scroll_y + cy - canvas_y) * z / currentzoom - cy + canvas_y;
1099 currentzoom = z;
1100}
1101
1102static void auto_zoom_w(void)
1103{
1104 currentzoom = fz_clamp(currentzoom * canvas_w / page_tex.w, MINRES, MAXRES);
1105}
1106
1107static void auto_zoom_h(void)
1108{
1109 currentzoom = fz_clamp(currentzoom * canvas_h / page_tex.h, MINRES, MAXRES);
1110}
1111
1112static void auto_zoom(void)
1113{
1114 float page_a = (float) page_tex.w / page_tex.h;
1115 float screen_a = (float) canvas_w / canvas_h;
1116 if (page_a > screen_a)
1117 auto_zoom_w();
1118 else
1119 auto_zoom_h();
1120}
1121
1122static void smart_move_backward(void)
1123{
1124 if (scroll_y <= 0)
1125 {
1126 if (scroll_x <= 0)
1127 {
1128 if (currentpage - 1 >= 0)
1129 {
1130 scroll_x = page_tex.w;
1131 scroll_y = page_tex.h;
1132 currentpage -= 1;
1133 }
1134 }
1135 else
1136 {
1137 scroll_y = page_tex.h;
1138 scroll_x -= canvas_w * 9 / 10;
1139 }
1140 }
1141 else
1142 {
1143 scroll_y -= canvas_h * 9 / 10;
1144 }
1145}
1146
1147static void smart_move_forward(void)
1148{
1149 if (scroll_y + canvas_h >= page_tex.h)
1150 {
1151 if (scroll_x + canvas_w >= page_tex.w)
1152 {
1153 if (currentpage + 1 < fz_count_pages(ctx, doc))
1154 {
1155 scroll_x = 0;
1156 scroll_y = 0;
1157 currentpage += 1;
1158 }
1159 }
1160 else
1161 {
1162 scroll_y = 0;
1163 scroll_x += canvas_w * 9 / 10;
1164 }
1165 }
1166 else
1167 {
1168 scroll_y += canvas_h * 9 / 10;
1169 }
1170}
1171
1172static void clear_search(void)
1173{
1174 showsearch = 0;
1175 search_hit_page = -1;
1176 search_hit_count = 0;
1177}
1178
1179static void do_app(void)
1180{
1181 if (ui.key == KEY_F4 && ui.mod == GLUT_ACTIVE_ALT)
1182 glutLeaveMainLoop();
1183
1184 if (ui.down || ui.middle || ui.right || ui.key)
1185 showinfo = 0;
1186
1187 if (!ui.focus && ui.key && ui.plain)
1188 {
1189 switch (ui.key)
1190 {
1191 case KEY_ESCAPE: clear_search(); selected_annot = NULL; break;
1192 case KEY_F1: ui.dialog = help_dialog; break;
1193 case 'a': toggle_annotate(); break;
1194 case 'o': toggle_outline(); break;
1195 case 'L': showlinks = !showlinks; break;
1196 case 'F': showform = !showform; break;
1197 case 'i': showinfo = !showinfo; break;
1198 case 'r': reload(); break;
1199 case 'q': glutLeaveMainLoop(); break;
1200 case 'S': do_save_pdf_file(); break;
1201
1202 case '>': layout_em = number > 0 ? number : layout_em + 1; relayout(); break;
1203 case '<': layout_em = number > 0 ? number : layout_em - 1; relayout(); break;
1204
1205 case 'C': currenttint = !currenttint; break;
1206 case 'I': currentinvert = !currentinvert; break;
1207 case 'e': currentseparations = !currentseparations; break;
1208 case 'E': currenticc = !currenticc; break;
1209 case 'f': toggle_fullscreen(); break;
1210 case 'w': shrinkwrap(); break;
1211 case 'W': auto_zoom_w(); break;
1212 case 'H': auto_zoom_h(); break;
1213 case 'Z': auto_zoom(); break;
1214 case 'z': set_zoom(number > 0 ? number : DEFRES, canvas_w/2, canvas_h/2); break;
1215 case '+': set_zoom(zoom_in(currentzoom), ui.x, ui.y); break;
1216 case '-': set_zoom(zoom_out(currentzoom), ui.x, ui.y); break;
1217 case '[': currentrotate -= 90; break;
1218 case ']': currentrotate += 90; break;
1219 case 'k': case KEY_UP: scroll_y -= 10; break;
1220 case 'j': case KEY_DOWN: scroll_y += 10; break;
1221 case 'h': case KEY_LEFT: scroll_x -= 10; break;
1222 case 'l': case KEY_RIGHT: scroll_x += 10; break;
1223
1224 case 'b': number = fz_maxi(number, 1); while (number--) smart_move_backward(); break;
1225 case ' ': number = fz_maxi(number, 1); while (number--) smart_move_forward(); break;
1226 case ',': case KEY_PAGE_UP: currentpage -= fz_maxi(number, 1); break;
1227 case '.': case KEY_PAGE_DOWN: currentpage += fz_maxi(number, 1); break;
1228 case 'g': jump_to_page(number - 1); break;
1229 case 'G': jump_to_page(fz_count_pages(ctx, doc) - 1); break;
1230
1231 case 'A':
1232 if (number == 0)
1233 currentaa = (currentaa == 8 ? 0 : 8);
1234 else
1235 currentaa = number;
1236 break;
1237
1238 case 'm':
1239 if (number == 0)
1240 push_history();
1241 else if (number > 0 && number < nelem(marks))
1242 marks[number] = save_mark();
1243 break;
1244 case 't':
1245 if (number == 0)
1246 {
1247 if (history_count > 0)
1248 pop_history();
1249 }
1250 else if (number > 0 && number < nelem(marks))
1251 {
1252 struct mark mark = marks[number];
1253 restore_mark(mark);
1254 jump_to_page(mark.page);
1255 }
1256 break;
1257 case 'T':
1258 if (number == 0)
1259 {
1260 if (future_count > 0)
1261 pop_future();
1262 }
1263 break;
1264
1265 case '/':
1266 clear_search();
1267 search_dir = 1;
1268 showsearch = 1;
1269 ui.focus = &search_input;
1270 search_input.p = search_input.text;
1271 search_input.q = search_input.end;
1272 break;
1273 case '?':
1274 clear_search();
1275 search_dir = -1;
1276 showsearch = 1;
1277 ui.focus = &search_input;
1278 search_input.p = search_input.text;
1279 search_input.q = search_input.end;
1280 break;
1281 case 'N':
1282 search_dir = -1;
1283 if (search_hit_page == currentpage)
1284 search_page = currentpage + search_dir;
1285 else
1286 search_page = currentpage;
1287 if (search_page >= 0 && search_page < fz_count_pages(ctx, doc))
1288 {
1289 search_hit_page = -1;
1290 if (search_needle)
1291 search_active = 1;
1292 }
1293 break;
1294 case 'n':
1295 search_dir = 1;
1296 if (search_hit_page == currentpage)
1297 search_page = currentpage + search_dir;
1298 else
1299 search_page = currentpage;
1300 if (search_page >= 0 && search_page < fz_count_pages(ctx, doc))
1301 {
1302 search_hit_page = -1;
1303 if (search_needle)
1304 search_active = 1;
1305 }
1306 break;
1307 }
1308
1309 if (ui.key >= '0' && ui.key <= '9')
1310 number = number * 10 + ui.key - '0';
1311 else
1312 number = 0;
1313
1314 currentpage = fz_clampi(currentpage, 0, fz_count_pages(ctx, doc) - 1);
1315 while (currentrotate < 0) currentrotate += 360;
1316 while (currentrotate >= 360) currentrotate -= 360;
1317
1318 if (search_hit_page != currentpage)
1319 search_hit_page = -1; /* clear highlights when navigating */
1320
1321 ui.key = 0; /* we ate the key event, so zap it */
1322 }
1323}
1324
1325static void do_info(void)
1326{
1327 char buf[100];
1328
1329 ui_dialog_begin(500, 14 * ui.lineheight);
1330 ui_layout(T, X, W, 0, 0);
1331
1332 if (fz_lookup_metadata(ctx, doc, FZ_META_INFO_TITLE, buf, sizeof buf) > 0)
1333 ui_label("Title: %s", buf);
1334 if (fz_lookup_metadata(ctx, doc, FZ_META_INFO_AUTHOR, buf, sizeof buf) > 0)
1335 ui_label("Author: %s", buf);
1336 if (fz_lookup_metadata(ctx, doc, FZ_META_FORMAT, buf, sizeof buf) > 0)
1337 ui_label("Format: %s", buf);
1338 if (fz_lookup_metadata(ctx, doc, FZ_META_ENCRYPTION, buf, sizeof buf) > 0)
1339 ui_label("Encryption: %s", buf);
1340 if (pdf_specifics(ctx, doc))
1341 {
1342 if (fz_lookup_metadata(ctx, doc, "info:Creator", buf, sizeof buf) > 0)
1343 ui_label("PDF Creator: %s", buf);
1344 if (fz_lookup_metadata(ctx, doc, "info:Producer", buf, sizeof buf) > 0)
1345 ui_label("PDF Producer: %s", buf);
1346 buf[0] = 0;
1347 if (fz_has_permission(ctx, doc, FZ_PERMISSION_PRINT))
1348 fz_strlcat(buf, "print, ", sizeof buf);
1349 if (fz_has_permission(ctx, doc, FZ_PERMISSION_COPY))
1350 fz_strlcat(buf, "copy, ", sizeof buf);
1351 if (fz_has_permission(ctx, doc, FZ_PERMISSION_EDIT))
1352 fz_strlcat(buf, "edit, ", sizeof buf);
1353 if (fz_has_permission(ctx, doc, FZ_PERMISSION_ANNOTATE))
1354 fz_strlcat(buf, "annotate, ", sizeof buf);
1355 if (strlen(buf) > 2)
1356 buf[strlen(buf)-2] = 0;
1357 else
1358 fz_strlcat(buf, "none", sizeof buf);
1359 ui_label("Permissions: %s", buf);
1360 }
1361 ui_label("Page: %d / %d", currentpage + 1, fz_count_pages(ctx, doc));
1362 {
1363 int w = (int)(page_bounds.x1 - page_bounds.x0 + 0.5f);
1364 int h = (int)(page_bounds.y1 - page_bounds.y0 + 0.5f);
1365 const char *size = paper_size_name(w, h);
1366 if (!size)
1367 size = paper_size_name(h, w);
1368 if (size)
1369 ui_label("Size: %d x %d (%s)", w, h, size);
1370 else
1371 ui_label("Size: %d x %d", w, h);
1372 }
1373 ui_label("ICC rendering: %s.", currenticc ? "on" : "off");
1374 ui_label("Spot rendering: %s.", currentseparations ? "on" : "off");
1375
1376 ui_dialog_end();
1377}
1378
1379static void do_canvas(void)
1380{
1381 static int saved_scroll_x = 0;
1382 static int saved_scroll_y = 0;
1383 static int saved_ui_x = 0;
1384 static int saved_ui_y = 0;
1385 fz_irect area;
1386 int page_x, page_y;
1387
1388 tooltip = NULL;
1389
1390 ui_layout(ALL, BOTH, NW, 0, 0);
1391 ui_pack_push(area = ui_pack(0, 0));
1392 glScissor(area.x0, ui.window_h-area.y1, area.x1-area.x0, area.y1-area.y0);
1393 glEnable(GL_SCISSOR_TEST);
1394
1395 canvas_x = area.x0;
1396 canvas_y = area.y0;
1397 canvas_w = area.x1 - area.x0;
1398 canvas_h = area.y1 - area.y0;
1399
1400 if (ui_mouse_inside(area))
1401 {
1402 ui.hot = doc;
1403 if (!ui.active && ui.middle)
1404 {
1405 ui.active = doc;
1406 saved_scroll_x = scroll_x;
1407 saved_scroll_y = scroll_y;
1408 saved_ui_x = ui.x;
1409 saved_ui_y = ui.y;
1410 }
1411 }
1412
1413 if (ui.hot == doc)
1414 {
1415 if (ui.mod == 0)
1416 {
1417 scroll_x -= ui.scroll_x * ui.lineheight * 3;
1418 scroll_y -= ui.scroll_y * ui.lineheight * 3;
1419 }
1420 else if (ui.mod == GLUT_ACTIVE_CTRL)
1421 {
1422 if (ui.scroll_y > 0) set_zoom(zoom_in(currentzoom), ui.x, ui.y);
1423 if (ui.scroll_y < 0) set_zoom(zoom_out(currentzoom), ui.x, ui.y);
1424 }
1425 }
1426
1427 render_page_if_changed();
1428
1429 if (ui.active == doc)
1430 {
1431 scroll_x = saved_scroll_x + saved_ui_x - ui.x;
1432 scroll_y = saved_scroll_y + saved_ui_y - ui.y;
1433 }
1434
1435 if (page_tex.w <= canvas_w)
1436 {
1437 scroll_x = 0;
1438 page_x = canvas_x + (canvas_w - page_tex.w) / 2;
1439 }
1440 else
1441 {
1442 scroll_x = fz_clamp(scroll_x, 0, page_tex.w - canvas_w);
1443 page_x = canvas_x - scroll_x;
1444 }
1445
1446 if (page_tex.h <= canvas_h)
1447 {
1448 scroll_y = 0;
1449 page_y = canvas_y + (canvas_h - page_tex.h) / 2;
1450 }
1451 else
1452 {
1453 scroll_y = fz_clamp(scroll_y, 0, page_tex.h - canvas_h);
1454 page_y = canvas_y - scroll_y;
1455 }
1456
1457 view_page_ctm = draw_page_ctm;
1458 view_page_ctm.e += page_x;
1459 view_page_ctm.f += page_y;
1460 view_page_inv_ctm = fz_invert_matrix(view_page_ctm);
1461 view_page_bounds = fz_transform_rect(page_bounds, view_page_ctm);
1462 view_page_area = fz_irect_from_rect(view_page_bounds);
1463
1464 ui_draw_image(&page_tex, page_x, page_y);
1465
1466 if (search_active)
1467 {
1468 ui_layout(T, X, NW, 0, 0);
1469 ui_panel_begin(0, ui.gridsize+8, 4, 4, 1);
1470 ui_layout(L, NONE, W, 2, 0);
1471 ui_label("Searching page %d of %d.", search_page + 1, fz_count_pages(ctx, doc));
1472 ui_panel_end();
1473 }
1474 else
1475 {
1476 if (pdf)
1477 {
1478 do_annotate_canvas(area);
1479 do_widget_canvas(area);
1480 }
1481 do_links(links);
1482 do_page_selection();
1483
1484 if (search_hit_page == currentpage && search_hit_count > 0)
1485 do_search_hits();
1486 }
1487
1488 if (showsearch)
1489 {
1490 ui_layout(T, X, NW, 0, 0);
1491 ui_panel_begin(0, ui.gridsize+8, 4, 4, 1);
1492 ui_layout(L, NONE, W, 2, 0);
1493 ui_label("Search:");
1494 ui_layout(ALL, X, E, 2, 0);
1495 if (ui_input(&search_input, 0, 1) == UI_INPUT_ACCEPT)
1496 {
1497 showsearch = 0;
1498 search_page = -1;
1499 if (search_needle)
1500 {
1501 fz_free(ctx, search_needle);
1502 search_needle = NULL;
1503 }
1504 if (search_input.end > search_input.text)
1505 {
1506 search_needle = fz_strdup(ctx, search_input.text);
1507 search_active = 1;
1508 search_page = currentpage;
1509 }
1510 }
1511 if (ui.focus != &search_input)
1512 showsearch = 0;
1513 ui_panel_end();
1514 }
1515
1516 if (tooltip)
1517 {
1518 ui_layout(B, X, N, 0, 0);
1519 ui_panel_begin(0, ui.gridsize, 4, 4, 1);
1520 ui_layout(L, NONE, W, 2, 0);
1521 ui_label("%s", tooltip);
1522 ui_panel_end();
1523 }
1524
1525 ui_pack_pop();
1526 glDisable(GL_SCISSOR_TEST);
1527}
1528
1529void do_main(void)
1530{
1531 if (search_active)
1532 {
1533 int start_time = glutGet(GLUT_ELAPSED_TIME);
1534
1535 if (ui.key == KEY_ESCAPE)
1536 search_active = 0;
1537
1538 /* ignore events during search */
1539 ui.key = ui.mod = ui.plain = 0;
1540 ui.down = ui.middle = ui.right = 0;
1541
1542 while (glutGet(GLUT_ELAPSED_TIME) < start_time + 200)
1543 {
1544 search_hit_count = fz_search_page_number(ctx, doc, search_page, search_needle,
1545 search_hit_quads, nelem(search_hit_quads));
1546 if (search_hit_count)
1547 {
1548 search_active = 0;
1549 search_hit_page = search_page;
1550 jump_to_page(search_hit_page);
1551 break;
1552 }
1553 else
1554 {
1555 search_page += search_dir;
1556 if (search_page < 0 || search_page == fz_count_pages(ctx, doc))
1557 {
1558 search_active = 0;
1559 break;
1560 }
1561 }
1562 }
1563
1564 /* keep searching later */
1565 if (search_active)
1566 glutPostRedisplay();
1567 }
1568
1569 do_app();
1570
1571 if (showoutline)
1572 do_outline(outline);
1573
1574 if (oldpage != currentpage || oldseparations != currentseparations || oldicc != currenticc)
1575 {
1576 load_page();
1577 update_title();
1578 }
1579
1580 if (showannotate)
1581 {
1582 ui_layout(R, BOTH, NW, 0, 0);
1583 ui_panel_begin(annotate_w, 0, 4, 4, 1);
1584 do_annotate_panel();
1585 ui_panel_end();
1586 }
1587
1588 do_canvas();
1589
1590 if (showinfo)
1591 do_info();
1592}
1593
1594void run_main_loop(void)
1595{
1596 if (currentinvert)
1597 glClearColor(0, 0, 0, 1);
1598 else
1599 glClearColor(0.3f, 0.3f, 0.3f, 1);
1600 ui_begin();
1601 fz_try(ctx)
1602 {
1603 if (ui.dialog)
1604 ui.dialog();
1605 else
1606 do_main();
1607 }
1608 fz_catch(ctx)
1609 ui_show_error_dialog("%s", fz_caught_message(ctx));
1610 ui_end();
1611}
1612
1613static void usage(const char *argv0)
1614{
1615 fprintf(stderr, "mupdf-gl version %s\n", FZ_VERSION);
1616 fprintf(stderr, "usage: %s [options] document [page]\n", argv0);
1617 fprintf(stderr, "\t-p -\tpassword\n");
1618 fprintf(stderr, "\t-r -\tresolution\n");
1619 fprintf(stderr, "\t-I\tinvert colors\n");
1620 fprintf(stderr, "\t-W -\tpage width for EPUB layout\n");
1621 fprintf(stderr, "\t-H -\tpage height for EPUB layout\n");
1622 fprintf(stderr, "\t-S -\tfont size for EPUB layout\n");
1623 fprintf(stderr, "\t-U -\tuser style sheet for EPUB layout\n");
1624 fprintf(stderr, "\t-X\tdisable document styles for EPUB layout\n");
1625 fprintf(stderr, "\t-J\tdisable javascript in PDF forms\n");
1626 fprintf(stderr, "\t-A -\tset anti-aliasing level (0-8,9,10)\n");
1627 fprintf(stderr, "\t-B -\tset black tint color (default: 303030)\n");
1628 fprintf(stderr, "\t-C -\tset white tint color (default: FFFFF0)\n");
1629 exit(1);
1630}
1631
1632static int document_filter(const char *filename)
1633{
1634 return !!fz_recognize_document(ctx, filename);
1635}
1636
1637static void do_open_document_dialog(void)
1638{
1639 if (ui_open_file(filename))
1640 {
1641 ui.dialog = NULL;
1642 if (filename[0] == 0)
1643 glutLeaveMainLoop();
1644 else
1645 load_document();
1646 if (doc)
1647 {
1648 load_page();
1649 render_page();
1650 shrinkwrap();
1651 update_title();
1652 }
1653 }
1654}
1655
1656static void cleanup(void)
1657{
1658 save_history();
1659
1660 ui_finish();
1661
1662#ifndef NDEBUG
1663 if (fz_atoi(getenv("FZ_DEBUG_STORE")))
1664 fz_debug_store(ctx);
1665#endif
1666
1667 fz_drop_stext_page(ctx, page_text);
1668 fz_drop_separations(ctx, seps);
1669 fz_drop_link(ctx, links);
1670 fz_drop_page(ctx, fzpage);
1671 fz_drop_outline(ctx, outline);
1672 fz_drop_document(ctx, doc);
1673 fz_drop_context(ctx);
1674}
1675
1676int reloadrequested = 0;
1677
1678#ifndef _WIN32
1679static void signal_handler(int signal)
1680{
1681 if (signal == SIGHUP)
1682 reloadrequested = 1;
1683}
1684#endif
1685
1686#ifdef _MSC_VER
1687int main_utf8(int argc, char **argv)
1688#else
1689int main(int argc, char **argv)
1690#endif
1691{
1692 int c;
1693
1694#ifndef _WIN32
1695 signal(SIGHUP, signal_handler);
1696#endif
1697
1698 glutInit(&argc, argv);
1699
1700 screen_w = glutGet(GLUT_SCREEN_WIDTH) - SCREEN_FURNITURE_W;
1701 screen_h = glutGet(GLUT_SCREEN_HEIGHT) - SCREEN_FURNITURE_H;
1702
1703 while ((c = fz_getopt(argc, argv, "p:r:IW:H:S:U:XJA:B:C:")) != -1)
1704 {
1705 switch (c)
1706 {
1707 default: usage(argv[0]); break;
1708 case 'p': password = fz_optarg; break;
1709 case 'r': currentzoom = fz_atof(fz_optarg); break;
1710 case 'I': currentinvert = !currentinvert; break;
1711 case 'W': layout_w = fz_atof(fz_optarg); break;
1712 case 'H': layout_h = fz_atof(fz_optarg); break;
1713 case 'S': layout_em = fz_atof(fz_optarg); break;
1714 case 'U': layout_css = fz_optarg; break;
1715 case 'X': layout_use_doc_css = 0; break;
1716 case 'J': enable_js = !enable_js; break;
1717 case 'A': currentaa = fz_atoi(fz_optarg); break;
1718 case 'C': currenttint = 1; tint_white = strtol(fz_optarg, NULL, 16); break;
1719 case 'B': currenttint = 1; tint_black = strtol(fz_optarg, NULL, 16); break;
1720 }
1721 }
1722
1723 ctx = fz_new_context(NULL, NULL, 0);
1724 fz_register_document_handlers(ctx);
1725 if (layout_css)
1726 {
1727 fz_buffer *buf = fz_read_file(ctx, layout_css);
1728 fz_set_user_css(ctx, fz_string_from_buffer(ctx, buf));
1729 fz_drop_buffer(ctx, buf);
1730 }
1731 fz_set_use_document_css(ctx, layout_use_doc_css);
1732
1733 if (fz_optind < argc)
1734 {
1735 fz_strlcpy(filename, argv[fz_optind++], sizeof filename);
1736 if (fz_optind < argc)
1737 anchor = argv[fz_optind++];
1738
1739 fz_try(ctx)
1740 {
1741 page_tex.w = 600;
1742 page_tex.h = 700;
1743 load_document();
1744 if (doc) load_page();
1745 }
1746 fz_always(ctx)
1747 {
1748 float sx = 1, sy = 1;
1749 if (screen_w > 0 && page_tex.w > screen_w)
1750 sx = (float)screen_w / page_tex.w;
1751 if (screen_h > 0 && page_tex.h > screen_h)
1752 sy = (float)screen_h / page_tex.h;
1753 if (sy < sx)
1754 sx = sy;
1755 if (sx < 1)
1756 {
1757 fz_irect area;
1758
1759 currentzoom *= sx;
1760 oldzoom = currentzoom;
1761
1762 /* compute bounds here for initial window size */
1763 page_bounds = fz_bound_page(ctx, fzpage);
1764 transform_page();
1765
1766 area = fz_irect_from_rect(draw_page_bounds);
1767 page_tex.w = area.x1 - area.x0;
1768 page_tex.h = area.y1 - area.y0;
1769 }
1770
1771 ui_init(page_tex.w, page_tex.h, "MuPDF: Loading...");
1772 ui_input_init(&search_input, "");
1773 }
1774 fz_catch(ctx)
1775 {
1776 ui_show_error_dialog("%s", fz_caught_message(ctx));
1777 }
1778
1779 fz_try(ctx)
1780 {
1781 if (doc)
1782 {
1783 render_page();
1784 update_title();
1785 }
1786 }
1787 fz_catch(ctx)
1788 {
1789 ui_show_error_dialog("%s", fz_caught_message(ctx));
1790 }
1791 }
1792 else
1793 {
1794#ifdef _WIN32
1795 win_install();
1796#endif
1797 ui_init(640, 700, "MuPDF: Open document");
1798 ui_input_init(&search_input, "");
1799 ui_init_open_file(".", document_filter);
1800 ui.dialog = do_open_document_dialog;
1801 }
1802
1803 annotate_w *= ui.lineheight;
1804 outline_w *= ui.lineheight;
1805
1806 glutMainLoop();
1807
1808 cleanup();
1809
1810 return 0;
1811}
1812
1813#ifdef _MSC_VER
1814int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
1815{
1816 int argc;
1817 LPWSTR *wargv = CommandLineToArgvW(GetCommandLineW(), &argc);
1818 char **argv = fz_argv_from_wargv(argc, wargv);
1819 int ret = main_utf8(argc, argv);
1820 fz_free_argv(argc, argv);
1821 return ret;
1822}
1823#endif
1824