From 8cbb4367abc78018c5d992ee9dbd0a5e60702d2c Mon Sep 17 00:00:00 2001 From: Sebastian Espei Date: Sat, 15 Jun 2024 00:39:30 +0200 Subject: [PATCH] PDF-to-Image different page formats fix (#1460) * Improve the PDF rendering process for pages of varying sizes This commit includes changes to handle the rendering of PDF documents with pages of different sizes. The updated code calculates the dimensions of each page upfront and assembles a final combined image that accommodates for the differing page dimensions. This approach avoids repetitive renderings of the same page sizes. * Refactor image preparation for Pdf to Image --- .../software/SPDF/utils/PdfUtils.java | 98 ++++++++++++++++--- 1 file changed, 83 insertions(+), 15 deletions(-) diff --git a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java index 225a2c12..91cbb5cd 100644 --- a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java @@ -1,27 +1,27 @@ package stirling.software.SPDF.utils; -import java.awt.Graphics; +import java.awt.*; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; +import java.util.HashMap; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -import javax.imageio.IIOImage; -import javax.imageio.ImageIO; -import javax.imageio.ImageReader; -import javax.imageio.ImageWriteParam; -import javax.imageio.ImageWriter; +import javax.imageio.*; import javax.imageio.stream.ImageOutputStream; import org.apache.pdfbox.Loader; import org.apache.pdfbox.cos.COSName; -import org.apache.pdfbox.pdmodel.*; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode; +import org.apache.pdfbox.pdmodel.PDResources; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.graphics.PDXObject; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; @@ -245,19 +245,64 @@ public class PdfUtils { writer.dispose(); } else { // Combine all images into a single big image - BufferedImage image = pdfRenderer.renderImageWithDPI(0, DPI, colorType); + + // Calculate the combined image dimensions + int maxWidth = 0; + int totalHeight = 0; + + BufferedImage pdfSizeImage = null; + int pdfSizeImageIndex = -1; + + // Using a map to store the rendered dimensions of each page size + // to avoid rendering the same page sizes multiple times + HashMap pageSizes = + new HashMap<>(); + for (int i = 0; i < pageCount; ++i) { + PDPage page = document.getPage(i); + PDRectangle mediaBox = page.getMediaBox(); + int rotation = page.getRotation(); + PdfRenderSettingsKey settings = + new PdfRenderSettingsKey( + mediaBox.getWidth(), mediaBox.getHeight(), rotation); + PdfImageDimensionValue dimension = pageSizes.get(settings); + if (dimension == null) { + // Render the image to get the dimensions + pdfSizeImage = pdfRenderer.renderImageWithDPI(i, DPI, colorType); + pdfSizeImageIndex = i; + dimension = + new PdfImageDimensionValue( + pdfSizeImage.getWidth(), pdfSizeImage.getHeight()); + pageSizes.put(settings, dimension); + if (pdfSizeImage.getWidth() > maxWidth) { + maxWidth = pdfSizeImage.getWidth(); + } + } + totalHeight += dimension.height(); + } + + // Create a new BufferedImage to store the combined images BufferedImage combined = - new BufferedImage( - image.getWidth(), - image.getHeight() * pageCount, - BufferedImage.TYPE_INT_RGB); + prepareImageForPdfToImage(maxWidth, totalHeight, imageType); Graphics g = combined.getGraphics(); + int currentHeight = 0; + BufferedImage pageImage; + + // Check if the first image is the last rendered image + boolean firstImageAlreadyRendered = pdfSizeImageIndex == 0; + for (int i = 0; i < pageCount; ++i) { - if (i != 0) { - image = pdfRenderer.renderImageWithDPI(i, DPI, colorType); + if (firstImageAlreadyRendered && i == 0) { + pageImage = pdfSizeImage; + } else { + pageImage = pdfRenderer.renderImageWithDPI(i, DPI, colorType); } - g.drawImage(image, 0, i * image.getHeight(), null); + + // Calculate the x-coordinate to center the image + int x = (maxWidth - pageImage.getWidth()) / 2; + + g.drawImage(pageImage, x, currentHeight, null); + currentHeight += pageImage.getHeight(); } // Write the image to the output stream @@ -296,6 +341,23 @@ public class PdfUtils { } } + private static BufferedImage prepareImageForPdfToImage( + int maxWidth, int height, String imageType) { + BufferedImage combined; + if ("png".equalsIgnoreCase(imageType)) { + combined = new BufferedImage(maxWidth, height, BufferedImage.TYPE_INT_ARGB); + } else { + combined = new BufferedImage(maxWidth, height, BufferedImage.TYPE_INT_RGB); + } + if (!"png".equalsIgnoreCase(imageType)) { + Graphics g = combined.getGraphics(); + g.setColor(Color.WHITE); + g.fillRect(0, 0, combined.getWidth(), combined.getHeight()); + g.dispose(); + } + return combined; + } + public static byte[] imageToPdf( MultipartFile[] files, String fitOption, boolean autoRotate, String colorType) throws IOException { @@ -443,4 +505,10 @@ public class PdfUtils { pdf.getDocumentInformation().setCreationDate(pdfMetadata.getCreationDate()); pdf.getDocumentInformation().setModificationDate(Calendar.getInstance()); } + + /** Key for storing the dimensions of a rendered image in a map. */ + private record PdfRenderSettingsKey(float mediaBoxWidth, float mediaBoxHeight, int rotation) {} + + /** Value for storing the dimensions of a rendered image in a map. */ + private record PdfImageDimensionValue(int width, int height) {} }