comparison mupdf-source/platform/java/example/Viewer.java @ 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-2022 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 package example;
24
25 import com.artifex.mupdf.fitz.*;
26
27 import java.awt.*;
28 import java.awt.event.*;
29 import java.awt.image.*;
30
31 import java.io.File;
32 import java.io.FilenameFilter;
33 import java.io.FileNotFoundException;
34 import java.io.FileOutputStream;
35 import java.io.PrintWriter;
36 import java.io.StringWriter;
37 import java.io.IOException;
38 import java.io.RandomAccessFile;
39 import java.lang.reflect.Field;
40 import java.util.Vector;
41
42 public class Viewer extends Frame implements WindowListener, ActionListener, ItemListener, KeyListener, MouseWheelListener, Context.Log, ViewerCore.Callback
43 {
44 protected Worker worker;
45 protected ViewerCore doc;
46 protected Location location;
47 protected int chapterNumber;
48 protected int pageNumber;
49 protected ViewerCore.OutlineItem[] outline;
50
51 protected int layoutWidth = 450;
52 protected int layoutHeight = 600;
53 protected int layoutEm = 12;
54
55 protected String title;
56 protected String author;
57 protected String format;
58 protected String encryption;
59 protected boolean print;
60 protected boolean copy;
61 protected boolean edit;
62 protected boolean annotate;
63 protected boolean form;
64 protected boolean accessibility;
65 protected boolean assemble;
66 protected boolean printHq;
67 protected boolean reflowable;
68 protected boolean linearized;
69 protected int updates;
70 protected int firstUpdate;
71 protected int chapters;
72 protected int pages;
73
74 protected Pixmap pixmap;
75 protected Rect bbox;
76 protected Rect[] links;
77 protected String[] linkURIs;
78 protected Quad[][] hits;
79
80 protected int pixmapWidth;
81 protected int pixmapHeight;
82 protected float pixelScale;
83 protected int screenDPI;
84 protected Dimension screenSize;
85
86 protected int searchDirection = 1;
87 protected String searchNeedle = null;
88
89 protected final int MIN_ZOOM_DPI = 18;
90 protected final int MAX_ZOOM_DPI = 288;
91 protected int[] zoomList = {
92 18, 24, 36, 54, 72, 96, 120, 144, 180, 216, 288
93 };
94 protected boolean customZoom = false;
95 protected int currentDPI = 72;
96
97 protected int rotate = 0;
98 protected boolean icc = true;
99 protected int antialias = 8;
100 protected boolean invert = false;
101 protected boolean tint = false;
102 protected int tintBlack = 0x303030;
103 protected int tintWhite = 0xFFFFF0;
104 protected boolean showLinks = false;
105 protected boolean isFullscreen = false;
106
107 protected ScrollPane pageScroll;
108 protected Panel pageHolder;
109 protected PageCanvas pageCanvas;
110
111 protected Button firstButton, prevButton, nextButton, lastButton;
112 protected TextField pageField;
113 protected Label pageLabel;
114 protected Button zoomInButton, zoomOutButton;
115 protected Choice zoomChoice;
116 protected Panel reflowPanel;
117 protected Button fontIncButton, fontDecButton;
118 protected Label fontSizeLabel;
119
120 protected TextField searchField;
121 protected Button searchPrevButton, searchNextButton;
122 protected Panel searchStatusPanel;
123 protected Label searchStatus;
124
125 protected Panel outlinePanel;
126 protected List outlineList;
127
128 protected OCRProgressmeter OCRmeter;
129 protected RenderProgressmeter renderMeter;
130
131 protected int number = 0;
132
133 protected class Mark {
134 Location loc;
135
136 protected Mark(Location loc) {
137 this.loc = loc;
138 }
139 }
140
141 protected int historyCount = 0;
142 protected Mark[] history = new Mark[256];
143 protected int futureCount = 0;
144 protected Mark[] future = new Mark[256];
145 protected Mark[] marks = new Mark[10];
146
147 protected static void messageBox(Frame owner, String title, String message) {
148 final Dialog box = new Dialog(owner, title, true);
149 box.add(new Label(message), BorderLayout.CENTER);
150 Panel buttonPane = new Panel(new FlowLayout());
151 Button okayButton = new Button("Okay");
152 okayButton.addActionListener(new ActionListener() {
153 public void actionPerformed(ActionEvent event) {
154 box.setVisible(false);
155 }
156 });
157 buttonPane.add(okayButton);
158 box.add(buttonPane, BorderLayout.SOUTH);
159 box.pack();
160 box.setVisible(true);
161 box.dispose();
162 }
163
164 protected static String passwordDialog(Frame owner, String title) {
165 final Dialog box = new Dialog(owner, title, true);
166 final TextField textField = new TextField(20);
167 textField.setEchoChar('*');
168 Panel buttonPane = new Panel(new FlowLayout());
169 Button cancelButton = new Button("Cancel");
170 Button okayButton = new Button("Okay");
171 cancelButton.addActionListener(new ActionListener() {
172 public void actionPerformed(ActionEvent event) {
173 textField.setText("");
174 box.setVisible(false);
175 }
176 });
177 okayButton.addActionListener(new ActionListener() {
178 public void actionPerformed(ActionEvent event) {
179 box.setVisible(false);
180 }
181 });
182 box.add(new Label("Password:"), BorderLayout.NORTH);
183 box.add(textField, BorderLayout.CENTER);
184 buttonPane.add(cancelButton);
185 buttonPane.add(okayButton);
186 box.add(buttonPane, BorderLayout.SOUTH);
187 box.pack();
188 box.setVisible(true);
189 box.dispose();
190 String pwd = textField.getText();
191 if (pwd.length() == 0)
192 return null;
193 return pwd;
194 }
195
196 public class LogDialog extends Dialog implements ActionListener, KeyListener{
197 TextArea info = new TextArea("");
198 Button okay = new Button("Okay");
199
200 public LogDialog(Frame parent, String title, String message) {
201 super(parent, title, true);
202
203 setLayout(new GridLayout(2, 1));
204 info.setText(message);
205 info.setEnabled(false);
206 add(info);
207
208 Panel buttonPanel = new Panel(new FlowLayout(FlowLayout.CENTER));
209 {
210 okay.addActionListener(this);
211 okay.addKeyListener(this);
212 buttonPanel.add(okay);
213 }
214 add(buttonPanel);
215
216 pack();
217 setResizable(false);
218 okay.requestFocusInWindow();
219 setLocationRelativeTo(parent);
220 setVisible(true);
221 }
222
223 public void actionPerformed(ActionEvent e) {
224 if (e.getSource() == okay)
225 dispose();
226 }
227
228 public void keyPressed(KeyEvent e) { }
229 public void keyReleased(KeyEvent e) { }
230
231 public void keyTyped(KeyEvent e) {
232 if (e.getKeyChar() == '\u001b')
233 dispose();
234 }
235 }
236
237 public void exception(Throwable t) {
238 StringWriter sw = new StringWriter();
239 PrintWriter pw = new PrintWriter(sw);
240 t.printStackTrace(pw);
241 System.out.println("Exception: " + sw.toString());
242 LogDialog ld = new LogDialog(this, "Exception!", "Exception: " + sw.toString());
243 }
244
245 public void error(String message) {
246 LogDialog ld = new LogDialog(this, "Error!", "Error: " + message);
247 }
248
249 public void warning(String message) {
250 LogDialog ld = new LogDialog(this, "Warning!", "Warning: " + message);
251 }
252
253 public Viewer(String documentPath) {
254 pixelScale = getRetinaScale();
255 screenDPI = getScreenDPI();
256 screenSize = getScreenSize();
257
258 currentDPI = zoomList[findNextLargerZoomLevel(screenDPI - 1)];
259
260 setTitle("MuPDF: ");
261
262 outlinePanel = new Panel(new BorderLayout());
263 {
264 outlineList = new List();
265 outlineList.addItemListener(this);
266 outlinePanel.add(outlineList, BorderLayout.CENTER);
267 }
268 this.add(outlinePanel, BorderLayout.WEST);
269 outlinePanel.setMinimumSize(new Dimension(300, 300));
270 outlinePanel.setPreferredSize(new Dimension(300, 300));
271 outlinePanel.setVisible(false);
272
273 Panel rightPanel = new Panel(new BorderLayout());
274 {
275 Panel toolpane = new Panel(new GridBagLayout());
276 {
277 GridBagConstraints c = new GridBagConstraints();
278 c.fill = GridBagConstraints.HORIZONTAL;
279 c.anchor = GridBagConstraints.WEST;
280
281 Panel toolbar = new Panel(new FlowLayout(FlowLayout.LEFT));
282 {
283 firstButton = new Button("|<");
284 firstButton.addActionListener(this);
285 prevButton = new Button("<");
286 prevButton.addActionListener(this);
287 nextButton = new Button(">");
288 nextButton.addActionListener(this);
289 lastButton = new Button(">|");
290 lastButton.addActionListener(this);
291 pageField = new TextField(4);
292 pageField.addActionListener(this);
293 pageLabel = new Label("/ " + pages);
294
295 toolbar.add(firstButton);
296 toolbar.add(prevButton);
297 toolbar.add(pageField);
298 toolbar.add(pageLabel);
299 toolbar.add(nextButton);
300 toolbar.add(lastButton);
301 }
302 c.gridy = 0;
303 toolpane.add(toolbar, c);
304
305 toolbar = new Panel(new FlowLayout(FlowLayout.LEFT));
306 {
307 zoomOutButton = new Button("Zoom-");
308 zoomOutButton.addActionListener(this);
309 zoomInButton = new Button("Zoom+");
310 zoomInButton.addActionListener(this);
311
312 zoomChoice = new Choice();
313 for (int i = 0; i < zoomList.length; ++i) {
314 zoomChoice.add(String.valueOf(zoomList[i]));
315 if (zoomList[i] == currentDPI)
316 zoomChoice.select(i);
317 }
318 zoomChoice.addItemListener(this);
319
320 toolbar.add(zoomOutButton);
321 toolbar.add(zoomChoice);
322 toolbar.add(zoomInButton);
323 }
324 c.gridy += 1;
325 toolpane.add(toolbar, c);
326
327 reflowPanel = new Panel(new FlowLayout(FlowLayout.LEFT));
328 {
329 fontDecButton = new Button("Font-");
330 fontDecButton.addActionListener(this);
331 fontIncButton = new Button("Font+");
332 fontIncButton.addActionListener(this);
333 fontSizeLabel = new Label(String.valueOf(layoutEm));
334
335 reflowPanel.add(fontDecButton);
336 reflowPanel.add(fontSizeLabel);
337 reflowPanel.add(fontIncButton);
338 }
339 c.gridy += 1;
340 toolpane.add(reflowPanel, c);
341
342 toolbar = new Panel(new FlowLayout(FlowLayout.LEFT));
343 {
344 searchField = new TextField(20);
345 searchField.addActionListener(this);
346 searchPrevButton = new Button("<");
347 searchPrevButton.addActionListener(this);
348 searchNextButton = new Button(">");
349 searchNextButton.addActionListener(this);
350
351 toolbar.add(searchField);
352 toolbar.add(searchPrevButton);
353 toolbar.add(searchNextButton);
354 }
355 searchField.addKeyListener(this);
356 c.gridy += 1;
357 toolpane.add(toolbar, c);
358
359 searchStatusPanel = new Panel(new FlowLayout(FlowLayout.LEFT));
360 {
361 searchStatus = new Label();
362
363 searchStatusPanel.add(searchStatus);
364 }
365 c.gridy += 1;
366 toolpane.add(searchStatusPanel, c);
367 }
368 rightPanel.add(toolpane, BorderLayout.NORTH);
369 }
370 this.add(rightPanel, BorderLayout.EAST);
371
372 pageScroll = new ScrollPane(ScrollPane.SCROLLBARS_NEVER);
373 {
374 pageHolder = new Panel(new GridBagLayout());
375 {
376 pageHolder.setBackground(Color.gray);
377 pageCanvas = new PageCanvas(pixelScale);
378 pageHolder.add(pageCanvas);
379 }
380 pageCanvas.addKeyListener(this);
381 pageCanvas.addMouseWheelListener(this);
382 pageScroll.add(pageHolder);
383 }
384 this.add(pageScroll, BorderLayout.CENTER);
385
386 addWindowListener(this);
387
388 Toolkit toolkit = Toolkit.getDefaultToolkit();
389 EventQueue eq = toolkit.getSystemEventQueue();
390 worker = new Worker(eq);
391 worker.start();
392
393 pack();
394
395 pageCanvas.requestFocusInWindow();
396
397 doc = new ViewerCore(worker, this, documentPath);
398 doc.openDocument(new ViewerCore.OnException() {
399 public void run(Throwable t) {
400 exception(t);
401 }
402 });
403 }
404
405 public void dispose() {
406 doc.cancelSearch(null);
407 doc.worker.stop();
408 super.dispose();
409 }
410
411 public void keyPressed(KeyEvent e) {
412 }
413
414 public void keyReleased(KeyEvent e) {
415 if (e.getSource() == pageCanvas) {
416 int c = e.getKeyCode();
417
418 switch(c)
419 {
420 case KeyEvent.VK_F1: showHelp(); break;
421
422 case KeyEvent.VK_LEFT: pan(-10, 0); break;
423 case KeyEvent.VK_RIGHT: pan(+10, 0); break;
424 case KeyEvent.VK_UP: pan(0, -10); break;
425 case KeyEvent.VK_DOWN: pan(0, +10); break;
426
427 case KeyEvent.VK_PAGE_UP: doc.flipPages(-number, null); break;
428 case KeyEvent.VK_PAGE_DOWN: doc.flipPages(+number, null); break;
429 }
430 }
431 }
432
433 public void keyTyped(KeyEvent e) {
434 if (e.getSource() == pageCanvas)
435 canvasKeyTyped(e);
436 else if (e.getSource() == searchField)
437 searchFieldKeyTyped(e);
438 }
439
440 protected void searchFieldKeyTyped(KeyEvent e) {
441 if (e.getExtendedKeyCodeForChar(e.getKeyChar()) == java.awt.event.KeyEvent.VK_ESCAPE)
442 clearSearch();
443
444 }
445
446 protected void canvasKeyTyped(KeyEvent e) {
447 char c = e.getKeyChar();
448
449 switch(c)
450 {
451 case 'r': doc.reloadDocument(null); break;
452 case 'q': dispose(); break;
453 case 'S': save(); break;
454
455 case 'f': toggleFullscreen(); break;
456
457 case 'm': mark(number); break;
458 case 't': jumpHistoryBack(number); break;
459 case 'T': jumpHistoryForward(number); break;
460
461 case '>': relayout(number > 0 ? number : +1); break;
462 case '<': relayout(number > 0 ? number : -1); break;
463
464 case 'I': toggleInvert(); break;
465 case 'E': toggleICC(); break;
466 case 'A': toggleAntiAlias(); break;
467 case 'C': toggleTint(); break;
468 case 'o': toggleOutline(); break;
469 case 'L': toggleLinks(); break;
470 case 'i': showInfo(); break;
471
472 case '[': rotate(-90); break;
473 case ']': rotate(+90); break;
474
475 case '+': zoomIn(); break;
476 case '-': zoomOut(); break;
477 case 'z': zoomToDPI(number); break;
478
479 case 'w': shrinkWrap(); break;
480 case 'W': fitWidth(); break;
481 case 'H': fitHeight(); break;
482 case 'Z': fitPage(); break;
483
484 case 'k': pan(0, pageCanvas != null ? pageCanvas.getHeight() / -10 : -10); break;
485 case 'j': pan(0, pageCanvas != null ? pageCanvas.getWidth() / +10 : +10); break;
486 case 'h': pan(pageCanvas != null ? pageCanvas.getHeight() / -10 : -10, 0); break;
487 case 'l': pan(pageCanvas != null ? pageCanvas.getWidth() / +10 : +10, 0); break;
488
489 case 'b': smartMove(-1, number); break;
490 case ' ': smartMove(+1, number); break;
491
492 case ',': flipPages(-number); break;
493 case '.': flipPages(+number); break;
494
495 case 'g': gotoPage(number); break;
496 case 'G': gotoLastPage(); break;
497
498 case '/': editSearchNeedle(+1); break;
499 case '?': editSearchNeedle(-1); break;
500 case 'N': search(-1); break;
501 case 'n': search(+1); break;
502 case '\u001b': clearSearch(); break;
503 }
504
505 if (c >= '0' && c <= '9')
506 number = number * 10 + c - '0';
507 else
508 number = 0;
509 }
510
511 public void mouseWheelMoved(MouseWheelEvent e) {
512 int mod = e.getModifiersEx();
513 int rot = e.getWheelRotation();
514 if ((mod & MouseWheelEvent.CTRL_DOWN_MASK) != 0) {
515 if (rot < 0)
516 zoomIn();
517 else
518 zoomOut();
519 } else if ((mod & MouseWheelEvent.SHIFT_DOWN_MASK) != 0) {
520 if (rot < 0)
521 pan(pageCanvas != null ? pageCanvas.getHeight() / -10 : -10, 0);
522 else
523 pan(pageCanvas != null ? pageCanvas.getHeight() / +10 : +10, 0);
524 } else if (mod == 0) {
525 if (rot < 0)
526 pan(0, pageCanvas != null ? pageCanvas.getHeight() / -10 : -10);
527 else
528 pan(0, pageCanvas != null ? pageCanvas.getHeight() / +10 : +10);
529 }
530 }
531
532 protected void editSearchNeedle(int direction) {
533 clearSearch();
534 searchDirection = direction;
535 searchField.requestFocusInWindow();
536 }
537
538 protected void cancelSearch() {
539 doc.cancelSearch(null);
540 }
541
542 protected void clearSearch() {
543 cancelSearch();
544 searchField.setText("");
545 searchStatus.setText("");
546 searchStatusPanel.validate();
547 validate();
548 hits = null;
549 redraw();
550 }
551
552 public void search(int direction) {
553 if (searchField.isEnabled()) {
554 cancelSearch();
555 searchField.setEnabled(false);
556 searchNextButton.setEnabled(false);
557 searchPrevButton.setEnabled(false);
558 pageCanvas.requestFocusInWindow();
559 doc.search(searchField.getText(), direction, new ViewerCore.OnException() {
560 public void run(Throwable t) {
561 exception(t);
562 }
563 });
564 }
565 }
566
567 protected void render() {
568 if (bbox == null)
569 return;
570
571 float width = bbox.x1 - bbox.x0;
572 float height = bbox.y1 - bbox.y0;
573 float scaleX = (float) Math.floor(width * (currentDPI/72.0f) * pixelScale + 0.5f) / width;
574 float scaleY = (float) Math.floor(height * (currentDPI/72.0f) * pixelScale + 0.5f) / height;
575 Matrix ctm = new Matrix().scale(scaleX, scaleY).rotate(rotate);
576
577 Rect atOrigin = new Rect(bbox).transform(ctm);
578 ctm.e -= atOrigin.x0;
579 ctm.f -= atOrigin.y0;
580 Rect bounds = new Rect(bbox).transform(ctm);
581
582 Cookie cookie = new Cookie();
583
584 renderMeter = new RenderProgressmeter(this, "Rendering...", cookie, 250);
585 renderMeter.setLocationRelativeTo(this);
586 pageCanvas.requestFocusInWindow();
587
588 doc.renderPage(ctm, bounds, icc, antialias, invert, tint, tintBlack, tintWhite, cookie,
589 new ViewerCore.OnException() {
590 public void run(Throwable t) {
591 if (!renderMeter.cancelled)
592 exception(t);
593 }
594 }
595 );
596 }
597
598 protected int findExactZoomLevel(int dpi) {
599 for (int level = 0; level < zoomList.length - 1; level++)
600 if (zoomList[level] == dpi)
601 return level;
602 return -1;
603 }
604
605 protected int findNextSmallerZoomLevel(int dpi) {
606 for (int level = zoomList.length - 1; level >= 0; level--)
607 if (zoomList[level] < dpi)
608 return level;
609 return 0;
610 }
611
612 protected int findNextLargerZoomLevel(int dpi) {
613 for (int level = 0; level < zoomList.length - 1; level++)
614 if (zoomList[level] > dpi)
615 return level;
616 return zoomList.length - 1;
617 }
618
619 protected void zoomToDPI(int newDPI) {
620 if (newDPI == 0)
621 newDPI = screenDPI;
622 if (newDPI < MIN_ZOOM_DPI)
623 newDPI = MIN_ZOOM_DPI;
624 if (newDPI > MAX_ZOOM_DPI)
625 newDPI = MAX_ZOOM_DPI;
626
627 if (newDPI == currentDPI)
628 return;
629
630 int level = findExactZoomLevel(newDPI);
631 if (level < 0) {
632 if (customZoom)
633 zoomChoice.remove(0);
634 customZoom = true;
635 zoomChoice.insert(String.valueOf(newDPI), 0);
636 zoomChoice.select(0);
637 } else {
638 if (customZoom) {
639 customZoom = false;
640 zoomChoice.remove(0);
641 }
642 zoomChoice.select(level);
643 }
644
645 currentDPI = newDPI;
646 render();
647 }
648
649 protected void zoomToLevel(int level) {
650 if (level < 0)
651 level = 0;
652 if (level >= zoomList.length - 1)
653 level = zoomList.length - 1;
654 zoomToDPI(zoomList[level]);
655 }
656
657 protected void zoomIn() {
658 zoomToLevel(findNextLargerZoomLevel(currentDPI));
659 }
660
661 protected void zoomOut() {
662 zoomToLevel(findNextSmallerZoomLevel(currentDPI));
663 }
664
665 protected void zoomToScale(float newZoom) {
666 zoomToDPI((int)(newZoom * 72));
667 }
668
669 protected void fit(float desired, float unscaled) {
670 zoomToScale(desired / unscaled);
671 }
672
673 protected float unscaledWidth() {
674 return bbox != null ? bbox.x1 - bbox.x0 : 0;
675 }
676
677 protected float unscaledHeight() {
678 return bbox != null ? bbox.y1 - bbox.y0 : 0;
679 }
680
681 protected void fitWidth() {
682 fit(pageScroll.getSize().width, unscaledWidth());
683 }
684
685 protected void fitHeight() {
686 fit(pageScroll.getSize().height, unscaledHeight());
687 }
688
689 protected void fitPage() {
690 Dimension size = pageScroll.getSize();
691 float width = bbox != null ? bbox.x1 - bbox.x0 : 0;
692 float height = bbox != null ? bbox.y1 - bbox.y0 : 0;
693 float pageAspect = (width == 0 || height == 0) ? 0 : (width / height);
694 float canvasAspect = (float) size.width / (float) size.height;
695 if (pageAspect > canvasAspect)
696 fitWidth();
697 else
698 fitHeight();
699 }
700
701 protected void rotate(int change) {
702 int newRotate = rotate + change;
703 while (newRotate < 0) newRotate += 360;
704 while (newRotate >= 360) newRotate -= 360;
705
706 if (newRotate - rotate == 0)
707 return;
708 rotate = newRotate;
709
710 render();
711 }
712
713 protected void toggleAntiAlias() {
714 int newAntialias = number != 0 ? number : (antialias == 8 ? 0 : 8);
715 if (newAntialias - antialias == 0)
716 return;
717
718 antialias = newAntialias;
719 render();
720 }
721
722 protected void toggleICC() {
723 icc = !icc;
724 render();
725 }
726
727 protected void toggleInvert() {
728 invert = !invert;
729 render();
730 }
731
732 protected void toggleTint() {
733 tint = !tint;
734 render();
735 }
736
737 protected void toggleOutline() {
738 if (outlineList.getItemCount() <= 0)
739 return;
740
741 outlinePanel.setVisible(!outlinePanel.isVisible());
742 pack();
743 validate();
744 }
745
746 protected void toggleLinks() {
747 showLinks = !showLinks;
748 redraw();
749 }
750
751 protected boolean isShrinkWrapped(int oldPixmapWidth, int oldPixmapHeight) {
752 Dimension size = pageScroll.getSize();
753 if (oldPixmapWidth == 0 && oldPixmapHeight == 0)
754 return true;
755 if (oldPixmapWidth + 4 == size.width && oldPixmapHeight + 4 == size.height)
756 return true;
757 return false;
758 }
759
760 protected void shrinkWrap() {
761 Dimension newSize = new Dimension(pixmapWidth, pixmapHeight);
762 newSize.width += 4;
763 newSize.height += 4;
764
765 if (newSize.width > screenSize.width)
766 newSize.width = screenSize.width;
767 if (newSize.height > screenSize.height)
768 newSize.height = screenSize.height;
769
770 pageScroll.setPreferredSize(newSize);
771 pack();
772 }
773
774 protected void redraw() {
775 boolean wasShrinkWrapped = isShrinkWrapped(pixmapWidth, pixmapHeight);
776
777 if (pixmap != null) {
778 pixmapWidth = pixmap.getWidth();
779 pixmapHeight = pixmap.getHeight();
780 BufferedImage image = new BufferedImage(pixmapWidth, pixmapHeight, BufferedImage.TYPE_3BYTE_BGR);
781 image.setRGB(0, 0, pixmapWidth, pixmapHeight, pixmap.getPixels(), 0, pixmapWidth);
782 pageCanvas.setPage(image, showLinks ? links : null, hits);
783 } else {
784 pixmapWidth = 0;
785 pixmapHeight = 0;
786 pageCanvas.setPage(null, null, null);
787 }
788
789 if (wasShrinkWrapped)
790 shrinkWrap();
791
792 pageCanvas.invalidate();
793 validate();
794 }
795
796 protected static String paperSizeName(int w, int h)
797 {
798 /* ISO A */
799 if (w == 2384 && h == 3370) return "A0";
800 if (w == 1684 && h == 2384) return "A1";
801 if (w == 1191 && h == 1684) return "A2";
802 if (w == 842 && h == 1191) return "A3";
803 if (w == 595 && h == 842) return "A4";
804 if (w == 420 && h == 595) return "A5";
805 if (w == 297 && h == 420) return "A6";
806
807 /* US */
808 if (w == 612 && h == 792) return "Letter";
809 if (w == 612 && h == 1008) return "Legal";
810 if (w == 792 && h == 1224) return "Ledger";
811 if (w == 1224 && h == 792) return "Tabloid";
812
813 return null;
814 }
815
816 protected void showHelp() {
817 final Dialog box = new Dialog(this, "Help", true);
818 box.addWindowListener(new WindowListener() {
819 public void windowActivated(WindowEvent event) { }
820 public void windowDeactivated(WindowEvent event) { }
821 public void windowIconified(WindowEvent event) { }
822 public void windowDeiconified(WindowEvent event) { }
823 public void windowOpened(WindowEvent event) { }
824 public void windowClosed(WindowEvent event) { }
825 public void windowClosing(WindowEvent event) {
826 box.setVisible(false);
827 pageCanvas.requestFocusInWindow();
828 }
829 });
830
831 String help[] = {
832 "The middle mouse button (scroll wheel button) pans the document view.",
833 //"The right mouse button selects a region and copies the marked text to the clipboard.",
834 //"",
835 //"",
836 "F1 - show this message",
837 "i - show document information",
838 "o - show document outline",
839 //"a - show annotation editor",
840 "L - highlight links",
841 //"F - highlight form fields",
842 "r - reload file",
843 "S - save file",
844 "q - quit",
845 "",
846 "< - decrease E-book font size",
847 "> - increase E-book font size",
848 "A - toggle anti-aliasing",
849 "I - toggle inverted color mode",
850 "C - toggle tinted color mode",
851 "E - toggle ICC color management",
852 //"e - toggle spot color emulation",
853 "",
854 "f - fullscreen window",
855 "w - shrink wrap window",
856 "W - fit to width",
857 "H - fit to height",
858 "Z - fit to page",
859 "z - reset zoom",
860 "[number] z - set zoom resolution in DPI",
861 "plus - zoom in",
862 "minus - zoom out",
863 "[ - rotate counter-clockwise",
864 "] - rotate clockwise",
865 "arrow keys - scroll in small increments",
866 "h, j, k, l - scroll in small increments",
867 "",
868 "b - smart move backward",
869 "space - smart move forward",
870 "comma or page up - go backward",
871 "period or page down - go forward",
872 "g - go to first page",
873 "G - go to last page",
874 "[number] g - go to page number",
875 "",
876 "m - save current location in history",
877 "t - go backward in history",
878 "T - go forward in history",
879 "[number] m - save current location in numbered bookmark",
880 "[number] t - go to numbered bookmark",
881 "",
882 "/ - search for text forward",
883 "? - search for text backward",
884 "n - repeat search",
885 "N - repeat search in reverse direction",
886 };
887
888 Panel helpPanel = new Panel(new GridLayout(help.length, 1));
889
890 for (int i = 0; i < help.length; i++)
891 helpPanel.add(new Label(help[i]));
892
893 Button button = new Button("OK");
894 button.addActionListener(new ActionListener() {
895 public void actionPerformed(ActionEvent event) {
896 box.setVisible(false);
897 }
898 });
899 button.addKeyListener(new KeyListener() {
900 public void keyPressed(KeyEvent e) { }
901 public void keyReleased(KeyEvent e) {
902 if (e.getKeyCode() == KeyEvent.VK_F1)
903 box.setVisible(false);
904 }
905 public void keyTyped(KeyEvent e) {
906 if (e.getKeyChar() == '\u001b' || e.getKeyChar() == '\r' || e.getKeyChar() == '\n')
907 box.setVisible(false);
908 }
909 });
910
911 Panel buttonPane = new Panel(new FlowLayout());
912 buttonPane.add(button);
913
914 box.add(helpPanel, BorderLayout.CENTER);
915 box.add(buttonPane, BorderLayout.SOUTH);
916
917 box.setResizable(false);
918 box.pack();
919
920 java.awt.Point winLoc = this.getLocation();
921 Dimension winDim = this.getSize();
922 int winCenterX = winLoc.x + winDim.width / 2;
923 int winCenterY = winLoc.y + winDim.height / 2;
924
925 Dimension diagDim = box.getSize();
926 int x = winCenterX - diagDim.width / 2;
927 int y = winCenterY - diagDim.height / 2;
928
929 box.setLocation(x, y);
930
931 button.requestFocusInWindow();
932 box.setVisible(true);
933
934 box.dispose();
935 }
936
937 protected void showInfo() {
938 StringBuffer buffer;
939
940 cancelSearch();
941
942 final Dialog box = new Dialog(this, "Document info", true);
943 box.addWindowListener(new WindowListener() {
944 public void windowActivated(WindowEvent event) { }
945 public void windowDeactivated(WindowEvent event) { }
946 public void windowIconified(WindowEvent event) { }
947 public void windowDeiconified(WindowEvent event) { }
948 public void windowOpened(WindowEvent event) { }
949 public void windowClosed(WindowEvent event) { }
950 public void windowClosing(WindowEvent event) {
951 box.setVisible(false);
952 pageCanvas.requestFocusInWindow();
953 }
954 });
955
956 Panel infoPanel = new Panel();
957 int rows = 0;
958
959 if (title != null) rows++;
960
961 if (author != null) rows++;
962
963 if (format != null) rows++;
964
965 if (encryption != null) rows++;
966
967 buffer = new StringBuffer();
968 if (print)
969 buffer.append("print, ");
970 if (copy)
971 buffer.append("copy, ");
972 if (edit)
973 buffer.append("edit, ");
974 if (annotate)
975 buffer.append("annotate, ");
976 if (form)
977 buffer.append("form, ");
978 if (accessibility)
979 buffer.append("accessibility, ");
980 if (assemble)
981 buffer.append("assemble, ");
982 if (printHq)
983 buffer.append("print-hq, ");
984 if (buffer.length() > 2)
985 buffer.delete(buffer.length() - 2, buffer.length());
986 String permissions = buffer.length() > 0 ? buffer.toString() : null;
987 if (permissions != null) rows++;
988
989 buffer = new StringBuffer();
990 if (doc.equals("PDF")) {
991 buffer.append("PDF ");
992 if (linearized)
993 buffer.append("linearized ");
994 buffer.append("document with ");
995 buffer.append(updates);
996 if (updates == 1)
997 buffer.append(" update");
998 else
999 buffer.append(" updates");
1000 }
1001 String versions = buffer.length() > 0 ? buffer.toString() : null;
1002 if (versions != null) rows++;
1003
1004 buffer = new StringBuffer();
1005 if (doc.equals("PDF")) {
1006 if (updates > 1) {
1007 if (firstUpdate == 0)
1008 buffer.append("Change firstUpdate seems valid.");
1009 else if (firstUpdate == 1)
1010 buffer.append("Invalid changes made to the document in the last update.");
1011 else if (firstUpdate == 2)
1012 buffer.append("Invalid changes made to the document in the penultimate update.");
1013 else {
1014 buffer.append("Invalid changes made to the document ");
1015 buffer.append(firstUpdate);
1016 buffer.append(" updates ago.");
1017 }
1018 }
1019 }
1020 String validation = buffer.length() > 0 ? buffer.toString() : null;
1021 if (validation != null) rows++;
1022
1023 buffer = new StringBuffer();
1024 int w = 0;
1025 int h = 0;
1026 if (bbox != null) {
1027 w = (int)(bbox.x1 - bbox.x0 + 0.5f);
1028 h = (int)(bbox.y1 - bbox.y0 + 0.5f);
1029 }
1030 buffer.append(w);
1031 buffer.append(" x ");
1032 buffer.append(h);
1033 String name = paperSizeName(w, h);
1034 if (name == null)
1035 name = paperSizeName(h, w);
1036 if (name != null)
1037 buffer.append("(" + name + ")");
1038 String paperSize = buffer.length() > 0 ? buffer.toString() : null;
1039 if (paperSize != null) rows++;
1040
1041 buffer = new StringBuffer();
1042 buffer.append(pageNumber + 1);
1043 buffer.append(" / ");
1044 buffer.append(pages);
1045 String page = buffer.length() > 0 ? buffer.toString() : null;
1046 if (page != null) rows++;
1047
1048 String iccstring = icc ? "on" : "off";
1049 rows++;
1050
1051 String antialiasstring = Integer.toString(antialias);
1052 rows++;
1053
1054 infoPanel.setLayout(new GridLayout(rows, 1));
1055
1056 if (title != null) infoPanel.add(new Label("Title: " + title));
1057 if (author != null) infoPanel.add(new Label("Author: " + author));
1058 if (format != null) infoPanel.add(new Label("Format: " + format));
1059 if (encryption != null) infoPanel.add(new Label("Encryption: " + encryption));
1060 if (permissions != null) infoPanel.add(new Label("Permissions: " + permissions));
1061 if (versions != null) infoPanel.add(new Label(versions));
1062 if (validation != null) infoPanel.add(new Label(validation));
1063 infoPanel.add(new Label("Size: " + paperSize));
1064 infoPanel.add(new Label("Page: " + page));
1065 infoPanel.add(new Label("ICC rendering: " + iccstring));
1066 infoPanel.add(new Label("Antialias rendering: " + antialiasstring));
1067
1068 Button button = new Button("OK");
1069 button.addActionListener(new ActionListener() {
1070 public void actionPerformed(ActionEvent event) {
1071 box.setVisible(false);
1072 }
1073 });
1074 button.addKeyListener(new KeyListener() {
1075 public void keyPressed(KeyEvent e) { }
1076 public void keyReleased(KeyEvent e) { }
1077 public void keyTyped(KeyEvent e) {
1078 if (e.getKeyChar() == '\u001b' || e.getKeyChar() == '\r' || e.getKeyChar() == '\n')
1079 box.setVisible(false);
1080 }
1081 });
1082
1083 Panel buttonPane = new Panel(new FlowLayout());
1084 buttonPane.add(button);
1085
1086 box.add(infoPanel, BorderLayout.CENTER);
1087 box.add(buttonPane, BorderLayout.SOUTH);
1088
1089 button.requestFocusInWindow();
1090
1091 box.setResizable(false);
1092 box.pack();
1093
1094 java.awt.Point winLoc = this.getLocation();
1095 Dimension winDim = this.getSize();
1096 int winCenterX = winLoc.x + winDim.width / 2;
1097 int winCenterY = winLoc.y + winDim.height / 2;
1098
1099 Dimension diagDim = box.getSize();
1100 int x = winCenterX - diagDim.width / 2;
1101 int y = winCenterY - diagDim.height / 2;
1102
1103 box.setLocation(x, y);
1104
1105 box.setVisible(true);
1106 box.dispose();
1107 }
1108
1109 protected void toggleFullscreen() {
1110 isFullscreen = !isFullscreen;
1111
1112 if (isFullscreen)
1113 setExtendedState(Frame.MAXIMIZED_BOTH);
1114 else
1115 setExtendedState(Frame.NORMAL);
1116 }
1117
1118 protected void mark(int number) {
1119 cancelSearch();
1120 if (number == 0)
1121 pushHistory();
1122 else if (number > 0 && number < marks.length)
1123 marks[number] = saveMark();
1124 }
1125
1126 protected void jumpHistoryBack(int number) {
1127 cancelSearch();
1128 if (number == 0) {
1129 if (historyCount > 0)
1130 popHistory();
1131 } else if (number > 0 && number < marks.length)
1132 restoreMark(marks[number]);
1133 }
1134
1135 protected void jumpHistoryForward(int number) {
1136 cancelSearch();
1137 if (number == 0) {
1138 if (futureCount > 0) {
1139 popFuture();
1140 }
1141 }
1142 }
1143
1144 protected Mark saveMark() {
1145 return new Mark(location);
1146 }
1147
1148 protected void restoreMark(Mark mark) {
1149 if (mark != null) {
1150 doc.gotoLocation(mark.loc, null);
1151 pageCanvas.requestFocusInWindow();
1152 }
1153 }
1154
1155 protected void pushHistory() {
1156 if (historyCount > 0 && location.equals(history[historyCount - 1].loc))
1157 {
1158 return;
1159 }
1160
1161 if (historyCount + 1 >= history.length) {
1162 for (int i = 0; i < history.length - 1; i++)
1163 history[i] = history[i + 1];
1164 history[historyCount] = saveMark();
1165 } else {
1166 history[historyCount++] = saveMark();
1167 }
1168 }
1169
1170 protected void pushFuture() {
1171 if (futureCount + 1 >= future.length) {
1172 for (int i = 0; i < future.length - 1; i++)
1173 future[i] = future[i + 1];
1174 future[futureCount] = saveMark();
1175 } else {
1176 future[futureCount++] = saveMark();
1177 }
1178 }
1179
1180 protected void clearFuture() {
1181 futureCount = 0;
1182 }
1183
1184 protected void popHistory() {
1185 Location here = location;
1186 pushFuture();
1187 while (historyCount > 0 && location.equals(here))
1188 restoreMark(history[--historyCount]);
1189 }
1190
1191 protected void popFuture() {
1192 Location here = location;
1193 pushHistory();
1194 while (futureCount > 0 && location.equals(here))
1195 restoreMark(future[--futureCount]);
1196 }
1197
1198 protected void pan(int panx, int pany) {
1199 Adjustable hadj = pageScroll.getHAdjustable();
1200 Adjustable vadj = pageScroll.getVAdjustable();
1201 int h = hadj.getValue();
1202 int v = vadj.getValue();
1203 int newh = h + panx;
1204 int newv = v + pany;
1205
1206 if (newh < hadj.getMinimum())
1207 newh = hadj.getMinimum();
1208 if (newh > hadj.getMaximum() - hadj.getVisibleAmount())
1209 newh = hadj.getMaximum() - hadj.getVisibleAmount();
1210 if (newv < vadj.getMinimum())
1211 newv = vadj.getMinimum();
1212 if (newv > vadj.getMaximum() - vadj.getVisibleAmount())
1213 newv = vadj.getMaximum() - vadj.getVisibleAmount();
1214
1215 if (newh == h && newv == v)
1216 return;
1217
1218 if (newh != h)
1219 hadj.setValue(newh);
1220 if (newv != v)
1221 vadj.setValue(newv);
1222 }
1223
1224 protected void smartMove(int direction, int moves) {
1225 cancelSearch();
1226
1227 if (moves < 1)
1228 moves = 1;
1229
1230 while (moves-- > 0)
1231 {
1232 Adjustable hadj = pageScroll.getHAdjustable();
1233 Adjustable vadj = pageScroll.getVAdjustable();
1234 int slop_x = hadj.getMaximum() / 20;
1235 int slop_y = vadj.getMaximum() / 20;
1236
1237 if (direction > 0) {
1238 int remaining_x = hadj.getMaximum() - hadj.getValue() - hadj.getVisibleAmount();
1239 int remaining_y = vadj.getMaximum() - vadj.getValue() - vadj.getVisibleAmount();
1240
1241 if (remaining_y > slop_y) {
1242 int value = vadj.getValue() + vadj.getVisibleAmount() * 9 / 10;
1243 if (value > vadj.getMaximum())
1244 value = vadj.getMaximum();
1245 vadj.setValue(value);
1246 } else if (remaining_x > slop_x) {
1247 vadj.setValue(vadj.getMinimum());
1248 int value = hadj.getValue() + hadj.getVisibleAmount() * 9 / 10;
1249 if (value > hadj.getMaximum())
1250 value = hadj.getMaximum();
1251 hadj.setValue(value);
1252 } else {
1253 doc.flipPages(+1, null);
1254 vadj.setValue(vadj.getMinimum());
1255 hadj.setValue(hadj.getMinimum());
1256 }
1257 } else {
1258 int remaining_x = Math.abs(hadj.getMinimum() - hadj.getValue());
1259 int remaining_y = Math.abs(vadj.getMinimum() - vadj.getValue());
1260
1261 if (remaining_y > slop_y) {
1262 int value = vadj.getValue() - vadj.getVisibleAmount() * 9 / 10;
1263 if (value < vadj.getMinimum())
1264 value = vadj.getMinimum();
1265 vadj.setValue(value);
1266 } else if (remaining_x > slop_x) {
1267 vadj.setValue(vadj.getMaximum());
1268 int value = hadj.getValue() - hadj.getVisibleAmount() * 9 / 10;
1269 if (value < hadj.getMinimum())
1270 value = hadj.getMinimum();
1271 hadj.setValue(value);
1272 } else {
1273 doc.flipPages(-1, null);
1274 vadj.setValue(vadj.getMaximum());
1275 hadj.setValue(hadj.getMaximum());
1276 }
1277 }
1278 }
1279 }
1280
1281 protected void flipPages(int number) {
1282 cancelSearch();
1283 doc.flipPages(number, null);
1284 }
1285
1286 protected void gotoPage(int number) {
1287 cancelSearch();
1288 doc.gotoPage(number - 1, null);
1289 }
1290
1291 protected void gotoLastPage() {
1292 cancelSearch();
1293 doc.gotoLastPage(null);
1294 }
1295
1296 protected void relayout(int change) {
1297 int newEm = layoutEm + change;
1298 if (newEm < 6)
1299 newEm = 6;
1300 if (newEm > 36)
1301 newEm = 36;
1302
1303 if (newEm == layoutEm)
1304 return;
1305
1306 layoutEm = newEm;
1307 fontSizeLabel.setText(String.valueOf(layoutEm));
1308 doc.relayoutDocument(layoutWidth, layoutHeight, layoutEm, null);
1309 }
1310
1311 public void actionPerformed(ActionEvent event) {
1312 Object source = event.getSource();
1313
1314 if (source == firstButton)
1315 doc.gotoFirstPage(null);
1316 if (source == lastButton)
1317 doc.gotoLastPage(null);
1318 if (source == prevButton)
1319 doc.flipPages(-1, null);
1320 if (source == nextButton)
1321 doc.flipPages(+1, null);
1322 if (source == pageField) {
1323 doc.gotoPage(Integer.parseInt(pageField.getText()) - 1, null);
1324 pageCanvas.requestFocusInWindow();
1325 }
1326
1327 if (source == searchField)
1328 search(searchDirection);
1329 if (source == searchNextButton)
1330 search(+1);
1331 if (source == searchPrevButton)
1332 search(-1);
1333
1334 if (source == fontIncButton && doc != null && reflowable)
1335 relayout(+1);
1336 if (source == fontDecButton && doc != null && reflowable)
1337 relayout(-1);
1338
1339 if (source == zoomOutButton) {
1340 zoomOut();
1341 pageCanvas.requestFocusInWindow();
1342 }
1343 if (source == zoomInButton) {
1344 zoomIn();
1345 pageCanvas.requestFocusInWindow();
1346 }
1347 }
1348
1349 public void itemStateChanged(ItemEvent event) {
1350 Object source = event.getSource();
1351 if (source == zoomChoice) {
1352 zoomToLevel(zoomChoice.getSelectedIndex());
1353 pageCanvas.requestFocusInWindow();
1354 }
1355 if (source == outlineList) {
1356 int i = outlineList.getSelectedIndex();
1357 doc.gotoLocation(outline[i].location, null);
1358 pageCanvas.requestFocusInWindow();
1359 }
1360 }
1361
1362 public void windowClosing(WindowEvent event) { dispose(); }
1363 public void windowActivated(WindowEvent event) { }
1364 public void windowDeactivated(WindowEvent event) { }
1365 public void windowIconified(WindowEvent event) { }
1366 public void windowDeiconified(WindowEvent event) { }
1367 public void windowOpened(WindowEvent event) { }
1368 public void windowClosed(WindowEvent event) { }
1369
1370 public void save() {
1371 cancelSearch();
1372
1373 SaveOptionsDialog dialog = new SaveOptionsDialog(this);
1374 dialog.populate();
1375 dialog.setLocationRelativeTo(this);
1376 dialog.setVisible(true);
1377 dialog.dispose();
1378
1379 final String options = dialog.getOptions();
1380 if (options == null)
1381 {
1382 pageCanvas.requestFocusInWindow();
1383 return;
1384 }
1385
1386 FileDialog fileDialog = new FileDialog(this, "MuPDF Save File", FileDialog.SAVE);
1387 fileDialog.setDirectory(System.getProperty("user.dir"));
1388 fileDialog.setFilenameFilter(new FilenameFilter() {
1389 public boolean accept(File dir, String name) {
1390 return Document.recognize(name);
1391 }
1392 });
1393 fileDialog.setFile(doc.documentPath);
1394 fileDialog.setVisible(true);
1395 fileDialog.dispose();
1396
1397 if (fileDialog.getFile() == null)
1398 {
1399 pageCanvas.requestFocusInWindow();
1400 return;
1401 }
1402
1403 final String selectedPath = new StringBuffer(fileDialog.getDirectory()).append(File.separatorChar).append(fileDialog.getFile()).toString();
1404 OCRmeter = new OCRProgressmeter(this, "Saving...", pages);
1405 OCRmeter.setLocationRelativeTo(this);
1406 OCRmeter.setVisible(true);
1407 pageCanvas.requestFocusInWindow();
1408
1409 if (options.indexOf("ocr-language=") < 0)
1410 doc.save(selectedPath, options, OCRmeter, new ViewerCore.OnException() {
1411 public void run(Throwable t) {
1412 if (t instanceof IOException)
1413 exception(t);
1414 else if (t instanceof RuntimeException && !OCRmeter.cancelled)
1415 exception(t);
1416 }
1417 });
1418 else
1419 {
1420 try {
1421 FileStream fs = new FileStream(selectedPath, "rw");
1422 doc.save(fs, options, OCRmeter, new ViewerCore.OnException() {
1423 public void run(Throwable t) {
1424 if (t instanceof RuntimeException && !OCRmeter.cancelled)
1425 exception(t);
1426 }
1427 });
1428 } catch (IOException e) {
1429 exception(e);
1430 }
1431 }
1432 }
1433
1434 public void onSaveComplete() {
1435 if (OCRmeter != null)
1436 OCRmeter.done();
1437 }
1438
1439 class SaveOptionsDialog extends Dialog implements ActionListener, ItemListener, KeyListener {
1440 Checkbox snapShot = new Checkbox("Snapshot", false);
1441 Checkbox highSecurity = new Checkbox("High security", false);
1442 Choice resolution = new Choice();
1443 TextField language = new TextField("eng");
1444 Checkbox incremental = new Checkbox("Incremental", false);
1445
1446 Checkbox prettyPrint = new Checkbox("Pretty print", false);
1447 Checkbox ascii = new Checkbox("Ascii", false);
1448 Checkbox decompress = new Checkbox("Decompress", false);
1449 Checkbox compress = new Checkbox("Compress", true);
1450 Checkbox compressImages = new Checkbox("Compress images", true);
1451 Checkbox compressFonts = new Checkbox("Compress fonts", true);
1452
1453 Checkbox linearize = new Checkbox("Linearize", false);
1454 Checkbox garbageCollect = new Checkbox("Garbage collect", false);
1455 Checkbox cleanSyntax = new Checkbox("Clean syntax", false);
1456 Checkbox sanitizeSyntax = new Checkbox("Sanitize syntax", false);
1457
1458 Choice encryption = new Choice();
1459 TextField userPassword = new TextField();
1460 TextField ownerPassword = new TextField();
1461
1462 Button cancel = new Button("Cancel");
1463 Button save = new Button("Save");
1464
1465 String options = null;
1466
1467 public SaveOptionsDialog(Frame parent) {
1468 super(parent, "MuPDF Save Options", true);
1469
1470 resolution.add("200dpi");
1471 resolution.add("300dpi");
1472 resolution.add("600dpi");
1473 resolution.add("1200dpi");
1474
1475 encryption.add("Keep");
1476 encryption.add("None");
1477 encryption.add("RC4, 40bit");
1478 encryption.add("RC4, 128bit");
1479 encryption.add("AES, 128bit");
1480 encryption.add("AES, 256bit");
1481
1482 snapShot.addItemListener(this);
1483 highSecurity.addItemListener(this);
1484 resolution.addItemListener(this);
1485 language.addActionListener(this);
1486 incremental.addItemListener(this);
1487 prettyPrint.addItemListener(this);
1488 ascii.addItemListener(this);
1489 decompress.addItemListener(this);
1490 compress.addItemListener(this);
1491 compressImages.addItemListener(this);
1492 compressFonts.addItemListener(this);
1493 linearize.addItemListener(this);
1494 garbageCollect.addItemListener(this);
1495 cleanSyntax.addItemListener(this);
1496 sanitizeSyntax.addItemListener(this);
1497
1498 encryption.addItemListener(this);
1499 userPassword.addActionListener(this);
1500 ownerPassword.addActionListener(this);
1501
1502 cancel.addActionListener(this);
1503 save.addActionListener(this);
1504 save.addKeyListener(this);
1505
1506 calculateOptions();
1507 }
1508
1509 void populate(Container container, GridBagConstraints c, Component component) {
1510 GridBagLayout gbl = (GridBagLayout) container.getLayout();
1511 gbl.setConstraints(component, c);
1512 container.add(component);
1513 }
1514
1515 void populate() {
1516 GridBagConstraints c = new GridBagConstraints();
1517
1518 c.fill = GridBagConstraints.BOTH;
1519 c.weightx = 1.0;
1520 c.gridwidth = GridBagConstraints.REMAINDER;
1521
1522 GridBagLayout gbl = new GridBagLayout();
1523 setLayout(gbl);
1524
1525 Panel left = new Panel();
1526 Panel right = new Panel();
1527 GridBagLayout lgbl = new GridBagLayout();
1528 GridBagLayout rgbl = new GridBagLayout();
1529 left.setLayout(lgbl);
1530 right.setLayout(rgbl);
1531
1532 populate(left, c, snapShot);
1533 populate(left, c, highSecurity);
1534 populate(left, c, resolution);
1535 populate(left, c, language);
1536 populate(left, c, incremental);
1537
1538 c.weighty = 1.5;
1539 populate(left, c, new Panel());
1540 c.weighty = 0.0;
1541
1542 populate(left, c, prettyPrint);
1543 populate(left, c, ascii);
1544 populate(left, c, decompress);
1545 populate(left, c, compress);
1546 populate(left, c, compressImages);
1547 populate(left, c, compressFonts);
1548
1549 populate(right, c, linearize);
1550 populate(right, c, garbageCollect);
1551 populate(right, c, cleanSyntax);
1552 populate(right, c, sanitizeSyntax);
1553
1554 c.weighty = 1.5;
1555 populate(right, c, new Panel());
1556 c.weighty = 0.0;
1557
1558 populate(right, c, new Label("Encryption"));
1559 populate(right, c, encryption);
1560 populate(right, c, new Label("User password"));
1561 populate(right, c, userPassword);
1562 populate(right, c, new Label("Owner password"));
1563 populate(right, c, ownerPassword);
1564
1565 c.gridwidth = GridBagConstraints.REMAINDER;
1566 populate(this, c, new Panel());
1567
1568 c.gridwidth = 1;
1569 populate(this, c, left);
1570 c.gridwidth = GridBagConstraints.REMAINDER;
1571 populate(this, c, right);
1572
1573 c.gridwidth = GridBagConstraints.REMAINDER;
1574 populate(this, c, new Panel());
1575
1576 c.gridwidth = 1;
1577 populate(this, c, cancel);
1578 c.gridwidth = GridBagConstraints.REMAINDER;
1579 populate(this, c, save);
1580
1581 pack();
1582 setResizable(false);
1583 save.requestFocusInWindow();
1584 }
1585
1586 public void keyPressed(KeyEvent e) { }
1587 public void keyReleased(KeyEvent e) { }
1588
1589 public void keyTyped(KeyEvent e) {
1590 if (e.getKeyChar() == '\u001b')
1591 cancel();
1592 else if (e.getKeyChar() == '\n')
1593 save();
1594 }
1595
1596 public void actionPerformed(ActionEvent e) {
1597 if (e.getSource() == cancel)
1598 cancel();
1599 else if (e.getSource() == save)
1600 save();
1601 }
1602
1603 void cancel() {
1604 options = null;
1605 setVisible(false);
1606 }
1607
1608 void save() {
1609 setVisible(false);
1610 }
1611
1612 public void itemStateChanged(ItemEvent e) {
1613 calculateOptions();
1614 }
1615
1616 void calculateOptions() {
1617 boolean isPDF = false;
1618 boolean canBeSavedIncrementally = false;
1619 boolean isRedacted = false;
1620
1621 if (isPDF && !canBeSavedIncrementally)
1622 incremental.setState(false);
1623
1624 if (highSecurity.getState()) {
1625 incremental.setState(false);
1626 prettyPrint.setState(false);
1627 ascii.setState(false);
1628 decompress.setState(false);
1629 compress.setState(true);
1630 compressImages.setState(false);
1631 compressFonts.setState(false);
1632 linearize.setState(false);
1633 garbageCollect.setState(false);
1634 cleanSyntax.setState(false);
1635 sanitizeSyntax.setState(false);
1636 encryption.select("None");
1637 userPassword.setText("");
1638 ownerPassword.setText("");
1639 } else if (incremental.getState()) {
1640 linearize.setState(false);
1641 garbageCollect.setState(false);
1642 cleanSyntax.setState(false);
1643 sanitizeSyntax.setState(false);
1644 encryption.select("Keep");
1645 userPassword.setText("");
1646 ownerPassword.setText("");
1647 }
1648
1649 highSecurity.setEnabled(snapShot.getState() == false);
1650 resolution.setEnabled(snapShot.getState() == false && highSecurity.getState() == true);
1651 language.setEnabled(snapShot.getState() == false && highSecurity.getState() == true);
1652 incremental.setEnabled(snapShot.getState() == false && highSecurity.getState() == false && isPDF && canBeSavedIncrementally);
1653 prettyPrint.setEnabled(snapShot.getState() == false && highSecurity.getState() == false);
1654 ascii.setEnabled(snapShot.getState() == false && highSecurity.getState() == false);
1655 decompress.setEnabled(snapShot.getState() == false && highSecurity.getState() == false);
1656 compress.setEnabled(snapShot.getState() == false && highSecurity.getState() == false);
1657 compressImages.setEnabled(snapShot.getState() == false && highSecurity.getState() == false);
1658 compressFonts.setEnabled(snapShot.getState() == false && highSecurity.getState() == false);
1659 linearize.setEnabled(snapShot.getState() == false && highSecurity.getState() == false && incremental.getState() == false);
1660 garbageCollect.setEnabled(snapShot.getState() == false && highSecurity.getState() == false && incremental.getState() == false);
1661 cleanSyntax.setEnabled(snapShot.getState() == false && highSecurity.getState() == false && incremental.getState() == false);
1662 sanitizeSyntax.setEnabled(snapShot.getState() == false && highSecurity.getState() == false && incremental.getState() == false);
1663 encryption.setEnabled(snapShot.getState() == false && highSecurity.getState() == false && incremental.getState() == false);
1664 userPassword.setEnabled(snapShot.getState() == false && highSecurity.getState() == false && incremental.getState() == false && encryption.getSelectedItem() != "Keep" && encryption.getSelectedItem() != "None");
1665 ownerPassword.setEnabled(snapShot.getState() == false && highSecurity.getState() == false && incremental.getState() == false && encryption.getSelectedItem() != "Keep" && encryption.getSelectedItem() != "None");
1666
1667 if (incremental.getState()) {
1668 garbageCollect.setState(false);
1669 linearize.setState(false);
1670 cleanSyntax.setState(false);
1671 sanitizeSyntax.setState(false);
1672 encryption.select("Keep");
1673 }
1674
1675 StringBuilder opts = new StringBuilder();
1676 if (highSecurity.getState()) {
1677 opts.append(",compression=flate");
1678 opts.append(",resolution=");
1679 opts.append(resolution.getSelectedItem());
1680 opts.append(",ocr-language=");
1681 opts.append(language.getText());
1682 } else {
1683 if (decompress.getState()) opts.append(",decompress=yes");
1684 if (compress.getState()) opts.append(",compress=yes");
1685 if (compressFonts.getState()) opts.append(",compress-fonts=yes");
1686 if (compressImages.getState()) opts.append(",compress-images=yes");
1687 if (ascii.getState()) opts.append(",ascii=yes");
1688 if (prettyPrint.getState()) opts.append(",pretty=yes");
1689 if (linearize.getState()) opts.append(",linearize=yes");
1690 if (cleanSyntax.getState()) opts.append(",clean=yes");
1691 if (sanitizeSyntax.getState()) opts.append(",sanitize=yes");
1692 if (encryption.getSelectedItem() == "None") opts.append(",decrypt=yes");
1693 if (encryption.getSelectedItem() == "Keep") opts.append(",decrypt=no");
1694 if (encryption.getSelectedItem() == "None") opts.append(",encrypt=no");
1695 if (encryption.getSelectedItem() == "Keep") opts.append(",encrypt=keep");
1696 if (encryption.getSelectedItem() == "RC4, 40bit") opts.append(",encrypt=rc4-40");
1697 if (encryption.getSelectedItem() == "RC4, 128bit") opts.append(",encrypt=rc4-128");
1698 if (encryption.getSelectedItem() == "AES, 128bit") opts.append(",encrypt=aes-128");
1699 if (encryption.getSelectedItem() == "AES, 256bit") opts.append(",encrypt=aes-256");
1700 if (userPassword.getText().length() > 0) {
1701 opts.append(",user-password=");
1702 opts.append(userPassword.getText());
1703 }
1704 if (ownerPassword.getText().length() > 0) {
1705 opts.append(",owner-password=");
1706 opts.append(ownerPassword.getText());
1707 }
1708 opts.append(",permissions=-1");
1709 if (garbageCollect.getState() && isPDF && isRedacted)
1710 opts.append(",garbage=yes");
1711 else
1712 opts.append(",garbage=compact");
1713 }
1714
1715 if (opts.charAt(0) == ',')
1716 opts.deleteCharAt(0);
1717
1718 options = opts.toString();
1719 }
1720
1721 String getOptions() {
1722 return options;
1723 }
1724 }
1725
1726 class Progressmeter extends Dialog implements ActionListener, KeyListener {
1727 Label info = new Label("", Label.CENTER);
1728 Button cancel = new Button("Cancel");
1729 boolean cancelled = false;
1730 boolean done = false;
1731
1732 public Progressmeter(Frame parent, String title, boolean modal, String initialText) {
1733 super(parent, title, modal);
1734
1735 setLayout(new GridLayout(2, 1));
1736
1737 info.setText(initialText);
1738 add(info);
1739
1740 cancel.addActionListener(this);
1741 cancel.addKeyListener(this);
1742 add(cancel);
1743
1744 pack();
1745 setResizable(false);
1746 cancel.requestFocusInWindow();
1747 }
1748
1749 public void actionPerformed(ActionEvent e) {
1750 if (e.getSource() == cancel)
1751 cancel();
1752 }
1753
1754 public void keyPressed(KeyEvent e) { }
1755 public void keyReleased(KeyEvent e) { }
1756
1757 public void keyTyped(KeyEvent e) {
1758 if (e.getKeyChar() == '\u001b')
1759 cancel();
1760 }
1761
1762 public void cancel() {
1763 cancelled = true;
1764 }
1765
1766 public void done() {
1767 done = true;
1768 }
1769
1770 public boolean progress(String text) {
1771 info.setText(text);
1772 return cancelled || done;
1773 }
1774 }
1775
1776 class OCRProgressmeter extends Progressmeter implements DocumentWriter.OCRListener {
1777 int pages;
1778
1779 public OCRProgressmeter(Frame parent, String title, int pages) {
1780 super(parent, title, true, "Progress: Page 65535/65535: 100%");
1781 this.pages = pages;
1782 progress(-1, 0);
1783 setVisible(true);
1784 }
1785
1786 public void done() {
1787 super.done();
1788 setVisible(false);
1789 dispose();
1790 }
1791
1792 public boolean progress(int page, int percent) {
1793 StringBuilder text = new StringBuilder();
1794
1795 if (page >= 0 || pages >= 0) {
1796 text.append("Page ");
1797 if (page >= 0)
1798 text.append(page + 1);
1799 else
1800 text.append("?");
1801 }
1802 if (pages >= 0) {
1803 text.append("/");
1804 text.append(pages);
1805 text.append(": ");
1806 }
1807
1808 text.append(percent);
1809 text.append("%");
1810
1811 return progress(text.toString());
1812 }
1813 }
1814
1815 class RenderProgressmeter extends Progressmeter {
1816 Cookie cookie;
1817
1818 public RenderProgressmeter(Frame parent, String title, Cookie cookie, final int update) {
1819 super(parent, title, false, "Progress: 100%");
1820 this.cookie = cookie;
1821
1822 (new Thread() {
1823 public void run() {
1824 try {
1825 int slept = 0;
1826 while (!progress(slept))
1827 {
1828 sleep(update);
1829 slept += update;
1830 }
1831 } catch (InterruptedException e) {
1832 }
1833 }
1834 }).start();
1835 }
1836
1837 public void cancel() {
1838 super.cancel();
1839 cookie.abort();
1840 }
1841
1842 public boolean progress(int slept) {
1843 int progress = cookie.getProgress();
1844 int max = cookie.getProgressMax();
1845
1846 if (max <= 0 && progress < 100)
1847 max = 100;
1848 else if (max <= 0 && progress > 100)
1849 {
1850 int v = progress;
1851 max = 10;
1852 while (v > 10)
1853 {
1854 v /= 10;
1855 max *= 10;
1856 }
1857 }
1858
1859 if (progress >= max)
1860 done = true;
1861
1862 int percent = (int) ((float) progress / max * 100.0f);
1863
1864 StringBuilder text = new StringBuilder();
1865 text.append("Progress: ");
1866 text.append(percent);
1867 text.append("%");
1868
1869 if (slept > 0)
1870 setVisible(true);
1871
1872 if (progress(text.toString()))
1873 {
1874 setVisible(false);
1875 dispose();
1876 return true;
1877 }
1878
1879 return false;
1880 }
1881 }
1882
1883 public static void main(String[] args) {
1884 String selectedPath;
1885
1886 if (args.length <= 0) {
1887 FileDialog fileDialog = new FileDialog((Frame)null, "MuPDF Open File", FileDialog.LOAD);
1888 fileDialog.setDirectory(System.getProperty("user.dir"));
1889 fileDialog.setFilenameFilter(new FilenameFilter() {
1890 public boolean accept(File dir, String name) {
1891 return Document.recognize(name);
1892 }
1893 });
1894 fileDialog.setVisible(true);
1895 if (fileDialog.getFile() == null)
1896 System.exit(0);
1897 selectedPath = new StringBuffer(fileDialog.getDirectory()).append(File.separatorChar).append(fileDialog.getFile()).toString();
1898 fileDialog.dispose();
1899 } else {
1900 selectedPath = args[0];
1901 }
1902
1903 try {
1904 Viewer app = new Viewer(selectedPath);
1905 app.setVisible(true);
1906 } catch (Exception e) {
1907 messageBox(null, "MuPDF Error", "Cannot open \"" + selectedPath + "\": " + e.getMessage() + ".");
1908 System.exit(1);
1909 }
1910 }
1911
1912 public float getRetinaScale() {
1913 // first try Oracle's VM (we should also test for 1.7.0_40 or higher)
1914 final String vendor = System.getProperty("java.vm.vendor");
1915 boolean isOracle = vendor != null && vendor.toLowerCase().contains("Oracle".toLowerCase());
1916 if (isOracle) {
1917 GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
1918 final GraphicsDevice device = env.getDefaultScreenDevice();
1919 try {
1920 Field field = device.getClass().getDeclaredField("scale");
1921 if (field != null) {
1922 field.setAccessible(true);
1923 Object scale = field.get(device);
1924 if (scale instanceof Integer && ((Integer)scale).intValue() == 2)
1925 return 2.0f;
1926 }
1927 }
1928 catch (Exception ignore) {
1929 }
1930 return 1.0f;
1931 }
1932
1933 // try Apple VM
1934 final Float scaleFactor = (Float)Toolkit.getDefaultToolkit().getDesktopProperty("apple.awt.contentScaleFactor");
1935 if (scaleFactor != null && scaleFactor.intValue() == 2)
1936 return 2.0f;
1937
1938 return 1.0f;
1939 }
1940
1941 public int getScreenDPI() {
1942 try {
1943 return Toolkit.getDefaultToolkit().getScreenResolution();
1944 } catch (HeadlessException e) {
1945 return 72;
1946 }
1947 }
1948
1949 public Dimension getScreenSize() {
1950 try {
1951 return Toolkit.getDefaultToolkit().getScreenSize();
1952 } catch (HeadlessException e) {
1953 return new Dimension(1920, 1080);
1954 }
1955 }
1956
1957 protected static class OutlineItem {
1958 protected String title;
1959 protected String uri;
1960 protected int page;
1961 public OutlineItem(String title, String uri, int page) {
1962 this.title = title;
1963 this.uri = uri;
1964 this.page = page;
1965 }
1966 public String toString() {
1967 return title;
1968 }
1969 }
1970
1971 public String askPassword() {
1972 return passwordDialog(null, "Password");
1973 }
1974 public void onChapterCountChange(int chapters) {
1975 this.chapters = chapters;
1976 }
1977 public void onPageCountChange(int pages) {
1978 this.pages = pages;
1979 pageLabel.setText("/ " + pages);
1980 }
1981 public void onPageChange(Location page, int chapterNumber, int pageNumber, Rect bbox) {
1982 this.location = page;
1983 this.chapterNumber = chapterNumber;
1984 this.pageNumber = pageNumber;
1985 this.bbox = bbox;
1986 if (pageNumber >= 0 && pageNumber < pages)
1987 pageField.setText(String.valueOf(pageNumber + 1));
1988 else
1989 pageField.setText("");
1990 render();
1991 }
1992 public void onReflowableChange(boolean reflowable) {
1993 this.reflowable = reflowable;
1994 fontIncButton.setEnabled(reflowable);
1995 fontDecButton.setEnabled(reflowable);
1996 fontSizeLabel.setEnabled(reflowable);
1997 }
1998 public void onLayoutChange(int width, int height, int em) {
1999 }
2000 public void onOutlineChange(ViewerCore.OutlineItem[] outline) {
2001 boolean hadOutline = this.outline != null;
2002 this.outline = outline;
2003 outlineList.removeAll();
2004 if (outline != null)
2005 for (int i = 0; i < outline.length; i++)
2006 outlineList.add(outline[i].title);
2007 if (hadOutline)
2008 outlinePanel.setVisible(outline != null);
2009 }
2010 public void onPageContentsChange(Pixmap pixmap, Rect[] links, String[] linkURIs, Quad[][] hits) {
2011 this.pixmap = pixmap;
2012 this.links = links;
2013 this.linkURIs = linkURIs;
2014 this.hits = hits;
2015 redraw();
2016 if (renderMeter != null)
2017 renderMeter.done();
2018 }
2019 public void onSearchStart(Location startPage, Location finalPage, int direction, String needle) {
2020 searchField.setEnabled(false);
2021 }
2022 public void onSearchPage(Location page, String needle) {
2023 String text;
2024 if (chapters > 1)
2025 text = "Searching " + (page.chapter + 1) + "/" + chapters + "-" + page.page + "/" + pages;
2026 else
2027 text = "Searching " + page.page + "/" + pages;
2028 searchStatus.setText(text);
2029 searchStatusPanel.validate();
2030 }
2031 public void onSearchStop(String needle, Location page) {
2032 searchField.setEnabled(true);
2033 searchNextButton.setEnabled(true);
2034 searchPrevButton.setEnabled(true);
2035 if (page != null) {
2036 doc.gotoLocation(page, null);
2037 searchStatus.setText("");
2038 } else if (needle != null)
2039 searchStatus.setText("Search text not found.");
2040 else
2041 searchStatus.setText("");
2042 searchStatusPanel.validate();
2043 pageCanvas.requestFocusInWindow();
2044 }
2045 public void onSearchCancelled() {
2046 searchField.setEnabled(true);
2047 searchNextButton.setEnabled(true);
2048 searchPrevButton.setEnabled(true);
2049 searchStatus.setText("");
2050 searchStatusPanel.validate();
2051 }
2052 public void onOutlineItemChange(int index) {
2053 if (index == -1) {
2054 int selected = outlineList.getSelectedIndex();
2055 if (selected >= 0)
2056 outlineList.deselect(selected);
2057 } else {
2058 outlineList.makeVisible(index);
2059 outlineList.select(index);
2060 }
2061 }
2062 public void onMetadataChange(String title, String author, String format, String encryption) {
2063 this.title = title;
2064 this.author = author;
2065 this.format = format;
2066 this.encryption = encryption;
2067 }
2068 public void onPermissionsChange(boolean print, boolean copy, boolean edit, boolean annotate, boolean form, boolean accessibility, boolean assemble, boolean printHq) {
2069 this.print = print;
2070 this.copy = copy;
2071 this.edit = edit;
2072 this.annotate = annotate;
2073 this.form = form;
2074 this.accessibility = accessibility;
2075 this.assemble = assemble;
2076 this.printHq = printHq;
2077 }
2078 public void onLinearizedChange(boolean linearized) {
2079 this.linearized = linearized;
2080 }
2081 public void onUpdatesChange(int updates, int firstUpdate) {
2082 this.updates = updates;
2083 this.firstUpdate = firstUpdate;
2084 }
2085 }