001/* 002 * Copyright 2011 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.flipbook.pdfbox; 017 018import java.awt.Dimension; 019import java.awt.Graphics; 020import java.awt.image.BufferedImage; 021import java.io.File; 022import java.io.FileFilter; 023import java.io.FileOutputStream; 024import java.io.IOException; 025import java.io.OutputStream; 026import java.util.ArrayList; 027import java.util.List; 028 029import javax.imageio.ImageIO; 030 031import org.apache.avalon.framework.logger.AbstractLogEnabled; 032import org.apache.pdfbox.pdmodel.PDDocument; 033import org.apache.pdfbox.pdmodel.PDPageTree; 034import org.apache.pdfbox.rendering.ImageType; 035import org.apache.pdfbox.rendering.PDFRenderer; 036import org.apache.pdfbox.tools.imageio.ImageIOUtil; 037 038import org.ametys.plugins.flipbook.Document2ImagesConvertorPolicy; 039import org.ametys.plugins.flipbook.FlipbookException; 040 041import net.coobird.thumbnailator.makers.FixedSizeThumbnailMaker; 042import net.coobird.thumbnailator.resizers.DefaultResizerFactory; 043 044/** 045 * PDF to PNG convertor which makes use of the pdfbox library. 046 * Based on pdfbox's {@link PDFRenderer} utility class, adding the possibility to specify the file name pattern. 047 */ 048public class PdfboxConvertor extends AbstractLogEnabled implements Document2ImagesConvertorPolicy 049{ 050 @Override 051 public void convert(File pdfFile, File folder) throws IOException, FlipbookException 052 { 053 String outputPrefix = "page"; 054 String imageFormat = "png"; 055 056 try (PDDocument document = PDDocument.load(pdfFile)) 057 { 058 if (document.isEncrypted()) 059 { 060 throw new IOException("The PDF file is encrypted, cannot read it."); 061 } 062 063 long start = System.currentTimeMillis(); 064 if (getLogger().isInfoEnabled()) 065 { 066 getLogger().info("Converting PDF to PNG images using pdfbox."); 067 } 068 069 writeImages(document, folder, imageFormat, outputPrefix, 120); 070 071 // Generate preview 072 writePreview(folder, outputPrefix); 073 074 long end = System.currentTimeMillis(); 075 if (getLogger().isInfoEnabled()) 076 { 077 getLogger().info("PDF converted to PNG in " + (end - start) + "ms."); 078 } 079 } 080 } 081 082 /** 083 * Converts a given page range of a PDF document to bitmap images. 084 * @param document the PDF document 085 * @param folder the folder where to write 086 * @param imageFormat the target format (ex. "png") 087 * @param outputPrefix used to construct the filename for the individual images 088 * @return true if the images were produced, false if there was an error 089 * @throws IOException if an I/O error occurs 090 * @throws FlipbookException if an error occurs when manipulating the flipbook 091 */ 092 protected List<String> writeImages(PDDocument document, File folder, String imageFormat, String outputPrefix) throws IOException, FlipbookException 093 { 094 return writeImages(document, folder, imageFormat, outputPrefix, 96); 095 } 096 097 /** 098 * Converts a given page range of a PDF document to bitmap images. 099 * @param document the PDF document 100 * @param folder the folder where to write 101 * @param imageFormat the target format (ex. "png") 102 * @param outputPrefix used to construct the filename for the individual images 103 * @param resolution the resolution in dpi (dots per inch) 104 * @return true if the images were produced, false if there was an error 105 * @throws IOException if an I/O error occurs 106 * @throws FlipbookException if an error occurs when manipulating the flipbook 107 */ 108 protected List<String> writeImages(PDDocument document, File folder, String imageFormat, String outputPrefix, int resolution) throws IOException, FlipbookException 109 { 110 return writeImages(document, folder, imageFormat, "", 1, Integer.MAX_VALUE, outputPrefix, ImageType.RGB, resolution, 1.0f); 111 } 112 113 /** 114 * Converts a given page range of a PDF document to bitmap images. 115 * @param document the PDF document 116 * @param folder the folder where to write 117 * @param imageFormat the target format (ex. "png") 118 * @param password the password (needed if the PDF is encrypted) 119 * @param startPage the start page (1 is the first page) 120 * @param endPage the end page (set to Integer.MAX_VALUE for all pages) 121 * @param outputPrefix used to construct the filename for the individual images 122 * @param imageType the image type (see {@link BufferedImage}.TYPE_*) 123 * @param resolution the resolution in dpi (dots per inch) 124 * @param quality the image compression quality (0 < quality < 1.0f). 125 * @return true if the images were produced, false if there was an error 126 * @throws IOException if an I/O error occurs 127 * @throws FlipbookException if an error occurs when manipulating the flipbook 128 */ 129 protected List<String> writeImages(PDDocument document, File folder, String imageFormat, String password, int startPage, int endPage, String outputPrefix, ImageType imageType, int resolution, float quality) throws IOException, FlipbookException 130 { 131 List<String> fileNames = new ArrayList<>(); 132 133 PDPageTree pageTree = document.getPages(); 134 int pageCount = pageTree.getCount(); 135 int digitCount = Integer.toString(pageCount).length(); 136 137 // %03d.png 138 String format = "%0" + digitCount + "d." + imageFormat; 139 for (int i = startPage - 1; i < endPage && i < pageCount; i++) 140 { 141 PDFRenderer pdfRenderer = new PDFRenderer(document); 142 143 BufferedImage image = pdfRenderer.renderImageWithDPI(i, resolution, imageType); 144 String fileName = outputPrefix + String.format(format, i + 1, imageFormat); 145 146 fileNames.add(fileName); 147 148 File imageFile = new File(folder, fileName + ".part"); 149 150 try (OutputStream os = new FileOutputStream(imageFile)) 151 { 152 if (!ImageIOUtil.writeImage(image, imageFormat, os, resolution, quality)) 153 { 154 throw new FlipbookException("Unable to write PDF page " + i + " to " + imageFile.getAbsolutePath()); 155 } 156 } 157 158 imageFile.renameTo(new File(folder, fileName)); 159 } 160 161 return fileNames; 162 } 163 164 public List<String> getSupportedMimeTypes() 165 { 166 List<String> mimeTypeSupported = new ArrayList<>(); 167 mimeTypeSupported.add("application/pdf"); 168 169 return mimeTypeSupported; 170 } 171 172 /** 173 * Generate the preview image 174 * @param folder The folder with the PDF images 175 * @param outputPrefix The prefix of PDF images 176 * @throws IOException if an I/O error occurs 177 */ 178 protected void writePreview(File folder, String outputPrefix) throws IOException 179 { 180 File[] images = folder.listFiles(new FileFilter() 181 { 182 public boolean accept(File pathname) 183 { 184 return pathname.getName().startsWith(outputPrefix); 185 } 186 }); 187 188 // Compute preview dimension from first image 189 File firstImage = images[0]; 190 BufferedImage bfi = ImageIO.read(firstImage); 191 double imgRatio = (double) bfi.getHeight() / bfi.getWidth(); 192 193 int rows = (int) Math.ceil(1 + images.length / 2); 194 195 int imgWidthInPreview = 56; 196 int imgHeightInPreview = (int) Math.round(imgWidthInPreview * imgRatio); 197 198 // Create a image on 2 columns 199 BufferedImage result = new BufferedImage(imgWidthInPreview * 2, imgHeightInPreview * rows, BufferedImage.TYPE_INT_RGB); 200 Graphics g = result.getGraphics(); 201 202 int x = 0; 203 int y = 0; 204 int count = 0; 205 for (File image : images) 206 { 207 BufferedImage bi = ImageIO.read(image); 208 BufferedImage ri = _resizeImage(bi, imgHeightInPreview, imgWidthInPreview); 209 g.drawImage(ri, x, y, null); 210 x += imgWidthInPreview; 211 if (count == 0 || x >= result.getWidth()) 212 { 213 x = 0; 214 y += ri.getHeight(); 215 } 216 count++; 217 } 218 219 ImageIO.write(result, "jpg" , new File(folder, "preview.jpg")); 220 } 221 222 private static BufferedImage _resizeImage(BufferedImage src, int maxHeight, int maxWidth) 223 { 224 int srcHeight = src.getHeight(); 225 int srcWidth = src.getWidth(); 226 227 int destHeight = 0; 228 int destWidth = 0; 229 230 boolean keepAspectRatio = true; 231 232 if (srcHeight <= maxHeight && srcWidth <= maxWidth || destHeight == srcHeight && destWidth == srcWidth) 233 { 234 // the source image is already smaller than the destination box or already the good format, don't change anything 235 return src; 236 } 237 238 destWidth = maxWidth; 239 destHeight = maxHeight; 240 241 Dimension srcDimension = new Dimension(srcWidth, srcHeight); 242 Dimension thumbnailDimension = new Dimension(destWidth, destHeight); 243 244 BufferedImage thumbImage = new FixedSizeThumbnailMaker(destWidth, destHeight, keepAspectRatio, true) 245 .resizer(DefaultResizerFactory.getInstance().getResizer(srcDimension, thumbnailDimension)) 246 .imageType(src.getColorModel().hasAlpha() ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB) 247 .make(src); 248 249 return thumbImage; 250 } 251}