diff options
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) { |