summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEthan Vrhel <ethanvrhel@gmail.com>2020-08-03 15:41:20 -0700
committerevrhel <ethanvrhel@gmail.com>2020-09-24 15:23:39 -0700
commitcb2be4f3b0635efd4616f567389b19a7351d078b (patch)
treeeceef537d0cc46e9b066211e25b05c24fda3cdce
parentf81dfc177f71d0d12b38371fae54b45e158833ce (diff)
downloadghostpdl-cb2be4f3b0635efd4616f567389b19a7351d078b.tar.gz
Added thread safety to the viewer.
The viewer now ensures that no more than one ghostscript call is happening at the same time and has multiple options for controlling the behavior if multiple operations are attempting to be called. Also began work on a utility class to make Ghostscript calls easier by having a class carry around a instance and a caller handle.
-rw-r--r--demos/java/gsjava/src/com/artifex/gsjava/GSAPI.java16
-rw-r--r--demos/java/gsjava/src/com/artifex/gsjava/GSInstance.java117
-rw-r--r--demos/java/gsjava/src/com/artifex/gsjava/util/AllocationError.java6
-rw-r--r--demos/java/gsjava/src/com/artifex/gsjava/util/BytePointer.java7
-rw-r--r--demos/java/gsjava/src/com/artifex/gsjava/util/StringUtil.java19
-rw-r--r--demos/java/gsviewer/src/com/artifex/gsviewer/Document.java335
-rw-r--r--demos/java/gsviewer/src/com/artifex/gsviewer/PageUpdateCallback.java15
-rw-r--r--demos/java/gsviewer/src/com/artifex/gsviewer/ViewerController.java105
-rw-r--r--demos/java/gsviewer/src/com/artifex/gsviewer/gui/ViewerWindow.java5
9 files changed, 467 insertions, 158 deletions
diff --git a/demos/java/gsjava/src/com/artifex/gsjava/GSAPI.java b/demos/java/gsjava/src/com/artifex/gsjava/GSAPI.java
index accaa08a7..f1cb06bcf 100644
--- a/demos/java/gsjava/src/com/artifex/gsjava/GSAPI.java
+++ b/demos/java/gsjava/src/com/artifex/gsjava/GSAPI.java
@@ -111,6 +111,12 @@ public class GSAPI {
public static final long DISPLAY_FIRSTROW_MASK = 0x00020000L;
+ /**
+ * Class used to store version information about Ghostscript.
+ *
+ * @author Ethan Vrhel
+ *
+ */
public static class Revision {
public volatile byte[] product;
public volatile byte[] copyright;
@@ -124,10 +130,20 @@ public class GSAPI {
this.revisionDate = 0L;
}
+ /**
+ * Returns the product information as a String.
+ *
+ * @return The product information.
+ */
public String getProduct() {
return new String(product);
}
+ /**
+ * Returns the copyright information as a String.
+ *
+ * @return The copyright information.
+ */
public String getCopyright() {
return new String(copyright);
}
diff --git a/demos/java/gsjava/src/com/artifex/gsjava/GSInstance.java b/demos/java/gsjava/src/com/artifex/gsjava/GSInstance.java
new file mode 100644
index 000000000..9d6a349e1
--- /dev/null
+++ b/demos/java/gsjava/src/com/artifex/gsjava/GSInstance.java
@@ -0,0 +1,117 @@
+package com.artifex.gsjava;
+
+import static com.artifex.gsjava.GSAPI.*;
+
+import java.util.List;
+
+import com.artifex.gsjava.callbacks.DisplayCallback;
+import com.artifex.gsjava.callbacks.ICalloutFunction;
+import com.artifex.gsjava.callbacks.IPollFunction;
+import com.artifex.gsjava.callbacks.IStdErrFunction;
+import com.artifex.gsjava.callbacks.IStdInFunction;
+import com.artifex.gsjava.callbacks.IStdOutFunction;
+import com.artifex.gsjava.util.ByteArrayReference;
+import com.artifex.gsjava.util.IntReference;
+import com.artifex.gsjava.util.LongReference;
+
+/**
+ * Utility class to make Ghostscript calls easier by automatically storing a
+ * Ghostscript instance and, optionally, a caller handle.
+ *
+ * @author Ethan Vrhel
+ *
+ */
+public class GSInstance {
+
+ private long instance;
+ private long callerHandle;
+
+ public GSInstance(long callerHandle) throws IllegalStateException {
+ LongReference ref = new LongReference();
+ int ret = gsapi_new_instance(ref, callerHandle);
+ if (ret != GS_ERROR_OK)
+ throw new IllegalStateException("Failed to create new instance: " + ret);
+ this.instance = ref.value;
+ this.callerHandle = callerHandle;
+ }
+
+ public GSInstance() throws IllegalStateException {
+ this(GS_NULL);
+ }
+
+ public void deleteInstance() {
+ gsapi_delete_instance(instance);
+ instance = GS_NULL;
+ }
+
+ public int setStdio(IStdInFunction stdin, IStdOutFunction stdout, IStdErrFunction stderr) {
+ return gsapi_set_stdio_with_handle(instance, stdin, stdout, stderr, callerHandle);
+ }
+
+ public int setPoll(IPollFunction pollfun) {
+ return gsapi_set_poll_with_handle(instance, pollfun, callerHandle);
+ }
+
+ public int setDisplayCallback(DisplayCallback displaycallback) {
+ return gsapi_set_display_callback(instance, displaycallback);
+ }
+
+ public int registerCallout(ICalloutFunction callout) {
+ return gsapi_register_callout(instance, callout, callerHandle);
+ }
+
+ public void deregisterCallout(ICalloutFunction callout) {
+ gsapi_deregister_callout(instance, callout, callerHandle);
+ }
+
+ public int setArgEncoding(int encoding) {
+ return gsapi_set_arg_encoding(instance, encoding);
+ }
+
+ public int setDefaultDeviceList(byte[] list, int listlen) {
+ return gsapi_set_default_device_list(instance, list, listlen);
+ }
+
+ public int getDefaultDeviceList(ByteArrayReference list, IntReference listlen) {
+ return gsapi_get_default_device_list(instance, list, listlen);
+ }
+
+ public int initWithArgs(int argc, byte[][] argv) {
+ return gsapi_init_with_args(instance, argc, argv);
+ }
+
+ public int initWithArgs(String[] argv) {
+ return gsapi_init_with_args(instance, argv);
+ }
+
+ public int initWithArgs(List<String> argv) {
+ return gsapi_init_with_args(instance, argv);
+ }
+
+ public int runStringBegin(int userErrors, IntReference pExitCode) {
+ return gsapi_run_string_begin(instance, userErrors, pExitCode);
+ }
+
+ @Override
+ public void finalize() {
+ deleteInstance();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null)
+ return false;
+ if (o == this)
+ return true;
+ if (o instanceof GSInstance) {
+ GSInstance g = (GSInstance)o;
+ return g.instance == instance && g.callerHandle == callerHandle;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "GSInstance[instance=0x" + Long.toHexString(instance) + "]";
+ }
+}
diff --git a/demos/java/gsjava/src/com/artifex/gsjava/util/AllocationError.java b/demos/java/gsjava/src/com/artifex/gsjava/util/AllocationError.java
index 22f4feb48..d2f6c99fa 100644
--- a/demos/java/gsjava/src/com/artifex/gsjava/util/AllocationError.java
+++ b/demos/java/gsjava/src/com/artifex/gsjava/util/AllocationError.java
@@ -1,5 +1,11 @@
package com.artifex.gsjava.util;
+/**
+ * An error thrown when native memory allocation fails.
+ *
+ * @author Ethan Vrhel
+ *
+ */
public class AllocationError extends Error {
private static final long serialVersionUID = 1L;
diff --git a/demos/java/gsjava/src/com/artifex/gsjava/util/BytePointer.java b/demos/java/gsjava/src/com/artifex/gsjava/util/BytePointer.java
index 261fe3692..8b63254e5 100644
--- a/demos/java/gsjava/src/com/artifex/gsjava/util/BytePointer.java
+++ b/demos/java/gsjava/src/com/artifex/gsjava/util/BytePointer.java
@@ -1,5 +1,12 @@
package com.artifex.gsjava.util;
+/**
+ * A byte pointer stores a pointer to a memory location containing
+ * bytes.
+ *
+ * @author Ethan Vrhel
+ *
+ */
public class BytePointer extends NativePointer implements NativeArray<Byte> {
private long length;
diff --git a/demos/java/gsjava/src/com/artifex/gsjava/util/StringUtil.java b/demos/java/gsjava/src/com/artifex/gsjava/util/StringUtil.java
index d2904e08a..0183ad81f 100644
--- a/demos/java/gsjava/src/com/artifex/gsjava/util/StringUtil.java
+++ b/demos/java/gsjava/src/com/artifex/gsjava/util/StringUtil.java
@@ -1,7 +1,19 @@
package com.artifex.gsjava.util;
+/**
+ * Contains utility methods to work with Strings.
+ *
+ * @author Ethan Vrhel
+ *
+ */
public class StringUtil {
+ /**
+ * Converts a Java String to a null-terminated byte array.
+ *
+ * @param str The string to convert.
+ * @return The result byte array.
+ */
public static byte[] toNullTerminatedByteArray(final String str) {
final byte[] barray = str.getBytes();
final byte[] result = new byte[barray.length + 1];
@@ -9,6 +21,13 @@ public class StringUtil {
return result;
}
+ /**
+ * Converts an array of Strings to a 2D byte array of null-terminated
+ * strings.
+ *
+ * @param strs The strings to convert.
+ * @return THe result 2D byte array.
+ */
public static byte[][] to2DByteArray(final String[] strs){
final byte[][] array = new byte[strs.length][];
for (int i = 0; i < strs.length; i++) {
diff --git a/demos/java/gsviewer/src/com/artifex/gsviewer/Document.java b/demos/java/gsviewer/src/com/artifex/gsviewer/Document.java
index 1f0deda68..c32b49514 100644
--- a/demos/java/gsviewer/src/com/artifex/gsviewer/Document.java
+++ b/demos/java/gsviewer/src/com/artifex/gsviewer/Document.java
@@ -12,9 +12,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import com.artifex.gsjava.callbacks.DisplayCallback;
@@ -23,23 +21,47 @@ import com.artifex.gsjava.util.LongReference;
import com.artifex.gsviewer.ImageUtil.ImageParams;
/**
- * A Document stores an ordered list of Pages.
+ * <p>A Document stores an ordered list of Pages. This class implements
+ * <code>java.util.List</code>, so it inherits all of a list's capabilities,
+ * such as the ability to iterate over the document.</p>
+ *
+ * <p>The Document class also handles loading of pages through Ghostcript
+ * and ensuring that two Ghostscript operations are not running at the
+ * same time.</p>
*
* @author Ethan Vrhel
*
*/
public class Document implements List<Page> {
- private static final Object slock = new Object();
- private static final DocumentLoader documentLoader = new DocumentLoader();
- private static final int format = GS_COLORS_RGB | GS_DISPLAY_DEPTH_8 | GS_DISPLAY_BIGENDIAN;
+ private static final DocumentLoader documentLoader = new DocumentLoader(); // The document loader
+ private static final int format = GS_COLORS_RGB | GS_DISPLAY_DEPTH_8 | GS_DISPLAY_BIGENDIAN; // Format
- private static final ReentrantLock operationLock = new ReentrantLock();
- private static final Condition operationCv = operationLock.newCondition();
- private static final AtomicBoolean operationInProgress = new AtomicBoolean(false);
+ private static final ReentrantLock operationLock = new ReentrantLock(); // The operation lock
+ private static final Condition operationCv = operationLock.newCondition(); // The operation condition variable
+ private static volatile boolean operationInProgress = false; // Whether an operation is in progress
+ /**
+ * Asynchronous execution dispatch returns.
+ */
public static final int ASYNC_DISPATCH_OK = 0, ASYNC_DISPATCH_OPERATION_IN_PROGRESS = 1;
- public static final int OPERATION_WAIT = 0, OPERATION_RETURN = 1, OPERATION_THROW = 2;
+
+ /**
+ * Operation mode indicating the method should wait until a running operation has finished.
+ */
+ public static final int OPERATION_WAIT = 0;
+
+ /**
+ * Operation mode indicating the method should immediately return if an operation is already
+ * running.
+ */
+ public static final int OPERATION_RETURN = 1;
+
+ /**
+ * Operation mode indicating the method should throw an <code>OperationInProgressException</code>
+ * if an operation is already running.
+ */
+ public static final int OPERATION_THROW = 2;
/**
* Loads a document on a separate thread.
@@ -175,24 +197,41 @@ public class Document implements List<Page> {
return loadDocumentAsync(filename, cb, null, operationMode);
}
+ /**
+ * Call, internally, on the start of an operation.
+ */
private static void startOperation() {
- operationInProgress.set(true);
+ operationInProgress = true;
}
+ /**
+ * Call, internally, on the start of an operation, before <code>startOperation()</code>.
+ *
+ * @param operationMode The operation mode.
+ * @return Whether the calling function should continue to its operation.
+ * @throws OperationInProgressException When an operation is in progress and <code>operationMode</code>
+ * is <code>OPERATION_THROW</code>. This should not be caught by the calling function, who should
+ * throw it instead.
+ * @throws IllegalArgumentException When <code>operationMode</code> is not a valid operation mode.
+ */
private static boolean handleOperationMode(final int operationMode)
throws OperationInProgressException, IllegalArgumentException {
- boolean inProgress = operationInProgress.get();
- if (inProgress && operationMode == OPERATION_RETURN)
+ if (operationInProgress && operationMode == OPERATION_RETURN)
return false;
- else if (inProgress && operationMode == OPERATION_THROW)
- throw new OperationInProgressException("loadDocument");
- else if (inProgress && operationMode == OPERATION_WAIT)
+ else if (operationInProgress && operationMode == OPERATION_THROW) {
+ StackTraceElement[] elems = new Exception().getStackTrace();
+ throw new OperationInProgressException(elems[1].getClassName() + "." + elems[1].getMethodName());
+ } else if (operationInProgress && operationMode == OPERATION_WAIT)
waitForOperation();
- else if (inProgress)
+ else if (operationInProgress)
throw new IllegalArgumentException("Unknown operation mode: " + operationMode);
return true;
}
+ /**
+ * Called internally by <code>handleOperationMode()</code>. Waits for an operation
+ * to finish.
+ */
private static void waitForOperation() {
operationLock.lock();
try {
@@ -204,11 +243,14 @@ public class Document implements List<Page> {
}
}
+ /**
+ * Call, internally, when an operation is done.
+ */
private static void operationDone() {
operationLock.lock();
- operationInProgress.set(false);
+ operationInProgress = false;
try {
- operationCv.notify();
+ operationCv.signal();
} finally {
operationLock.unlock();
}
@@ -255,6 +297,12 @@ public class Document implements List<Page> {
}
}
+ /**
+ * Initializes an instance of Ghostcript.
+ *
+ * @param instanceRef A reference to the instance of Ghostscript.
+ * @throws IllegalStateException When any Ghostscript operation fails to execute.
+ */
private static void initDocInstance(LongReference instanceRef) throws IllegalStateException {
int code = gsapi_new_instance(instanceRef, GS_NULL);
if (code != GS_ERROR_OK) {
@@ -275,6 +323,12 @@ public class Document implements List<Page> {
}
}
+ /**
+ * Class which handles loading a Document.
+ *
+ * @author Ethan Vrhel
+ *
+ */
private static class DocumentLoader extends DisplayCallback {
private int pageWidth, pageHeight, pageRaster;
private BytePointer pimage;
@@ -337,9 +391,10 @@ public class Document implements List<Page> {
* @throws FileNotFoundException When <code>file</code> does not exist.
* @throws IllegalStateException When Ghostscript fails to initialize or load the document.
* @throws NullPointerException When <code>file</code> is <code>null</code>.
- * @throws OperationInProgressException When an operation is already in progress.
+ * @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>ASYNC_OPERTAION_WAIT</coded> or <code>ASYNC_OPERATION_RETURN</code>.
+ * <code>OPERATION_WAIT</coded>, <code>OPERATION_RETURN</code>, or <code>OPERATION_THROW</code>.
*/
public Document(final File file, final DocLoadProgressCallback loadCallback, int operationMode)
throws FileNotFoundException, IllegalStateException, NullPointerException,
@@ -388,11 +443,19 @@ public class Document implements List<Page> {
* Creates and loads a document from a filename.
*
* @param file The file to load.
+ * @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>.
*
* @throws FileNotFoundException When <code>file</code> does not exist.
* @throws IllegalStateException When Ghostscript fails to initialize or load the document.
* @throws NullPointerException When <code>file</code> is <code>null</code>.
- * @throws OperationInProgressException When an operation is already in progress.
+ * @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 Document(final File file, final int operationMode)
throws FileNotFoundException, IllegalStateException, NullPointerException,
@@ -405,11 +468,19 @@ public class Document implements List<Page> {
*
* @param filename The name of the file to load.
* @param loadCallback The callback to indicate when progress has occurred while loading.
+ * @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>.
*
* @throws FileNotFoundException When <code>file</code> does not exist.
* @throws IllegalStateException When Ghostscript fails to initialize or load the document.
* @throws NullPointerException When <code>file</code> is <code>null</code>.
- * @throws OperationInProgressException When an operation is already in progress.
+ * @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 Document(final String filename, final DocLoadProgressCallback loadCallback,
final int operationMode)
@@ -422,19 +493,37 @@ public class Document implements List<Page> {
* Creates and loads a document from a filename.
*
* @param filename The name of the file to load.
- * @throws FileNotFoundException If the given filename does not exist.
- * @throws NullPointerException If <code>filename</code> is <code>null</code>.
+ * @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>.
+ *
+ * @throws FileNotFoundException When <code>filename</code> does not exist.
+ * @throws IllegalStateException When Ghostscript fails to initialize or load the document.
+ * @throws NullPointerException When <code>filename</code> is <code>null</code>.
+ * @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 Document(final String filename, final int operationMode)
- throws FileNotFoundException, NullPointerException {
+ throws FileNotFoundException, IllegalStateException, NullPointerException,
+ OperationInProgressException {
this(filename, null, operationMode);
}
/**
* Loads the high resolution images in a range of images.
*
+ * @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 startPage The first page to load.
* @param endPage The end page to load.
+ *
* @throws IndexOutOfBoundsException When <code>firstPage</code> or <code>endPage</code>
* are not in the document or <code>endPage</code> is less than <code>firstPage</code>.
* @throws IllegalStateException When Ghostscript fails to initialize or load the document.
@@ -476,45 +565,68 @@ public class Document implements List<Page> {
/**
* Loads the high resolution image of a singular page.
*
+ * @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 page The page to load.
+ *
* @throws IndexOutOfBoundsException When <code>page</code> is not in the document.
- * @throws IllegalStateException When Ghostscript fails to initialize or load the
- * images.
+ * @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 loadHighRes(int operationMode, int page)
- throws IndexOutOfBoundsException, IllegalStateException {
- loadHighRes(page, page, operationMode);
+ throws IndexOutOfBoundsException, IllegalStateException, OperationInProgressException {
+ loadHighRes(operationMode, page, 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
- * images.
+ * @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 {
+ public void loadHighResList(int operationMode, final int[] pages)
+ throws IndexOutOfBoundsException, IllegalStateException, OperationInProgressException {
if (pages.length > 0) {
- 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]);
- }
+ if (!handleOperationMode(operationMode))
+ return;
+ try {
+ startOperation();
- 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() };
+ 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() };
- synchronized (slock) {
LongReference instanceRef = new LongReference();
initDocInstance(instanceRef);
@@ -529,6 +641,8 @@ public class Document implements List<Page> {
for (final BufferedImage img : documentLoader.images) {
this.pages.get(pages[ind] - 1).setHighRes(img);
}
+ } finally {
+ operationDone();
}
}
}
@@ -538,6 +652,7 @@ public class Document implements List<Page> {
*
* @param startPage The start page to unload the high resolution image from.
* @param endPage The end page to unload the high resolution image from.
+ *
* @throws IndexOutOfBoundsException When <code>startPage</code> and <code>endPage</code>
* are outside document or <code>endPage</code> is less than <code>startPage</code>.
*/
@@ -552,21 +667,22 @@ public class Document implements List<Page> {
* Unloads a high resolution image at the given page.
*
* @param page The page to unload the high resolution image.
+ *
* @throws IndexOutOfBoundsException When page is not a page in the document.
*/
public void unloadHighRes(int page) throws IndexOutOfBoundsException {
unloadHighRes(page, page);
}
- public void loadZoomed(int operationMode, int startPage, int endPage, double scale)
- throws IndexOutOfBoundsException {
- checkBounds(startPage, endPage);
- }
-
- public void loadZoomed(int operationMode, int page, double scale) throws IndexOutOfBoundsException {
-
- }
-
+ /**
+ * Unloads the zoomed images for a range of pages.
+ *
+ * @param startPage The start page to unload.
+ * @param endPage The end page to unload.
+ *
+ * @throws IndexOutOfBoundsException When <code>startPage</code> and <code>endPage</code>
+ * are outside document or <code>endPage</code> is less than <code>startPage</code>.
+ */
public void unloadZoomed(int startPage, int endPage) throws IndexOutOfBoundsException {
checkBounds(startPage, endPage);
for (int i = startPage; i <= endPage; i++) {
@@ -574,6 +690,13 @@ public class Document implements List<Page> {
}
}
+ /**
+ * Unloads a zoomed image for a page.
+ *
+ * @param page The page to unload.
+ *
+ * @throws IndexOutOfBoundsException When <code>page</code> is not in the document.
+ */
public void unloadZoomed(int page) throws IndexOutOfBoundsException {
unloadZoomed(page, page);
}
@@ -598,50 +721,80 @@ public class Document implements List<Page> {
return file.getName();
}
- public void zoomArea(final int operationMode, final int page, final double zoom) {
+ /**
+ * Zooms the area around page <code>page</code>. The area around <code>page</code>
+ * is <code>page - 1</code> to <code>page + 1</code>. The method will automatically
+ * handle if <code>page - 1</code> or <code>page + 1</code> are out of range. However,
+ * it does not handle if <code>page</code> is not in the document.
+ *
+ * @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 page The page to load around.
+ * @param zoom The zoom of the pages.
+ *
+ * @throws IndexOutOfBoundsException When <code>page</code> is not 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 zoomArea(final int operationMode, final int page, final double zoom)
+ throws IndexOutOfBoundsException {
checkBounds(page, page);
if (!handleOperationMode(operationMode))
return;
- startOperation();
-
- int start = page - 1;
- start = start < 1 ? page : start;
- int end = page + 1;
- end = end > pages.size() ? page : end;
+ try {
+ startOperation();
+
+ int start = page - 1;
+ start = start < 1 ? page : start;
+ int end = page + 1;
+ end = end > pages.size() ? page : end;
+
+ final String[] gargs = {
+ "gs", "-dNOPAUSE", "-dSAFER", "-I%rom%Resource%/Init/",
+ "-dBATCH", "-r" + (int)(Page.PAGE_HIGH_DPI * zoom),
+ "-sDEVICE=display", "-dFirstPage=" + start, "-dLastPage=" + end,
+ "-dDisplayFormat=" + format,
+ "-dTextAlphaBits=4", "-dGraphicsAlphaBits=4",
+ "-f", file.getAbsolutePath() };
- final String[] gargs = {
- "gs", "-dNOPAUSE", "-dSAFER", "-I%rom%Resource%/Init/",
- "-dBATCH", "-r" + (int)(Page.PAGE_HIGH_DPI * zoom),
- "-sDEVICE=display", "-dFirstPage=" + start, "-dLastPage=" + end,
- "-dDisplayFormat=" + format,
- "-dTextAlphaBits=4", "-dGraphicsAlphaBits=4",
- "-f", file.getAbsolutePath() };
+ LongReference instanceRef = new LongReference();
+ initDocInstance(instanceRef);
- LongReference instanceRef = new LongReference();
- initDocInstance(instanceRef);
+ int code = gsapi_init_with_args(instanceRef.value, gargs);
+ gsapi_exit(instanceRef.value);
+ gsapi_delete_instance(instanceRef.value);
+ if (code != GS_ERROR_OK) {
+ operationDone();
+ throw new IllegalStateException("Failed to gsapi_init_with_args code=" + code);
+ }
- int code = gsapi_init_with_args(instanceRef.value, gargs);
- gsapi_exit(instanceRef.value);
- gsapi_delete_instance(instanceRef.value);
- if (code != GS_ERROR_OK) {
+ int ind = start - 1;
+ for (final BufferedImage img : documentLoader.images) {
+ this.pages.get(ind++).setZoomed(img);
+ }
+ } finally {
operationDone();
- throw new IllegalStateException("Failed to gsapi_init_with_args code=" + code);
}
-
- int ind = start - 1;
- for (final BufferedImage img : documentLoader.images) {
- this.pages.get(ind++).setZoomed(img);
- }
-
- operationDone();
- }
-
- public void unZoomPage(final int page) {
- checkBounds(page, page);
}
+ /**
+ * Checks to make sure the pages <code>start</code> through
+ * <code>end</code> are in the document, and throws an
+ * <code>IndexOutOfBoundsException</code> if they are not.
+ *
+ * @param start The start page.
+ * @param end The end page.
+ * @throws IndexOutOfBoundsException When <code>startPage</code> and <code>endPage</code>
+ * are outside document or <code>endPage</code> is less than <code>startPage</code>.
+ */
private void checkBounds(int start, int end) throws IndexOutOfBoundsException {
if (start < 1 || start > pages.size())
throw new IndexOutOfBoundsException("start=" + start);
@@ -651,6 +804,8 @@ public class Document implements List<Page> {
throw new IndexOutOfBoundsException("end < start");
}
+ // Implementations of inherited methods from java.util.List.
+
@Override
public int size() {
return pages.size();
diff --git a/demos/java/gsviewer/src/com/artifex/gsviewer/PageUpdateCallback.java b/demos/java/gsviewer/src/com/artifex/gsviewer/PageUpdateCallback.java
new file mode 100644
index 000000000..6efaf69e8
--- /dev/null
+++ b/demos/java/gsviewer/src/com/artifex/gsviewer/PageUpdateCallback.java
@@ -0,0 +1,15 @@
+package com.artifex.gsviewer;
+
+public interface PageUpdateCallback {
+
+ public void onPageUpdate();
+
+ public void onLoadLowRes();
+ public void onUnloadLowRes();
+
+ public void onLoadHighRes();
+ public void onUnloadHighRes();
+
+ public void onLoadZoomed();
+ public void onUnloadZoomed();
+}
diff --git a/demos/java/gsviewer/src/com/artifex/gsviewer/ViewerController.java b/demos/java/gsviewer/src/com/artifex/gsviewer/ViewerController.java
index 361cba2cf..c0b6b464c 100644
--- a/demos/java/gsviewer/src/com/artifex/gsviewer/ViewerController.java
+++ b/demos/java/gsviewer/src/com/artifex/gsviewer/ViewerController.java
@@ -25,8 +25,6 @@ public class ViewerController implements ViewerGUIListener {
private static final Lock lock = new ReentrantLock();
private static final Condition cv = lock.newCondition();
- private static final AtomicBoolean operationInProgress = new AtomicBoolean(false);
-
private ViewerWindow source;
private Document currentDocument;
@@ -35,32 +33,18 @@ public class ViewerController implements ViewerGUIListener {
close();
Document.loadDocumentAsync(file, (final Document doc, final Exception exception) -> {
// Don't allow multiple ghostscript operations at once
- if (operationInProgress.get()) {
- source.showWarningDialog("Error", "An operation is already in progress");
- return;
- }
-
- operationInProgress.set(true);
-
+ //source.showWarningDialog("Error", "An operation is already in progress");
+ source.loadDocumentToViewer(doc);
source.setLoadProgress(0);
if (exception != null) {
source.showErrorDialog("Failed to load", exception.toString());
- operationInProgress.set(false);
} else {
this.currentDocument = doc;
- source.loadDocumentToViewer(doc);
-
- operationInProgress.set(false);
-
- synchronized (operationInProgress) {
- operationInProgress.notifyAll();
- }
-
dispatchSmartLoader();
}
}, (int progress) -> {
source.setLoadProgress(progress);
- });
+ }, Document.OPERATION_RETURN);
}
public void close() {
@@ -98,18 +82,13 @@ public class ViewerController implements ViewerGUIListener {
if (newZoom > 1.0) {
int currentPage = source.getCurrentPage();
Runnable r = () -> {
- if (operationInProgress.get()) {
- source.showWarningDialog("Error", "An operation is already in progress");
- return;
- }
-
- operationInProgress.set(true);
-
- currentDocument.zoomArea(currentPage, newZoom);
+ //source.showWarningDialog("Error", "An operation is already in progress");
+ //return;
- operationInProgress.set(false);
- synchronized (operationInProgress) {
- operationInProgress.notifyAll();
+ 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);
@@ -155,7 +134,33 @@ public class ViewerController implements ViewerGUIListener {
public void onSettingsOpen() { }
private void dispatchSmartLoader() {
- Runnable r = () -> {
+ Thread t = new Thread(new SmartLoader());
+ t.setDaemon(true);
+ t.setName("Document-Smart-Loader-Thread");
+ t.start();
+ }
+
+ private class UnhandledExceptionHandler implements Thread.UncaughtExceptionHandler {
+
+ @Override
+ public void uncaughtException(Thread t, Throwable e) {
+ if (source != null) {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ PrintStream out = new PrintStream(os);
+ e.printStackTrace(out);
+ String errorMessage = new String(os.toByteArray());
+ source.showErrorDialog("Unhandled Exception", errorMessage);
+ }
+ DefaultUnhandledExceptionHandler.INSTANCE.uncaughtException(t, e);
+ }
+
+ }
+
+ private class SmartLoader implements Runnable {
+
+ @Override
+ public void run() {
+ System.out.println("Smart loader dispatched.");
boolean[] loaded = new boolean[currentDocument.size()];
Document doc;
while ((doc = source.getLoadedDocument()) != null) {
@@ -164,25 +169,11 @@ public class ViewerController implements ViewerGUIListener {
currentPage, currentPage - 1, currentPage + 1,
currentPage - 2, currentPage + 2 };
- if (operationInProgress.get()) {
- synchronized (operationInProgress) {
- while (operationInProgress.get()) {
- try {
- operationInProgress.wait();
- } catch (InterruptedException e) {
- source.showErrorDialog("Error", e.toString());
- }
- }
- }
- }
-
- operationInProgress.set(true);
-
int ind = 0;
for (int page : toLoad) {
if (page >= 1 && page <= doc.size()) {
if (!loaded[page - 1]) {
- doc.loadHighRes(page);
+ doc.loadHighRes(Document.OPERATION_WAIT, page);
loaded[page - 1] = true;
}
}
@@ -191,8 +182,6 @@ public class ViewerController implements ViewerGUIListener {
}
source.setLoadProgress(0);
- operationInProgress.set(false);
-
lock.lock();
try {
cv.await();
@@ -204,26 +193,6 @@ public class ViewerController implements ViewerGUIListener {
lock.unlock();
}
}
- };
- Thread t = new Thread(r);
- t.setDaemon(true);
- t.setName("Document-Smart-Loader-Thread");
- t.start();
- }
-
- private class UnhandledExceptionHandler implements Thread.UncaughtExceptionHandler {
-
- @Override
- public void uncaughtException(Thread t, Throwable e) {
- if (source != null) {
- ByteArrayOutputStream os = new ByteArrayOutputStream();
- PrintStream out = new PrintStream(os);
- e.printStackTrace(out);
- String errorMessage = new String(os.toByteArray());
- source.showErrorDialog("Unhandled Exception", errorMessage);
- }
- DefaultUnhandledExceptionHandler.INSTANCE.uncaughtException(t, e);
}
-
}
}
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 03c1ba0bd..9ecc9d44e 100644
--- a/demos/java/gsviewer/src/com/artifex/gsviewer/gui/ViewerWindow.java
+++ b/demos/java/gsviewer/src/com/artifex/gsviewer/gui/ViewerWindow.java
@@ -644,6 +644,7 @@ public class ViewerWindow extends javax.swing.JFrame {
increaseZoomButton.setEnabled(true);
decreaseZoomButton.setEnabled(true);
zoomSlider.setValue(50);
+ zoomSlider.setFocusable(true);
nextPageButton.setEnabled(true);
lastPageButton.setEnabled(true);
@@ -659,6 +660,10 @@ public class ViewerWindow extends javax.swing.JFrame {
this.scrollMap = null;
this.assumePage(0);
this.assumeMaxPages(0);
+ this.zoomSlider.setValue(50);
+ this.zoomSlider.setEnabled(false);
+ this.zoomSlider.setFocusable(false);
+ this.pageNumberField.setEditable(false);
setTitle("Viewer");
for (final PagePanel panel : viewerPagePanels) {