diff --git a/engines/imagemagick/src/main/resources/imagemagick_engine_config.json b/engines/imagemagick/src/main/resources/imagemagick_engine_config.json
index add6f6a7..f7cdcc74 100644
--- a/engines/imagemagick/src/main/resources/imagemagick_engine_config.json
+++ b/engines/imagemagick/src/main/resources/imagemagick_engine_config.json
@@ -932,7 +932,7 @@
{"sourceMediaType": "image/tiff", "targetMediaType": "image/x-raw-minolta" },
{"sourceMediaType": "image/tiff", "targetMediaType": "image/x-raw-nikon" },
{"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-portable-graymap" },
{"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-xpixmap" },
{"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-sony" },
diff --git a/engines/misc/src/main/java/org/alfresco/transform/misc/transformers/ImageToPdfTransformer.java b/engines/misc/src/main/java/org/alfresco/transform/misc/transformers/ImageToPdfTransformer.java
new file mode 100644
index 00000000..0afc5f02
--- /dev/null
+++ b/engines/misc/src/main/java/org/alfresco/transform/misc/transformers/ImageToPdfTransformer.java
@@ -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 .
+ * #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 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 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 Optional parseOptionIfPresent(final Map transformOptions, final String parameter, final Class 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);
+ }
+ }
+}
diff --git a/engines/misc/src/main/resources/misc_engine_config.json b/engines/misc/src/main/resources/misc_engine_config.json
index 41700f84..62c466c4 100644
--- a/engines/misc/src/main/resources/misc_engine_config.json
+++ b/engines/misc/src/main/resources/misc_engine_config.json
@@ -8,6 +8,11 @@
],
"metadataOptions": [
{"value": {"name": "extractMapping"}}
+ ],
+ "imageToPdfOptions": [
+ {"value": {"name": "startPage"}},
+ {"value": {"name": "endPage"}},
+ {"value": {"name": "pdfFormat"}}
]
},
"transformers": [
@@ -91,6 +96,15 @@
"transformOptions": [
"metadataOptions"
]
+ },
+ {
+ "transformerName": "imageToPdf",
+ "supportedSourceAndTargetList": [
+ {"sourceMediaType": "image/tiff", "targetMediaType": "application/pdf"}
+ ],
+ "transformOptions": [
+ "imageToPdfOptions"
+ ]
}
]
}
\ No newline at end of file
diff --git a/engines/misc/src/test/java/org/alfresco/transform/misc/MiscTransformsIT.java b/engines/misc/src/test/java/org/alfresco/transform/misc/MiscTransformsIT.java
index b6984c6c..577779e5 100644
--- a/engines/misc/src/test/java/org/alfresco/transform/misc/MiscTransformsIT.java
+++ b/engines/misc/src/test/java/org/alfresco/transform/misc/MiscTransformsIT.java
@@ -26,21 +26,12 @@
*/
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.util.function.Function.identity;
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.HttpClient.sendTRequest;
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_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_XML;
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.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
*/
@@ -146,6 +150,8 @@ public class MiscTransformsIT
SourceTarget.of("application/dita+xml", "application/pdf"),
SourceTarget.of("text/xml", "application/pdf"),
+ SourceTarget.of(MIMETYPE_IMAGE_TIFF, MIMETYPE_PDF),
+
SourceTarget.of("message/rfc822", "text/plain")
);
}
@@ -164,9 +170,17 @@ public class MiscTransformsIT
try
{
+ // when
final ResponseEntity response = sendTRequest(ENGINE_URL, sourceFile,
sourceMimetype, targetMimetype, targetExtension);
+
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)
{
diff --git a/engines/misc/src/test/java/org/alfresco/transform/misc/transformers/ImageToPdfTransformerTest.java b/engines/misc/src/test/java/org/alfresco/transform/misc/transformers/ImageToPdfTransformerTest.java
new file mode 100644
index 00000000..714c79f5
--- /dev/null
+++ b/engines/misc/src/test/java/org/alfresco/transform/misc/transformers/ImageToPdfTransformerTest.java
@@ -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 .
+ * #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 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 defaultTransformOptions()
+ {
+ return Stream.of(
+ TransformOptions.none(),
+ TransformOptions.of(0, null),
+ TransformOptions.of(0, 0)
+ );
+ }
+
+ static Stream 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 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 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 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 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 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 toMap()
+ {
+ final Map 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 + '}';
+ }
+ }
+}
\ No newline at end of file
diff --git a/engines/misc/src/test/java/org/alfresco/transform/misc/util/ArgumentsCartesianProduct.java b/engines/misc/src/test/java/org/alfresco/transform/misc/util/ArgumentsCartesianProduct.java
new file mode 100644
index 00000000..d08387a3
--- /dev/null
+++ b/engines/misc/src/test/java/org/alfresco/transform/misc/util/ArgumentsCartesianProduct.java
@@ -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 .
+ * #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 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 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 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 of(final Stream>... argumentsStreams)
+ {
+ return cartesianProductOf(argumentsStreams).map(arguments -> Arguments.of(arguments.toArray()));
+ }
+
+ private static Stream> cartesianProductOf(final Stream>... streams)
+ {
+ if (streams == null)
+ {
+ return Stream.empty();
+ }
+
+ return Stream.of(streams)
+ .filter(Objects::nonNull)
+ .map(stream -> stream.map(Collections::