mirror of
https://github.com/Alfresco/alfresco-transform-core.git
synced 2025-05-12 17:04:48 +00:00
ATS-995, MNT-23248: TIFF to PDF ImageMagick transform don't work
- introducing new custom transformer-misc: ImageToPdf
This commit is contained in:
parent
888294d28b
commit
5bd2430513
@ -932,7 +932,7 @@
|
|||||||
{"sourceMediaType": "image/tiff", "targetMediaType": "image/x-raw-minolta" },
|
{"sourceMediaType": "image/tiff", "targetMediaType": "image/x-raw-minolta" },
|
||||||
{"sourceMediaType": "image/tiff", "targetMediaType": "image/x-raw-nikon" },
|
{"sourceMediaType": "image/tiff", "targetMediaType": "image/x-raw-nikon" },
|
||||||
{"sourceMediaType": "image/tiff", "targetMediaType": "image/x-raw-olympus" },
|
{"sourceMediaType": "image/tiff", "targetMediaType": "image/x-raw-olympus" },
|
||||||
{"sourceMediaType": "image/tiff", "targetMediaType": "image/x-portable-bitmap" },
|
{"sourceMediaType": "image/tiff", "targetMediaType": "image/x-portable-bitmap" },
|
||||||
{"sourceMediaType": "image/tiff", "targetMediaType": "image/x-raw-pentax" },
|
{"sourceMediaType": "image/tiff", "targetMediaType": "image/x-raw-pentax" },
|
||||||
{"sourceMediaType": "image/tiff", "targetMediaType": "image/x-portable-graymap" },
|
{"sourceMediaType": "image/tiff", "targetMediaType": "image/x-portable-graymap" },
|
||||||
{"sourceMediaType": "image/tiff", "targetMediaType": "image/x-portable-anymap" },
|
{"sourceMediaType": "image/tiff", "targetMediaType": "image/x-portable-anymap" },
|
||||||
@ -950,7 +950,6 @@
|
|||||||
{"sourceMediaType": "image/tiff", "targetMediaType": "image/x-xbitmap" },
|
{"sourceMediaType": "image/tiff", "targetMediaType": "image/x-xbitmap" },
|
||||||
{"sourceMediaType": "image/tiff", "targetMediaType": "image/x-xpixmap" },
|
{"sourceMediaType": "image/tiff", "targetMediaType": "image/x-xpixmap" },
|
||||||
{"sourceMediaType": "image/tiff", "targetMediaType": "image/x-xwindowdump" },
|
{"sourceMediaType": "image/tiff", "targetMediaType": "image/x-xwindowdump" },
|
||||||
{"sourceMediaType": "image/tiff", "targetMediaType": "application/pdf" },
|
|
||||||
|
|
||||||
{"sourceMediaType": "image/x-raw-sigma", "targetMediaType": "image/x-raw-hasselblad" },
|
{"sourceMediaType": "image/x-raw-sigma", "targetMediaType": "image/x-raw-hasselblad" },
|
||||||
{"sourceMediaType": "image/x-raw-sigma", "targetMediaType": "image/x-raw-sony" },
|
{"sourceMediaType": "image/x-raw-sigma", "targetMediaType": "image/x-raw-sony" },
|
||||||
|
@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Transform Core
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2022 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 <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.transform.misc.transformers;
|
||||||
|
|
||||||
|
import static org.alfresco.transform.common.RequestParamMap.END_PAGE;
|
||||||
|
import static org.alfresco.transform.common.RequestParamMap.PDF_FORMAT;
|
||||||
|
import static org.alfresco.transform.common.RequestParamMap.START_PAGE;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.alfresco.transform.base.TransformManager;
|
||||||
|
import org.alfresco.transform.base.util.CustomTransformerFileAdaptor;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts image files into PDF files. Transformer uses PDF Box to perform conversions.
|
||||||
|
* During conversion image might be scaled down (keeping proportions) to match width or height of the PDF document.
|
||||||
|
* If the image is smaller than PDF page size, the image will be placed in the top left-hand side of the PDF document page.
|
||||||
|
* Transformer takes 3 optional transform options:
|
||||||
|
* - startPage - page number of image (for multipage images) from which transformer should start conversion. Default: first page of the image.
|
||||||
|
* - endPage - page number of image (for multipage images) up to which transformation should be performed. Default: last page of the image.
|
||||||
|
* - pdfFormat - output PDF file format. Available formats: A0, A1, A2, A3, A4, A5, A6, LETTER, LEGAL. Default: A4.
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class ImageToPdfTransformer implements CustomTransformerFileAdaptor
|
||||||
|
{
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ImageToPdfTransformer.class);
|
||||||
|
|
||||||
|
private static final String NEGATIVE_START_PAGE_ERROR_MESSAGE = "Start page number cannot be a negative number.";
|
||||||
|
private static final String NEGATIVE_END_PAGE_ERROR_MESSAGE = "End page number cannot be a negative number.";
|
||||||
|
private static final String START_PAGE_GREATER_THAN_END_PAGE_ERROR_MESSAGE = "Start page number cannot be greater than end page.";
|
||||||
|
private static final String INVALID_OPTION_ERROR_MESSAGE = "Parameter '%s' is invalid: \"%s\" - it must be an integer.";
|
||||||
|
private static final String INVALID_IMAGE_ERROR_MESSAGE = "Image file (%s) format (%s) not supported by ImageIO.";
|
||||||
|
private static final String DEFAULT_PDF_FORMAT_STRING = "A4";
|
||||||
|
private static final PDRectangle DEFAULT_PDF_FORMAT = PDRectangle.A4;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTransformerName()
|
||||||
|
{
|
||||||
|
return "imageToPdf";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transform(
|
||||||
|
String sourceMimetype, String targetMimetype, Map<String, String> transformOptions,
|
||||||
|
File imageFile, File pdfFile, TransformManager transformManager
|
||||||
|
) throws Exception {
|
||||||
|
try (
|
||||||
|
ImageInputStream imageInputStream = ImageIO.createImageInputStream(imageFile);
|
||||||
|
PDDocument pdfDocument = new PDDocument()
|
||||||
|
) {
|
||||||
|
final Integer startPage = parseOptionIfPresent(transformOptions, START_PAGE, Integer.class).orElse(null);
|
||||||
|
final Integer endPage = parseOptionIfPresent(transformOptions, END_PAGE, Integer.class).orElse(null);
|
||||||
|
final String pdfFormat = parseOptionIfPresent(transformOptions, PDF_FORMAT, String.class).orElse(DEFAULT_PDF_FORMAT_STRING);
|
||||||
|
verifyOptions(startPage, endPage);
|
||||||
|
|
||||||
|
final ImageReader imageReader = findImageReader(imageInputStream, imageFile.getName(), sourceMimetype);
|
||||||
|
for (int i = 0; i < imageReader.getNumImages(true); i++)
|
||||||
|
{
|
||||||
|
if (startPage != null && i < startPage)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (endPage != null && i > endPage)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
scaleAndDrawImage(pdfDocument, imageReader.read(i), pdfFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
pdfDocument.save(pdfFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImageReader findImageReader(final ImageInputStream imageInputStream, final String imageName, final String mimetype) throws IOException
|
||||||
|
{
|
||||||
|
final Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(imageInputStream);
|
||||||
|
if (imageReaders == null || !imageReaders.hasNext())
|
||||||
|
{
|
||||||
|
throw new IOException(String.format(INVALID_IMAGE_ERROR_MESSAGE, imageName, mimetype));
|
||||||
|
}
|
||||||
|
final ImageReader imageReader = imageReaders.next();
|
||||||
|
imageReader.setInput(imageInputStream);
|
||||||
|
|
||||||
|
return imageReader;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scaleAndDrawImage(final PDDocument pdfDocument, final BufferedImage bufferedImage, final String pdfFormat) throws IOException
|
||||||
|
{
|
||||||
|
final PDPage pdfPage = new PDPage(resolvePdfFormat(pdfFormat));
|
||||||
|
pdfDocument.addPage(pdfPage);
|
||||||
|
final PDImageXObject image = LosslessFactory.createFromImage(pdfDocument, bufferedImage);
|
||||||
|
try (PDPageContentStream pdfPageContent = new PDPageContentStream(pdfDocument, pdfPage))
|
||||||
|
{
|
||||||
|
final PDRectangle pageSize = pdfPage.getMediaBox();
|
||||||
|
final float withRatio = pageSize.getWidth() / image.getWidth();
|
||||||
|
final float heightRatio = pageSize.getHeight() / image.getHeight();
|
||||||
|
final float ratio = Stream.of(withRatio, heightRatio, 1f).min(Comparator.naturalOrder()).get();
|
||||||
|
// find image bottom
|
||||||
|
final float y = pageSize.getHeight() - image.getHeight() * ratio;
|
||||||
|
// drawing starts from bottom left corner
|
||||||
|
pdfPageContent.drawImage(image, 0, y, image.getWidth() * ratio, image.getHeight() * ratio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PDRectangle resolvePdfFormat(final String pdfFormat)
|
||||||
|
{
|
||||||
|
switch (pdfFormat.toUpperCase()) {
|
||||||
|
case "A4":
|
||||||
|
return DEFAULT_PDF_FORMAT;
|
||||||
|
case "LETTER":
|
||||||
|
return PDRectangle.LETTER;
|
||||||
|
case "A0":
|
||||||
|
return PDRectangle.A0;
|
||||||
|
case "A1":
|
||||||
|
return PDRectangle.A1;
|
||||||
|
case "A2":
|
||||||
|
return PDRectangle.A2;
|
||||||
|
case "A3":
|
||||||
|
return PDRectangle.A3;
|
||||||
|
case "A5":
|
||||||
|
return PDRectangle.A5;
|
||||||
|
case "A6":
|
||||||
|
return PDRectangle.A6;
|
||||||
|
case "LEGAL":
|
||||||
|
return PDRectangle.LEGAL;
|
||||||
|
default:
|
||||||
|
log.info("PDF format: '{}' not supported. Using default: '{}'", pdfFormat, DEFAULT_PDF_FORMAT_STRING);
|
||||||
|
return DEFAULT_PDF_FORMAT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> Optional<T> parseOptionIfPresent(final Map<String, String> transformOptions, final String parameter, final Class<T> targetType)
|
||||||
|
{
|
||||||
|
if (transformOptions.containsKey(parameter))
|
||||||
|
{
|
||||||
|
final String option = transformOptions.get(parameter);
|
||||||
|
if (targetType == Integer.class)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Optional.of(targetType.cast(Integer.parseInt(option)));
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException(String.format(INVALID_OPTION_ERROR_MESSAGE, parameter, option));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Optional.of(targetType.cast(option));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void verifyOptions(final Integer startPage, final Integer endPage)
|
||||||
|
{
|
||||||
|
if (startPage != null && startPage < 0)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException(NEGATIVE_START_PAGE_ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endPage != null && endPage < 0)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException(NEGATIVE_END_PAGE_ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startPage != null && endPage != null && startPage > endPage)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException(START_PAGE_GREATER_THAN_END_PAGE_ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,11 @@
|
|||||||
],
|
],
|
||||||
"metadataOptions": [
|
"metadataOptions": [
|
||||||
{"value": {"name": "extractMapping"}}
|
{"value": {"name": "extractMapping"}}
|
||||||
|
],
|
||||||
|
"imageToPdfOptions": [
|
||||||
|
{"value": {"name": "startPage"}},
|
||||||
|
{"value": {"name": "endPage"}},
|
||||||
|
{"value": {"name": "pdfFormat"}}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"transformers": [
|
"transformers": [
|
||||||
@ -91,6 +96,15 @@
|
|||||||
"transformOptions": [
|
"transformOptions": [
|
||||||
"metadataOptions"
|
"metadataOptions"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"transformerName": "imageToPdf",
|
||||||
|
"supportedSourceAndTargetList": [
|
||||||
|
{"sourceMediaType": "image/tiff", "targetMediaType": "application/pdf"}
|
||||||
|
],
|
||||||
|
"transformOptions": [
|
||||||
|
"imageToPdfOptions"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -26,21 +26,12 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.transform.misc;
|
package org.alfresco.transform.misc;
|
||||||
|
|
||||||
import org.alfresco.transform.base.clients.FileInfo;
|
|
||||||
import org.alfresco.transform.base.clients.SourceTarget;
|
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import static java.text.MessageFormat.format;
|
import static java.text.MessageFormat.format;
|
||||||
import static java.util.function.Function.identity;
|
import static java.util.function.Function.identity;
|
||||||
import static java.util.stream.Collectors.toMap;
|
import static java.util.stream.Collectors.toMap;
|
||||||
import static org.alfresco.transform.base.clients.HttpClient.sendTRequest;
|
|
||||||
import static org.alfresco.transform.base.clients.FileInfo.testFile;
|
import static org.alfresco.transform.base.clients.FileInfo.testFile;
|
||||||
|
import static org.alfresco.transform.base.clients.HttpClient.sendTRequest;
|
||||||
import static org.alfresco.transform.common.Mimetype.MIMETYPE_DITA;
|
import static org.alfresco.transform.common.Mimetype.MIMETYPE_DITA;
|
||||||
import static org.alfresco.transform.common.Mimetype.MIMETYPE_EXCEL;
|
import static org.alfresco.transform.common.Mimetype.MIMETYPE_EXCEL;
|
||||||
import static org.alfresco.transform.common.Mimetype.MIMETYPE_HTML;
|
import static org.alfresco.transform.common.Mimetype.MIMETYPE_HTML;
|
||||||
@ -67,9 +58,22 @@ import static org.alfresco.transform.common.Mimetype.MIMETYPE_TEXT_PLAIN;
|
|||||||
import static org.alfresco.transform.common.Mimetype.MIMETYPE_WORD;
|
import static org.alfresco.transform.common.Mimetype.MIMETYPE_WORD;
|
||||||
import static org.alfresco.transform.common.Mimetype.MIMETYPE_XML;
|
import static org.alfresco.transform.common.Mimetype.MIMETYPE_XML;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
import static org.springframework.http.HttpStatus.OK;
|
import static org.springframework.http.HttpStatus.OK;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.alfresco.transform.base.clients.FileInfo;
|
||||||
|
import org.alfresco.transform.base.clients.SourceTarget;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Cezar Leahu
|
* @author Cezar Leahu
|
||||||
*/
|
*/
|
||||||
@ -146,6 +150,8 @@ public class MiscTransformsIT
|
|||||||
SourceTarget.of("application/dita+xml", "application/pdf"),
|
SourceTarget.of("application/dita+xml", "application/pdf"),
|
||||||
SourceTarget.of("text/xml", "application/pdf"),
|
SourceTarget.of("text/xml", "application/pdf"),
|
||||||
|
|
||||||
|
SourceTarget.of(MIMETYPE_IMAGE_TIFF, MIMETYPE_PDF),
|
||||||
|
|
||||||
SourceTarget.of("message/rfc822", "text/plain")
|
SourceTarget.of("message/rfc822", "text/plain")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -164,9 +170,17 @@ public class MiscTransformsIT
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// when
|
||||||
final ResponseEntity<Resource> response = sendTRequest(ENGINE_URL, sourceFile,
|
final ResponseEntity<Resource> response = sendTRequest(ENGINE_URL, sourceFile,
|
||||||
sourceMimetype, targetMimetype, targetExtension);
|
sourceMimetype, targetMimetype, targetExtension);
|
||||||
|
|
||||||
assertEquals(OK, response.getStatusCode(), descriptor);
|
assertEquals(OK, response.getStatusCode(), descriptor);
|
||||||
|
if (MIMETYPE_PDF.equals(targetMimetype))
|
||||||
|
{
|
||||||
|
// verify if PDF isn't corrupted
|
||||||
|
final PDDocument pdfFile = PDDocument.load(Objects.requireNonNull(response.getBody()).getInputStream());
|
||||||
|
assertNotNull(pdfFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,350 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Transform Core
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2022 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 <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.transform.misc.transformers;
|
||||||
|
|
||||||
|
import static org.alfresco.transform.common.Mimetype.MIMETYPE_IMAGE_GIF;
|
||||||
|
import static org.alfresco.transform.common.Mimetype.MIMETYPE_IMAGE_JPEG;
|
||||||
|
import static org.alfresco.transform.common.Mimetype.MIMETYPE_IMAGE_PNG;
|
||||||
|
import static org.alfresco.transform.common.Mimetype.MIMETYPE_IMAGE_TIFF;
|
||||||
|
import static org.alfresco.transform.common.Mimetype.MIMETYPE_PDF;
|
||||||
|
import static org.alfresco.transform.common.RequestParamMap.END_PAGE;
|
||||||
|
import static org.alfresco.transform.common.RequestParamMap.PDF_FORMAT;
|
||||||
|
import static org.alfresco.transform.common.RequestParamMap.START_PAGE;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.BDDMockito.then;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.alfresco.transform.base.TransformManager;
|
||||||
|
import org.alfresco.transform.misc.util.ArgumentsCartesianProduct;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
class ImageToPdfTransformerTest
|
||||||
|
{
|
||||||
|
private static final File sourceFile = loadFile("quick.gif");
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private TransformManager transformManager;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private ImageToPdfTransformer transformer;
|
||||||
|
|
||||||
|
private File targetFile = null;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() throws IOException
|
||||||
|
{
|
||||||
|
MockitoAnnotations.openMocks(this);
|
||||||
|
|
||||||
|
targetFile = File.createTempFile("temp_target", ".pdf");
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<ImageFile> imageFiles()
|
||||||
|
{
|
||||||
|
return Stream.of(
|
||||||
|
ImageFile.of("quick.jpg", MIMETYPE_IMAGE_JPEG),
|
||||||
|
ImageFile.of("quick.gif", MIMETYPE_IMAGE_GIF),
|
||||||
|
ImageFile.of("quick.png", MIMETYPE_IMAGE_PNG)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<TransformOptions> defaultTransformOptions()
|
||||||
|
{
|
||||||
|
return Stream.of(
|
||||||
|
TransformOptions.none(),
|
||||||
|
TransformOptions.of(0, null),
|
||||||
|
TransformOptions.of(0, 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<TransformOptions> tiffTransformOptions()
|
||||||
|
{
|
||||||
|
return Stream.of(
|
||||||
|
TransformOptions.of(0, 0),
|
||||||
|
TransformOptions.of(0, 1),
|
||||||
|
TransformOptions.of(1, 1),
|
||||||
|
TransformOptions.of(null, 0), // expected 1 page in target file
|
||||||
|
TransformOptions.of(null, 1), // expected 2 pages in target file
|
||||||
|
TransformOptions.of(0, null), // expected all (2) pages in target file
|
||||||
|
TransformOptions.of(1, null), // expected 1 page in target file
|
||||||
|
TransformOptions.none() // expected all (2) pages in target file
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<Arguments> transformSourcesAndOptions()
|
||||||
|
{
|
||||||
|
return Stream.of(
|
||||||
|
ArgumentsCartesianProduct.of(imageFiles(), defaultTransformOptions()),
|
||||||
|
ArgumentsCartesianProduct.of(ImageFile.of("quick.tiff", MIMETYPE_IMAGE_TIFF, 2), tiffTransformOptions())
|
||||||
|
).flatMap(Function.identity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("transformSourcesAndOptions")
|
||||||
|
void testTransformImageToPdf(ImageFile imageFile, TransformOptions transformOptions) throws Exception
|
||||||
|
{
|
||||||
|
File sourceFile = loadFile(imageFile.fileName);
|
||||||
|
|
||||||
|
// when
|
||||||
|
transformer.transform(imageFile.mimetype, MIMETYPE_PDF, transformOptions.toMap(), sourceFile, targetFile, transformManager);
|
||||||
|
|
||||||
|
then(transformManager).shouldHaveNoInteractions();
|
||||||
|
try (PDDocument actualPdfDocument = PDDocument.load(targetFile))
|
||||||
|
{
|
||||||
|
int expectedNumberOfPages = calculateExpectedNumberOfPages(transformOptions, imageFile.firstPage(), imageFile.lastPage());
|
||||||
|
assertNotNull(actualPdfDocument);
|
||||||
|
assertEquals(expectedNumberOfPages, actualPdfDocument.getNumberOfPages());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int calculateExpectedNumberOfPages(TransformOptions transformOptions, int firstPage, int lastPage)
|
||||||
|
{
|
||||||
|
int startPage = Optional.ofNullable(transformOptions.startPage).orElse(firstPage);
|
||||||
|
int endPage = Math.min(Optional.ofNullable(transformOptions.endPage).orElse(lastPage), lastPage);
|
||||||
|
return endPage - startPage + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<TransformOptions> improperTransformOptions()
|
||||||
|
{
|
||||||
|
return Stream.of(
|
||||||
|
TransformOptions.of(1, 0),
|
||||||
|
TransformOptions.of(-1, 0),
|
||||||
|
TransformOptions.of(0, -1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("improperTransformOptions")
|
||||||
|
void testTransformTiffToPdf_withImproperOptions(TransformOptions transformOptions)
|
||||||
|
{
|
||||||
|
// when
|
||||||
|
assertThrows(IllegalArgumentException.class, () ->
|
||||||
|
transformer.transform(MIMETYPE_IMAGE_TIFF, MIMETYPE_PDF, transformOptions.toMap(), sourceFile, targetFile, transformManager));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testTransformTiffToPdf_withInvalidStartPageOption()
|
||||||
|
{
|
||||||
|
Map<String, String> transformOptions = TransformOptions.none().toMap();
|
||||||
|
transformOptions.put(START_PAGE, "a");
|
||||||
|
|
||||||
|
// when
|
||||||
|
assertThrows(IllegalArgumentException.class, () ->
|
||||||
|
transformer.transform(MIMETYPE_IMAGE_TIFF, MIMETYPE_PDF, transformOptions, sourceFile, targetFile, transformManager));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testTransformTiffToPdf_withInvalidEndPageOption()
|
||||||
|
{
|
||||||
|
Map<String, String> transformOptions = TransformOptions.none().toMap();
|
||||||
|
transformOptions.put(END_PAGE, "z");
|
||||||
|
|
||||||
|
// when
|
||||||
|
assertThrows(IllegalArgumentException.class, () ->
|
||||||
|
transformer.transform(MIMETYPE_IMAGE_TIFF, MIMETYPE_PDF, transformOptions, sourceFile, targetFile, transformManager));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<String> validPdfFormats()
|
||||||
|
{
|
||||||
|
return Stream.of("A0", "a0", "A1", "A2", "A3", "A4", "A5", "A6", "a6", "LETTER", "letter", "LEGAL", "legal");
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("validPdfFormats")
|
||||||
|
void testTransformImageToPDF_withVariousPdfFormats(String pdfFormat) throws Exception
|
||||||
|
{
|
||||||
|
TransformOptions transformOptions = TransformOptions.of(pdfFormat);
|
||||||
|
|
||||||
|
// when
|
||||||
|
transformer.transform(MIMETYPE_IMAGE_TIFF, MIMETYPE_PDF, transformOptions.toMap(), sourceFile, targetFile, transformManager);
|
||||||
|
|
||||||
|
try (PDDocument actualPdfDocument = PDDocument.load(targetFile))
|
||||||
|
{
|
||||||
|
PDRectangle expectedPdfFormat = resolveExpectedPdfFormat(pdfFormat);
|
||||||
|
assertNotNull(actualPdfDocument);
|
||||||
|
assertEquals(expectedPdfFormat.getWidth(), actualPdfDocument.getPage(0).getMediaBox().getWidth());
|
||||||
|
assertEquals(expectedPdfFormat.getHeight(), actualPdfDocument.getPage(0).getMediaBox().getHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testTransformImageToPDF_withInvalidPdfFormatAndUsingDefaultOne() throws Exception
|
||||||
|
{
|
||||||
|
TransformOptions transformOptions = TransformOptions.of("INVALID");
|
||||||
|
|
||||||
|
// when
|
||||||
|
transformer.transform(MIMETYPE_IMAGE_TIFF, MIMETYPE_PDF, transformOptions.toMap(), sourceFile, targetFile, transformManager);
|
||||||
|
|
||||||
|
try (PDDocument actualPdfDocument = PDDocument.load(targetFile))
|
||||||
|
{
|
||||||
|
assertNotNull(actualPdfDocument);
|
||||||
|
assertEquals(PDRectangle.A4.getWidth(), actualPdfDocument.getPage(0).getMediaBox().getWidth());
|
||||||
|
assertEquals(PDRectangle.A4.getHeight(), actualPdfDocument.getPage(0).getMediaBox().getHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------- Helper methods and classes -----------------------------------------------
|
||||||
|
|
||||||
|
private static PDRectangle resolveExpectedPdfFormat(String pdfFormat)
|
||||||
|
{
|
||||||
|
switch (pdfFormat.toUpperCase()) {
|
||||||
|
case "LETTER":
|
||||||
|
return PDRectangle.LETTER;
|
||||||
|
case "LEGAL":
|
||||||
|
return PDRectangle.LEGAL;
|
||||||
|
case "A0":
|
||||||
|
return PDRectangle.A0;
|
||||||
|
case "A1":
|
||||||
|
return PDRectangle.A1;
|
||||||
|
case "A2":
|
||||||
|
return PDRectangle.A2;
|
||||||
|
case "A3":
|
||||||
|
return PDRectangle.A3;
|
||||||
|
case "A5":
|
||||||
|
return PDRectangle.A5;
|
||||||
|
case "A6":
|
||||||
|
return PDRectangle.A6;
|
||||||
|
case "A4":
|
||||||
|
default:
|
||||||
|
return PDRectangle.A4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File loadFile(String fileName)
|
||||||
|
{
|
||||||
|
return new File(Objects.requireNonNull(ImageToPdfTransformerTest.class.getClassLoader().getResource(fileName)).getFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ImageFile
|
||||||
|
{
|
||||||
|
String fileName;
|
||||||
|
String mimetype;
|
||||||
|
int numberOfPages;
|
||||||
|
|
||||||
|
private ImageFile(String fileName, String mimetype, int numberOfPages)
|
||||||
|
{
|
||||||
|
this.fileName = fileName;
|
||||||
|
this.mimetype = mimetype;
|
||||||
|
this.numberOfPages = numberOfPages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ImageFile of(String fileName, String mimetype, int numberOfPages)
|
||||||
|
{
|
||||||
|
return new ImageFile(fileName, mimetype, numberOfPages);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ImageFile of(String fileName, String mimetype)
|
||||||
|
{
|
||||||
|
return of(fileName, mimetype, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int firstPage()
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int lastPage()
|
||||||
|
{
|
||||||
|
return numberOfPages - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "ImageFile{" + "fileName='" + fileName + '\'' + ", mimetype='" + mimetype + '\'' + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TransformOptions
|
||||||
|
{
|
||||||
|
Integer startPage;
|
||||||
|
Integer endPage;
|
||||||
|
String pdfFormat;
|
||||||
|
|
||||||
|
private TransformOptions(Integer startPage, Integer endPage, String pdfFormat)
|
||||||
|
{
|
||||||
|
this.startPage = startPage;
|
||||||
|
this.endPage = endPage;
|
||||||
|
this.pdfFormat = pdfFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> toMap()
|
||||||
|
{
|
||||||
|
final Map<String, String> transformOptions = new HashMap<>();
|
||||||
|
if (startPage != null)
|
||||||
|
{
|
||||||
|
transformOptions.put(START_PAGE, startPage.toString());
|
||||||
|
}
|
||||||
|
if (endPage != null)
|
||||||
|
{
|
||||||
|
transformOptions.put(END_PAGE, endPage.toString());
|
||||||
|
}
|
||||||
|
if (pdfFormat != null)
|
||||||
|
{
|
||||||
|
transformOptions.put(PDF_FORMAT, pdfFormat);
|
||||||
|
}
|
||||||
|
return transformOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TransformOptions of(Integer startPage, Integer endPage)
|
||||||
|
{
|
||||||
|
return new TransformOptions(startPage, endPage, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TransformOptions of(String pdfFormat)
|
||||||
|
{
|
||||||
|
return new TransformOptions(null, null, pdfFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TransformOptions none()
|
||||||
|
{
|
||||||
|
return TransformOptions.of(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "TransformOption{" + "startPage=" + startPage + ", endPage=" + endPage + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Transform Core
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2022 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 <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.transform.misc.util;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a cartesian product of arguments provided either in streams or as an objects. Result is a {@link Stream} of JUnit's {@link Arguments}.
|
||||||
|
*/
|
||||||
|
public class ArgumentsCartesianProduct
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Creates cartesian product of fixed argument and a stream of arguments.
|
||||||
|
* Example: a ✕ {x,y,z} = {a,x}, {a,y}, {a,z}
|
||||||
|
*/
|
||||||
|
public static Stream<Arguments> of(final Object fixedFirstArgument, final Stream<?> secondArguments)
|
||||||
|
{
|
||||||
|
return secondArguments.map(secondArgument -> Arguments.of(fixedFirstArgument, secondArgument));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates cartesian product of a stream of arguments and fixed arguments.
|
||||||
|
* Example: {a,b,c} ✕ y ✕ z = {a,y,z}, {b,y,z}, {c,y,z}
|
||||||
|
*/
|
||||||
|
public static Stream<Arguments> of(final Stream<?> firstArguments, final Object... otherFixedArguments)
|
||||||
|
{
|
||||||
|
return firstArguments.map(firstArgument -> Arguments.of(firstArgument, otherFixedArguments));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates cartesian product of two streams of arguments.
|
||||||
|
* Example: {a,b} ✕ {y,z} = {a,y}, {a,z}, {b,y}, {b,z}
|
||||||
|
*/
|
||||||
|
public static Stream<Arguments> of(final Stream<?> firstArguments, final Stream<?> secondArguments)
|
||||||
|
{
|
||||||
|
return cartesianProductOf(firstArguments, secondArguments).map(arguments -> Arguments.of(arguments.toArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates cartesian product of multiple streams of arguments.
|
||||||
|
* Example: {a,b} ✕ {k,l,m} ✕ ... ✕ {y,z} = {a,k,...,y}, {a,k,...,z}, {a,l,...,y}, ..., {b,m,...,z}
|
||||||
|
*/
|
||||||
|
public static Stream<Arguments> of(final Stream<?>... argumentsStreams)
|
||||||
|
{
|
||||||
|
return cartesianProductOf(argumentsStreams).map(arguments -> Arguments.of(arguments.toArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Stream<?>> cartesianProductOf(final Stream<?>... streams)
|
||||||
|
{
|
||||||
|
if (streams == null)
|
||||||
|
{
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Stream.of(streams)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(stream -> stream.map(Collections::<Object>singletonList))
|
||||||
|
.reduce((result, nextElements) -> {
|
||||||
|
final List<List<Object>> nextElementsCopy = nextElements.collect(Collectors.toList());
|
||||||
|
return result.flatMap(resultPortion -> nextElementsCopy.stream().map(nextElementsPortion -> {
|
||||||
|
final List<Object> extendedResultPortion = new ArrayList<>();
|
||||||
|
extendedResultPortion.addAll(resultPortion);
|
||||||
|
extendedResultPortion.addAll(nextElementsPortion);
|
||||||
|
return extendedResultPortion;
|
||||||
|
}));
|
||||||
|
}).orElse(Stream.empty())
|
||||||
|
.map(Collection::stream);
|
||||||
|
}
|
||||||
|
}
|
BIN
engines/misc/src/test/resources/quick.gif
Normal file
BIN
engines/misc/src/test/resources/quick.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.0 KiB |
BIN
engines/misc/src/test/resources/quick.jpg
Normal file
BIN
engines/misc/src/test/resources/quick.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
BIN
engines/misc/src/test/resources/quick.png
Normal file
BIN
engines/misc/src/test/resources/quick.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.1 KiB |
BIN
engines/misc/src/test/resources/quick.tiff
Normal file
BIN
engines/misc/src/test/resources/quick.tiff
Normal file
Binary file not shown.
@ -64,6 +64,7 @@ public interface RequestParamMap
|
|||||||
String INCLUDE_CONTENTS = "includeContents";
|
String INCLUDE_CONTENTS = "includeContents";
|
||||||
String NOT_EXTRACT_BOOKMARKS_TEXT = "notExtractBookmarksText";
|
String NOT_EXTRACT_BOOKMARKS_TEXT = "notExtractBookmarksText";
|
||||||
String PAGE_LIMIT = "pageLimit";
|
String PAGE_LIMIT = "pageLimit";
|
||||||
|
String PDF_FORMAT = "pdfFormat";
|
||||||
|
|
||||||
// Parameters interpreted by the TransformController
|
// Parameters interpreted by the TransformController
|
||||||
String DIRECT_ACCESS_URL = "directAccessUrl";
|
String DIRECT_ACCESS_URL = "directAccessUrl";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user