From b84a5613f3180a15d4e3588836a995c26690bd1b Mon Sep 17 00:00:00 2001 From: Krystian Dabrowski Date: Mon, 27 Mar 2023 16:30:32 +0200 Subject: [PATCH] ATS-996: TIFF to PDF - invalid output format --- .../transformers/ImageToPdfTransformer.java | 84 +++++++++---- .../main/resources/misc_engine_config.json | 3 +- .../ImageToPdfTransformerTest.java | 115 ++++++++++++++---- .../transform/common/RequestParamMap.java | 1 + 4 files changed, 155 insertions(+), 48 deletions(-) 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 index e8c611e4..faff055f 100644 --- 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 @@ -28,6 +28,7 @@ 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.PDF_ORIENTATION; import static org.alfresco.transform.common.RequestParamMap.START_PAGE; import javax.imageio.ImageIO; @@ -58,10 +59,11 @@ 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. + * Transformer accepts bellow optional transform parameters: + * - startPage - page number of image (for multi-page images) from which transformer should start conversion. Default: first page of the image. + * - endPage - page number of image (for multi-page images) up to which transformation should be performed. Default: last page of the image. + * - pdfFormat - output PDF file format. Available formats: DEFAULT, A0, A1, A2, A3, A4, A5, A6, LETTER, LEGAL. Default: original image size. + * - pdfOrientation - output PDF file orientation. Available options: DEFAULT, PORTRAIT, LANDSCAPE. Default: original image orientation. */ @Component public class ImageToPdfTransformer implements CustomTransformerFileAdaptor @@ -73,8 +75,8 @@ public class ImageToPdfTransformer implements CustomTransformerFileAdaptor 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; + private static final String DEFAULT_PDF_FORMAT_STRING = "DEFAULT"; + private static final String DEFAULT_PDF_ORIENTATION_STRING = "DEFAULT"; @Override public String getTransformerName() @@ -94,6 +96,7 @@ public class ImageToPdfTransformer implements CustomTransformerFileAdaptor 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); + final String pdfOrientation = parseOptionIfPresent(transformOptions, PDF_ORIENTATION, String.class).orElse(DEFAULT_PDF_ORIENTATION_STRING); verifyOptions(startPage, endPage); final ImageReader imageReader = findImageReader(imageInputStream, imageFile.getName(), sourceMimetype); @@ -108,7 +111,7 @@ public class ImageToPdfTransformer implements CustomTransformerFileAdaptor break; } - scaleAndDrawImage(pdfDocument, imageReader.read(i), pdfFormat); + scaleAndDrawImage(pdfDocument, imageReader.read(i), pdfFormat, pdfOrientation); } pdfDocument.save(pdfFile); @@ -128,11 +131,12 @@ public class ImageToPdfTransformer implements CustomTransformerFileAdaptor return imageReader; } - private void scaleAndDrawImage(final PDDocument pdfDocument, final BufferedImage bufferedImage, final String pdfFormat) throws IOException + private void scaleAndDrawImage(final PDDocument pdfDocument, final BufferedImage bufferedImage, final String pdfFormat, final String pdfOrientation) + throws IOException { - final PDPage pdfPage = new PDPage(resolvePdfFormat(pdfFormat)); - pdfDocument.addPage(pdfPage); final PDImageXObject image = LosslessFactory.createFromImage(pdfDocument, bufferedImage); + final PDPage pdfPage = new PDPage(resolvePdfFormat(pdfFormat, pdfOrientation, image.getWidth(), image.getHeight())); + pdfDocument.addPage(pdfPage); try (PDPageContentStream pdfPageContent = new PDPageContentStream(pdfDocument, pdfPage)) { final PDRectangle pageSize = pdfPage.getMediaBox(); @@ -146,31 +150,67 @@ public class ImageToPdfTransformer implements CustomTransformerFileAdaptor } } - private PDRectangle resolvePdfFormat(final String pdfFormat) + private PDRectangle resolvePdfFormat(final String pdfFormat, final String pdfOrientation, final int actualWidth, final int actualHeight) { + PDRectangle pdRectangle; switch (pdfFormat.toUpperCase()) { + case "DEFAULT": + pdRectangle = new PDRectangle(actualWidth, actualHeight); + break; case "A4": - return DEFAULT_PDF_FORMAT; + pdRectangle = PDRectangle.A4; + break; case "LETTER": - return PDRectangle.LETTER; + pdRectangle = PDRectangle.LETTER; + break; case "A0": - return PDRectangle.A0; + pdRectangle = PDRectangle.A0; + break; case "A1": - return PDRectangle.A1; + pdRectangle = PDRectangle.A1; + break; case "A2": - return PDRectangle.A2; + pdRectangle = PDRectangle.A2; + break; case "A3": - return PDRectangle.A3; + pdRectangle = PDRectangle.A3; + break; case "A5": - return PDRectangle.A5; + pdRectangle = PDRectangle.A5; + break; case "A6": - return PDRectangle.A6; + pdRectangle = PDRectangle.A6; + break; case "LEGAL": - return PDRectangle.LEGAL; + pdRectangle = PDRectangle.LEGAL; + break; default: - log.info("PDF format: '{}' not supported. Using default: '{}'", pdfFormat, DEFAULT_PDF_FORMAT_STRING); - return DEFAULT_PDF_FORMAT; + log.warn("PDF format: '{}' not supported. Maintaining the default one.", pdfFormat); + pdRectangle = new PDRectangle(actualWidth, actualHeight); + break; } + + switch (pdfOrientation.toUpperCase()) { + case "DEFAULT": + break; + case "PORTRAIT": + if (pdRectangle.getWidth() > pdRectangle.getHeight()) + { + pdRectangle = new PDRectangle(pdRectangle.getHeight(), pdRectangle.getWidth()); + } + break; + case "LANDSCAPE": + if (pdRectangle.getHeight() > pdRectangle.getWidth()) + { + pdRectangle = new PDRectangle(pdRectangle.getHeight(), pdRectangle.getWidth()); + } + break; + default: + log.warn("PDF orientation: '{}' not supported. Maintaining the default one.", pdfOrientation); + break; + } + + return pdRectangle; } private static Optional parseOptionIfPresent(final Map transformOptions, final String parameter, final Class targetType) diff --git a/engines/misc/src/main/resources/misc_engine_config.json b/engines/misc/src/main/resources/misc_engine_config.json index 62c466c4..e32b1f90 100644 --- a/engines/misc/src/main/resources/misc_engine_config.json +++ b/engines/misc/src/main/resources/misc_engine_config.json @@ -12,7 +12,8 @@ "imageToPdfOptions": [ {"value": {"name": "startPage"}}, {"value": {"name": "endPage"}}, - {"value": {"name": "pdfFormat"}} + {"value": {"name": "pdfFormat"}}, + {"value": {"name": "pdfOrientation"}} ] }, "transformers": [ 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 index cd519e49..00ce999a 100644 --- 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 @@ -33,12 +33,15 @@ 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.PDF_ORIENTATION; import static org.alfresco.transform.common.RequestParamMap.START_PAGE; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.BDDMockito.then; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.HashMap; @@ -108,16 +111,17 @@ class ImageToPdfTransformerTest 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 pages in target file - TransformOptions.of(1, null), // expected 1 page in target file + TransformOptions.of(1, null), // expected all except first page in target file TransformOptions.none() // expected all pages in target file ); } static Stream transformSourcesAndOptions() { + ImageFile tiffImage = ImageFile.of("sample.tiff", MIMETYPE_IMAGE_TIFF, 6); return Stream.of( ArgumentsCartesianProduct.of(imageFiles(), defaultTransformOptions()), - ArgumentsCartesianProduct.of(ImageFile.of("sample.tiff", MIMETYPE_IMAGE_TIFF, 6), tiffTransformOptions()) + ArgumentsCartesianProduct.of(tiffImage, tiffTransformOptions()) ).flatMap(Function.identity()); } @@ -186,23 +190,27 @@ class ImageToPdfTransformerTest transformer.transform(MIMETYPE_IMAGE_TIFF, MIMETYPE_PDF, transformOptions, sourceFile, targetFile, transformManager)); } - static Stream validPdfFormats() + static Stream validPdfFormatsAndOrientations() { - return Stream.of("A0", "a0", "A1", "A2", "A3", "A4", "A5", "A6", "a6", "LETTER", "letter", "LEGAL", "legal"); + return ArgumentsCartesianProduct.of( + Stream.of("default", "DEFAULT", "A0", "a0", "A1", "A2", "A3", "A4", "A5", "A6", "a6", "LETTER", "letter", "LEGAL", "legal"), + Stream.of("default", "DEFAULT", "portrait", "PORTRAIT", "landscape", "LANDSCAPE") + ); } @ParameterizedTest - @MethodSource("validPdfFormats") - void testTransformImageToPDF_withVariousPdfFormats(String pdfFormat) throws Exception + @MethodSource("validPdfFormatsAndOrientations") + void testTransformImageToPDF_withVariousPdfFormatsAndOrientations(String pdfFormat, String pdfOrientation) throws Exception { - TransformOptions transformOptions = TransformOptions.of(pdfFormat); + TransformOptions transformOptions = TransformOptions.of(pdfFormat, pdfOrientation); // when transformer.transform(MIMETYPE_IMAGE_TIFF, MIMETYPE_PDF, transformOptions.toMap(), sourceFile, targetFile, transformManager); try (PDDocument actualPdfDocument = PDDocument.load(targetFile)) { - PDRectangle expectedPdfFormat = resolveExpectedPdfFormat(pdfFormat); + BufferedImage actualImage = ImageIO.read(sourceFile); + PDRectangle expectedPdfFormat = resolveExpectedPdfFormat(pdfFormat, pdfOrientation, actualImage.getWidth(), actualImage.getHeight()); assertNotNull(actualPdfDocument); assertEquals(expectedPdfFormat.getWidth(), actualPdfDocument.getPage(0).getMediaBox().getWidth()); assertEquals(expectedPdfFormat.getHeight(), actualPdfDocument.getPage(0).getMediaBox().getHeight()); @@ -219,37 +227,83 @@ class ImageToPdfTransformerTest try (PDDocument actualPdfDocument = PDDocument.load(targetFile)) { + BufferedImage actualImage = ImageIO.read(sourceFile); assertNotNull(actualPdfDocument); - assertEquals(PDRectangle.A4.getWidth(), actualPdfDocument.getPage(0).getMediaBox().getWidth()); - assertEquals(PDRectangle.A4.getHeight(), actualPdfDocument.getPage(0).getMediaBox().getHeight()); + assertEquals(actualImage.getWidth(), actualPdfDocument.getPage(0).getMediaBox().getWidth()); + assertEquals(actualImage.getHeight(), actualPdfDocument.getPage(0).getMediaBox().getHeight()); + } + } + + @Test + void testTransformImageToPDF_withInvalidPdfOrientationAndUsingDefaultOne() throws Exception + { + TransformOptions transformOptions = TransformOptions.of(null, "INVALID"); + + // when + transformer.transform(MIMETYPE_IMAGE_TIFF, MIMETYPE_PDF, transformOptions.toMap(), sourceFile, targetFile, transformManager); + + try (PDDocument actualPdfDocument = PDDocument.load(targetFile)) + { + BufferedImage actualImage = ImageIO.read(sourceFile); + assertNotNull(actualPdfDocument); + assertEquals(actualImage.getWidth(), actualPdfDocument.getPage(0).getMediaBox().getWidth()); + assertEquals(actualImage.getHeight(), actualPdfDocument.getPage(0).getMediaBox().getHeight()); } } //----------------------------------------------- Helper methods and classes ----------------------------------------------- - private static PDRectangle resolveExpectedPdfFormat(String pdfFormat) + private static PDRectangle resolveExpectedPdfFormat(String pdfFormat, String pdfOrientation, int defaultWidth, int defaultHeight) { + PDRectangle pdRectangle; switch (pdfFormat.toUpperCase()) { case "LETTER": - return PDRectangle.LETTER; + pdRectangle = PDRectangle.LETTER; + break; case "LEGAL": - return PDRectangle.LEGAL; + pdRectangle = PDRectangle.LEGAL; + break; case "A0": - return PDRectangle.A0; + pdRectangle = PDRectangle.A0; + break; case "A1": - return PDRectangle.A1; + pdRectangle = PDRectangle.A1; + break; case "A2": - return PDRectangle.A2; + pdRectangle = PDRectangle.A2; + break; case "A3": - return PDRectangle.A3; - case "A5": - return PDRectangle.A5; - case "A6": - return PDRectangle.A6; + pdRectangle = PDRectangle.A3; + break; case "A4": + pdRectangle = PDRectangle.A4; + break; + case "A5": + pdRectangle = PDRectangle.A5; + break; + case "A6": + pdRectangle = PDRectangle.A6; + break; default: - return PDRectangle.A4; + pdRectangle = new PDRectangle(defaultWidth, defaultHeight); } + + switch (pdfOrientation.toUpperCase()) { + case "PORTRAIT": + if (pdRectangle.getWidth() > pdRectangle.getHeight()) + { + pdRectangle = new PDRectangle(pdRectangle.getHeight(), pdRectangle.getWidth()); + } + break; + case "LANDSCAPE": + if (pdRectangle.getHeight() > pdRectangle.getWidth()) + { + pdRectangle = new PDRectangle(pdRectangle.getHeight(), pdRectangle.getWidth()); + } + break; + } + + return pdRectangle; } private static File loadFile(String fileName) @@ -302,12 +356,14 @@ class ImageToPdfTransformerTest Integer startPage; Integer endPage; String pdfFormat; + String pdfOrientation; - private TransformOptions(Integer startPage, Integer endPage, String pdfFormat) + private TransformOptions(Integer startPage, Integer endPage, String pdfFormat, String pdfOrientation) { this.startPage = startPage; this.endPage = endPage; this.pdfFormat = pdfFormat; + this.pdfOrientation = pdfOrientation; } public Map toMap() @@ -325,17 +381,26 @@ class ImageToPdfTransformerTest { transformOptions.put(PDF_FORMAT, pdfFormat); } + if (pdfOrientation != null) + { + transformOptions.put(PDF_ORIENTATION, pdfOrientation); + } return transformOptions; } public static TransformOptions of(Integer startPage, Integer endPage) { - return new TransformOptions(startPage, endPage, null); + return new TransformOptions(startPage, endPage, null, null); } public static TransformOptions of(String pdfFormat) { - return new TransformOptions(null, null, pdfFormat); + return new TransformOptions(null, null, pdfFormat, null); + } + + public static TransformOptions of(String pdfFormat, String pdfOrientation) + { + return new TransformOptions(null, null, pdfFormat, pdfOrientation); } public static TransformOptions none() diff --git a/model/src/main/java/org/alfresco/transform/common/RequestParamMap.java b/model/src/main/java/org/alfresco/transform/common/RequestParamMap.java index 31195e1a..1647accc 100644 --- a/model/src/main/java/org/alfresco/transform/common/RequestParamMap.java +++ b/model/src/main/java/org/alfresco/transform/common/RequestParamMap.java @@ -65,6 +65,7 @@ public interface RequestParamMap String NOT_EXTRACT_BOOKMARKS_TEXT = "notExtractBookmarksText"; String PAGE_LIMIT = "pageLimit"; String PDF_FORMAT = "pdfFormat"; + String PDF_ORIENTATION = "pdfOrientation"; // Parameters interpreted by the TransformController String DIRECT_ACCESS_URL = "directAccessUrl";