diff --git a/config/alfresco/content-services-context.xml b/config/alfresco/content-services-context.xml index fefae178e6..b96f18360c 100644 --- a/config/alfresco/content-services-context.xml +++ b/config/alfresco/content-services-context.xml @@ -656,7 +656,29 @@ + + + + + + + + transformer.worker.Pdfium + + + + org.alfresco.repo.content.transform.ContentTransformerWorker + + + + + + + + + + + + + + + + + + + + + + + + ${pdfium.exe} + SPLIT:${options} + ${source} + ${target} + + + + + + + + + + + + + 1,2,255,400,405,410,415,420,425,430,435,440,450,455,460,465,470,475,480,485,490,495,499,700,705,710,715,720,725,730,735,740,750,755,760,765,770,775,780,785,790,795,799 + + + + + + + + + + ${pdfium.root} + + + + + + + + + + ${pdfium.root} + + + + + + \ No newline at end of file diff --git a/config/alfresco/subsystems/thirdparty/default/pdfium-transform.properties b/config/alfresco/subsystems/thirdparty/default/pdfium-transform.properties new file mode 100644 index 0000000000..df9d4ce023 --- /dev/null +++ b/config/alfresco/subsystems/thirdparty/default/pdfium-transform.properties @@ -0,0 +1,4 @@ +# External executable locations +pdfium.root=/Users/p3700670/work +pdfium.exe=${pdfium.root}/alfresco_pdfium_convert + diff --git a/source/java/org/alfresco/repo/content/transform/pdfium/PdfiumContentTransformerWorker.java b/source/java/org/alfresco/repo/content/transform/pdfium/PdfiumContentTransformerWorker.java new file mode 100644 index 0000000000..28bff3d343 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/pdfium/PdfiumContentTransformerWorker.java @@ -0,0 +1,301 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.repo.content.transform.pdfium; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.transform.ContentTransformerHelper; +import org.alfresco.repo.content.transform.ContentTransformerWorker; +import org.alfresco.repo.content.transform.magick.ImageResizeOptions; +import org.alfresco.repo.content.transform.magick.ImageTransformationOptions; +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.service.cmr.repository.PagedSourceOptions; +import org.alfresco.service.cmr.repository.TransformationOptions; +import org.alfresco.util.TempFileProvider; +import org.alfresco.util.exec.RuntimeExec; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; + +public class PdfiumContentTransformerWorker extends ContentTransformerHelper + implements ContentTransformerWorker, InitializingBean { + + /** options variable name */ + private static final String KEY_OPTIONS = "options"; + /** source variable name */ + private static final String VAR_SOURCE = "source"; + /** target variable name */ + private static final String VAR_TARGET = "target"; + + private static final Log logger = LogFactory.getLog(PdfiumContentTransformerWorker.class); + + /** the system command executer */ + private RuntimeExec executer; + + /** the output from the check command */ + private String versionString; + + private boolean available; + + public PdfiumContentTransformerWorker() { + this.available = false; + } + + @Override + public void afterPropertiesSet() throws Exception { + if (executer == null) { + throw new AlfrescoRuntimeException("System runtime executer not set"); + } + setAvailable(true); + } + + /** + * Set the runtime command executer that must be executed in order to run + * ImageMagick. Whether or not this is the full path to the convertCommand + * or just the convertCommand itself depends the environment setup. + *

+ * The command must contain the variables ${source} and + * ${target}, which will be replaced by the names of the file to + * be transformed and the name of the output file respectively. + *

+     *    convert ${source} ${target}
+     * 
+ * + * @param executer the system command executer + */ + public void setExecuter(RuntimeExec executer) + { + executer.setProcessProperty( + "PDFIUM_TMPDIR", TempFileProvider.getTempDir().getAbsolutePath()); + this.executer = executer; + } + + /** + * @return Returns true if the transformer is functioning otherwise false + */ + @Override + public boolean isAvailable() { + return available; + } + + /** + * Make the transformer available + * + * @param available + * boolean + */ + protected void setAvailable(boolean available) { + this.available = available; + } + + @Override + public String getVersionString() { + return this.versionString; + } + + @Override + public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) { + if (!available) { + return false; + } + + // Add limited support (so lots of other transforms are not supported) + // for PDF to PNG. + if ((MimetypeMap.MIMETYPE_PDF.equals(sourceMimetype) + || MimetypeMap.MIMETYPE_APPLICATION_ILLUSTRATOR.equals(sourceMimetype)) + && MimetypeMap.MIMETYPE_IMAGE_PNG.equals(targetMimetype)) { + return true; + } + + return false; + } + + @Override + public void transform(ContentReader reader, ContentWriter writer, TransformationOptions options) throws Exception { + // get mimetypes + String sourceMimetype = getMimetype(reader); + String targetMimetype = getMimetype(writer); + + // get the extensions to use + MimetypeService mimetypeService = getMimetypeService(); + String sourceExtension = mimetypeService.getExtension(sourceMimetype); + String targetExtension = mimetypeService.getExtension(targetMimetype); + if (sourceExtension == null || targetExtension == null) { + throw new AlfrescoRuntimeException("Unknown extensions for mimetypes: \n" + " source mimetype: " + + sourceMimetype + "\n" + " source extension: " + sourceExtension + "\n" + " target mimetype: " + + targetMimetype + "\n" + " target extension: " + targetExtension); + } + + // create required temp files + File sourceFile = TempFileProvider.createTempFile(getClass().getSimpleName() + "_source_", + "." + sourceExtension); + File targetFile = TempFileProvider.createTempFile(getClass().getSimpleName() + "_target_", + "." + targetExtension); + + // pull reader file into source temp file + reader.getContent(sourceFile); + + transformInternal(sourceFile, sourceMimetype, targetFile, targetMimetype, options); + + // check that the file was created + if (!targetFile.exists() || targetFile.length() == 0) { + throw new ContentIOException("Pdfium transformation failed to write output file"); + } + // upload the output image + writer.putContent(targetFile); + // done + if (logger.isDebugEnabled()) { + logger.debug("Transformation completed: \n" + " source: " + reader + "\n" + " target: " + writer + "\n" + + " options: " + options); + } + + } + + /** + * Transform the pdf content from the source file to the target file + */ + + private void transformInternal(File sourceFile, String sourceMimetype, File targetFile, String targetMimetype, + TransformationOptions options) throws Exception { + Map properties = new HashMap(5); + // set properties + if (options instanceof ImageTransformationOptions) { + ImageTransformationOptions imageOptions = (ImageTransformationOptions)options; + ImageResizeOptions resizeOptions = imageOptions.getResizeOptions(); + String commandOptions = imageOptions.getCommandOptions(); + if (commandOptions == null) + { + commandOptions = ""; + } + + if(resizeOptions.getHeight() > -1){ + commandOptions += " --height=" + resizeOptions.getHeight(); + } + if(resizeOptions.getWidth() > -1){ + commandOptions += " --width=" + resizeOptions.getHeight(); + } + if(resizeOptions.getAllowEnlargement()){ + commandOptions += " --allow-enlargement"; + } + if(resizeOptions.isMaintainAspectRatio()){ + commandOptions += " --maintain-aspect-ratio"; + } + commandOptions += " --page="+ getSourcePageRange(imageOptions, sourceMimetype, targetMimetype); + + properties.put(KEY_OPTIONS, commandOptions); + } + + properties.put(VAR_SOURCE, + sourceFile.getAbsolutePath()); + properties.put(VAR_TARGET, targetFile.getAbsolutePath()); + + // execute the statement + long timeoutMs = options.getTimeoutMs(); + RuntimeExec.ExecutionResult result = executer.execute(properties, timeoutMs); + if (result.getExitValue() != 0 && result.getStdErr() != null && result.getStdErr().length() > 0) { + throw new ContentIOException("Failed to perform Pdfium transformation: \n" + result); + } + // success + if (logger.isDebugEnabled()) { + logger.debug("Pdfium executed successfully: \n" + executer); + } + } + + @Override + public String getComments(boolean available) { + StringBuilder sb = new StringBuilder(); + sb.append("# Supports transformations between mimetypes"); + sb.append("# pdf or ai to png.\n"); + return sb.toString(); + } + + + /** + * Determines whether or not a single page range is required for the given + * source and target mimetypes. + * + * @param sourceMimetype + * @param targetMimetype + * @return whether or not a page range must be specified for the transformer + * to read the target files + */ + private boolean isSingleSourcePageRangeRequired(String sourceMimetype, String targetMimetype) { + return true; + } + + /** + * Gets the page range from the source to use in the command line. + * + * @param options + * the transformation options + * @param sourceMimetype + * the source mimetype + * @param targetMimetype + * the target mimetype + * @return the source page range for the command line + */ + private String getSourcePageRange(TransformationOptions options, String sourceMimetype, String targetMimetype) { + if (options instanceof ImageTransformationOptions) { + ImageTransformationOptions imageOptions = (ImageTransformationOptions) options; + PagedSourceOptions pagedSourceOptions = imageOptions.getSourceOptions(PagedSourceOptions.class); + if (pagedSourceOptions != null) { + if (pagedSourceOptions.getStartPageNumber() != null && pagedSourceOptions.getEndPageNumber() != null) { + if (pagedSourceOptions.getStartPageNumber().equals(pagedSourceOptions.getEndPageNumber())) { + return "" + (pagedSourceOptions.getStartPageNumber() - 1) ; + } else { + if (isSingleSourcePageRangeRequired(sourceMimetype, targetMimetype)) { + throw new AlfrescoRuntimeException( + "A single page is required for targets of type " + targetMimetype); + } + return "" + (pagedSourceOptions.getStartPageNumber() - 1) + "-" + + (pagedSourceOptions.getEndPageNumber() - 1); + } + } else { + // TODO specified start to end of doc and start of doc to + // specified end not yet supported + // Just grab a single page specified by either start or end + if (pagedSourceOptions.getStartPageNumber() != null) + return "" + (pagedSourceOptions.getStartPageNumber() - 1) ; + if (pagedSourceOptions.getEndPageNumber() != null) + return "" + (pagedSourceOptions.getEndPageNumber() - 1) ; + } + } + } + if (options.getPageLimit() == 1 || isSingleSourcePageRangeRequired(sourceMimetype, targetMimetype)) { + return "0"; + } else { + return ""; + } + } + +}