comparison mupdf-source/platform/x11/pdfapp.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 "pdfapp.h"
24 #include "curl_stream.h"
25 #include "mupdf/helpers/pkcs7-openssl.h"
26
27 #include <string.h>
28 #include <limits.h>
29 #include <stdlib.h>
30 #include <stdio.h>
31
32 #ifdef _WIN32
33 #include <windows.h>
34 #include <direct.h> /* for getcwd */
35 #else
36 #include <unistd.h> /* for getcwd */
37 #include <sys/stat.h> /* for mkdir */
38 #endif
39
40 #define BEYOND_THRESHHOLD 40
41
42 #ifndef MAX
43 #define MAX(a,b) ((a) > (b) ? (a) : (b))
44 #endif
45
46 static int create_accel_path(fz_context *ctx, char outname[], size_t len, int create, const char *absname, ...)
47 {
48 va_list args;
49 char *s = outname;
50 size_t z, remain = len;
51 char *arg;
52
53 va_start(args, absname);
54
55 while ((arg = va_arg(args, char *)) != NULL)
56 {
57 z = fz_snprintf(s, remain, "%s", arg);
58 if (z+1 > remain)
59 goto fail; /* won't fit */
60
61 if (create)
62 (void) fz_mkdir(outname);
63 if (!fz_is_directory(ctx, outname))
64 goto fail; /* directory creation failed, or that dir doesn't exist! */
65 #ifdef _WIN32
66 s[z] = '\\';
67 #else
68 s[z] = '/';
69 #endif
70 s[z+1] = 0;
71 s += z+1;
72 remain -= z+1;
73 }
74
75 if (fz_snprintf(s, remain, "%s.accel", absname) >= remain)
76 goto fail; /* won't fit */
77
78 va_end(args);
79
80 return 1;
81
82 fail:
83 va_end(args);
84
85 return 0;
86 }
87
88 static int convert_to_accel_path(fz_context *ctx, char outname[], char *absname, size_t len, int create)
89 {
90 char *tmpdir;
91 char *s;
92
93 if (absname[0] == '/' || absname[0] == '\\')
94 ++absname;
95
96 s = absname;
97 while (*s) {
98 if (*s == '/' || *s == '\\' || *s == ':')
99 *s = '%';
100 ++s;
101 }
102
103 #ifdef _WIN32
104 tmpdir = getenv("USERPROFILE");
105 if (tmpdir && create_accel_path(ctx, outname, len, create, absname, tmpdir, ".config", "mupdf", NULL))
106 return 1; /* OK! */
107 /* TEMP and TMP are user-specific on modern windows. */
108 tmpdir = getenv("TEMP");
109 if (tmpdir && create_accel_path(ctx, outname, len, create, absname, tmpdir, "mupdf", NULL))
110 return 1; /* OK! */
111 tmpdir = getenv("TMP");
112 if (tmpdir && create_accel_path(ctx, outname, len, create, absname, tmpdir, "mupdf", NULL))
113 return 1; /* OK! */
114 #else
115 tmpdir = getenv("XDG_CACHE_HOME");
116 if (tmpdir && create_accel_path(ctx, outname, len, create, absname, tmpdir, "mupdf", NULL))
117 return 1; /* OK! */
118 tmpdir = getenv("HOME");
119 if (tmpdir && create_accel_path(ctx, outname, len, create, absname, tmpdir, ".cache", "mupdf", NULL))
120 return 1; /* OK! */
121 #endif
122 return 0; /* Fail */
123 }
124
125 static int get_accelerator_filename(fz_context *ctx, char outname[], size_t len, const char *filename, int create)
126 {
127 char absname[PATH_MAX];
128 if (!fz_realpath(filename, absname))
129 return 0;
130 if (!convert_to_accel_path(ctx, outname, absname, len, create))
131 return 0;
132 return 1;
133 }
134
135 static void save_accelerator(fz_context *ctx, fz_document *doc, const char *filename)
136 {
137 char absname[PATH_MAX];
138
139 if (!doc)
140 return;
141 if (!fz_document_supports_accelerator(ctx, doc))
142 return;
143 if (!get_accelerator_filename(ctx, absname, sizeof(absname), filename, 1))
144 return;
145
146 fz_save_accelerator(ctx, doc, absname);
147 }
148
149 enum panning
150 {
151 DONT_PAN = 0,
152 PAN_TO_TOP,
153 PAN_TO_BOTTOM
154 };
155
156 enum
157 {
158 PDFAPP_OUTLINE_DEFERRED = 1,
159 PDFAPP_OUTLINE_LOAD_NOW = 2
160 };
161
162 #ifdef HAVE_CURL
163 static void pdfapp_sleep(int ms)
164 {
165 #ifdef _WIN32
166 Sleep(ms);
167 #else
168 usleep(ms * 1000);
169 #endif
170 }
171 #endif
172
173 static void pdfapp_showpage(pdfapp_t *app, int loadpage, int drawpage, int repaint, int transition, int searching);
174
175 static const int zoomlist[] = {
176 18, 24, 36, 54, 72, 96, 120, 144, 180,
177 216, 288, 360, 432, 504, 576, 648, 720,
178 792, 864, 936, 1008, 1080, 1152
179 };
180
181 static int zoom_in(int oldres)
182 {
183 int i;
184 for (i = 0; i < (int)nelem(zoomlist) - 1; ++i)
185 if (zoomlist[i] <= oldres && zoomlist[i+1] > oldres)
186 return zoomlist[i+1];
187 return zoomlist[i];
188 }
189
190 static int zoom_out(int oldres)
191 {
192 int i;
193 for (i = 0; i < (int)nelem(zoomlist) - 1; ++i)
194 if (zoomlist[i] < oldres && zoomlist[i+1] >= oldres)
195 return zoomlist[i];
196 return zoomlist[0];
197 }
198
199 void pdfapp_warn(pdfapp_t *app, const char *fmt, ...)
200 {
201 char buf[1024];
202 va_list ap;
203 va_start(ap, fmt);
204 fz_vsnprintf(buf, sizeof(buf), fmt, ap);
205 va_end(ap);
206 buf[sizeof(buf)-1] = 0;
207 winwarn(app, buf);
208 }
209
210 void pdfapp_error(pdfapp_t *app, char *msg)
211 {
212 winerror(app, msg);
213 }
214
215 char *pdfapp_version(pdfapp_t *app)
216 {
217 return
218 "MuPDF " FZ_VERSION "\n"
219 "Copyright 2006-2022 Artifex Software, Inc.\n";
220 }
221
222 char *pdfapp_usage(pdfapp_t *app)
223 {
224 return
225 "[\t\t-- rotate left\n"
226 "]\t\t-- rotate right\n"
227 "h left\t\t-- scroll left\n"
228 "j down\t\t-- scroll down\n"
229 "k up\t\t-- scroll up\n"
230 "l right\t\t-- scroll right\n"
231 "+\t\t-- zoom in\n"
232 "-\t\t-- zoom out\n"
233 "W\t\t-- zoom to fit window width\n"
234 "H\t\t-- zoom to fit window height\n"
235 "Z\t\t-- zoom to fit page\n"
236 "z\t\t-- reset zoom\n"
237 "<\t\t-- decrease font size (EPUB only)\n"
238 ">\t\t-- increase font size (EPUB only)\n"
239 "w\t\t-- shrinkwrap\n"
240 "f\t\t-- fullscreen\n"
241 "r\t\t-- reload file\n"
242 ". pgdn space\t-- next page\n"
243 ", pgup b\t-- previous page\n"
244 "m\t\t-- mark page for snap back\n"
245 "t\t\t-- pop back to latest mark\n"
246 "1m\t\t-- mark page in register 1\n"
247 "1t\t\t-- go to page in register 1\n"
248 "G\t\t-- go to last page\n"
249 "123g\t\t-- go to page 123\n"
250 "/\t\t-- search forwards for text\n"
251 "?\t\t-- search backwards for text\n"
252 "n\t\t-- find next search result\n"
253 "N\t\t-- find previous search result\n"
254 "c\t\t-- toggle between color and grayscale\n"
255 "I\t\t-- toggle inverted color mode\n"
256 "C\t\t-- toggle tinted color mode\n"
257 "E\t\t-- enable/disable ICC color mode\n"
258 "e\t\t-- enable/disable spot color mode\n"
259 "q\t\t-- quit\n"
260 ;
261 }
262
263 void pdfapp_init(fz_context *ctx, pdfapp_t *app)
264 {
265 memset(app, 0, sizeof(pdfapp_t));
266 app->scrw = 640;
267 app->scrh = 480;
268 app->resolution = 72;
269 app->ctx = ctx;
270
271 app->layout_w = FZ_DEFAULT_LAYOUT_W;
272 app->layout_h = FZ_DEFAULT_LAYOUT_H;
273 app->layout_em = FZ_DEFAULT_LAYOUT_EM;
274 app->layout_css = NULL;
275 app->layout_use_doc_css = 1;
276
277 app->transition.duration = 0.25f;
278 app->transition.type = FZ_TRANSITION_FADE;
279 #ifdef _WIN32
280 app->colorspace = fz_device_bgr(ctx);
281 #else
282 app->colorspace = fz_device_rgb(ctx);
283 #endif
284 app->tint_white = 0xFFFAF0;
285
286 app->useicc = 1;
287 app->useseparations = 0;
288 app->aalevel = 8;
289 }
290
291 void pdfapp_setresolution(pdfapp_t *app, int res)
292 {
293 app->default_resolution = res;
294 app->resolution = res;
295 }
296
297 void pdfapp_invert(pdfapp_t *app, fz_rect rect)
298 {
299 fz_invert_pixmap_rect(app->ctx, app->image, fz_round_rect(rect));
300 }
301
302 void pdfapp_reloadfile(pdfapp_t *app)
303 {
304 char filename[PATH_MAX];
305 fz_strlcpy(filename, app->docpath, PATH_MAX);
306 pdfapp_close(app);
307 pdfapp_open(app, filename, 1);
308 }
309
310 static void event_cb(fz_context *ctx, pdf_document *doc, pdf_doc_event *evt, void *data)
311 {
312 pdfapp_t *app = (pdfapp_t *)data;
313
314 switch (evt->type)
315 {
316 case PDF_DOCUMENT_EVENT_ALERT:
317 {
318 pdf_alert_event *alert = pdf_access_alert_event(ctx, evt);
319 winalert(app, alert);
320 }
321 break;
322
323 case PDF_DOCUMENT_EVENT_PRINT:
324 winprint(app);
325 break;
326
327 case PDF_DOCUMENT_EVENT_EXEC_MENU_ITEM:
328 {
329 const char *item = pdf_access_exec_menu_item_event(ctx, evt);
330
331 if (!strcmp(item, "Print"))
332 winprint(app);
333 else
334 pdfapp_warn(app, "The document attempted to execute menu item: %s. (Not supported)", item);
335 }
336 break;
337
338 case PDF_DOCUMENT_EVENT_LAUNCH_URL:
339 {
340 pdf_launch_url_event *launch_url = pdf_access_launch_url_event(ctx, evt);
341
342 pdfapp_warn(app, "The document attempted to open url: %s. (Not supported by app)", launch_url->url);
343 }
344 break;
345
346 case PDF_DOCUMENT_EVENT_MAIL_DOC:
347 {
348 pdf_mail_doc_event *mail_doc = pdf_access_mail_doc_event(ctx, evt);
349
350 pdfapp_warn(app, "The document attempted to mail the document%s%s%s%s%s%s%s%s (Not supported)",
351 mail_doc->to[0]?", To: ":"", mail_doc->to,
352 mail_doc->cc[0]?", Cc: ":"", mail_doc->cc,
353 mail_doc->bcc[0]?", Bcc: ":"", mail_doc->bcc,
354 mail_doc->subject[0]?", Subject: ":"", mail_doc->subject);
355 }
356 break;
357 }
358 }
359
360 void pdfapp_open(pdfapp_t *app, char *filename, int reload)
361 {
362 pdfapp_open_progressive(app, filename, reload, 0);
363 }
364
365 #ifdef HAVE_CURL
366 static void
367 pdfapp_more_data(void *app_, int complete)
368 {
369 pdfapp_t *app = (pdfapp_t *)app_;
370 if (complete && app->outline_deferred == PDFAPP_OUTLINE_DEFERRED)
371 {
372 app->outline_deferred = PDFAPP_OUTLINE_LOAD_NOW;
373 winreloadpage(app);
374 }
375 else if (app->incomplete)
376 winreloadpage(app);
377 }
378 #endif
379
380 static int make_fake_doc(pdfapp_t *app)
381 {
382 fz_context *ctx = app->ctx;
383 pdf_document *pdf = NULL;
384 fz_buffer *contents = NULL;
385 pdf_obj *page_obj = NULL;
386
387 fz_var(contents);
388 fz_var(page_obj);
389
390 fz_try(ctx)
391 {
392 fz_rect mediabox = { 0, 0, app->winw, app->winh };
393 int i;
394
395 pdf = pdf_create_document(ctx);
396
397 contents = fz_new_buffer(ctx, 100);
398 fz_append_printf(ctx, contents, "1 0 0 RG %g w 0 0 m %g %g l 0 %g m %g 0 l s\n",
399 fz_min(mediabox.x1, mediabox.y1) / 20,
400 mediabox.x1, mediabox.y1,
401 mediabox.y1, mediabox.x1);
402
403 /* Create enough copies of our blank(ish) page so that the
404 * page number is preserved if and when a subsequent load
405 * works. */
406 page_obj = pdf_add_page(ctx, pdf, mediabox, 0, NULL, contents);
407 for (i = 0; i < app->pagecount; i++)
408 pdf_insert_page(ctx, pdf, -1, page_obj);
409 }
410 fz_always(ctx)
411 {
412 pdf_drop_obj(ctx, page_obj);
413 fz_drop_buffer(ctx, contents);
414 }
415 fz_catch(ctx)
416 {
417 fz_report_error(ctx);
418 fz_drop_document(ctx, (fz_document *) pdf);
419 return 1;
420 }
421
422 app->doc = (fz_document*)pdf;
423 return 0;
424 }
425
426 void pdfapp_open_progressive(pdfapp_t *app, char *filename, int reload, int kbps)
427 {
428 fz_context *ctx = app->ctx;
429 char *password = "";
430 pdf_document *idoc;
431
432 fz_try(ctx)
433 {
434 fz_register_document_handlers(ctx);
435
436 if (app->layout_css)
437 fz_load_user_css(ctx, app->layout_css);
438
439 fz_set_use_document_css(ctx, app->layout_use_doc_css);
440
441 #ifdef HAVE_CURL
442 if (!strncmp(filename, "http://", 7) || !strncmp(filename, "https://", 8))
443 {
444 app->stream = fz_open_url(ctx, filename, kbps, pdfapp_more_data, app);
445 while (1)
446 {
447 fz_try(ctx)
448 {
449 fz_seek(ctx, app->stream, 0, SEEK_SET);
450 app->doc = fz_open_document_with_stream(ctx, filename, app->stream);
451 }
452 fz_catch(ctx)
453 {
454 if (fz_caught(ctx) == FZ_ERROR_TRYLATER)
455 {
456 fz_ignore_error(ctx);
457 pdfapp_sleep(100);
458 continue;
459 }
460 fz_rethrow(ctx);
461 }
462 break;
463 }
464 }
465 else if (kbps > 0)
466 {
467 app->stream = fz_open_file_progressive(ctx, filename, kbps, pdfapp_more_data, app);
468 while (1)
469 {
470 fz_try(ctx)
471 {
472 fz_seek(ctx, app->stream, 0, SEEK_SET);
473 app->doc = fz_open_document_with_stream(ctx, filename, app->stream);
474 }
475 fz_catch(ctx)
476 {
477 if (fz_caught(ctx) == FZ_ERROR_TRYLATER)
478 {
479 fz_ignore_error(ctx);
480 pdfapp_sleep(100);
481 continue;
482 }
483 fz_rethrow(ctx);
484 }
485 break;
486 }
487 }
488 else
489 #endif
490 {
491 char accelpath[PATH_MAX];
492 char *accel = NULL;
493 time_t atime;
494 time_t dtime;
495
496 /* If there was an accelerator to load, what would it be called? */
497 if (get_accelerator_filename(ctx, accelpath, sizeof(accelpath), filename, 0))
498 {
499 /* Check whether that file exists, and isn't older than
500 * the document. */
501 atime = fz_stat_mtime(accelpath);
502 dtime = fz_stat_mtime(filename);
503 if (atime == 0)
504 {
505 /* No accelerator */
506 }
507 else if (atime > dtime)
508 accel = accelpath;
509 else
510 {
511 /* Accelerator data is out of date */
512 #ifdef _WIN32
513 fz_remove_utf8(accelpath);
514 #else
515 remove(accelpath);
516 #endif
517 accel = NULL; /* In case we have jumped up from below */
518 }
519 }
520
521 app->doc = fz_open_accelerated_document(ctx, filename, accel);
522 }
523 }
524 fz_catch(ctx)
525 {
526 fz_report_error(ctx);
527 if (!reload || make_fake_doc(app))
528 {
529 fz_report_error(ctx);
530 pdfapp_error(app, "cannot open document");
531 }
532 }
533
534 idoc = pdf_specifics(app->ctx, app->doc);
535 if (idoc)
536 {
537 fz_try(ctx)
538 {
539 pdf_enable_js(ctx, idoc);
540 pdf_set_doc_event_callback(ctx, idoc, event_cb, NULL, app);
541 }
542 fz_catch(ctx)
543 {
544 fz_report_error(ctx);
545 pdfapp_error(app, "cannot load javascript embedded in document");
546 }
547 }
548
549 fz_try(ctx)
550 {
551
552 if (fz_needs_password(app->ctx, app->doc))
553 {
554 int okay = fz_authenticate_password(app->ctx, app->doc, password);
555 while (!okay)
556 {
557 password = winpassword(app, filename);
558 if (!password)
559 fz_throw(ctx, FZ_ERROR_GENERIC, "Needs a password");
560 okay = fz_authenticate_password(app->ctx, app->doc, password);
561 if (!okay)
562 pdfapp_warn(app, "Invalid password.");
563 }
564 }
565
566 app->docpath = fz_strdup(ctx, filename);
567 app->doctitle = fz_strdup(ctx, fz_basename(filename));
568
569 fz_layout_document(app->ctx, app->doc, app->layout_w, app->layout_h, app->layout_em);
570
571 while (1)
572 {
573 fz_try(ctx)
574 {
575 app->pagecount = fz_count_pages(app->ctx, app->doc);
576 if (app->pagecount <= 0)
577 fz_throw(ctx, FZ_ERROR_GENERIC, "No pages in document");
578 }
579 fz_catch(ctx)
580 {
581 if (fz_caught(ctx) == FZ_ERROR_TRYLATER)
582 {
583 fz_ignore_error(ctx);
584 continue;
585 }
586 fz_rethrow(ctx);
587 }
588 break;
589 }
590 while (1)
591 {
592 fz_try(ctx)
593 {
594 app->outline = fz_load_outline(app->ctx, app->doc);
595 }
596 fz_catch(ctx)
597 {
598 app->outline = NULL;
599 if (fz_caught(ctx) == FZ_ERROR_TRYLATER)
600 {
601 fz_ignore_error(ctx);
602 app->outline_deferred = PDFAPP_OUTLINE_DEFERRED;
603 }
604 else
605 {
606 fz_report_error(ctx);
607 pdfapp_warn(app, "Failed to load outline.");
608 }
609 }
610 break;
611 }
612 }
613 fz_catch(ctx)
614 {
615 fz_report_error(ctx);
616 pdfapp_error(app, "cannot open document");
617 }
618
619 if (app->pageno < 1)
620 app->pageno = 1;
621 if (app->pageno > app->pagecount)
622 app->pageno = app->pagecount;
623 if (app->resolution < MINRES)
624 app->resolution = MINRES;
625 if (app->resolution > MAXRES)
626 app->resolution = MAXRES;
627
628 if (!reload)
629 {
630 app->shrinkwrap = 1;
631 app->rotate = 0;
632 app->panx = 0;
633 app->pany = 0;
634 }
635
636 pdfapp_showpage(app, 1, 1, 1, 0, 0);
637 }
638
639 void pdfapp_close(pdfapp_t *app)
640 {
641 fz_drop_display_list(app->ctx, app->page_list);
642 app->page_list = NULL;
643
644 fz_drop_display_list(app->ctx, app->annotations_list);
645 app->annotations_list = NULL;
646
647 fz_drop_separations(app->ctx, app->seps);
648 app->seps = NULL;
649
650 fz_drop_stext_page(app->ctx, app->page_text);
651 app->page_text = NULL;
652
653 fz_drop_link(app->ctx, app->page_links);
654 app->page_links = NULL;
655
656 fz_free(app->ctx, app->doctitle);
657 app->doctitle = NULL;
658
659 fz_free(app->ctx, app->docpath);
660 app->docpath = NULL;
661
662 fz_drop_pixmap(app->ctx, app->image);
663 app->image = NULL;
664
665 fz_drop_pixmap(app->ctx, app->new_image);
666 app->new_image = NULL;
667
668 fz_drop_pixmap(app->ctx, app->old_image);
669 app->old_image = NULL;
670
671 fz_drop_outline(app->ctx, app->outline);
672 app->outline = NULL;
673
674 fz_drop_page(app->ctx, app->page);
675 app->page = NULL;
676
677 fz_drop_document(app->ctx, app->doc);
678 app->doc = NULL;
679
680 #ifdef HAVE_CURL
681 fz_drop_stream(app->ctx, app->stream);
682 #endif
683
684 fz_flush_warnings(app->ctx);
685 }
686
687 static int gen_tmp_file(char *buf, int len)
688 {
689 int i;
690 char *name = strrchr(buf, '/');
691
692 if (name == NULL)
693 name = strrchr(buf, '\\');
694
695 if (name != NULL)
696 name++;
697 else
698 name = buf;
699
700 for (i = 0; i < 10000; i++)
701 {
702 FILE *f;
703 sprintf(name, "tmp%04d", i);
704 f = fopen(buf, "r");
705 if (f == NULL)
706 return 1;
707 fclose(f);
708 }
709
710 return 0;
711 }
712
713 static int pdfapp_save(pdfapp_t *app)
714 {
715 char buf[PATH_MAX];
716
717 pdf_document *idoc = pdf_specifics(app->ctx, app->doc);
718 if (!idoc)
719 return 0;
720
721 if (wingetsavepath(app, buf, PATH_MAX))
722 {
723 pdf_write_options opts = pdf_default_write_options;
724
725 opts.do_incremental = pdf_can_be_saved_incrementally(app->ctx, idoc);
726
727 if (strcmp(buf, app->docpath) != 0)
728 {
729 wincopyfile(app, app->docpath, buf);
730 pdf_save_document(app->ctx, idoc, buf, &opts);
731 pdfapp_close(app);
732 pdfapp_open(app, buf, 1);
733 return 1;
734 }
735
736 if (gen_tmp_file(buf, PATH_MAX))
737 {
738 int written = 0;
739
740 fz_try(app->ctx)
741 {
742 wincopyfile(app, app->docpath, buf);
743 pdf_save_document(app->ctx, idoc, buf, &opts);
744 written = 1;
745 }
746 fz_catch(app->ctx)
747 {
748 fz_report_error(app->ctx);
749 /* Ignore any error, so we drop out with
750 * failure below. */
751 }
752
753 if (written)
754 {
755 char buf2[PATH_MAX];
756 fz_strlcpy(buf2, app->docpath, PATH_MAX);
757 pdfapp_close(app);
758 winreplacefile(app, buf, buf2);
759 pdfapp_open(app, buf2, 1);
760
761 return written;
762 }
763 }
764 }
765
766 return 0;
767 }
768
769 int pdfapp_preclose(pdfapp_t *app)
770 {
771 pdf_document *idoc = pdf_specifics(app->ctx, app->doc);
772
773 if (idoc && pdf_has_unsaved_changes(app->ctx, idoc))
774 {
775 switch (winsavequery(app))
776 {
777 case DISCARD:
778 return 1;
779
780 case CANCEL:
781 return 0;
782
783 case SAVE:
784 return pdfapp_save(app);
785 }
786 }
787
788 return 1;
789 }
790
791 static fz_matrix pdfapp_viewctm(pdfapp_t *app)
792 {
793 return fz_transform_page(app->page_bbox, app->resolution, app->rotate);
794 }
795
796 static void pdfapp_panview(pdfapp_t *app, int newx, int newy)
797 {
798 if (newx > 0)
799 newx = 0;
800 if (newy > 0)
801 newy = 0;
802
803 if (newx + app->imgw < app->winw)
804 newx = app->winw - app->imgw;
805 if (newy + app->imgh < app->winh)
806 newy = app->winh - app->imgh;
807
808 if (app->winw >= app->imgw)
809 newx = (app->winw - app->imgw) / 2;
810 if (app->winh >= app->imgh)
811 newy = (app->winh - app->imgh) / 2;
812
813 if (newx != app->panx || newy != app->pany)
814 winrepaint(app);
815
816 app->panx = newx;
817 app->pany = newy;
818 }
819
820 static void pdfapp_loadpage(pdfapp_t *app, int no_cache)
821 {
822 fz_device *mdev = NULL;
823 int errored = 0;
824 fz_cookie cookie = { 0 };
825
826 fz_var(mdev);
827
828 fz_drop_display_list(app->ctx, app->page_list);
829 fz_drop_display_list(app->ctx, app->annotations_list);
830 fz_drop_separations(app->ctx, app->seps);
831 fz_drop_stext_page(app->ctx, app->page_text);
832 fz_drop_link(app->ctx, app->page_links);
833 fz_drop_page(app->ctx, app->page);
834
835 app->page_list = NULL;
836 app->annotations_list = NULL;
837 app->seps = NULL;
838 app->page_text = NULL;
839 app->page_links = NULL;
840 app->page = NULL;
841 app->page_bbox.x0 = 0;
842 app->page_bbox.y0 = 0;
843 app->page_bbox.x1 = 100;
844 app->page_bbox.y1 = 100;
845
846 app->incomplete = 0;
847
848 fz_try(app->ctx)
849 {
850 app->page = fz_load_page(app->ctx, app->doc, app->pageno - 1);
851 if (app->page && app->page->incomplete)
852 app->incomplete = 1;
853 app->page_bbox = fz_bound_page(app->ctx, app->page);
854 app->page_links = fz_load_links(app->ctx, app->page);
855 }
856 fz_catch(app->ctx)
857 {
858 if (fz_caught(app->ctx) == FZ_ERROR_TRYLATER)
859 {
860 fz_ignore_error(app->ctx);
861 app->incomplete = 1;
862 }
863 else
864 {
865 fz_report_error(app->ctx);
866 pdfapp_warn(app, "Failed to load page.");
867 }
868 return;
869 }
870
871 if (app->useicc)
872 fz_enable_icc(app->ctx);
873 else
874 fz_disable_icc(app->ctx);
875
876 fz_set_aa_level(app->ctx, app->aalevel);
877
878 if (app->useseparations)
879 {
880 fz_try(app->ctx)
881 {
882 app->seps = fz_page_separations(app->ctx, app->page);
883 if (app->seps)
884 {
885 int i, n = fz_count_separations(app->ctx, app->seps);
886 for (i = 0; i < n; i++)
887 fz_set_separation_behavior(app->ctx, app->seps, i, FZ_SEPARATION_COMPOSITE);
888 }
889 else if (fz_page_uses_overprint(app->ctx, app->page))
890 {
891 /* This page uses overprint, so we need an empty
892 * sep object to force the overprint simulation on. */
893 app->seps = fz_new_separations(app->ctx, 0);
894 }
895 else if (fz_document_output_intent(app->ctx, app->doc))
896 {
897 /* We have an output intent. Force the overprint
898 *simulation on, because this ensures that
899 * we 'simulate' the output intent too. */
900 app->seps = fz_new_separations(app->ctx, 0);
901 }
902 }
903 fz_catch(app->ctx)
904 {
905 if (fz_caught(app->ctx) == FZ_ERROR_TRYLATER)
906 {
907 fz_ignore_error(app->ctx);
908 app->incomplete = 1;
909 }
910 else
911 {
912 fz_report_error(app->ctx);
913 pdfapp_warn(app, "Failed to load page.");
914 }
915 errored = 1;
916 }
917 }
918
919 fz_try(app->ctx)
920 {
921 /* Create display lists */
922 app->page_list = fz_new_display_list(app->ctx, fz_infinite_rect);
923 mdev = fz_new_list_device(app->ctx, app->page_list);
924 if (no_cache)
925 fz_enable_device_hints(app->ctx, mdev, FZ_NO_CACHE);
926 fz_run_page_contents(app->ctx, app->page, mdev, fz_identity, &cookie);
927 fz_close_device(app->ctx, mdev);
928 fz_drop_device(app->ctx, mdev);
929 mdev = NULL;
930 app->annotations_list = fz_new_display_list(app->ctx, fz_infinite_rect);
931 mdev = fz_new_list_device(app->ctx, app->annotations_list);
932 fz_run_page_annots(app->ctx, app->page, mdev, fz_identity, &cookie);
933 fz_run_page_widgets(app->ctx, app->page, mdev, fz_identity, &cookie);
934 if (cookie.incomplete)
935 {
936 app->incomplete = 1;
937 }
938 else if (cookie.errors)
939 {
940 pdfapp_warn(app, "Errors found on page.");
941 errored = 1;
942 }
943 fz_close_device(app->ctx, mdev);
944 }
945 fz_always(app->ctx)
946 {
947 fz_drop_device(app->ctx, mdev);
948 }
949 fz_catch(app->ctx)
950 {
951 if (fz_caught(app->ctx) == FZ_ERROR_TRYLATER)
952 {
953 fz_ignore_error(app->ctx);
954 app->incomplete = 1;
955 }
956 else
957 {
958 fz_report_error(app->ctx);
959 pdfapp_warn(app, "Failed to load page.");
960 }
961 errored = 1;
962 }
963
964 app->errored = errored;
965 }
966
967 static void pdfapp_runpage(pdfapp_t *app, fz_device *dev, const fz_matrix ctm, fz_rect scissor, fz_cookie *cookie)
968 {
969 if (app->page_list)
970 fz_run_display_list(app->ctx, app->page_list, dev, ctm, scissor, cookie);
971 if (app->annotations_list)
972 fz_run_display_list(app->ctx, app->annotations_list, dev, ctm, scissor, cookie);
973 }
974
975 #define MAX_TITLE 256
976
977 void pdfapp_reloadpage(pdfapp_t *app)
978 {
979 if (app->outline_deferred == PDFAPP_OUTLINE_LOAD_NOW)
980 {
981 fz_try(app->ctx)
982 {
983 app->outline = fz_load_outline(app->ctx, app->doc);
984 }
985 fz_catch(app->ctx)
986 {
987 fz_report_error(app->ctx);
988 app->outline = NULL;
989 }
990 app->outline_deferred = 0;
991 }
992 pdfapp_showpage(app, 1, 1, 1, 0, 0);
993 }
994
995 static void pdfapp_showpage(pdfapp_t *app, int loadpage, int drawpage, int repaint, int transition, int searching)
996 {
997 char buf[MAX_TITLE];
998 fz_device *idev = NULL;
999 fz_device *tdev;
1000 fz_colorspace *colorspace;
1001 fz_matrix ctm;
1002 fz_rect bounds;
1003 fz_irect ibounds;
1004 fz_cookie cookie = { 0 };
1005
1006 if (!app->nowaitcursor)
1007 wincursor(app, WAIT);
1008
1009 if (!app->transitions_enabled || !app->presentation_mode)
1010 transition = 0;
1011
1012 if (transition)
1013 {
1014 app->old_image = app->image;
1015 app->image = NULL;
1016 app->imgw = 0;
1017 app->imgh = 0;
1018 }
1019
1020 /* Always reload page if it was flagged incomplete */
1021 if (app->incomplete)
1022 loadpage = 1;
1023
1024 if (loadpage)
1025 {
1026 fz_rect mediabox;
1027 pdfapp_loadpage(app, searching);
1028
1029 /* Zero search hit position */
1030 app->hit_count = 0;
1031
1032 /* Extract text */
1033 fz_try(app->ctx)
1034 mediabox = fz_bound_page(app->ctx, app->page);
1035 fz_catch(app->ctx)
1036 {
1037 fz_rethrow_unless(app->ctx, FZ_ERROR_TRYLATER);
1038 fz_ignore_error(app->ctx);
1039 mediabox = fz_make_rect(0, 0, 100, 100);
1040 app->incomplete = 1;
1041 }
1042
1043 app->page_text = fz_new_stext_page(app->ctx, mediabox);
1044
1045 if (app->page_list || app->annotations_list)
1046 {
1047 tdev = fz_new_stext_device(app->ctx, app->page_text, NULL);
1048 fz_try(app->ctx)
1049 {
1050 pdfapp_runpage(app, tdev, fz_identity, fz_infinite_rect, &cookie);
1051 fz_close_device(app->ctx, tdev);
1052 }
1053 fz_always(app->ctx)
1054 fz_drop_device(app->ctx, tdev);
1055 fz_catch(app->ctx)
1056 fz_rethrow(app->ctx);
1057 }
1058 }
1059
1060 if (drawpage)
1061 {
1062 char buf2[64];
1063 size_t len;
1064
1065 while (!winisresolutionacceptable(app, pdfapp_viewctm(app)))
1066 {
1067 app->resolution = zoom_out(app->resolution);
1068 }
1069
1070 sprintf(buf2, " - %d/%d (%g dpi)",
1071 app->pageno, app->pagecount, app->resolution);
1072 len = MAX_TITLE-strlen(buf2);
1073 if (strlen(app->doctitle) >= len)
1074 {
1075 fz_strlcpy(buf, app->doctitle, len-3);
1076 fz_strlcat(buf, "...", MAX_TITLE);
1077 fz_strlcat(buf, buf2, MAX_TITLE);
1078 }
1079 else
1080 {
1081 fz_strlcpy(buf, app->doctitle, MAX_TITLE);
1082 fz_strlcat(buf, buf2, MAX_TITLE);
1083 }
1084 wintitle(app, buf);
1085
1086 ctm = pdfapp_viewctm(app);
1087 bounds = fz_transform_rect(app->page_bbox, ctm);
1088 ibounds = fz_round_rect(bounds);
1089 bounds = fz_rect_from_irect(ibounds);
1090
1091 /* Draw */
1092 fz_drop_pixmap(app->ctx, app->image);
1093 if (app->grayscale)
1094 colorspace = fz_device_gray(app->ctx);
1095 else
1096 colorspace = app->colorspace;
1097
1098 app->image = NULL;
1099 app->imgw = 0;
1100 app->imgh = 0;
1101
1102 fz_var(app->image);
1103 fz_var(idev);
1104
1105 fz_try(app->ctx)
1106 {
1107 app->image = fz_new_pixmap_with_bbox(app->ctx, colorspace, ibounds, app->seps, 1);
1108 app->imgw = fz_pixmap_width(app->ctx, app->image);
1109 app->imgh = fz_pixmap_height(app->ctx, app->image);
1110
1111 fz_clear_pixmap_with_value(app->ctx, app->image, 255);
1112 if (app->page_list || app->annotations_list)
1113 {
1114 idev = fz_new_draw_device(app->ctx, fz_identity, app->image);
1115 pdfapp_runpage(app, idev, ctm, bounds, &cookie);
1116 fz_close_device(app->ctx, idev);
1117 }
1118 if (app->invert)
1119 {
1120 fz_invert_pixmap_luminance(app->ctx, app->image);
1121 fz_gamma_pixmap(app->ctx, app->image, 1 / 1.4f);
1122 }
1123 if (app->tint)
1124 fz_tint_pixmap(app->ctx, app->image, 0, app->tint_white);
1125 }
1126 fz_always(app->ctx)
1127 fz_drop_device(app->ctx, idev);
1128 fz_catch(app->ctx)
1129 {
1130 fz_report_error(app->ctx);
1131 cookie.errors++;
1132 }
1133 }
1134
1135 if (transition && drawpage)
1136 {
1137 app->new_image = app->image;
1138 app->image = NULL;
1139 app->imgw = 0;
1140 app->imgh = 0;
1141
1142 if (app->grayscale)
1143 colorspace = fz_device_gray(app->ctx);
1144 else
1145 colorspace = app->colorspace;
1146 app->image = fz_new_pixmap_with_bbox(app->ctx, colorspace, ibounds, app->seps, 1);
1147 app->imgw = fz_pixmap_width(app->ctx, app->image);
1148 app->imgh = fz_pixmap_height(app->ctx, app->image);
1149
1150 app->duration = 0;
1151 fz_page_presentation(app->ctx, app->page, &app->transition, &app->duration);
1152 if (app->duration == 0)
1153 app->duration = app->presentation_time_in_seconds;
1154 app->in_transit = fz_generate_transition(app->ctx, app->image, app->old_image, app->new_image, 0, &app->transition);
1155 if (!app->in_transit)
1156 {
1157 if (app->duration != 0)
1158 winadvancetimer(app, app->duration);
1159 }
1160 app->start_time = clock();
1161 }
1162
1163 if (repaint)
1164 {
1165 pdfapp_panview(app, app->panx, app->pany);
1166
1167 if (!app->image)
1168 {
1169 /* there is no image to blit, but there might be an error message */
1170 winresize(app, app->layout_w, app->layout_h);
1171 }
1172 else if (app->shrinkwrap)
1173 {
1174 int w = app->imgw;
1175 int h = app->imgh;
1176 if (app->winw == w)
1177 app->panx = 0;
1178 if (app->winh == h)
1179 app->pany = 0;
1180 if (w > app->scrw * 90 / 100)
1181 w = app->scrw * 90 / 100;
1182 if (h > app->scrh * 90 / 100)
1183 h = app->scrh * 90 / 100;
1184 if (w != app->winw || h != app->winh)
1185 winresize(app, w, h);
1186 }
1187
1188 winrepaint(app);
1189
1190 wincursor(app, ARROW);
1191 }
1192
1193 if (cookie.errors && app->errored == 0)
1194 {
1195 app->errored = 1;
1196 pdfapp_warn(app, "Errors found on page. Page rendering may be incomplete.");
1197 }
1198
1199 fz_flush_warnings(app->ctx);
1200 }
1201
1202 static void pdfapp_gotouri(pdfapp_t *app, char *uri)
1203 {
1204 char buf[PATH_MAX];
1205
1206 /* Relative file:// URI, make it absolute! */
1207 if (!strncmp(uri, "file://", 7) && uri[7] != '/')
1208 {
1209 char buf_base[PATH_MAX];
1210 char buf_cwd[PATH_MAX];
1211 if (getcwd(buf_cwd, sizeof buf_cwd))
1212 {
1213 fz_dirname(buf_base, app->docpath, sizeof buf_base);
1214 fz_snprintf(buf, sizeof buf, "file://%s/%s/%s", buf_cwd, buf_base, uri+7);
1215 fz_cleanname(buf+7);
1216 uri = buf;
1217 }
1218 }
1219
1220 if (strncmp(uri, "file://", 7) && strncmp(uri, "http://", 7) && strncmp(uri, "https://", 8) && strncmp(uri, "mailto:", 7))
1221 {
1222 fz_warn(app->ctx, "refusing to open unknown link (%s)", uri);
1223 return;
1224 }
1225
1226 winopenuri(app, uri);
1227 }
1228
1229 void pdfapp_gotopage(pdfapp_t *app, int number)
1230 {
1231 app->issearching = 0;
1232 winrepaint(app);
1233
1234 if (number < 1)
1235 number = 1;
1236 if (number > app->pagecount)
1237 number = app->pagecount;
1238
1239 if (number == app->pageno)
1240 return;
1241
1242 if (app->histlen + 1 == 256)
1243 {
1244 memmove(app->hist, app->hist + 1, sizeof(int) * 255);
1245 app->histlen --;
1246 }
1247 app->hist[app->histlen++] = app->pageno;
1248 app->pageno = number;
1249 pdfapp_showpage(app, 1, 1, 1, 0, 0);
1250 }
1251
1252 void pdfapp_inverthit(pdfapp_t *app)
1253 {
1254 fz_rect bbox;
1255 fz_matrix ctm;
1256 int i;
1257
1258 ctm = pdfapp_viewctm(app);
1259
1260 for (i = 0; i < app->hit_count; i++)
1261 {
1262 bbox = fz_rect_from_quad(app->hit_bbox[i]);
1263 bbox = fz_transform_rect(bbox, ctm);
1264 pdfapp_invert(app, bbox);
1265 }
1266 }
1267
1268 static void pdfapp_search_in_direction(pdfapp_t *app, enum panning *panto, int dir)
1269 {
1270 int firstpage, page;
1271
1272 /* abort if no search string */
1273 if (app->search[0] == 0)
1274 {
1275 winrepaint(app);
1276 return;
1277 }
1278
1279 wincursor(app, WAIT);
1280
1281 firstpage = app->pageno;
1282 if (app->searchpage == app->pageno)
1283 page = app->pageno + dir;
1284 else
1285 page = app->pageno;
1286
1287 if (page < 1) page = app->pagecount;
1288 if (page > app->pagecount) page = 1;
1289
1290 do
1291 {
1292 if (page != app->pageno)
1293 {
1294 app->pageno = page;
1295 pdfapp_showpage(app, 1, 0, 0, 0, 1);
1296 }
1297
1298 app->hit_count = fz_search_stext_page(app->ctx, app->page_text, app->search, NULL, app->hit_bbox, nelem(app->hit_bbox));
1299 if (app->hit_count > 0)
1300 {
1301 *panto = dir == 1 ? PAN_TO_TOP : PAN_TO_BOTTOM;
1302 app->searchpage = app->pageno;
1303 wincursor(app, HAND);
1304 winrepaint(app);
1305 return;
1306 }
1307
1308 page += dir;
1309 if (page < 1) page = app->pagecount;
1310 if (page > app->pagecount) page = 1;
1311 } while (page != firstpage);
1312
1313 pdfapp_warn(app, "String '%s' not found.", app->search);
1314
1315 app->pageno = firstpage;
1316 pdfapp_showpage(app, 1, 0, 0, 0, 0);
1317 wincursor(app, HAND);
1318 winrepaint(app);
1319 }
1320
1321 void pdfapp_onresize(pdfapp_t *app, int w, int h)
1322 {
1323 if (app->winw != w || app->winh != h)
1324 {
1325 app->winw = w;
1326 app->winh = h;
1327 pdfapp_panview(app, app->panx, app->pany);
1328 winrepaint(app);
1329 }
1330 }
1331
1332 void pdfapp_autozoom_vertical(pdfapp_t *app)
1333 {
1334 app->resolution *= (float) app->winh / app->imgh;
1335 if (app->resolution > MAXRES)
1336 app->resolution = MAXRES;
1337 else if (app->resolution < MINRES)
1338 app->resolution = MINRES;
1339 pdfapp_showpage(app, 0, 1, 1, 0, 0);
1340 }
1341
1342 void pdfapp_autozoom_horizontal(pdfapp_t *app)
1343 {
1344 app->resolution *= (float) app->winw / app->imgw;
1345 if (app->resolution > MAXRES)
1346 app->resolution = MAXRES;
1347 else if (app->resolution < MINRES)
1348 app->resolution = MINRES;
1349 pdfapp_showpage(app, 0, 1, 1, 0, 0);
1350 }
1351
1352 void pdfapp_autozoom(pdfapp_t *app)
1353 {
1354 float page_aspect = (float) app->imgw / app->imgh;
1355 float win_aspect = (float) app->winw / app->winh;
1356 if (page_aspect > win_aspect)
1357 pdfapp_autozoom_horizontal(app);
1358 else
1359 pdfapp_autozoom_vertical(app);
1360 }
1361
1362 void pdfapp_onkey(pdfapp_t *app, int c, int modifiers)
1363 {
1364 int oldpage = app->pageno;
1365 enum panning panto = PAN_TO_TOP;
1366 int loadpage = 1;
1367
1368 if (app->issearching)
1369 {
1370 size_t n = strlen(app->search);
1371 if (c < ' ')
1372 {
1373 if (c == '\b' && n > 0)
1374 {
1375 app->search[n - 1] = 0;
1376 winrepaintsearch(app);
1377 }
1378 if (c == '\n' || c == '\r')
1379 {
1380 app->issearching = 0;
1381 if (n > 0)
1382 {
1383 winrepaintsearch(app);
1384
1385 if (app->searchdir < 0)
1386 {
1387 if (app->pageno == 1)
1388 app->pageno = app->pagecount;
1389 else
1390 app->pageno--;
1391 pdfapp_showpage(app, 1, 1, 0, 0, 1);
1392 }
1393
1394 pdfapp_onkey(app, 'n', 0);
1395 }
1396 else
1397 winrepaint(app);
1398 }
1399 if (c == '\033')
1400 {
1401 app->issearching = 0;
1402 winrepaint(app);
1403 }
1404 }
1405 else
1406 {
1407 if (n + 2 < sizeof app->search)
1408 {
1409 app->search[n] = c;
1410 app->search[n + 1] = 0;
1411 winrepaintsearch(app);
1412 }
1413 }
1414 return;
1415 }
1416
1417 /*
1418 * Save numbers typed for later
1419 */
1420
1421 if (c >= '0' && c <= '9')
1422 {
1423 app->number[app->numberlen++] = c;
1424 app->number[app->numberlen] = '\0';
1425 }
1426
1427 key_rewritten:
1428 switch (c)
1429 {
1430 case 'q':
1431 save_accelerator(app->ctx, app->doc, app->docpath);
1432 winclose(app);
1433 break;
1434
1435 case '<':
1436 if (fz_is_document_reflowable(app->ctx, app->doc) && app->layout_em > 6)
1437 {
1438 fz_bookmark mark = fz_make_bookmark(app->ctx, app->doc, fz_location_from_page_number(app->ctx, app->doc, app->pageno));
1439 app->layout_em -= 1;
1440 fz_layout_document(app->ctx, app->doc, app->layout_w, app->layout_h, app->layout_em);
1441 app->pagecount = fz_count_pages(app->ctx, app->doc);
1442 app->pageno = fz_page_number_from_location(app->ctx, app->doc, fz_lookup_bookmark(app->ctx, app->doc, mark));
1443 pdfapp_showpage(app, 1, 1, 1, 0, 0);
1444 }
1445 break;
1446 case '>':
1447 if (fz_is_document_reflowable(app->ctx, app->doc) && app->layout_em < 36)
1448 {
1449 fz_bookmark mark = fz_make_bookmark(app->ctx, app->doc, fz_location_from_page_number(app->ctx, app->doc, app->pageno));
1450 app->layout_em += 1;
1451 fz_layout_document(app->ctx, app->doc, app->layout_w, app->layout_h, app->layout_em);
1452 app->pagecount = fz_count_pages(app->ctx, app->doc);
1453 app->pageno = fz_page_number_from_location(app->ctx, app->doc, fz_lookup_bookmark(app->ctx, app->doc, mark));
1454 pdfapp_showpage(app, 1, 1, 1, 0, 0);
1455 }
1456 break;
1457
1458 /*
1459 * Zoom and rotate
1460 */
1461
1462 case '+':
1463 app->resolution = zoom_in(app->resolution);
1464 pdfapp_showpage(app, 0, 1, 1, 0, 0);
1465 break;
1466 case '-':
1467 app->resolution = zoom_out(app->resolution);
1468 pdfapp_showpage(app, 0, 1, 1, 0, 0);
1469 break;
1470
1471 case 'W':
1472 pdfapp_autozoom_horizontal(app);
1473 break;
1474 case 'H':
1475 pdfapp_autozoom_vertical(app);
1476 break;
1477 case 'Z':
1478 pdfapp_autozoom(app);
1479 break;
1480 case 'z':
1481 if (app->numberlen > 0)
1482 app->resolution = atoi(app->number);
1483 else
1484 app->resolution = app->default_resolution;
1485 pdfapp_showpage(app, 0, 1, 1, 0, 0);
1486 break;
1487
1488 case '[':
1489 if (app->numberlen > 0)
1490 app->rotate -= atoi(app->number);
1491 else
1492 app->rotate -= 90;
1493 pdfapp_showpage(app, 0, 1, 1, 0, 0);
1494 break;
1495 case ']':
1496 if (app->numberlen > 0)
1497 app->rotate += atoi(app->number);
1498 else
1499 app->rotate += 90;
1500 pdfapp_showpage(app, 0, 1, 1, 0, 0);
1501 break;
1502
1503 /*
1504 * Rendering and color management parameters.
1505 */
1506
1507 case 'C':
1508 app->tint ^= 1;
1509 pdfapp_showpage(app, 0, 1, 1, 0, 0);
1510 break;
1511
1512 case 'c':
1513 app->grayscale ^= 1;
1514 pdfapp_showpage(app, 0, 1, 1, 0, 0);
1515 break;
1516
1517 case 'I':
1518 app->invert ^= 1;
1519 pdfapp_showpage(app, 0, 1, 1, 0, 0);
1520 break;
1521
1522 case 'E':
1523 app->useicc ^= 1;
1524 if (app->useicc)
1525 pdfapp_warn(app, "Using icc.");
1526 else
1527 pdfapp_warn(app, "Not using icc.");
1528 pdfapp_showpage(app, 1, 1, 1, 0, 0);
1529 break;
1530
1531 case 'e':
1532 app->useseparations ^= 1;
1533 if (app->useseparations)
1534 pdfapp_warn(app, "Using separations.");
1535 else
1536 pdfapp_warn(app, "Not using separations.");
1537 pdfapp_showpage(app, 1, 1, 1, 0, 0);
1538 break;
1539
1540 case 'A':
1541 if (app->numberlen > 0)
1542 app->aalevel = atoi(app->number);
1543 else
1544 app->aalevel = (app->aalevel == 8 ? 0 : 8);
1545 pdfapp_showpage(app, 1, 1, 1, 0, 0);
1546 break;
1547
1548 /*
1549 * Pan view, but don't need to repaint image
1550 */
1551
1552 case 'f':
1553 app->shrinkwrap = 0;
1554 winfullscreen(app, !app->fullscreen);
1555 app->fullscreen = !app->fullscreen;
1556 break;
1557
1558 case 'w':
1559 if (app->fullscreen)
1560 {
1561 winfullscreen(app, 0);
1562 app->fullscreen = 0;
1563 }
1564 app->shrinkwrap = 1;
1565 app->panx = app->pany = 0;
1566 pdfapp_showpage(app, 0, 0, 1, 0, 0);
1567 break;
1568
1569 case 'h':
1570 if (modifiers & 8)
1571 {
1572 /* Alt pressed. Treat this as 't'. (i.e. ALT-Left) */
1573 modifiers &= ~8;
1574 c = 't';
1575 goto key_rewritten;
1576 }
1577 app->panx += app->imgw / 10;
1578 pdfapp_showpage(app, 0, 0, 1, 0, 0);
1579 break;
1580
1581 case 'j':
1582 {
1583 if (app->imgh <= app->winh || app->pany <= app->winh - app->imgh)
1584 {
1585 panto = PAN_TO_TOP;
1586 app->pageno++;
1587 }
1588 else
1589 {
1590 app->pany -= app->imgh / 10;
1591 pdfapp_showpage(app, 0, 0, 1, 0, 0);
1592 }
1593 break;
1594 }
1595
1596 case 'k':
1597 {
1598 if (app->imgh <= app->winh || app->pany == 0)
1599 {
1600 panto = PAN_TO_BOTTOM;
1601 app->pageno--;
1602 }
1603 else
1604 {
1605 app->pany += app->imgh / 10;
1606 pdfapp_showpage(app, 0, 0, 1, 0, 0);
1607 }
1608 break;
1609 }
1610
1611 case 'l':
1612 app->panx -= app->imgw / 10;
1613 pdfapp_showpage(app, 0, 0, 1, 0, 0);
1614 break;
1615
1616 /*
1617 * Page navigation
1618 */
1619
1620 case 'g':
1621 if (app->numberlen > 0)
1622 pdfapp_gotopage(app, atoi(app->number));
1623 else
1624 pdfapp_gotopage(app, 1);
1625 break;
1626
1627 case 'G':
1628 pdfapp_gotopage(app, app->pagecount);
1629 break;
1630
1631 case 'm':
1632 if (app->numberlen > 0)
1633 {
1634 int idx = atoi(app->number);
1635 if (idx >= 0 && idx < (int)nelem(app->marks))
1636 app->marks[idx] = app->pageno;
1637 }
1638 else
1639 {
1640 if (app->histlen + 1 == 256)
1641 {
1642 memmove(app->hist, app->hist + 1, sizeof(int) * 255);
1643 app->histlen --;
1644 }
1645 app->hist[app->histlen++] = app->pageno;
1646 }
1647 break;
1648
1649 case 't':
1650 if (app->numberlen > 0)
1651 {
1652 int idx = atoi(app->number);
1653
1654 if (idx >= 0 && idx < (int)nelem(app->marks))
1655 if (app->marks[idx] > 0)
1656 app->pageno = app->marks[idx];
1657 }
1658 else if (app->histlen > 0)
1659 app->pageno = app->hist[--app->histlen];
1660 break;
1661
1662 case 'p':
1663 app->presentation_mode = !app->presentation_mode;
1664 app->presentation_time_in_seconds = (app->numberlen > 0) ? atoi(app->number) : 5;
1665 break;
1666
1667 /*
1668 * Back and forth ...
1669 */
1670
1671 case ',':
1672 panto = DONT_PAN;
1673 if (app->numberlen > 0)
1674 app->pageno -= atoi(app->number);
1675 else
1676 app->pageno--;
1677 break;
1678
1679 case '.':
1680 panto = DONT_PAN;
1681 if (app->numberlen > 0)
1682 app->pageno += atoi(app->number);
1683 else
1684 app->pageno++;
1685 break;
1686
1687 case 'b':
1688 {
1689 int number = 1;
1690 if (app->numberlen > 0)
1691 number = fz_maxi(atoi(app->number), number);
1692 while (number--)
1693 {
1694 if (app->pany >= -app->imgh/20)
1695 {
1696 if (app->panx >= -app->imgw/20)
1697 {
1698 if (app->pageno - 1 > 0)
1699 {
1700 app->panx = INT_MIN;
1701 app->pany = INT_MIN;
1702 app->pageno--;
1703 panto = DONT_PAN;
1704 }
1705 }
1706 else
1707 {
1708 app->pany = -app->imgh;
1709 app->panx += app->winw * 9 / 10;
1710 pdfapp_showpage(app, 0, 0, 1, 0, 0);
1711 }
1712 }
1713 else
1714 {
1715 app->pany += app->winh * 9 / 10;
1716 pdfapp_showpage(app, 0, 0, 1, 0, 0);
1717 }
1718 }
1719 }
1720 break;
1721
1722 case ' ':
1723 {
1724 int number = 1;
1725 if (app->numberlen > 0)
1726 number = fz_maxi(atoi(app->number), number);
1727 while (number--)
1728 {
1729 if (app->imgh + app->pany <= app->winh + app->imgh/20)
1730 {
1731 if (app->imgw + app->panx <= app->winw + app->imgw/20)
1732 {
1733 if (app->pageno + 1 <= app->pagecount)
1734 {
1735 app->panx = 0;
1736 app->pany = 0;
1737 app->pageno++;
1738 panto = DONT_PAN;
1739 }
1740 }
1741 else
1742 {
1743 app->pany = 0;
1744 app->panx -= app->winw * 9 / 10;
1745 pdfapp_showpage(app, 0, 0, 1, 0, 0);
1746 }
1747 }
1748 else
1749 {
1750 app->pany -= app->winh * 9 / 10;
1751 pdfapp_showpage(app, 0, 0, 1, 0, 0);
1752 }
1753 }
1754 }
1755 break;
1756
1757 /*
1758 * Saving the file
1759 */
1760 case 'S':
1761 pdfapp_save(app);
1762 break;
1763
1764 /*
1765 * Reloading the file...
1766 */
1767
1768 case 'r':
1769 panto = DONT_PAN;
1770 oldpage = -1;
1771 pdfapp_reloadfile(app);
1772 break;
1773
1774 /*
1775 * Searching
1776 */
1777
1778 case '?':
1779 app->issearching = 1;
1780 app->searchdir = -1;
1781 app->search[0] = 0;
1782 app->hit_count = 0;
1783 app->searchpage = -1;
1784 winrepaintsearch(app);
1785 break;
1786
1787 case '/':
1788 app->issearching = 1;
1789 app->searchdir = 1;
1790 app->search[0] = 0;
1791 app->hit_count = 0;
1792 app->searchpage = -1;
1793 winrepaintsearch(app);
1794 break;
1795
1796 case 'n':
1797 if (app->searchdir > 0)
1798 pdfapp_search_in_direction(app, &panto, 1);
1799 else
1800 pdfapp_search_in_direction(app, &panto, -1);
1801 loadpage = 0;
1802 break;
1803
1804 case 'N':
1805 if (app->searchdir > 0)
1806 pdfapp_search_in_direction(app, &panto, -1);
1807 else
1808 pdfapp_search_in_direction(app, &panto, 1);
1809 loadpage = 0;
1810 break;
1811 }
1812
1813 if (c < '0' || c > '9')
1814 app->numberlen = 0;
1815
1816 if (app->pageno < 1)
1817 app->pageno = 1;
1818 if (app->pageno > app->pagecount)
1819 app->pageno = app->pagecount;
1820
1821 if (app->pageno != oldpage)
1822 {
1823 switch (panto)
1824 {
1825 case PAN_TO_TOP:
1826 app->pany = 0;
1827 break;
1828 case PAN_TO_BOTTOM:
1829 app->pany = INT_MIN;
1830 break;
1831 case DONT_PAN:
1832 break;
1833 }
1834 pdfapp_showpage(app, loadpage, 1, 1, 1, 0);
1835 }
1836 }
1837
1838 static void handlescroll(pdfapp_t *app, int modifiers, int dir)
1839 {
1840 app->ispanning = app->iscopying = 0;
1841 if (modifiers & (1<<2))
1842 {
1843 /* zoom in/out if ctrl is pressed */
1844 if (dir > 0)
1845 app->resolution = zoom_in(app->resolution);
1846 else
1847 app->resolution = zoom_out(app->resolution);
1848 if (app->resolution > MAXRES)
1849 app->resolution = MAXRES;
1850 if (app->resolution < MINRES)
1851 app->resolution = MINRES;
1852 pdfapp_showpage(app, 0, 1, 1, 0, 0);
1853 }
1854 else
1855 {
1856 /* scroll up/down, or left/right if
1857 shift is pressed */
1858 int xstep = 0;
1859 int ystep = 0;
1860 int pagestep = 0;
1861 if (modifiers & (1<<0))
1862 {
1863 if (dir > 0 && app->panx >= 0)
1864 pagestep = -1;
1865 else if (dir < 0 && app->panx <= app->winw - app->imgw)
1866 pagestep = 1;
1867 else
1868 xstep = 20 * dir;
1869 }
1870 else
1871 {
1872 if (dir > 0 && app->pany >= 0)
1873 pagestep = -1;
1874 else if (dir < 0 && app->pany <= app->winh - app->imgh)
1875 pagestep = 1;
1876 else
1877 ystep = 20 * dir;
1878 }
1879 if (pagestep == 0)
1880 pdfapp_panview(app, app->panx + xstep, app->pany + ystep);
1881 else if (pagestep > 0 && app->pageno < app->pagecount)
1882 {
1883 app->pageno++;
1884 app->pany = 0;
1885 pdfapp_showpage(app, 1, 1, 1, 0, 0);
1886 }
1887 else if (pagestep < 0 && app->pageno > 1)
1888 {
1889 app->pageno--;
1890 app->pany = INT_MIN;
1891 pdfapp_showpage(app, 1, 1, 1, 0, 0);
1892 }
1893 }
1894 }
1895
1896 void pdfapp_onmouse(pdfapp_t *app, int x, int y, int btn, int modifiers, int state)
1897 {
1898 fz_context *ctx = app->ctx;
1899 fz_irect irect = { 0, 0, app->layout_w, app->layout_h };
1900 fz_link *link;
1901 fz_matrix ctm;
1902 fz_point p;
1903 int processed = 0;
1904
1905 if (app->image)
1906 irect = fz_pixmap_bbox(app->ctx, app->image);
1907 p.x = x - app->panx + irect.x0;
1908 p.y = y - app->pany + irect.y0;
1909
1910 ctm = pdfapp_viewctm(app);
1911 ctm = fz_invert_matrix(ctm);
1912
1913 p = fz_transform_point(p, ctm);
1914
1915 for (link = app->page_links; link; link = link->next)
1916 {
1917 if (p.x >= link->rect.x0 && p.x <= link->rect.x1)
1918 if (p.y >= link->rect.y0 && p.y <= link->rect.y1)
1919 break;
1920 }
1921
1922 if (link)
1923 {
1924 wincursor(app, HAND);
1925 if (btn == 1 && state == 1 && !processed)
1926 {
1927 if (fz_is_external_link(ctx, link->uri))
1928 pdfapp_gotouri(app, link->uri);
1929 else
1930 {
1931 fz_location loc = fz_resolve_link(ctx, app->doc, link->uri, NULL, NULL);
1932 pdfapp_gotopage(app, fz_page_number_from_location(ctx, app->doc, loc)+1);
1933 }
1934 return;
1935 }
1936 }
1937 else
1938 {
1939 wincursor(app, ARROW);
1940 }
1941
1942 if (state == 1 && !processed)
1943 {
1944 if (btn == 1 && !app->iscopying)
1945 {
1946 app->ispanning = 1;
1947 app->selx = x;
1948 app->sely = y;
1949 app->beyondy = 0;
1950 }
1951 if (btn == 3 && !app->ispanning)
1952 {
1953 app->iscopying = 1;
1954 app->selx = x;
1955 app->sely = y;
1956 app->selr.x0 = x;
1957 app->selr.x1 = x;
1958 app->selr.y0 = y;
1959 app->selr.y1 = y;
1960 }
1961 if (btn == 4 || btn == 5) /* scroll wheel */
1962 {
1963 handlescroll(app, modifiers, btn == 4 ? 1 : -1);
1964 }
1965 if (btn == 6 || btn == 7) /* scroll wheel (horizontal) */
1966 {
1967 /* scroll left/right or up/down if shift is pressed */
1968 handlescroll(app, modifiers ^ (1<<0), btn == 6 ? 1 : -1);
1969 }
1970 if (app->presentation_mode)
1971 {
1972 if (btn == 1 && app->pageno < app->pagecount)
1973 {
1974 app->pageno++;
1975 pdfapp_showpage(app, 1, 1, 1, 0, 0);
1976 }
1977 if (btn == 3 && app->pageno > 1)
1978 {
1979 app->pageno--;
1980 pdfapp_showpage(app, 1, 1, 1, 0, 0);
1981 }
1982 }
1983 }
1984
1985 else if (state == -1)
1986 {
1987 if (app->iscopying)
1988 {
1989 app->iscopying = 0;
1990 app->selr.x0 = fz_mini(app->selx, x) - app->panx + irect.x0;
1991 app->selr.x1 = fz_maxi(app->selx, x) - app->panx + irect.x0;
1992 app->selr.y0 = fz_mini(app->sely, y) - app->pany + irect.y0;
1993 app->selr.y1 = fz_maxi(app->sely, y) - app->pany + irect.y0;
1994 winrepaint(app);
1995 if (app->selr.x0 < app->selr.x1 && app->selr.y0 < app->selr.y1)
1996 windocopy(app);
1997 }
1998 app->ispanning = 0;
1999 }
2000
2001 else if (app->ispanning)
2002 {
2003 int newx = app->panx + x - app->selx;
2004 int newy = app->pany + y - app->sely;
2005 int imgh = app->winh;
2006 if (app->image)
2007 imgh = fz_pixmap_height(app->ctx, app->image);
2008
2009 /* Scrolling beyond limits implies flipping pages */
2010 /* Are we requested to scroll beyond limits? */
2011 if (newy + imgh < app->winh || newy > 0)
2012 {
2013 /* Yes. We can assume that deltay != 0 */
2014 int deltay = y - app->sely;
2015 /* Check whether the panning has occurred in the
2016 * direction that we are already crossing the
2017 * limit it. If not, we can conclude that we
2018 * have switched ends of the page and will thus
2019 * start over counting.
2020 */
2021 if( app->beyondy == 0 || (app->beyondy ^ deltay) >= 0 )
2022 {
2023 /* Updating how far we are beyond and
2024 * flipping pages if beyond threshold
2025 */
2026 app->beyondy += deltay;
2027 if (app->beyondy > BEYOND_THRESHHOLD)
2028 {
2029 if( app->pageno > 1 )
2030 {
2031 app->pageno--;
2032 pdfapp_showpage(app, 1, 1, 1, 0, 0);
2033 if (app->image)
2034 newy = -fz_pixmap_height(app->ctx, app->image);
2035 }
2036 app->beyondy = 0;
2037 }
2038 else if (app->beyondy < -BEYOND_THRESHHOLD)
2039 {
2040 if( app->pageno < app->pagecount )
2041 {
2042 app->pageno++;
2043 pdfapp_showpage(app, 1, 1, 1, 0, 0);
2044 newy = 0;
2045 }
2046 app->beyondy = 0;
2047 }
2048 }
2049 else
2050 app->beyondy = 0;
2051 }
2052 /* Although at this point we've already determined that
2053 * or that no scrolling will be performed in
2054 * y-direction, the x-direction has not yet been taken
2055 * care off. Therefore
2056 */
2057 pdfapp_panview(app, newx, newy);
2058
2059 app->selx = x;
2060 app->sely = y;
2061 }
2062
2063 else if (app->iscopying)
2064 {
2065 app->selr.x0 = fz_mini(app->selx, x) - app->panx + irect.x0;
2066 app->selr.x1 = fz_maxi(app->selx, x) - app->panx + irect.x0;
2067 app->selr.y0 = fz_mini(app->sely, y) - app->pany + irect.y0;
2068 app->selr.y1 = fz_maxi(app->sely, y) - app->pany + irect.y0;
2069 winrepaint(app);
2070 }
2071 }
2072
2073 void pdfapp_oncopy(pdfapp_t *app, unsigned short *ucsbuf, int ucslen)
2074 {
2075 fz_matrix ctm;
2076 fz_stext_page *page = app->page_text;
2077 int p, need_newline;
2078 fz_stext_block *block;
2079 fz_stext_line *line;
2080 fz_stext_char *ch;
2081 fz_rect sel;
2082
2083 ctm = pdfapp_viewctm(app);
2084 ctm = fz_invert_matrix(ctm);
2085 sel = fz_transform_rect(app->selr, ctm);
2086
2087 p = 0;
2088 need_newline = 0;
2089
2090 for (block = page->first_block; block; block = block->next)
2091 {
2092 if (block->type != FZ_STEXT_BLOCK_TEXT)
2093 continue;
2094
2095 for (line = block->u.t.first_line; line; line = line->next)
2096 {
2097 int saw_text = 0;
2098 for (ch = line->first_char; ch; ch = ch->next)
2099 {
2100 fz_rect bbox = fz_rect_from_quad(ch->quad);
2101 int c = ch->c;
2102 if (c < 32)
2103 c = 0xFFFD;
2104 if (bbox.x1 >= sel.x0 && bbox.x0 <= sel.x1 && bbox.y1 >= sel.y0 && bbox.y0 <= sel.y1)
2105 {
2106 saw_text = 1;
2107 if (need_newline)
2108 {
2109 #ifdef _WIN32
2110 if (p < ucslen - 1)
2111 ucsbuf[p++] = '\r';
2112 #endif
2113 if (p < ucslen - 1)
2114 ucsbuf[p++] = '\n';
2115 need_newline = 0;
2116 }
2117 if (p < ucslen - 1)
2118 ucsbuf[p++] = c;
2119 }
2120 }
2121 if (saw_text)
2122 need_newline = 1;
2123 }
2124 }
2125
2126 ucsbuf[p] = 0;
2127 }
2128
2129 void pdfapp_postblit(pdfapp_t *app)
2130 {
2131 clock_t time;
2132 float seconds;
2133 int llama;
2134
2135 app->transitions_enabled = 1;
2136 if (!app->in_transit)
2137 return;
2138 time = clock();
2139 seconds = (float)(time - app->start_time) / CLOCKS_PER_SEC;
2140 llama = seconds * 256 / app->transition.duration;
2141 if (llama >= 256)
2142 {
2143 /* Completed. */
2144 fz_drop_pixmap(app->ctx, app->image);
2145 app->image = app->new_image;
2146 app->new_image = NULL;
2147 app->imgw = fz_pixmap_width(app->ctx, app->image);
2148 app->imgh = fz_pixmap_height(app->ctx, app->image);
2149 fz_drop_pixmap(app->ctx, app->old_image);
2150 app->old_image = NULL;
2151 if (app->duration != 0)
2152 winadvancetimer(app, app->duration);
2153 }
2154 else
2155 fz_generate_transition(app->ctx, app->image, app->old_image, app->new_image, llama, &app->transition);
2156 winrepaint(app);
2157 if (llama >= 256)
2158 {
2159 /* Completed. */
2160 app->in_transit = 0;
2161 }
2162 }
2163
2164 void pdfapp_load_profile(pdfapp_t *app, char *profile_name)
2165 {
2166 fz_buffer *profile_data = NULL;
2167 fz_var(profile_data);
2168 fz_try(app->ctx)
2169 {
2170 profile_data = fz_read_file(app->ctx, profile_name);
2171 #ifdef _WIN32
2172 app->colorspace = fz_new_icc_colorspace(app->ctx, FZ_COLORSPACE_BGR, 0, NULL, profile_data);
2173 #else
2174 app->colorspace = fz_new_icc_colorspace(app->ctx, FZ_COLORSPACE_RGB, 0, NULL, profile_data);
2175 #endif
2176 }
2177 fz_always(app->ctx)
2178 {
2179 fz_drop_buffer(app->ctx, profile_data);
2180 }
2181 fz_catch(app->ctx)
2182 {
2183 fz_report_error(app->ctx);
2184 pdfapp_error(app, "cannot load color profile");
2185 }
2186 }