update pdfbox 2.0.28, bytebuddy, Ghostscript loading reworked, added dispose()

This commit is contained in:
Jörg Prante 2023-04-17 09:35:04 +02:00
parent 9cbc3f3d10
commit a0d193eb7f
13 changed files with 213 additions and 241 deletions

View file

@ -1,5 +1,5 @@
group = org.xbib.graphics group = org.xbib.graphics
name = graphics name = graphics
version = 4.3.3 version = 4.3.4
org.gradle.warning.mode = ALL org.gradle.warning.mode = ALL

View file

@ -7,13 +7,13 @@ dependencies {
test { test {
useJUnitPlatform() useJUnitPlatform()
failFast = false failFast = true
environment 'TMPDIR', '/var/tmp/gs' environment 'TMPDIR', '/var/tmp/gs'
file('/var/tmp/gs').mkdirs() file('/var/tmp/gs').mkdirs()
systemProperty 'java.awt.headless', 'true' systemProperty 'java.awt.headless', 'true'
systemProperty 'java.io.tmpdir', '/var/tmp/' systemProperty 'java.io.tmpdir', '/var/tmp/'
systemProperty 'jna.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' systemProperty 'java.util.logging.config.file', 'src/test/resources/logging.properties'
testLogging { testLogging {
events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED' events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED'

View file

@ -23,8 +23,8 @@ public class FontAnalyzer {
"-sFile=" + path.toAbsolutePath(), "-sFile=" + path.toAbsolutePath(),
"-sOutputFile=%stdout", "-sOutputFile=%stdout",
"-f", "-"}; "-f", "-"};
Ghostscript gs = Ghostscript.getInstance(); try (Ghostscript gs = new Ghostscript();
try (InputStream is = this.getClass().getClassLoader().getResourceAsStream("script/AnalyzePDFFonts.ps"); InputStream is = this.getClass().getClassLoader().getResourceAsStream("script/AnalyzePDFFonts.ps");
ByteArrayOutputStream baos = new ByteArrayOutputStream()) { ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
gs.setStdIn(is); gs.setStdIn(is);
gs.setStdOut(baos); gs.setStdOut(baos);
@ -56,8 +56,6 @@ public class FontAnalyzer {
} }
} }
return result; return result;
} finally {
gs.close();
} }
} }
} }

View file

@ -2,6 +2,8 @@ package org.xbib.graphics.ghostscript;
import com.sun.jna.Pointer; import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference; 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.ErrorCodes;
import org.xbib.graphics.ghostscript.internal.GhostscriptLibrary; import org.xbib.graphics.ghostscript.internal.GhostscriptLibrary;
import org.xbib.graphics.ghostscript.internal.GhostscriptLibraryLoader; import org.xbib.graphics.ghostscript.internal.GhostscriptLibraryLoader;
@ -23,13 +25,13 @@ import java.util.logging.Logger;
/** /**
* The Ghostscript native library API. * The Ghostscript native library API.
*/ */
public class Ghostscript { public class Ghostscript implements AutoCloseable {
private static final Logger logger = Logger.getLogger(Ghostscript.class.getName()); private static final Logger logger = Logger.getLogger(Ghostscript.class.getName());
public static final String ENCODING_PARAMETER = "org.xbib.graphics.ghostscript.encoding"; public static final String ENCODING_PARAMETER = "org.xbib.graphics.ghostscript.encoding";
private static Ghostscript INSTANCE; private final GhostscriptLibrary libraryInstance;
private InputStream stdIn; private InputStream stdIn;
@ -37,19 +39,16 @@ public class Ghostscript {
private OutputStream stdErr; private OutputStream stdErr;
private GhostscriptLibrary libraryInstance; private final AtomicBoolean closed;
private Ghostscript(GhostscriptLibrary libraryInstance) { public Ghostscript() throws IOException {
this.libraryInstance = libraryInstance; prepareTmp();
GhostscriptLibraryLoader libraryLoader = new GhostscriptLibraryLoader();
this.libraryInstance = libraryLoader.getGhostscriptLibrary();
Objects.requireNonNull(this.libraryInstance);
this.stdOut = new LoggingOutputStream(logger); this.stdOut = new LoggingOutputStream(logger);
this.stdErr = new LoggingOutputStream(logger); this.stdErr = new LoggingOutputStream(logger);
} this.closed = new AtomicBoolean(false);
public synchronized static Ghostscript getInstance() {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
return INSTANCE;
} }
/** /**
@ -57,9 +56,9 @@ public class Ghostscript {
* *
* @return the Ghostscript revision data. * @return the Ghostscript revision data.
*/ */
public synchronized static GhostscriptRevision getRevision() { public synchronized GhostscriptRevision getRevision() {
GhostscriptLibrary.gsapi_revision_s revision = new GhostscriptLibrary.gsapi_revision_s(); 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(); GhostscriptRevision result = new GhostscriptRevision();
result.setProduct(revision.product); result.setProduct(revision.product);
result.setCopyright(revision.copyright); result.setCopyright(revision.copyright);
@ -257,33 +256,29 @@ public class Ghostscript {
} }
} }
@Override
public synchronized void close() throws IOException { public synchronized void close() throws IOException {
if (stdIn != null) { if (closed.compareAndSet(false, true)) {
stdIn.close(); if (stdIn != null) {
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 { private static void prepareTmp() throws IOException {

View file

@ -1,5 +1,6 @@
package org.xbib.graphics.ghostscript; package org.xbib.graphics.ghostscript;
import java.security.SecureRandom;
import org.xbib.graphics.ghostscript.internal.LoggingOutputStream; import org.xbib.graphics.ghostscript.internal.LoggingOutputStream;
import java.io.IOException; import java.io.IOException;
@ -20,6 +21,8 @@ public class PDFConverter {
private static final Logger logger = Logger.getLogger(PDFConverter.class.getName()); 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_NONE = 0;
public static final int OPTION_AUTOROTATEPAGES_ALL = 1; public static final int OPTION_AUTOROTATEPAGES_ALL = 1;
public static final int OPTION_AUTOROTATEPAGES_PAGEBYPAGE = 2; public static final int OPTION_AUTOROTATEPAGES_PAGEBYPAGE = 2;
@ -72,8 +75,6 @@ public class PDFConverter {
*/ */
private final PaperSize paperSize; private final PaperSize paperSize;
private final Path tmpPath;
public PDFConverter() { public PDFConverter() {
this(OPTION_AUTOROTATEPAGES_OFF, OPTION_PROCESSCOLORMODEL_RGB, this(OPTION_AUTOROTATEPAGES_OFF, OPTION_PROCESSCOLORMODEL_RGB,
OPTION_PDFSETTINGS_PRINTER, "1.4", false, PaperSize.A4); OPTION_PDFSETTINGS_PRINTER, "1.4", false, PaperSize.A4);
@ -91,7 +92,6 @@ public class PDFConverter {
this.compatibilityLevel = compatibilityLevel; this.compatibilityLevel = compatibilityLevel;
this.pdfx = pdfx; this.pdfx = pdfx;
this.paperSize = paperSize; 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) { if (outputStream == null) {
return; return;
} }
Ghostscript gs = Ghostscript.getInstance(); Path tmpPath = createTmpPath();
try { try (Ghostscript gs = new Ghostscript()) {
prepare(tmpPath);
Path output = Files.createTempFile(tmpPath, "pdf", "pdf"); Path output = Files.createTempFile(tmpPath, "pdf", "pdf");
List<String> gsArgs = new LinkedList<>(); List<String> gsArgs = new LinkedList<>();
gsArgs.add("-ps2pdf"); gsArgs.add("-ps2pdf");
gsArgs.add("-dNOPAUSE"); gsArgs.add("-dNOPAUSE");
//gsArgs.add("-dQUIET");
gsArgs.add("-dBATCH"); gsArgs.add("-dBATCH");
gsArgs.add("-dSAFER"); gsArgs.add("-dSAFER");
switch (autoRotatePages) { switch (autoRotatePages) {
case OPTION_AUTOROTATEPAGES_NONE: case OPTION_AUTOROTATEPAGES_NONE -> gsArgs.add("-dAutoRotatePages=/None");
gsArgs.add("-dAutoRotatePages=/None"); case OPTION_AUTOROTATEPAGES_ALL -> gsArgs.add("-dAutoRotatePages=/All");
break; case OPTION_AUTOROTATEPAGES_PAGEBYPAGE -> gsArgs.add("-dAutoRotatePages=/PageByPage");
case OPTION_AUTOROTATEPAGES_ALL: default -> {
gsArgs.add("-dAutoRotatePages=/All"); }
break;
case OPTION_AUTOROTATEPAGES_PAGEBYPAGE:
gsArgs.add("-dAutoRotatePages=/PageByPage");
break;
default:
break;
} }
switch (processColorModel) { switch (processColorModel) {
case OPTION_PROCESSCOLORMODEL_CMYK: case OPTION_PROCESSCOLORMODEL_CMYK -> gsArgs.add("-dProcessColorModel=/DeviceCMYK");
gsArgs.add("-dProcessColorModel=/DeviceCMYK"); case OPTION_PROCESSCOLORMODEL_GRAY -> gsArgs.add("-dProcessColorModel=/DeviceGray");
break; default -> gsArgs.add("-dProcessColorModel=/DeviceRGB");
case OPTION_PROCESSCOLORMODEL_GRAY:
gsArgs.add("-dProcessColorModel=/DeviceGray");
break;
default:
gsArgs.add("-dProcessColorModel=/DeviceRGB");
break;
} }
switch (pdfsettings) { switch (pdfsettings) {
case OPTION_PDFSETTINGS_EBOOK: case OPTION_PDFSETTINGS_EBOOK -> gsArgs.add("-dPDFSETTINGS=/ebook");
gsArgs.add("-dPDFSETTINGS=/ebook"); case OPTION_PDFSETTINGS_SCREEN -> gsArgs.add("-dPDFSETTINGS=/screen");
break; case OPTION_PDFSETTINGS_PRINTER -> gsArgs.add("-dPDFSETTINGS=/printer");
case OPTION_PDFSETTINGS_SCREEN: case OPTION_PDFSETTINGS_PREPRESS -> gsArgs.add("-dPDFSETTINGS=/prepress");
gsArgs.add("-dPDFSETTINGS=/screen"); default -> gsArgs.add("-dPDFSETTINGS=/default");
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;
} }
gsArgs.add("-dCompatibilityLevel=" + compatibilityLevel); gsArgs.add("-dCompatibilityLevel=" + compatibilityLevel);
gsArgs.add("-dPDFX=" + pdfx); gsArgs.add("-dPDFX=" + pdfx);
@ -163,28 +139,19 @@ public class PDFConverter {
gsArgs.add("-dDEVICEHEIGHTPOINTS=" + paperSize.getHeight()); gsArgs.add("-dDEVICEHEIGHTPOINTS=" + paperSize.getHeight());
gsArgs.add("-sDEVICE=pdfwrite"); gsArgs.add("-sDEVICE=pdfwrite");
gsArgs.add("-sOutputFile=" + output.toAbsolutePath().toString()); gsArgs.add("-sOutputFile=" + output.toAbsolutePath().toString());
//gsArgs.add("-q");
gsArgs.add("-f"); gsArgs.add("-f");
gsArgs.add("-"); gsArgs.add("-");
try { gs.setStdIn(inputStream);
gs.setStdIn(inputStream); gs.setStdOut(new LoggingOutputStream(logger));
gs.setStdOut(new LoggingOutputStream(logger)); gs.setStdErr(new LoggingOutputStream(logger));
gs.setStdErr(new LoggingOutputStream(logger)); gs.run(gsArgs.toArray(new String[0]));
gs.run(gsArgs.toArray(new String[gsArgs.size()])); Files.copy(output.toAbsolutePath(), outputStream);
Files.copy(output.toAbsolutePath(), outputStream);
} finally {
delete(tmpPath);
}
} finally { } finally {
gs.close(); deleteTmpPath(tmpPath);
} }
} }
private static void prepare(Path path) throws IOException { private static void deleteTmpPath(Path path) throws IOException {
Files.createDirectories(path);
}
private static void delete(Path path) throws IOException {
if (path == null) { if (path == null) {
return; return;
} }
@ -211,4 +178,12 @@ public class PDFConverter {
Files.deleteIfExists(path); 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;
}
} }

View file

@ -1,5 +1,6 @@
package org.xbib.graphics.ghostscript; package org.xbib.graphics.ghostscript;
import java.security.SecureRandom;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer; import java.util.function.Consumer;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
@ -45,6 +46,8 @@ public class PDFRasterizer {
private static final Logger logger = Logger.getLogger(PDFRasterizer.class.getName()); 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 static final int MAX_IMAGE_SIZE_BYTES = 128 * 1024 * 1024;
private String creator; private String creator;
@ -53,10 +56,7 @@ public class PDFRasterizer {
private String subject; private String subject;
private final Path tmpPath;
public PDFRasterizer() { public PDFRasterizer() {
this.tmpPath = Paths.get(System.getProperty("java.io.tmpdir", "/var/tmp")).resolve(toString());
} }
public void setCreator(String creator) { public void setCreator(String creator) {
@ -82,51 +82,54 @@ public class PDFRasterizer {
if (Files.size(source) == 0) { if (Files.size(source) == 0) {
throw new IOException("empty file at " + source); throw new IOException("empty file at " + source);
} }
prepare(tmpPath); Path tmpPath = createTmpPath();
Path tmp = Files.createTempDirectory(tmpPath, "pdf-rasterize");
if (!Files.isWritable(tmp)) {
throw new IOException("unable to write to " + tmp);
}
try { try {
pdfToImage(source, tmp, "pdf", null); Path path = Files.createTempDirectory(tmpPath, "pdf-rasterize");
Path tmpTarget = tmp.resolve(target.getFileName()); pdfToImage(source, path, "pdf", null);
mergeImagesToPDF(tmp, tmpTarget); Path tmpTarget = path.resolve(target.getFileName());
mergeImagesToPDF(path, tmpTarget);
scalePDF(tmpTarget, target); scalePDF(tmpTarget, target);
logger.info("convert source=" + source + " done"); logger.info("convert source=" + source + " done");
} finally { } finally {
delete(tmpPath); deleteTmpPath(tmpPath);
} }
} }
public synchronized void screenConvert(Path source, Path target) throws IOException { 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())) { if (!Files.exists(source.toAbsolutePath())) {
throw new FileNotFoundException(source.toString()); throw new FileNotFoundException(source.toString());
} }
if (!Files.isReadable(source.toAbsolutePath())) { if (!Files.isReadable(source.toAbsolutePath())) {
throw new IOException("unable to read " + source.toString()); throw new IOException("unable to read " + source.toString());
} }
prepare(tmpPath); if (Files.size(source) == 0) {
Path tmp = Files.createTempDirectory(tmpPath, "pdf-rasterize"); throw new IOException("empty file at " + source);
}
Path tmpPath = createTmpPath();
try { try {
pdfToGrayScreenImage(source.toAbsolutePath(), tmp); Path path = Files.createTempDirectory(tmpPath, "pdf-rasterize");
Path tmpTarget = tmp.resolve(target.getFileName()); pdfToGrayScreenImage(source.toAbsolutePath(), path);
mergeImagesToPDF(tmp, tmpTarget); Path tmpTarget = path.resolve(target.getFileName());
mergeImagesToPDF(path, tmpTarget);
//toPDFA(tmpTarget, target); //toPDFA(tmpTarget, target);
scalePDF(tmpTarget, target); scalePDF(tmpTarget, target);
logger.info("convert source=" + source.toAbsolutePath() + " done"); logger.info("screen convert source=" + source.toAbsolutePath() + " done");
} finally { } finally {
delete(tmpPath); deleteTmpPath(tmpPath);
} }
} }
public synchronized void pdfToGrayScreenImage(Path source, Path target) throws IOException { public synchronized void pdfToGrayScreenImage(Path source, Path target) throws IOException {
logger.info("pdfToGrayScreenImage source=" + source + " target=" + target); logger.info("pdfToGrayScreenImage source=" + source + " target=" + target);
if (!Files.exists(source.toAbsolutePath())) { if (!Files.exists(source.toAbsolutePath())) {
throw new FileNotFoundException(source.toString()); throw new FileNotFoundException("not found: " + source);
} }
if (!Files.isReadable(source.toAbsolutePath())) { 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<String> gsArgs = new LinkedList<>(); List<String> gsArgs = new LinkedList<>();
gsArgs.add("-dNOPAUSE"); gsArgs.add("-dNOPAUSE");
@ -141,16 +144,12 @@ public class PDFRasterizer {
gsArgs.add("-f"); gsArgs.add("-f");
gsArgs.add(source.toString()); gsArgs.add(source.toString());
logger.info("pdfToImage args=" + gsArgs); logger.info("pdfToImage args=" + gsArgs);
// reset stdin try (Ghostscript gs = new Ghostscript()) {
Ghostscript gs = Ghostscript.getInstance();
try {
gs.setStdIn(null); gs.setStdIn(null);
gs.setStdOut(new LoggingOutputStream(logger)); gs.setStdOut(new LoggingOutputStream(logger));
gs.setStdErr(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"); logger.info("pdfToImage done");
} finally {
gs.close();
} }
} }
@ -182,16 +181,13 @@ public class PDFRasterizer {
gsArgs.add("-f"); gsArgs.add("-f");
gsArgs.add(sourceFile.toString()); gsArgs.add(sourceFile.toString());
logger.info("pdfToImage args=" + gsArgs); logger.info("pdfToImage args=" + gsArgs);
Ghostscript gs = Ghostscript.getInstance(); try (Ghostscript gs = new Ghostscript()) {
try {
// reset stdin // reset stdin
gs.setStdIn(null); gs.setStdIn(null);
gs.setStdOut(new LoggingOutputStream(logger)); gs.setStdOut(new LoggingOutputStream(logger));
gs.setStdErr(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"); logger.info("pdfToImage done");
} finally {
gs.close();
} }
} }
@ -294,59 +290,52 @@ public class PDFRasterizer {
gsArgs.add("-sPAPERSIZE=a4"); gsArgs.add("-sPAPERSIZE=a4");
gsArgs.add("-sOutputFile=" + targetFile.toString()); gsArgs.add("-sOutputFile=" + targetFile.toString());
gsArgs.add(sourceFile.toString()); gsArgs.add(sourceFile.toString());
Ghostscript gs = Ghostscript.getInstance(); try (Ghostscript gs = new Ghostscript()) {
try {
gs.setStdIn(null); gs.setStdIn(null);
logger.info(gsArgs.toString()); 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"); logger.info("scalePDF: source = " + sourceFile + " target = " + targetFile + " done");
} finally {
gs.close();
} }
} }
public void toPDFA(Path source, Path target) throws IOException { public void toPDFA(Path source, Path target) throws IOException {
prepare(tmpPath); Path tmpPath = createTmpPath();
Path iccPath = Files.createTempFile(tmpPath, "srgb", ".icc"); try (Ghostscript gs = new Ghostscript()) {
Path pdfapsPathTmp = Files.createTempFile(tmpPath, "PDFA_def", ".tmp"); Path iccPath = Files.createTempFile(tmpPath, "srgb", ".icc");
Path pdfapsPath = Files.createTempFile(tmpPath, "PDFA_def", ".ps"); Path pdfapsPathTmp = Files.createTempFile(tmpPath, "PDFA_def", ".tmp");
try (InputStream srgbIcc = getClass().getResourceAsStream("/iccprofiles/srgb.icc")) { Path pdfapsPath = Files.createTempFile(tmpPath, "PDFA_def", ".ps");
if (srgbIcc != null) { try (InputStream srgbIcc = getClass().getResourceAsStream("/iccprofiles/srgb.icc")) {
Files.copy(srgbIcc, iccPath, StandardCopyOption.REPLACE_EXISTING); if (srgbIcc != null) {
Files.copy(srgbIcc, iccPath, StandardCopyOption.REPLACE_EXISTING);
}
} }
} try (InputStream pdfaPs = getClass().getResourceAsStream("/lib/PDFA_def.ps")) {
try (InputStream pdfaPs = getClass().getResourceAsStream("/lib/PDFA_def.ps")) { if (pdfaPs != null) {
if (pdfaPs != null) { Files.copy(pdfaPs, pdfapsPathTmp, StandardCopyOption.REPLACE_EXISTING);
Files.copy(pdfaPs, pdfapsPathTmp, StandardCopyOption.REPLACE_EXISTING); }
} }
} copyAndReplace(pdfapsPathTmp, pdfapsPath, "srgb.icc", iccPath.toAbsolutePath().toString());
copyAndReplace(pdfapsPathTmp, pdfapsPath, "srgb.icc", iccPath.toAbsolutePath().toString()); List<String> gsArgs = new LinkedList<>();
List<String> gsArgs = new LinkedList<>(); gsArgs.add("-E");
gsArgs.add("-E"); gsArgs.add("-dNOPAUSE");
gsArgs.add("-dNOPAUSE"); gsArgs.add("-dBATCH");
gsArgs.add("-dBATCH"); gsArgs.add("-dQUIET"); // do not print to stdout
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("-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("-dPDFA=2"); // PDF/A-2b gsArgs.add("-sColorConversionStrategy=/sRGB");
gsArgs.add("-sColorConversionStrategy=/sRGB"); gsArgs.add("-sOutputICCProfile=" + iccPath.toAbsolutePath().toString());
gsArgs.add("-sOutputICCProfile=" + iccPath.toAbsolutePath().toString()); gsArgs.add("-sDEVICE=pdfwrite");
gsArgs.add("-sDEVICE=pdfwrite"); gsArgs.add("-dPDFSETTINGS=/printer");
gsArgs.add("-dPDFSETTINGS=/printer"); gsArgs.add("-sPAPERSIZE=a4");
gsArgs.add("-sPAPERSIZE=a4"); gsArgs.add("-dPDFFitPage");
gsArgs.add("-dPDFFitPage"); gsArgs.add("-dAutoRotatePages=/PageByPage");
gsArgs.add("-dAutoRotatePages=/PageByPage"); gsArgs.add("-sOutputFile=" + target.toString());
gsArgs.add("-sOutputFile=" + target.toString()); gsArgs.add(pdfapsPath.toAbsolutePath().toString());
gsArgs.add(pdfapsPath.toAbsolutePath().toString()); gsArgs.add(source.toString());
gsArgs.add(source.toString());
Ghostscript gs = Ghostscript.getInstance();
try {
gs.setStdIn(null); gs.setStdIn(null);
gs.run(gsArgs.toArray(new String[gsArgs.size()])); gs.run(gsArgs.toArray(new String[0]));
} finally { } finally {
delete(pdfapsPathTmp); deleteTmpPath(tmpPath);
delete(pdfapsPath);
delete(iccPath);
gs.close();
} }
} }
@ -420,11 +409,7 @@ public class PDFRasterizer {
return pos >= 0 ? filename.substring(pos + 1).toLowerCase(Locale.ROOT) : null; return pos >= 0 ? filename.substring(pos + 1).toLowerCase(Locale.ROOT) : null;
} }
private static void prepare(Path path) throws IOException { private static void deleteTmpPath(Path path) throws IOException {
Files.createDirectories(path);
}
private static void delete(Path path) throws IOException {
if (path == null) { if (path == null) {
return; return;
} }
@ -451,4 +436,15 @@ public class PDFRasterizer {
Files.deleteIfExists(path); 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;
}
} }

View file

@ -21,6 +21,11 @@ import java.util.List;
*/ */
public interface GhostscriptLibrary extends Library { 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 * This function returns the revision numbers and strings of the Ghostscript
* interpreter library. You should call it before any other interpreter * interpreter library. You should call it before any other interpreter

View file

@ -1,13 +1,13 @@
package org.xbib.graphics.ghostscript.internal; package org.xbib.graphics.ghostscript.internal;
import static com.sun.jna.Platform.LINUX;
import static com.sun.jna.Platform.MAC; import static com.sun.jna.Platform.MAC;
import com.sun.jna.Function; import com.sun.jna.Function;
import com.sun.jna.InvocationMapper;
import com.sun.jna.Library; import com.sun.jna.Library;
import com.sun.jna.Native; import com.sun.jna.Native;
import com.sun.jna.Platform; import com.sun.jna.Platform;
import java.util.HashMap; import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -28,29 +28,39 @@ public class GhostscriptLibraryLoader {
"libgs.9.25", "libgs.9.25",
"gs.9.25", "gs.9.25",
"libgs.dylib", "libgs.dylib",
"libgs",
"gs", "gs",
}; };
public static GhostscriptLibrary loadLibrary() { private GhostscriptLibrary ghostscriptLibrary;
Map<String, Object> options = new HashMap<>();
options.put(Library.OPTION_CALLING_CONVENTION, Function.C_CONVENTION); public GhostscriptLibraryLoader() {
String[] LIBNAMES = {}; Map<String, Object> options = Map.of(Library.OPTION_CALLING_CONVENTION, Function.C_CONVENTION,
switch (Platform.getOSType()) { Library.OPTION_INVOCATION_MAPPER, (InvocationMapper) (lib, m) -> {
case LINUX: if (m.getName().equals("dispose")) {
LIBNAMES = LINUX_LIBNAMES; return (proxy, method, args) -> {
break; lib.dispose();
case MAC: return null;
LIBNAMES = MAC_LIBNAMES; };
break; }
return null;
});
String[] libnames = LINUX_LIBNAMES;
if (Platform.getOSType() == MAC) {
libnames = MAC_LIBNAMES;
} }
for (String libname : LIBNAMES) { for (String libname : libnames) {
try { try {
return Native.load(libname, GhostscriptLibrary.class, options); this.ghostscriptLibrary = Native.load(libname, GhostscriptLibrary.class, options);
} catch (Error e) { } catch (Error e) {
logger.log(Level.WARNING, "library " + libname + " not found", 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;
} }
} }

View file

@ -9,7 +9,8 @@ public class GhostScriptLibraryTester {
private final GhostscriptLibrary ghostscriptLibrary; private final GhostscriptLibrary ghostscriptLibrary;
public GhostScriptLibraryTester() { public GhostScriptLibraryTester() {
ghostscriptLibrary = GhostscriptLibraryLoader.loadLibrary(); GhostscriptLibraryLoader loader = new GhostscriptLibraryLoader();
this.ghostscriptLibrary = loader.getGhostscriptLibrary();
} }
public String getRevisionProduct() { public String getRevisionProduct() {

View file

@ -1,8 +1,6 @@
package org.xbib.graphics.ghostscript.test; package org.xbib.graphics.ghostscript.test;
import java.io.IOException; 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.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.xbib.graphics.ghostscript.Ghostscript; 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/"; 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 @Test
public void testGetRevision() { public void testGetRevision() throws IOException {
GhostscriptRevision revision = Ghostscript.getRevision(); try (Ghostscript gs = new Ghostscript()) {
logger.log(Level.INFO, "product = " + revision.getProduct()); GhostscriptRevision revision = gs.getRevision();
assertNotNull(revision.getProduct()); logger.log(Level.INFO, "product = " + revision.getProduct());
logger.log(Level.INFO, "copyright = " + revision.getCopyright()); assertNotNull(revision.getProduct());
assertNotNull(revision.getCopyright()); logger.log(Level.INFO, "copyright = " + revision.getCopyright());
logger.log(Level.INFO, "revision date = " + revision.getRevisionDate()); assertNotNull(revision.getCopyright());
assertNotNull(revision.getRevisionDate()); logger.log(Level.INFO, "revision date = " + revision.getRevisionDate());
logger.log(Level.INFO, "number = " + revision.getNumber()); assertNotNull(revision.getRevisionDate());
assertNotNull(revision.getNumber()); logger.log(Level.INFO, "number = " + revision.getNumber());
assertNotNull(revision.getNumber());
}
} }
@Test @Test
public void testExit() { public void testExit() {
try { try (Ghostscript gs = new Ghostscript()) {
String[] args = {"-dNODISPLAY", "-dQUIET"}; String[] args = {"-dNODISPLAY", "-dQUIET"};
gs.run(args); gs.run(args);
} catch (Exception e) { } catch (Exception e) {
logger.log(Level.SEVERE, e.getMessage(), e);
fail(e.getMessage()); fail(e.getMessage());
} }
} }
@Test @Test
public void testRunString() { public void testRunString() {
try { try (Ghostscript gs = new Ghostscript()) {
String[] args = {"-dNODISPLAY", "-dQUIET"}; String[] args = {"-dNODISPLAY", "-dQUIET"};
gs.run(args, "devicenames ==", null); gs.run(args, "devicenames ==", null);
} catch (Exception e) { } catch (Exception e) {
@ -73,7 +62,7 @@ public class GhostscriptTest {
@Test @Test
public void testRunFile() { public void testRunFile() {
try { try (Ghostscript gs = new Ghostscript()) {
String[] args = {"-dNODISPLAY", "-dQUIET", "-dNOPAUSE", "-dBATCH", "-dSAFER"}; String[] args = {"-dNODISPLAY", "-dQUIET", "-dNOPAUSE", "-dBATCH", "-dSAFER"};
gs.run(args, null, dir + "input.ps"); gs.run(args, null, dir + "input.ps");
} catch (Exception e) { } catch (Exception e) {
@ -90,7 +79,8 @@ public class GhostscriptTest {
@Disabled("core dump") @Disabled("core dump")
@Test @Test
public void testStdIn() { 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); gs.setStdIn(is);
String[] args = {"-dNODISPLAY", "-dQUIET", "-dNOPAUSE", "-dBATCH", "-sOutputFile=%stdout", "-f", "-"}; String[] args = {"-dNODISPLAY", "-dQUIET", "-dNOPAUSE", "-dBATCH", "-sOutputFile=%stdout", "-f", "-"};
gs.run(args); gs.run(args);
@ -101,7 +91,8 @@ public class GhostscriptTest {
@Test @Test
public void testStdOut() { 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); gs.setStdIn(is);
String[] args = { "-dNODISPLAY", "-sOutputFile=%stdout", "-f", "-" }; String[] args = { "-dNODISPLAY", "-sOutputFile=%stdout", "-f", "-" };
gs.run(args); gs.run(args);
@ -112,7 +103,8 @@ public class GhostscriptTest {
@Test @Test
public void testStdErr() { 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); gs.setStdIn(is);
String[] args = { "-dNODISPLAY", "-sOutputFile=%stdout", "-f", "-" }; String[] args = { "-dNODISPLAY", "-sOutputFile=%stdout", "-f", "-" };
gs.run(args); gs.run(args);

View file

@ -45,17 +45,17 @@ public class PDFRasterizerTest {
public void testPDFUnpackRasterAndScale() throws IOException { public void testPDFUnpackRasterAndScale() throws IOException {
Path source = Paths.get("src/test/resources/org/xbib/graphics/ghostscript/test/20200024360.pdf"); Path source = Paths.get("src/test/resources/org/xbib/graphics/ghostscript/test/20200024360.pdf");
Path target = Paths.get("build/20200024360-new.pdf"); Path target = Paths.get("build/20200024360-new.pdf");
Path tmp = Files.createTempDirectory("graphics-test"); Path path = Files.createTempDirectory("graphics-test");
int pagecount = 0; int pagecount = 0;
try { try {
PDFRasterizer pdfRasterizer = new PDFRasterizer(); PDFRasterizer pdfRasterizer = new PDFRasterizer();
pdfRasterizer.pdfToImage(source, tmp, null, null); pdfRasterizer.pdfToImage(source, path, null, null);
Path tmpTarget = tmp.resolve(target.getFileName()); Path tmpTarget = path.resolve(target.getFileName());
pagecount = pdfRasterizer.mergeImagesToPDF(tmp, tmpTarget); pagecount = pdfRasterizer.mergeImagesToPDF(path, tmpTarget);
logger.info("pagecount = " + pagecount); logger.info("pagecount = " + pagecount);
pdfRasterizer.scalePDF(tmpTarget, target); pdfRasterizer.scalePDF(tmpTarget, target);
} finally { } finally {
delete(tmp); delete(path);
} }
assertEquals(28, pagecount); assertEquals(28, pagecount);
} }

View file

@ -14,6 +14,6 @@ dependencies {
testImplementation libs.groovy.templates testImplementation libs.groovy.templates
testImplementation libs.groovy.test testImplementation libs.groovy.test
testImplementation libs.spock testImplementation libs.spock
testImplementation libs.cglib testImplementation libs.bytebuddy
testImplementation libs.objenesis testImplementation libs.objenesis
} }

View file

@ -36,10 +36,10 @@ dependencyResolutionManagement {
library('groovy-templates', 'org.apache.groovy', 'groovy-templates').versionRef('groovy') library('groovy-templates', 'org.apache.groovy', 'groovy-templates').versionRef('groovy')
library('groovy-test', 'org.apache.groovy', 'groovy-test').versionRef('groovy') library('groovy-test', 'org.apache.groovy', 'groovy-test').versionRef('groovy')
library('spock', 'org.spockframework', 'spock-core').versionRef('spock') 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('objenesis', 'org.objenesis', 'objenesis').version('2.6')
library('jna', 'net.java.dev.jna', 'jna').version('5.12.1') library('jna', 'net.java.dev.jna', 'jna').version('5.13.0')
library('pdfbox', 'org.apache.pdfbox', 'pdfbox').version('2.0.27') library('pdfbox', 'org.apache.pdfbox', 'pdfbox').version('2.0.28')
library('zxing', 'com.google.zxing', 'javase').version('3.4.1') library('zxing', 'com.google.zxing', 'javase').version('3.4.1')
library('reflections', 'org.reflections', 'reflections').version('0.9.11') library('reflections', 'org.reflections', 'reflections').version('0.9.11')
library('jfreechart', 'org.jfree', 'jfreechart').version('1.5.2') library('jfreechart', 'org.jfree', 'jfreechart').version('1.5.2')