View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.iview2.frontend;
20  
21  import java.awt.Graphics2D;
22  import java.awt.RenderingHints;
23  import java.awt.image.BufferedImage;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.nio.file.Files;
27  import java.nio.file.Path;
28  import java.nio.file.attribute.BasicFileAttributes;
29  
30  import org.apache.logging.log4j.LogManager;
31  import org.apache.logging.log4j.Logger;
32  import org.apache.pdfbox.pdmodel.PDDocument;
33  import org.apache.pdfbox.pdmodel.PDPage;
34  import org.apache.pdfbox.pdmodel.common.PDDestinationOrAction;
35  import org.apache.pdfbox.pdmodel.interactive.action.PDActionGoTo;
36  import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination;
37  import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageDestination;
38  import org.apache.pdfbox.rendering.PDFRenderer;
39  import org.mycore.common.content.MCRContent;
40  import org.mycore.tools.MCRPNGTools;
41  
42  /**
43   * @author Thomas Scheffler (yagee)
44   */
45  public class MCRPDFTools implements AutoCloseable {
46  
47      static final int PDF_DEFAULT_DPI = 72; // from private org.apache.pdfbox.pdmodel.PDPage.DEFAULT_USER_SPACE_UNIT_DPI
48  
49      private static final Logger LOGGER = LogManager.getLogger(MCRPDFTools.class);
50  
51      private final MCRPNGTools pngTools;
52  
53      MCRPDFTools() {
54          this.pngTools = new MCRPNGTools();
55      }
56  
57      /**
58       * The old method did not take the thumbnail size into account, if centered =
59       * false;
60       *
61       * @see #getThumbnail(int, Path, boolean)
62       */
63      @Deprecated
64      public static BufferedImage getThumbnail(Path pdfFile, int thumbnailSize, boolean centered) throws IOException {
65          return getThumbnail(-1, pdfFile, false);
66      }
67  
68      /**
69       * This method returns a Buffered Image as thumbnail if an initial page was set,
70       * it will be return - if not the first page
71       *
72       * @param thumbnailSize - the size: size = max(width, height) 
73       *                        a size &lt; 0 will return the original size and centered parameter will be ignored
74       * @param pdfFile       - the file from which the thumbnail will be taken
75       * @param centered      - if true, a square (thumbnail with same width and
76       *                      height) will be returned
77       * @return a BufferedImage as thumbnail
78       *
79       * @throws IOException
80       */
81      public static BufferedImage getThumbnail(int thumbnailSize, Path pdfFile, boolean centered) throws IOException {
82          InputStream fileIS = Files.newInputStream(pdfFile);
83          try (PDDocument pdf = PDDocument.load(fileIS)) {
84              PDFRenderer pdfRenderer = new PDFRenderer(pdf);
85              final PDPage page = resolveOpenActionPage(pdf);
86              BufferedImage level1Image = pdfRenderer.renderImage(pdf.getPages().indexOf(page));
87              if (thumbnailSize < 0) {
88                  return level1Image;
89              }
90              return scalePage(thumbnailSize, centered, level1Image);
91          }
92      }
93  
94      private static BufferedImage scalePage(int thumbnailSize, boolean centered, BufferedImage level1Image) {
95          int imageType = BufferedImage.TYPE_INT_ARGB;
96          final double width = level1Image.getWidth();
97          final double height = level1Image.getHeight();
98          LOGGER.info("new PDFBox: {}x{}", width, height);
99          LOGGER.info("temporary image dimensions: {}x{}", width, height);
100         final int newWidth = calculateNewWidth(thumbnailSize, width, height);
101         final int newHeight = calculateNewHeight(thumbnailSize, width, height);
102         // if centered make thumbnailSize x thumbnailSize image
103         final BufferedImage bicubicScaledPage = new BufferedImage(centered ? thumbnailSize : newWidth,
104             centered ? thumbnailSize : newHeight, imageType);
105         LOGGER.info("target image dimensions: {}x{}", bicubicScaledPage.getWidth(), bicubicScaledPage.getHeight());
106         final Graphics2D bg = bicubicScaledPage.createGraphics();
107         try {
108             bg.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
109             int x = centered ? (thumbnailSize - newWidth) / 2 : 0;
110             int y = centered ? (thumbnailSize - newHeight) / 2 : 0;
111             if (x != 0 && y != 0) {
112                 LOGGER.warn("Writing at position {},{}", x, y);
113             }
114             bg.drawImage(level1Image, x, y, x + newWidth, y + newHeight, 0, 0, (int) Math.ceil(width),
115                 (int) Math.ceil(height), null);
116         } finally {
117             bg.dispose();
118         }
119         return bicubicScaledPage;
120     }
121 
122     private static int calculateNewHeight(int thumbnailSize, double width, double height) {
123         return width < height ? thumbnailSize : (int) Math.ceil(thumbnailSize * height / width);
124     }
125 
126     private static int calculateNewWidth(int thumbnailSize, double width, double height) {
127         return width < height ? (int) Math.ceil(thumbnailSize * width / height) : thumbnailSize;
128     }
129 
130     /**
131      *
132      * @param pdf - the pdf document
133      * @return
134      * @throws IOException
135      *
136      * @see org.mycore.media.services.MCRPdfThumbnailGenerator
137      */
138     private static PDPage resolveOpenActionPage(PDDocument pdf) throws IOException {
139         PDDestinationOrAction openAction = pdf.getDocumentCatalog().getOpenAction();
140 
141         if (openAction instanceof PDActionGoTo) {
142             final PDDestination destination = ((PDActionGoTo) openAction).getDestination();
143             if (destination instanceof PDPageDestination) {
144                 openAction = destination;
145             }
146         }
147 
148         if (openAction instanceof PDPageDestination) {
149             final PDPageDestination namedDestination = (PDPageDestination) openAction;
150             final PDPage pdPage = namedDestination.getPage();
151             if (pdPage != null) {
152                 return pdPage;
153             } else {
154                 int pageNumber = namedDestination.getPageNumber();
155                 if (pageNumber != -1) {
156                     return pdf.getPage(pageNumber);
157                 }
158             }
159         }
160 
161         return pdf.getPage(0);
162     }
163 
164     MCRContent getThumnail(Path pdfFile, BasicFileAttributes attrs, int thumbnailSize, boolean centered)
165         throws IOException {
166         BufferedImage thumbnail = MCRPDFTools.getThumbnail(pdfFile, thumbnailSize, centered);
167         MCRContent pngContent = pngTools.toPNGContent(thumbnail);
168         BasicFileAttributes fattrs = attrs != null ? attrs : Files.readAttributes(pdfFile, BasicFileAttributes.class);
169         pngContent.setLastModified(fattrs.lastModifiedTime().toMillis());
170         return pngContent;
171     }
172 
173     @Override
174     public void close() throws Exception {
175         this.pngTools.close();
176     }
177 
178 }