diff options
author | Ethan Vrhel <ethanvrhel@gmail.com> | 2020-08-20 14:36:48 -0700 |
---|---|---|
committer | evrhel <ethanvrhel@gmail.com> | 2020-09-24 15:23:44 -0700 |
commit | eda31b70496337caefd0dc912c5a0850de769147 (patch) | |
tree | 6a10dec0787d35e3b7f3af954b54c108468ff0ec | |
parent | 5b08d9eadfe64d87cf26869c47a2a5f78755dd4b (diff) | |
download | ghostpdl-eda31b70496337caefd0dc912c5a0850de769147.tar.gz |
Updated how zooming is handled
Zooming is now done through the SmartLoader and is no longer
a separate operation. Fixed an issue when the SmartLoader could
become out of date due to the condition variable being signaled
before the SmartLoader had finished.
4 files changed, 137 insertions, 122 deletions
diff --git a/demos/java/gsviewer/src/com/artifex/gsviewer/DefaultUnhandledExceptionHandler.java b/demos/java/gsviewer/src/com/artifex/gsviewer/DefaultUnhandledExceptionHandler.java index 9e31a2e03..6ae6389fe 100644 --- a/demos/java/gsviewer/src/com/artifex/gsviewer/DefaultUnhandledExceptionHandler.java +++ b/demos/java/gsviewer/src/com/artifex/gsviewer/DefaultUnhandledExceptionHandler.java @@ -4,6 +4,10 @@ import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.PrintStream; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Enumeration; +import java.util.Properties; public class DefaultUnhandledExceptionHandler implements Thread.UncaughtExceptionHandler { @@ -22,7 +26,38 @@ public class DefaultUnhandledExceptionHandler implements Thread.UncaughtExceptio public void uncaughtException(Thread t, Throwable e) { StringBuilder builder = new StringBuilder(); + DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); builder.append("Unhandled exception!\n"); + + //builder.append("USER\n"); + builder.append("Time: " + dtf.format(LocalDateTime.now()) + "\n\n"); + + builder.append("PROPERTIES\n"); + Properties props = System.getProperties(); + for (Enumeration<Object> keys = props.keys(); keys.hasMoreElements();) { + String key = keys.nextElement().toString(); + String prop = props.getProperty(key); + String friendly = "\""; + for (int i = 0; i < prop.length(); i++) { + char c = prop.charAt(i); + switch (c) { + case '\n': + friendly += "\\n"; + break; + case '\r': + friendly += "\\r"; + break; + default: + friendly += c; + break; + } + } + friendly += "\""; + builder.append(key + " = " + friendly + "\n"); + } + builder.append('\n'); + + builder.append("EXCEPTION\n"); builder.append("Exception: " + e.getClass().getName() + "\n"); builder.append("Message: " + e.getMessage() + "\n"); builder.append("Cause: " + (e.getCause() == null ? "[Unknown]" : @@ -30,6 +65,7 @@ public class DefaultUnhandledExceptionHandler implements Thread.UncaughtExceptio builder.append("Thread: " + t.getName() + " (id=" + t.getId() + ")\n"); builder.append("Stack trace:" + "\n"); builder.append(stackTraceToString(e)); + String fullMessage = builder.toString(); System.err.println(fullMessage); try { diff --git a/demos/java/gsviewer/src/com/artifex/gsviewer/Document.java b/demos/java/gsviewer/src/com/artifex/gsviewer/Document.java index 5e2c4af91..4b07c5b8d 100644 --- a/demos/java/gsviewer/src/com/artifex/gsviewer/Document.java +++ b/demos/java/gsviewer/src/com/artifex/gsviewer/Document.java @@ -231,11 +231,6 @@ public class Document implements List<Page> { if (code != GS_ERROR_OK) throw new IllegalStateException("Failed to distill document (code = " + code + ")"); - - /*Reference<Integer> pExitCode = new Reference<>(); - int code = gsInstance.run_file(out.getAbsolutePath(), 0, pExitCode); - if (code != GS_ERROR_OK) - throw new IllegalStateException("failed to run file (code = " + code + ")");*/ } finally { deleteGSInstance(); } @@ -682,77 +677,6 @@ public class Document implements List<Page> { } /** - * Loads the high resolution images of a list of pages. - * - * @param operationMode How the loader should behave if an operation is already in - * progress. Can be <code>OPERATION_WAIT</code>, meaning the loader should - * wait until a running operation has finished, <code>OPERATION_RETURN</code> meaning - * the loader should immediately return, or <code>OPERATION_THROW</code> meaning the loader - * should throw an <code>OperationInProgressException</code>. - * @param pages The pages to load. - * - * @throws IndexOutOfBoundsException When any page is not a page in the document. - * @throws IllegalStateException When Ghostscript fails to initialize or load the document. - * @throws OperationInProgressException When an operation is already in progress - * and <code>operationMode</code> is <code>OPERATION_THROW</code>. - * @throws IllegalArgumentException When <code>operationMode</code> is not - * <code>OPERATION_WAIT</coded>, <code>OPERATION_RETURN</code>, or <code>OPERATION_THROW</code>. - */ - public void loadHighResList(int operationMode, final int[] pages) - throws IndexOutOfBoundsException, IllegalStateException, OperationInProgressException { - throw new UnsupportedOperationException("loadHighResList"); - /*if (pages.length > 0) { - if (!handleOperationMode(operationMode)) - return; - - try { - startOperation(); - - final StringBuilder builder = new StringBuilder(); - if (pages[0] < 1 || pages[0] > this.pages.size()) - throw new IndexOutOfBoundsException("page=" + pages[0]); - builder.append(pages[0]); - - for (int i = 1; i < pages.length; i++) { - if (pages[i] < 1 || pages[i] > this.pages.size()) - throw new IndexOutOfBoundsException("page=" + pages[i]); - builder.append(',').append(pages[i]); - } - - - final String[] gargs = { "gs", "-dNOPAUSE", "-dSAFER", "-I%rom%Resource%/Init/", "-dBATCH", "-r" + Page.PAGE_HIGH_DPI, - "-sDEVICE=display", "-sPageList=" + builder.toString(), "-dDisplayFormat=" + format, - "-dTextAlphaBits=4", "-dGraphicsAlphaBits=4", - "-f", file.getAbsolutePath() }; - - GSInstance instance = null; - try { - instance = initDocInstance(); - } catch (IllegalStateException e) { - operationDone(); - } - - if (instance == null) - throw new IllegalStateException("Failed to initialize Ghoscript"); - - int code = instance.init_with_args(gargs); - instance.exit(); - instance.delete_instance(); - if (code != GS_ERROR_OK) { - throw new IllegalStateException("Failed to gsapi_init_with_args code=" + code); - } - - int ind = 0; - for (final BufferedImage img : documentLoader.images) { - this.pages.get(pages[ind] - 1).setHighRes(img); - } - } finally { - operationDone(); - } - }*/ - } - - /** * Unloads the high resolution images in a range of pages. * * @param startPage The start page to unload the high resolution image from. @@ -885,6 +809,34 @@ public class Document implements List<Page> { } } + public void zoomPage(final int operationMode, final int page, final double zoom) + throws IndexOutOfBoundsException { + checkBounds(page, page); + + if (!handleOperationMode(operationMode)) + return; + + try { + startOperation(); + + setParams((int)(Page.PAGE_HIGH_DPI * zoom), page, page); + + Reference<Integer> exitCode = new Reference<>(); + int code = gsInstance.run_file(file.getAbsolutePath(), 0, exitCode); + + if (code != GS_ERROR_OK) + throw new IllegalStateException("Failed to run file (code = " + code + ")"); + + int ind = page - 1; + for (final BufferedImage img : documentLoader.images) { + this.pages.get(ind++).setZoomed(img); + } + + } finally { + operationDone(); + } + } + /** * Checks to make sure the pages <code>start</code> through * <code>end</code> are in the document, and throws an diff --git a/demos/java/gsviewer/src/com/artifex/gsviewer/ViewerController.java b/demos/java/gsviewer/src/com/artifex/gsviewer/ViewerController.java index 39b3aae7f..f062ab949 100644 --- a/demos/java/gsviewer/src/com/artifex/gsviewer/ViewerController.java +++ b/demos/java/gsviewer/src/com/artifex/gsviewer/ViewerController.java @@ -24,6 +24,7 @@ public class ViewerController implements ViewerGUIListener { private static final Lock lock = new ReentrantLock(); private static final Condition cv = lock.newCondition(); + private static volatile boolean outOfDate = true; private ViewerWindow source; private Document currentDocument; @@ -38,10 +39,14 @@ public class ViewerController implements ViewerGUIListener { try { JFileChooser chooser = new JFileChooser(); chooser.setCurrentDirectory(new File(".")); + chooser.setFileFilter(PDFFileFilter.INSTANCE); ret = chooser.showSaveDialog(source); if (ret != JFileChooser.APPROVE_OPTION) return; File out = chooser.getSelectedFile(); + String filepath = out.getAbsolutePath(); + if (filepath.lastIndexOf('.') == -1) + out = new File(filepath + ".pdf"); if (out.exists()) { ret = source.showConfirmDialog("Overwrite?", out.getName() + " already exists. Overwrite?"); if (ret != ViewerWindow.YES) @@ -98,46 +103,21 @@ public class ViewerController implements ViewerGUIListener { @Override public void onPageChange(int oldPage, int newPage) { - lock.lock(); - try { - cv.signalAll(); - } catch (IllegalMonitorStateException e) { - System.err.println("Exception on signaling: " + e); - } finally { - lock.unlock(); - } + smartLoader.signalOutOfDate(); } @Override public void onZoomChange(double oldZoom, double newZoom) { if (newZoom > 1.0) { - int currentPage = source.getCurrentPage(); - Runnable r = () -> { - //source.showWarningDialog("Error", "An operation is already in progress"); - //return; - - try { - currentDocument.zoomArea(Document.OPERATION_THROW, currentPage, newZoom); - } catch (Document.OperationInProgressException e) { - source.showWarningDialog("Error", "An operation is already in progress"); - } - }; - Thread t = new Thread(r); - t.setName("Zoom-Thread"); - t.start(); + smartLoader.resetZoom(); + smartLoader.signalOutOfDate(); } } @Override public void onScrollChange(int newScroll) { - lock.lock(); - try { - cv.signalAll(); - } catch (IllegalMonitorStateException e) { - System.err.println("Exception on signaling: " + e); - } finally { - lock.unlock(); - } + if (smartLoader != null) + smartLoader.signalOutOfDate(); } @Override @@ -184,7 +164,6 @@ public class ViewerController implements ViewerGUIListener { source.showErrorDialog("Unhandled Exception", errorMessage); } DefaultUnhandledExceptionHandler.INSTANCE.uncaughtException(t, e); - } } @@ -192,15 +171,17 @@ public class ViewerController implements ViewerGUIListener { private class SmartLoader implements Runnable { private volatile boolean[] loaded; + private volatile boolean[] zoomLoaded; private volatile boolean shouldRun; private Thread thread; - private SmartLoader(Document doc) { + SmartLoader(Document doc) { loaded = new boolean[doc.size()]; + zoomLoaded = new boolean[doc.size()]; shouldRun = true; } - private void start() { + void start() { if (thread != null) stop(); shouldRun = true; @@ -210,11 +191,12 @@ public class ViewerController implements ViewerGUIListener { thread.start(); } - private void stop() { - shouldRun = false; + void stop() { lock.lock(); cv.signalAll(); lock.unlock(); + outOfDate = false; + shouldRun = false; try { thread.join(); } catch (InterruptedException e) { @@ -223,6 +205,24 @@ public class ViewerController implements ViewerGUIListener { thread = null; } + void resetZoom() { + for (int i = 0; i < zoomLoaded.length; i++) { + zoomLoaded[i] = false; + } + } + + void signalOutOfDate() { + outOfDate = true; + lock.lock(); + try { + cv.signalAll(); + } catch (IllegalMonitorStateException e) { + System.err.println("Exception on signaling: " + e); + } finally { + lock.unlock(); + } + } + @Override public void run() { System.out.println("Smart loader dispatched."); @@ -238,9 +238,23 @@ public class ViewerController implements ViewerGUIListener { int ind = 0; for (int page : toLoad) { if (page >= 1 && page <= currentDocument.size()) { - if (!loaded[page - 1]) { - currentDocument.loadHighRes(Document.OPERATION_WAIT, page); - loaded[page - 1] = true; + if (source.getZoom() > 1.0) { + + // Load the zoomed page view only if it has not already been loaded. + if (!zoomLoaded[page - 1]) { + currentDocument.zoomArea(Document.OPERATION_WAIT, page, source.getZoom()); + zoomLoaded[page - 1] = true; + } + } else { + + // Unload any zoomed image to save memory consumption + currentDocument.unloadZoomed(page); + + // Load the high-resolution page view only if it has not already been loaded + if (!loaded[page - 1]) { + currentDocument.loadHighRes(Document.OPERATION_WAIT, page); + loaded[page - 1] = true; + } } } ind++; @@ -248,13 +262,20 @@ public class ViewerController implements ViewerGUIListener { } source.setLoadProgress(0); - lock.lock(); - try { - cv.await(); - } catch (InterruptedException e) { - System.err.println("Interrupted in smart loader: " + e); - } finally { - lock.unlock(); + // First check if the current view is out of date and if so, just immediately continue + // so the thread does not get stuck in the lock until another event has occurred. + if (!outOfDate) { + outOfDate = false; + lock.lock(); + try { + cv.await(); + } catch (InterruptedException e) { + System.err.println("Interrupted in smart loader: " + e); + } finally { + lock.unlock(); + } + } else { + outOfDate = false; } } } diff --git a/demos/java/gsviewer/src/com/artifex/gsviewer/gui/ViewerWindow.java b/demos/java/gsviewer/src/com/artifex/gsviewer/gui/ViewerWindow.java index 0b701ddc3..9ada11a62 100644 --- a/demos/java/gsviewer/src/com/artifex/gsviewer/gui/ViewerWindow.java +++ b/demos/java/gsviewer/src/com/artifex/gsviewer/gui/ViewerWindow.java @@ -496,8 +496,10 @@ public class ViewerWindow extends javax.swing.JFrame { img = page.getZoomedImage(); Image result = img; - if (img == page.getLowResImage() || currentZoom < 1.0) + if (img == page.getLowResImage() || currentZoom < 1.0 || + (img == page.getHighResImage() && currentZoom > 1.0 && page.getZoomedImage() == null)) { result = img.getScaledInstance(actualSize.width, actualSize.height, Image.SCALE_FAST); + } return result; } else { return null; @@ -790,6 +792,10 @@ public class ViewerWindow extends javax.swing.JFrame { return JOptionPane.showConfirmDialog(this, message, title, JOptionPane.YES_NO_CANCEL_OPTION); } + public double getZoom() { + return currentZoom; + } + /** * Refresh each button's state. */ |