diff --git a/gradle.properties b/gradle.properties index 55b8d93..0398880 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group = org.xbib.graphics name = graphics -version = 4.3.3 +version = 4.3.4 org.gradle.warning.mode = ALL diff --git a/gradle/test/junit5.gradle b/gradle/test/junit5.gradle index c7913c0..9d3ff2b 100644 --- a/gradle/test/junit5.gradle +++ b/gradle/test/junit5.gradle @@ -7,13 +7,13 @@ dependencies { test { useJUnitPlatform() - failFast = false + failFast = true environment 'TMPDIR', '/var/tmp/gs' file('/var/tmp/gs').mkdirs() systemProperty 'java.awt.headless', 'true' systemProperty 'java.io.tmpdir', '/var/tmp/' systemProperty 'jna.tmpdir', '/var/tmp/' - systemProperty 'jna.debug', 'false' + systemProperty 'jna.debug', 'true' systemProperty 'java.util.logging.config.file', 'src/test/resources/logging.properties' testLogging { events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED' diff --git a/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/FontAnalyzer.java b/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/FontAnalyzer.java index b7f5ee0..d8b5952 100644 --- a/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/FontAnalyzer.java +++ b/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/FontAnalyzer.java @@ -23,8 +23,8 @@ public class FontAnalyzer { "-sFile=" + path.toAbsolutePath(), "-sOutputFile=%stdout", "-f", "-"}; - Ghostscript gs = Ghostscript.getInstance(); - try (InputStream is = this.getClass().getClassLoader().getResourceAsStream("script/AnalyzePDFFonts.ps"); + try (Ghostscript gs = new Ghostscript(); + InputStream is = this.getClass().getClassLoader().getResourceAsStream("script/AnalyzePDFFonts.ps"); ByteArrayOutputStream baos = new ByteArrayOutputStream()) { gs.setStdIn(is); gs.setStdOut(baos); @@ -56,8 +56,6 @@ public class FontAnalyzer { } } return result; - } finally { - gs.close(); } } } diff --git a/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/Ghostscript.java b/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/Ghostscript.java index ef07044..4208a46 100644 --- a/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/Ghostscript.java +++ b/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/Ghostscript.java @@ -2,6 +2,8 @@ package org.xbib.graphics.ghostscript; import com.sun.jna.Pointer; import com.sun.jna.ptr.IntByReference; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; import org.xbib.graphics.ghostscript.internal.ErrorCodes; import org.xbib.graphics.ghostscript.internal.GhostscriptLibrary; import org.xbib.graphics.ghostscript.internal.GhostscriptLibraryLoader; @@ -23,13 +25,13 @@ import java.util.logging.Logger; /** * The Ghostscript native library API. */ -public class Ghostscript { +public class Ghostscript implements AutoCloseable { private static final Logger logger = Logger.getLogger(Ghostscript.class.getName()); public static final String ENCODING_PARAMETER = "org.xbib.graphics.ghostscript.encoding"; - private static Ghostscript INSTANCE; + private final GhostscriptLibrary libraryInstance; private InputStream stdIn; @@ -37,19 +39,16 @@ public class Ghostscript { private OutputStream stdErr; - private GhostscriptLibrary libraryInstance; + private final AtomicBoolean closed; - private Ghostscript(GhostscriptLibrary libraryInstance) { - this.libraryInstance = libraryInstance; + public Ghostscript() throws IOException { + prepareTmp(); + GhostscriptLibraryLoader libraryLoader = new GhostscriptLibraryLoader(); + this.libraryInstance = libraryLoader.getGhostscriptLibrary(); + Objects.requireNonNull(this.libraryInstance); this.stdOut = new LoggingOutputStream(logger); this.stdErr = new LoggingOutputStream(logger); - } - - public synchronized static Ghostscript getInstance() { - if (INSTANCE == null) { - INSTANCE = createInstance(); - } - return INSTANCE; + this.closed = new AtomicBoolean(false); } /** @@ -57,9 +56,9 @@ public class Ghostscript { * * @return the Ghostscript revision data. */ - public synchronized static GhostscriptRevision getRevision() { + public synchronized GhostscriptRevision getRevision() { GhostscriptLibrary.gsapi_revision_s revision = new GhostscriptLibrary.gsapi_revision_s(); - INSTANCE.libraryInstance.gsapi_revision(revision, revision.size()); + libraryInstance.gsapi_revision(revision, revision.size()); GhostscriptRevision result = new GhostscriptRevision(); result.setProduct(revision.product); result.setCopyright(revision.copyright); @@ -257,33 +256,29 @@ public class Ghostscript { } } + @Override public synchronized void close() throws IOException { - if (stdIn != null) { - stdIn.close(); - stdIn = null; + if (closed.compareAndSet(false, true)) { + if (stdIn != null) { + stdIn.close(); + stdIn = null; + } + if (stdOut != null) { + stdOut.close(); + stdOut = null; + } + if (stdErr != null) { + stdErr.close(); + stdErr = null; + } + if (libraryInstance != null) { + libraryInstance.dispose(); + System.gc(); + logger.log(Level.INFO, "ghostscript library instance disposed and gc'ed()"); + } else { + logger.log(Level.WARNING, "ghostscript library instance is null"); + } } - if (stdOut != null) { - stdOut.close(); - stdOut = null; - } - if (stdErr != null) { - stdErr.close(); - stdErr = null; - } - libraryInstance = null; - INSTANCE = null; - System.gc(); - logger.log(Level.INFO, "ghostscript instance closed and gc'ed()"); - } - - private static Ghostscript createInstance() { - try { - prepareTmp(); - return new Ghostscript(GhostscriptLibraryLoader.loadLibrary()); - } catch (Exception e) { - logger.log(Level.SEVERE, e.getMessage(), e); - } - return null; } private static void prepareTmp() throws IOException { diff --git a/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/PDFConverter.java b/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/PDFConverter.java index 8ac2cb1..a1c1814 100644 --- a/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/PDFConverter.java +++ b/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/PDFConverter.java @@ -1,5 +1,6 @@ package org.xbib.graphics.ghostscript; +import java.security.SecureRandom; import org.xbib.graphics.ghostscript.internal.LoggingOutputStream; import java.io.IOException; @@ -20,6 +21,8 @@ public class PDFConverter { private static final Logger logger = Logger.getLogger(PDFConverter.class.getName()); + private static final SecureRandom random = new SecureRandom(); + public static final int OPTION_AUTOROTATEPAGES_NONE = 0; public static final int OPTION_AUTOROTATEPAGES_ALL = 1; public static final int OPTION_AUTOROTATEPAGES_PAGEBYPAGE = 2; @@ -72,8 +75,6 @@ public class PDFConverter { */ private final PaperSize paperSize; - private final Path tmpPath; - public PDFConverter() { this(OPTION_AUTOROTATEPAGES_OFF, OPTION_PROCESSCOLORMODEL_RGB, OPTION_PDFSETTINGS_PRINTER, "1.4", false, PaperSize.A4); @@ -91,7 +92,6 @@ public class PDFConverter { this.compatibilityLevel = compatibilityLevel; this.pdfx = pdfx; this.paperSize = paperSize; - this.tmpPath = Paths.get(System.getProperty("java.io.tmpdir", "/var/tmp")).resolve(toString()); } /** @@ -106,56 +106,32 @@ public class PDFConverter { if (outputStream == null) { return; } - Ghostscript gs = Ghostscript.getInstance(); - try { - prepare(tmpPath); + Path tmpPath = createTmpPath(); + try (Ghostscript gs = new Ghostscript()) { Path output = Files.createTempFile(tmpPath, "pdf", "pdf"); List gsArgs = new LinkedList<>(); gsArgs.add("-ps2pdf"); gsArgs.add("-dNOPAUSE"); - //gsArgs.add("-dQUIET"); gsArgs.add("-dBATCH"); gsArgs.add("-dSAFER"); switch (autoRotatePages) { - case OPTION_AUTOROTATEPAGES_NONE: - gsArgs.add("-dAutoRotatePages=/None"); - break; - case OPTION_AUTOROTATEPAGES_ALL: - gsArgs.add("-dAutoRotatePages=/All"); - break; - case OPTION_AUTOROTATEPAGES_PAGEBYPAGE: - gsArgs.add("-dAutoRotatePages=/PageByPage"); - break; - default: - break; + case OPTION_AUTOROTATEPAGES_NONE -> gsArgs.add("-dAutoRotatePages=/None"); + case OPTION_AUTOROTATEPAGES_ALL -> gsArgs.add("-dAutoRotatePages=/All"); + case OPTION_AUTOROTATEPAGES_PAGEBYPAGE -> gsArgs.add("-dAutoRotatePages=/PageByPage"); + default -> { + } } switch (processColorModel) { - case OPTION_PROCESSCOLORMODEL_CMYK: - gsArgs.add("-dProcessColorModel=/DeviceCMYK"); - break; - case OPTION_PROCESSCOLORMODEL_GRAY: - gsArgs.add("-dProcessColorModel=/DeviceGray"); - break; - default: - gsArgs.add("-dProcessColorModel=/DeviceRGB"); - break; + case OPTION_PROCESSCOLORMODEL_CMYK -> gsArgs.add("-dProcessColorModel=/DeviceCMYK"); + case OPTION_PROCESSCOLORMODEL_GRAY -> gsArgs.add("-dProcessColorModel=/DeviceGray"); + default -> gsArgs.add("-dProcessColorModel=/DeviceRGB"); } switch (pdfsettings) { - case OPTION_PDFSETTINGS_EBOOK: - gsArgs.add("-dPDFSETTINGS=/ebook"); - break; - case OPTION_PDFSETTINGS_SCREEN: - gsArgs.add("-dPDFSETTINGS=/screen"); - break; - case OPTION_PDFSETTINGS_PRINTER: - gsArgs.add("-dPDFSETTINGS=/printer"); - break; - case OPTION_PDFSETTINGS_PREPRESS: - gsArgs.add("-dPDFSETTINGS=/prepress"); - break; - default: - gsArgs.add("-dPDFSETTINGS=/default"); - break; + case OPTION_PDFSETTINGS_EBOOK -> gsArgs.add("-dPDFSETTINGS=/ebook"); + case OPTION_PDFSETTINGS_SCREEN -> gsArgs.add("-dPDFSETTINGS=/screen"); + case OPTION_PDFSETTINGS_PRINTER -> gsArgs.add("-dPDFSETTINGS=/printer"); + case OPTION_PDFSETTINGS_PREPRESS -> gsArgs.add("-dPDFSETTINGS=/prepress"); + default -> gsArgs.add("-dPDFSETTINGS=/default"); } gsArgs.add("-dCompatibilityLevel=" + compatibilityLevel); gsArgs.add("-dPDFX=" + pdfx); @@ -163,28 +139,19 @@ public class PDFConverter { gsArgs.add("-dDEVICEHEIGHTPOINTS=" + paperSize.getHeight()); gsArgs.add("-sDEVICE=pdfwrite"); gsArgs.add("-sOutputFile=" + output.toAbsolutePath().toString()); - //gsArgs.add("-q"); gsArgs.add("-f"); gsArgs.add("-"); - try { - gs.setStdIn(inputStream); - gs.setStdOut(new LoggingOutputStream(logger)); - gs.setStdErr(new LoggingOutputStream(logger)); - gs.run(gsArgs.toArray(new String[gsArgs.size()])); - Files.copy(output.toAbsolutePath(), outputStream); - } finally { - delete(tmpPath); - } + gs.setStdIn(inputStream); + gs.setStdOut(new LoggingOutputStream(logger)); + gs.setStdErr(new LoggingOutputStream(logger)); + gs.run(gsArgs.toArray(new String[0])); + Files.copy(output.toAbsolutePath(), outputStream); } finally { - gs.close(); + deleteTmpPath(tmpPath); } } - private static void prepare(Path path) throws IOException { - Files.createDirectories(path); - } - - private static void delete(Path path) throws IOException { + private static void deleteTmpPath(Path path) throws IOException { if (path == null) { return; } @@ -211,4 +178,12 @@ public class PDFConverter { Files.deleteIfExists(path); } } + + private static Path createTmpPath() throws IOException { + int integer = random.nextInt(); + Path tmpPath = Paths.get(System.getProperty("java.io.tmpdir", "/var/tmp")) + .resolve("gs" + Math.abs(integer)); + Files.createDirectories(tmpPath); + return tmpPath; + } } diff --git a/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/PDFRasterizer.java b/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/PDFRasterizer.java index 75de4e6..173be12 100644 --- a/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/PDFRasterizer.java +++ b/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/PDFRasterizer.java @@ -1,5 +1,6 @@ package org.xbib.graphics.ghostscript; +import java.security.SecureRandom; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import javax.imageio.ImageIO; @@ -45,6 +46,8 @@ public class PDFRasterizer { private static final Logger logger = Logger.getLogger(PDFRasterizer.class.getName()); + private static final SecureRandom random = new SecureRandom(); + private static final int MAX_IMAGE_SIZE_BYTES = 128 * 1024 * 1024; private String creator; @@ -53,10 +56,7 @@ public class PDFRasterizer { private String subject; - private final Path tmpPath; - public PDFRasterizer() { - this.tmpPath = Paths.get(System.getProperty("java.io.tmpdir", "/var/tmp")).resolve(toString()); } public void setCreator(String creator) { @@ -82,51 +82,54 @@ public class PDFRasterizer { if (Files.size(source) == 0) { throw new IOException("empty file at " + source); } - prepare(tmpPath); - Path tmp = Files.createTempDirectory(tmpPath, "pdf-rasterize"); - if (!Files.isWritable(tmp)) { - throw new IOException("unable to write to " + tmp); - } + Path tmpPath = createTmpPath(); try { - pdfToImage(source, tmp, "pdf", null); - Path tmpTarget = tmp.resolve(target.getFileName()); - mergeImagesToPDF(tmp, tmpTarget); + Path path = Files.createTempDirectory(tmpPath, "pdf-rasterize"); + pdfToImage(source, path, "pdf", null); + Path tmpTarget = path.resolve(target.getFileName()); + mergeImagesToPDF(path, tmpTarget); scalePDF(tmpTarget, target); logger.info("convert source=" + source + " done"); } finally { - delete(tmpPath); + deleteTmpPath(tmpPath); } } public synchronized void screenConvert(Path source, Path target) throws IOException { - logger.info("convert source=" + source.toAbsolutePath() + " target=" + target); + logger.info("screen convert source=" + source.toAbsolutePath() + " target=" + target); if (!Files.exists(source.toAbsolutePath())) { throw new FileNotFoundException(source.toString()); } if (!Files.isReadable(source.toAbsolutePath())) { throw new IOException("unable to read " + source.toString()); } - prepare(tmpPath); - Path tmp = Files.createTempDirectory(tmpPath, "pdf-rasterize"); + if (Files.size(source) == 0) { + throw new IOException("empty file at " + source); + } + Path tmpPath = createTmpPath(); try { - pdfToGrayScreenImage(source.toAbsolutePath(), tmp); - Path tmpTarget = tmp.resolve(target.getFileName()); - mergeImagesToPDF(tmp, tmpTarget); + Path path = Files.createTempDirectory(tmpPath, "pdf-rasterize"); + pdfToGrayScreenImage(source.toAbsolutePath(), path); + Path tmpTarget = path.resolve(target.getFileName()); + mergeImagesToPDF(path, tmpTarget); //toPDFA(tmpTarget, target); scalePDF(tmpTarget, target); - logger.info("convert source=" + source.toAbsolutePath() + " done"); + logger.info("screen convert source=" + source.toAbsolutePath() + " done"); } finally { - delete(tmpPath); + deleteTmpPath(tmpPath); } } public synchronized void pdfToGrayScreenImage(Path source, Path target) throws IOException { logger.info("pdfToGrayScreenImage source=" + source + " target=" + target); if (!Files.exists(source.toAbsolutePath())) { - throw new FileNotFoundException(source.toString()); + throw new FileNotFoundException("not found: " + source); } if (!Files.isReadable(source.toAbsolutePath())) { - throw new IOException("unable to read " + source.toString()); + throw new IOException("unable to read " + source); + } + if (Files.size(source) == 0) { + throw new IOException("empty file at " + source); } List gsArgs = new LinkedList<>(); gsArgs.add("-dNOPAUSE"); @@ -141,16 +144,12 @@ public class PDFRasterizer { gsArgs.add("-f"); gsArgs.add(source.toString()); logger.info("pdfToImage args=" + gsArgs); - // reset stdin - Ghostscript gs = Ghostscript.getInstance(); - try { + try (Ghostscript gs = new Ghostscript()) { gs.setStdIn(null); gs.setStdOut(new LoggingOutputStream(logger)); gs.setStdErr(new LoggingOutputStream(logger)); - gs.run(gsArgs.toArray(new String[gsArgs.size()])); + gs.run(gsArgs.toArray(new String[0])); logger.info("pdfToImage done"); - } finally { - gs.close(); } } @@ -182,16 +181,13 @@ public class PDFRasterizer { gsArgs.add("-f"); gsArgs.add(sourceFile.toString()); logger.info("pdfToImage args=" + gsArgs); - Ghostscript gs = Ghostscript.getInstance(); - try { + try (Ghostscript gs = new Ghostscript()) { // reset stdin gs.setStdIn(null); gs.setStdOut(new LoggingOutputStream(logger)); gs.setStdErr(new LoggingOutputStream(logger)); - gs.run(gsArgs.toArray(new String[gsArgs.size()])); + gs.run(gsArgs.toArray(new String[0])); logger.info("pdfToImage done"); - } finally { - gs.close(); } } @@ -294,59 +290,52 @@ public class PDFRasterizer { gsArgs.add("-sPAPERSIZE=a4"); gsArgs.add("-sOutputFile=" + targetFile.toString()); gsArgs.add(sourceFile.toString()); - Ghostscript gs = Ghostscript.getInstance(); - try { + try (Ghostscript gs = new Ghostscript()) { gs.setStdIn(null); logger.info(gsArgs.toString()); - gs.run(gsArgs.toArray(new String[gsArgs.size()])); + gs.run(gsArgs.toArray(new String[0])); logger.info("scalePDF: source = " + sourceFile + " target = " + targetFile + " done"); - } finally { - gs.close(); } } public void toPDFA(Path source, Path target) throws IOException { - prepare(tmpPath); - Path iccPath = Files.createTempFile(tmpPath, "srgb", ".icc"); - Path pdfapsPathTmp = Files.createTempFile(tmpPath, "PDFA_def", ".tmp"); - Path pdfapsPath = Files.createTempFile(tmpPath, "PDFA_def", ".ps"); - try (InputStream srgbIcc = getClass().getResourceAsStream("/iccprofiles/srgb.icc")) { - if (srgbIcc != null) { - Files.copy(srgbIcc, iccPath, StandardCopyOption.REPLACE_EXISTING); + Path tmpPath = createTmpPath(); + try (Ghostscript gs = new Ghostscript()) { + Path iccPath = Files.createTempFile(tmpPath, "srgb", ".icc"); + Path pdfapsPathTmp = Files.createTempFile(tmpPath, "PDFA_def", ".tmp"); + Path pdfapsPath = Files.createTempFile(tmpPath, "PDFA_def", ".ps"); + try (InputStream srgbIcc = getClass().getResourceAsStream("/iccprofiles/srgb.icc")) { + if (srgbIcc != null) { + Files.copy(srgbIcc, iccPath, StandardCopyOption.REPLACE_EXISTING); + } } - } - try (InputStream pdfaPs = getClass().getResourceAsStream("/lib/PDFA_def.ps")) { - if (pdfaPs != null) { - Files.copy(pdfaPs, pdfapsPathTmp, StandardCopyOption.REPLACE_EXISTING); + try (InputStream pdfaPs = getClass().getResourceAsStream("/lib/PDFA_def.ps")) { + if (pdfaPs != null) { + Files.copy(pdfaPs, pdfapsPathTmp, StandardCopyOption.REPLACE_EXISTING); + } } - } - copyAndReplace(pdfapsPathTmp, pdfapsPath, "srgb.icc", iccPath.toAbsolutePath().toString()); - List gsArgs = new LinkedList<>(); - gsArgs.add("-E"); - gsArgs.add("-dNOPAUSE"); - gsArgs.add("-dBATCH"); - gsArgs.add("-dQUIET"); // do not print to stdout - gsArgs.add("-dNOSAFER"); // do not use SAFER because we need access to PDFA_def.ps and srgb.icc - gsArgs.add("-dPDFA=2"); // PDF/A-2b - gsArgs.add("-sColorConversionStrategy=/sRGB"); - gsArgs.add("-sOutputICCProfile=" + iccPath.toAbsolutePath().toString()); - gsArgs.add("-sDEVICE=pdfwrite"); - gsArgs.add("-dPDFSETTINGS=/printer"); - gsArgs.add("-sPAPERSIZE=a4"); - gsArgs.add("-dPDFFitPage"); - gsArgs.add("-dAutoRotatePages=/PageByPage"); - gsArgs.add("-sOutputFile=" + target.toString()); - gsArgs.add(pdfapsPath.toAbsolutePath().toString()); - gsArgs.add(source.toString()); - Ghostscript gs = Ghostscript.getInstance(); - try { + copyAndReplace(pdfapsPathTmp, pdfapsPath, "srgb.icc", iccPath.toAbsolutePath().toString()); + List gsArgs = new LinkedList<>(); + gsArgs.add("-E"); + gsArgs.add("-dNOPAUSE"); + gsArgs.add("-dBATCH"); + gsArgs.add("-dQUIET"); // do not print to stdout + gsArgs.add("-dNOSAFER"); // do not use SAFER because we need access to PDFA_def.ps and srgb.icc + gsArgs.add("-dPDFA=2"); // PDF/A-2b + gsArgs.add("-sColorConversionStrategy=/sRGB"); + gsArgs.add("-sOutputICCProfile=" + iccPath.toAbsolutePath().toString()); + gsArgs.add("-sDEVICE=pdfwrite"); + gsArgs.add("-dPDFSETTINGS=/printer"); + gsArgs.add("-sPAPERSIZE=a4"); + gsArgs.add("-dPDFFitPage"); + gsArgs.add("-dAutoRotatePages=/PageByPage"); + gsArgs.add("-sOutputFile=" + target.toString()); + gsArgs.add(pdfapsPath.toAbsolutePath().toString()); + gsArgs.add(source.toString()); gs.setStdIn(null); - gs.run(gsArgs.toArray(new String[gsArgs.size()])); + gs.run(gsArgs.toArray(new String[0])); } finally { - delete(pdfapsPathTmp); - delete(pdfapsPath); - delete(iccPath); - gs.close(); + deleteTmpPath(tmpPath); } } @@ -420,11 +409,7 @@ public class PDFRasterizer { return pos >= 0 ? filename.substring(pos + 1).toLowerCase(Locale.ROOT) : null; } - private static void prepare(Path path) throws IOException { - Files.createDirectories(path); - } - - private static void delete(Path path) throws IOException { + private static void deleteTmpPath(Path path) throws IOException { if (path == null) { return; } @@ -451,4 +436,15 @@ public class PDFRasterizer { Files.deleteIfExists(path); } } + + private static Path createTmpPath() throws IOException { + int integer = random.nextInt(); + Path tmpPath = Paths.get(System.getProperty("java.io.tmpdir", "/var/tmp")) + .resolve("gs" + Math.abs(integer)); + Files.createDirectories(tmpPath); + if (!Files.isWritable(tmpPath)) { + throw new IOException("unable to write to " + tmpPath); + } + return tmpPath; + } } diff --git a/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/internal/GhostscriptLibrary.java b/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/internal/GhostscriptLibrary.java index 2b125b7..23a0cef 100644 --- a/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/internal/GhostscriptLibrary.java +++ b/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/internal/GhostscriptLibrary.java @@ -21,6 +21,11 @@ import java.util.List; */ public interface GhostscriptLibrary extends Library { + /** + * A default method to call JNA's dispose(). + */ + void dispose(); + /** * This function returns the revision numbers and strings of the Ghostscript * interpreter library. You should call it before any other interpreter diff --git a/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/internal/GhostscriptLibraryLoader.java b/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/internal/GhostscriptLibraryLoader.java index 8b0fc1a..f7a9977 100644 --- a/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/internal/GhostscriptLibraryLoader.java +++ b/graphics-ghostscript/src/main/java/org/xbib/graphics/ghostscript/internal/GhostscriptLibraryLoader.java @@ -1,13 +1,13 @@ package org.xbib.graphics.ghostscript.internal; -import static com.sun.jna.Platform.LINUX; import static com.sun.jna.Platform.MAC; import com.sun.jna.Function; +import com.sun.jna.InvocationMapper; import com.sun.jna.Library; import com.sun.jna.Native; import com.sun.jna.Platform; -import java.util.HashMap; +import java.util.Arrays; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -28,29 +28,39 @@ public class GhostscriptLibraryLoader { "libgs.9.25", "gs.9.25", "libgs.dylib", - "libgs", "gs", }; - public static GhostscriptLibrary loadLibrary() { - Map options = new HashMap<>(); - options.put(Library.OPTION_CALLING_CONVENTION, Function.C_CONVENTION); - String[] LIBNAMES = {}; - switch (Platform.getOSType()) { - case LINUX: - LIBNAMES = LINUX_LIBNAMES; - break; - case MAC: - LIBNAMES = MAC_LIBNAMES; - break; + private GhostscriptLibrary ghostscriptLibrary; + + public GhostscriptLibraryLoader() { + Map options = Map.of(Library.OPTION_CALLING_CONVENTION, Function.C_CONVENTION, + Library.OPTION_INVOCATION_MAPPER, (InvocationMapper) (lib, m) -> { + if (m.getName().equals("dispose")) { + return (proxy, method, args) -> { + lib.dispose(); + return null; + }; + } + return null; + }); + String[] libnames = LINUX_LIBNAMES; + if (Platform.getOSType() == MAC) { + libnames = MAC_LIBNAMES; } - for (String libname : LIBNAMES) { + for (String libname : libnames) { try { - return Native.load(libname, GhostscriptLibrary.class, options); + this.ghostscriptLibrary = Native.load(libname, GhostscriptLibrary.class, options); } catch (Error e) { logger.log(Level.WARNING, "library " + libname + " not found", e); } } - return null; + if (this.ghostscriptLibrary == null) { + throw new IllegalStateException("library not found in libs: " + Arrays.asList(libnames)); + } + } + + public GhostscriptLibrary getGhostscriptLibrary() { + return ghostscriptLibrary; } } diff --git a/graphics-ghostscript/src/test/java/org/xbib/graphics/ghostscript/test/GhostScriptLibraryTester.java b/graphics-ghostscript/src/test/java/org/xbib/graphics/ghostscript/test/GhostScriptLibraryTester.java index 8a41cc8..ec0138f 100644 --- a/graphics-ghostscript/src/test/java/org/xbib/graphics/ghostscript/test/GhostScriptLibraryTester.java +++ b/graphics-ghostscript/src/test/java/org/xbib/graphics/ghostscript/test/GhostScriptLibraryTester.java @@ -9,7 +9,8 @@ public class GhostScriptLibraryTester { private final GhostscriptLibrary ghostscriptLibrary; public GhostScriptLibraryTester() { - ghostscriptLibrary = GhostscriptLibraryLoader.loadLibrary(); + GhostscriptLibraryLoader loader = new GhostscriptLibraryLoader(); + this.ghostscriptLibrary = loader.getGhostscriptLibrary(); } public String getRevisionProduct() { diff --git a/graphics-ghostscript/src/test/java/org/xbib/graphics/ghostscript/test/GhostscriptTest.java b/graphics-ghostscript/src/test/java/org/xbib/graphics/ghostscript/test/GhostscriptTest.java index 05223a0..a4c3214 100644 --- a/graphics-ghostscript/src/test/java/org/xbib/graphics/ghostscript/test/GhostscriptTest.java +++ b/graphics-ghostscript/src/test/java/org/xbib/graphics/ghostscript/test/GhostscriptTest.java @@ -1,8 +1,6 @@ package org.xbib.graphics.ghostscript.test; import java.io.IOException; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.xbib.graphics.ghostscript.Ghostscript; @@ -23,44 +21,35 @@ public class GhostscriptTest { private static final String dir = "src/test/resources/org/xbib/graphics/ghostscript/test/"; - static Ghostscript gs; - - @BeforeAll - public static void init() { - gs = Ghostscript.getInstance(); - } - - @AfterAll - public static void shutdown() throws IOException { - gs.close(); - } - @Test - public void testGetRevision() { - GhostscriptRevision revision = Ghostscript.getRevision(); - logger.log(Level.INFO, "product = " + revision.getProduct()); - assertNotNull(revision.getProduct()); - logger.log(Level.INFO, "copyright = " + revision.getCopyright()); - assertNotNull(revision.getCopyright()); - logger.log(Level.INFO, "revision date = " + revision.getRevisionDate()); - assertNotNull(revision.getRevisionDate()); - logger.log(Level.INFO, "number = " + revision.getNumber()); - assertNotNull(revision.getNumber()); + public void testGetRevision() throws IOException { + try (Ghostscript gs = new Ghostscript()) { + GhostscriptRevision revision = gs.getRevision(); + logger.log(Level.INFO, "product = " + revision.getProduct()); + assertNotNull(revision.getProduct()); + logger.log(Level.INFO, "copyright = " + revision.getCopyright()); + assertNotNull(revision.getCopyright()); + logger.log(Level.INFO, "revision date = " + revision.getRevisionDate()); + assertNotNull(revision.getRevisionDate()); + logger.log(Level.INFO, "number = " + revision.getNumber()); + assertNotNull(revision.getNumber()); + } } @Test public void testExit() { - try { + try (Ghostscript gs = new Ghostscript()) { String[] args = {"-dNODISPLAY", "-dQUIET"}; gs.run(args); } catch (Exception e) { + logger.log(Level.SEVERE, e.getMessage(), e); fail(e.getMessage()); } } @Test public void testRunString() { - try { + try (Ghostscript gs = new Ghostscript()) { String[] args = {"-dNODISPLAY", "-dQUIET"}; gs.run(args, "devicenames ==", null); } catch (Exception e) { @@ -73,7 +62,7 @@ public class GhostscriptTest { @Test public void testRunFile() { - try { + try (Ghostscript gs = new Ghostscript()) { String[] args = {"-dNODISPLAY", "-dQUIET", "-dNOPAUSE", "-dBATCH", "-dSAFER"}; gs.run(args, null, dir + "input.ps"); } catch (Exception e) { @@ -90,7 +79,8 @@ public class GhostscriptTest { @Disabled("core dump") @Test public void testStdIn() { - try (InputStream is = new FileInputStream(dir + "input.ps")) { + try (Ghostscript gs = new Ghostscript(); + InputStream is = new FileInputStream(dir + "input.ps")) { gs.setStdIn(is); String[] args = {"-dNODISPLAY", "-dQUIET", "-dNOPAUSE", "-dBATCH", "-sOutputFile=%stdout", "-f", "-"}; gs.run(args); @@ -101,7 +91,8 @@ public class GhostscriptTest { @Test public void testStdOut() { - try (InputStream is = new ByteArrayInputStream("devicenames ==\n".getBytes())) { + try (Ghostscript gs = new Ghostscript(); + InputStream is = new ByteArrayInputStream("devicenames ==\n".getBytes())) { gs.setStdIn(is); String[] args = { "-dNODISPLAY", "-sOutputFile=%stdout", "-f", "-" }; gs.run(args); @@ -112,7 +103,8 @@ public class GhostscriptTest { @Test public void testStdErr() { - try (InputStream is = new ByteArrayInputStream("stupid\n".getBytes())) { + try (Ghostscript gs = new Ghostscript(); + InputStream is = new ByteArrayInputStream("stupid\n".getBytes())) { gs.setStdIn(is); String[] args = { "-dNODISPLAY", "-sOutputFile=%stdout", "-f", "-" }; gs.run(args); diff --git a/graphics-ghostscript/src/test/java/org/xbib/graphics/ghostscript/test/PDFRasterizerTest.java b/graphics-ghostscript/src/test/java/org/xbib/graphics/ghostscript/test/PDFRasterizerTest.java index dd3f3c3..c29e258 100644 --- a/graphics-ghostscript/src/test/java/org/xbib/graphics/ghostscript/test/PDFRasterizerTest.java +++ b/graphics-ghostscript/src/test/java/org/xbib/graphics/ghostscript/test/PDFRasterizerTest.java @@ -45,17 +45,17 @@ public class PDFRasterizerTest { public void testPDFUnpackRasterAndScale() throws IOException { Path source = Paths.get("src/test/resources/org/xbib/graphics/ghostscript/test/20200024360.pdf"); Path target = Paths.get("build/20200024360-new.pdf"); - Path tmp = Files.createTempDirectory("graphics-test"); + Path path = Files.createTempDirectory("graphics-test"); int pagecount = 0; try { PDFRasterizer pdfRasterizer = new PDFRasterizer(); - pdfRasterizer.pdfToImage(source, tmp, null, null); - Path tmpTarget = tmp.resolve(target.getFileName()); - pagecount = pdfRasterizer.mergeImagesToPDF(tmp, tmpTarget); + pdfRasterizer.pdfToImage(source, path, null, null); + Path tmpTarget = path.resolve(target.getFileName()); + pagecount = pdfRasterizer.mergeImagesToPDF(path, tmpTarget); logger.info("pagecount = " + pagecount); pdfRasterizer.scalePDF(tmpTarget, target); } finally { - delete(tmp); + delete(path); } assertEquals(28, pagecount); } diff --git a/graphics-pdfbox-groovy/build.gradle b/graphics-pdfbox-groovy/build.gradle index c0708ad..008f06b 100644 --- a/graphics-pdfbox-groovy/build.gradle +++ b/graphics-pdfbox-groovy/build.gradle @@ -14,6 +14,6 @@ dependencies { testImplementation libs.groovy.templates testImplementation libs.groovy.test testImplementation libs.spock - testImplementation libs.cglib + testImplementation libs.bytebuddy testImplementation libs.objenesis } diff --git a/settings.gradle b/settings.gradle index 684d4da..e64c063 100644 --- a/settings.gradle +++ b/settings.gradle @@ -36,10 +36,10 @@ dependencyResolutionManagement { library('groovy-templates', 'org.apache.groovy', 'groovy-templates').versionRef('groovy') library('groovy-test', 'org.apache.groovy', 'groovy-test').versionRef('groovy') library('spock', 'org.spockframework', 'spock-core').versionRef('spock') - library('cglib', 'cglib', 'cglib-nodep').version('3.3.0') + library('bytebuddy', 'net.bytebuddy', 'byte-buddy').version('1.14.4') library('objenesis', 'org.objenesis', 'objenesis').version('2.6') - library('jna', 'net.java.dev.jna', 'jna').version('5.12.1') - library('pdfbox', 'org.apache.pdfbox', 'pdfbox').version('2.0.27') + library('jna', 'net.java.dev.jna', 'jna').version('5.13.0') + library('pdfbox', 'org.apache.pdfbox', 'pdfbox').version('2.0.28') library('zxing', 'com.google.zxing', 'javase').version('3.4.1') library('reflections', 'org.reflections', 'reflections').version('0.9.11') library('jfreechart', 'org.jfree', 'jfreechart').version('1.5.2')