summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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) {