]> source.dussan.org Git - poi.git/commitdiff
#64867 - Provide PDF rendering with PPTX2PNG
authorAndreas Beeker <kiwiwings@apache.org>
Mon, 2 Nov 2020 23:56:27 +0000 (23:56 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Mon, 2 Nov 2020 23:56:27 +0000 (23:56 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1883074 13f79535-47bb-0310-9956-ffa450edef68

build.xml
src/java/org/apache/poi/sl/draw/DrawTextFragment.java
src/ooxml/java/org/apache/poi/xslf/util/BitmapFormat.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xslf/util/OutputFormat.java
src/ooxml/java/org/apache/poi/xslf/util/PDFFormat.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java
src/ooxml/java/org/apache/poi/xslf/util/SVGFormat.java [new file with mode: 0644]

index d998365c5ab28c1f9dbb8b46856e53fc99a3059e..673d1bb983de1588089b626aa8fce75ff7ec20ab 100644 (file)
--- a/build.xml
+++ b/build.xml
@@ -294,10 +294,13 @@ under the License.
     <!-- only used for signing the release - not used with the ooxml signatures -->
     <dependency prefix="dsig.bouncycastle-bcpg" artifact="org.bouncycastle:bcpg-jdk15on:1.66" usage="util"/>
 
-    <!-- svg/batik libs - not part of the distribution -->
+    <!-- svg/batik/pdf libs - not part of the distribution -->
     <dependency prefix="svg.xml-apis-ext" artifact="xml-apis:xml-apis-ext:1.3.04" usage="ooxml-provided"/>
     <dependency prefix="svg.xmlgraphics-commons" artifact="org.apache.xmlgraphics:xmlgraphics-commons:2.4" usage="ooxml-provided"/>
     <dependency prefix="svg.batik-all" artifact="org.apache.xmlgraphics:batik-all:1.13" usage="ooxml-provided"/>
+    <dependency prefix="pdf.pdfbox" artifact="org.apache.pdfbox:pdfbox:2.0.19" usage="ooxml-provided"/>
+    <dependency prefix="pdf.fontbox" artifact="org.apache.pdfbox:fontbox:2.0.19" usage="ooxml-provided"/>
+    <dependency prefix="pdf.graphics2d" artifact="de.rototor.pdfbox:graphics2d:0.27" usage="ooxml-provided"/>
 
     <!-- jars in the ooxml-lib directory, see the fetch-ooxml-jars target-->
     <dependency prefix="ooxml.curvesapi" artifact="com.github.virtuald:curvesapi:1.06" usage="ooxml"/>
@@ -450,10 +453,17 @@ under the License.
         <pathelement location="${svg.xmlgraphics-commons.jar}"/>
     </path>
 
+    <path id="pdfbox.classpath">
+        <pathelement location="${pdf.pdfbox.jar}"/>
+        <pathelement location="${pdf.fontbox.jar}"/>
+        <pathelement location="${pdf.graphics2d.jar}"/>
+    </path>
+
     <path id="ooxml.classpath">
         <pathelement location="${ooxml.xsds.jar}"/>
         <path refid="ooxml.base.classpath"/>
         <path refid="batik.classpath"/>
+        <path refid="pdfbox.classpath"/>
     </path>
 
     <path id="ooxml.lite.verify.classpath">
@@ -712,6 +722,9 @@ under the License.
                     <available file="${svg.xml-apis-ext.jar}"/>
                     <available file="${svg.batik-all.jar}"/>
                     <available file="${svg.xmlgraphics-commons.jar}"/>
+                    <available file="${pdf.pdfbox.jar}"/>
+                    <available file="${pdf.fontbox.jar}"/>
+                    <available file="${pdf.graphics2d.jar}"/>
                 </and>
                 <isset property="disconnected"/>
             </or>
@@ -730,6 +743,9 @@ under the License.
         <downloadfile src="${svg.batik-all.url}" dest="${svg.batik-all.jar}"/>
         <downloadfile src="${svg.xml-apis-ext.url}" dest="${svg.xml-apis-ext.jar}"/>
         <downloadfile src="${svg.xmlgraphics-commons.url}" dest="${svg.xmlgraphics-commons.jar}"/>
+        <downloadfile src="${pdf.pdfbox.url}" dest="${pdf.pdfbox.jar}"/>
+        <downloadfile src="${pdf.fontbox.url}" dest="${pdf.fontbox.jar}"/>
+        <downloadfile src="${pdf.graphics2d.url}" dest="${pdf.graphics2d.jar}"/>
     </target>
 
     <target name="check-svn-jars">
index cb2ef66df9b4893ff881bec6b663f82a6e459d27..7bc23cfa7c5ca399be515dc087ce72a21397a4ed 100644 (file)
@@ -17,7 +17,9 @@
 
 package org.apache.poi.sl.draw;
 
+import java.awt.Color;
 import java.awt.Graphics2D;
+import java.awt.font.TextAttribute;
 import java.awt.font.TextLayout;
 import java.text.AttributedCharacterIterator;
 import java.text.AttributedString;
@@ -50,7 +52,22 @@ public class DrawTextFragment implements Drawable  {
         if(textMode != null && textMode == Drawable.TEXT_AS_SHAPES){
             layout.draw(graphics, (float)x, (float)yBaseline);
         } else {
-            graphics.drawString(str.getIterator(), (float)x, (float)yBaseline );
+            try {
+                graphics.drawString(str.getIterator(), (float) x, (float) yBaseline);
+            } catch (ClassCastException e) {
+                // workaround: batik issue, which expects only Color as forground color
+                replaceForgroundPaintWithBlack(str);
+                graphics.drawString(str.getIterator(), (float) x, (float) yBaseline);
+            }
+        }
+    }
+
+    private void replaceForgroundPaintWithBlack(AttributedString as) {
+        AttributedCharacterIterator iter = as.getIterator(new TextAttribute[]{TextAttribute.FOREGROUND});
+        for (char ch = iter.first();
+             ch != CharacterIterator.DONE;
+             ch = iter.next()) {
+            as.addAttribute(TextAttribute.FOREGROUND, Color.BLACK, iter.getBeginIndex(), iter.getEndIndex());
         }
     }
 
diff --git a/src/ooxml/java/org/apache/poi/xslf/util/BitmapFormat.java b/src/ooxml/java/org/apache/poi/xslf/util/BitmapFormat.java
new file mode 100644 (file)
index 0000000..093eb87
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ *  ====================================================================
+ *    Licensed to the Apache Software Foundation (ASF) under one or more
+ *    contributor license agreements.  See the NOTICE file distributed with
+ *    this work for additional information regarding copyright ownership.
+ *    The ASF licenses this file to You under the Apache License, Version 2.0
+ *    (the "License"); you may not use this file except in compliance with
+ *    the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ * ====================================================================
+ */
+
+package org.apache.poi.xslf.util;
+
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+
+import javax.imageio.ImageIO;
+
+import org.apache.poi.sl.draw.Drawable;
+import org.apache.poi.util.Internal;
+
+@Internal
+public class BitmapFormat implements OutputFormat {
+    private final String format;
+    private BufferedImage img;
+    private Graphics2D graphics;
+
+    public BitmapFormat(String format) {
+        this.format = format;
+    }
+
+    @Override
+    public Graphics2D addSlide(double width, double height) {
+        img = new BufferedImage((int)width, (int)height, BufferedImage.TYPE_INT_ARGB);
+        graphics = img.createGraphics();
+        graphics.setRenderingHint(Drawable.BUFFERED_IMAGE, new WeakReference<>(img));
+        return graphics;
+    }
+
+    @Override
+    public void writeSlide(MFProxy proxy, File outFile) throws IOException {
+        if (!"null".equals(format)) {
+            ImageIO.write(img, format, outFile);
+        }
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (graphics != null) {
+            graphics.dispose();
+            img.flush();
+        }
+    }
+}
index 23c71b0d76ae1172c51eb30a40a2ecd1ee913c99..cfa8df2c830c3ef810d7f80b16f8822ccf7b9603 100644 (file)
 
 package org.apache.poi.xslf.util;
 
-import java.awt.Dimension;
 import java.awt.Graphics2D;
-import java.awt.image.BufferedImage;
 import java.io.Closeable;
 import java.io.File;
 import java.io.IOException;
-import java.lang.ref.WeakReference;
 
-import javax.imageio.ImageIO;
-
-import org.apache.batik.dom.GenericDOMImplementation;
-import org.apache.batik.svggen.SVGGraphics2D;
-import org.apache.poi.sl.draw.Drawable;
 import org.apache.poi.util.Internal;
-import org.apache.poi.xslf.draw.SVGPOIGraphics2D;
-import org.w3c.dom.DOMImplementation;
-import org.w3c.dom.Document;
 
 /**
  * Output formats for PPTX2PNG
@@ -43,72 +32,12 @@ import org.w3c.dom.Document;
 @Internal
 interface OutputFormat extends Closeable {
 
-    Graphics2D getGraphics2D(double width, double height);
-
-    void writeOut(MFProxy proxy, File outFile) throws IOException;
-
-    class SVGFormat implements OutputFormat {
-        static final String svgNS = "http://www.w3.org/2000/svg";
-        private SVGGraphics2D svgGenerator;
-        private final boolean textAsShapes;
-
-        SVGFormat(boolean textAsShapes) {
-            this.textAsShapes = textAsShapes;
-        }
-
-        @Override
-        public Graphics2D getGraphics2D(double width, double height) {
-            // Get a DOMImplementation.
-            DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();
-
-            // Create an instance of org.w3c.dom.Document.
-            Document document = domImpl.createDocument(svgNS, "svg", null);
-            svgGenerator = new SVGPOIGraphics2D(document, textAsShapes);
-            svgGenerator.setSVGCanvasSize(new Dimension((int)width, (int)height));
-            return svgGenerator;
-        }
-
-        @Override
-        public void writeOut(MFProxy proxy, File outFile) throws IOException {
-            svgGenerator.stream(outFile.getCanonicalPath(), true);
-        }
-
-        @Override
-        public void close() throws IOException {
-            svgGenerator.dispose();
-        }
-    }
+    Graphics2D addSlide(double width, double height) throws IOException;
 
-    class BitmapFormat implements OutputFormat {
-        private final String format;
-        private BufferedImage img;
-        private Graphics2D graphics;
+    void writeSlide(MFProxy proxy, File outFile) throws IOException;
 
-        BitmapFormat(String format) {
-            this.format = format;
-        }
+    default void writeDocument(MFProxy proxy, File outFile) throws IOException {};
 
-        @Override
-        public Graphics2D getGraphics2D(double width, double height) {
-            img = new BufferedImage((int)width, (int)height, BufferedImage.TYPE_INT_ARGB);
-            graphics = img.createGraphics();
-            graphics.setRenderingHint(Drawable.BUFFERED_IMAGE, new WeakReference<>(img));
-            return graphics;
-        }
 
-        @Override
-        public void writeOut(MFProxy proxy, File outFile) throws IOException {
-            if (!"null".equals(format)) {
-                ImageIO.write(img, format, outFile);
-            }
-        }
 
-        @Override
-        public void close() throws IOException {
-            if (graphics != null) {
-                graphics.dispose();
-                img.flush();
-            }
-        }
-    }
 }
diff --git a/src/ooxml/java/org/apache/poi/xslf/util/PDFFormat.java b/src/ooxml/java/org/apache/poi/xslf/util/PDFFormat.java
new file mode 100644 (file)
index 0000000..2ada1c7
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ *  ====================================================================
+ *    Licensed to the Apache Software Foundation (ASF) under one or more
+ *    contributor license agreements.  See the NOTICE file distributed with
+ *    this work for additional information regarding copyright ownership.
+ *    The ASF licenses this file to You under the Apache License, Version 2.0
+ *    (the "License"); you may not use this file except in compliance with
+ *    the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ * ====================================================================
+ */
+
+package org.apache.poi.xslf.util;
+
+import java.awt.Graphics2D;
+import java.io.File;
+import java.io.IOException;
+
+import de.rototor.pdfbox.graphics2d.PdfBoxGraphics2D;
+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.form.PDFormXObject;
+import org.apache.poi.util.Internal;
+
+@Internal
+public class PDFFormat implements OutputFormat {
+    private final PDDocument document;
+    private PDPageContentStream contentStream;
+    private PdfBoxGraphics2D pdfBoxGraphics2D;
+
+    public PDFFormat() {
+        document = new PDDocument();
+    }
+
+    @Override
+    public Graphics2D addSlide(double width, double height)  throws IOException {
+        PDPage page = new PDPage(new PDRectangle((float) width, (float) height));
+        document.addPage(page);
+        contentStream = new PDPageContentStream(document, page);
+        pdfBoxGraphics2D = new PdfBoxGraphics2D(document, (float)width, (float)height);
+        return pdfBoxGraphics2D;
+    }
+
+    @Override
+    public void writeSlide(MFProxy proxy, File outFile) throws IOException {
+        pdfBoxGraphics2D.dispose();
+
+        PDFormXObject appearanceStream = pdfBoxGraphics2D.getXFormObject();
+        contentStream.drawForm(appearanceStream);
+        contentStream.close();
+    }
+
+    @Override
+    public void writeDocument(MFProxy proxy, File outFile) throws IOException {
+        document.save(new File(outFile.getCanonicalPath()));
+    }
+
+    @Override
+    public void close() throws IOException {
+        document.close();
+    }
+}
index f4c57325da5c358cfcad433745b43ee70e7eb654..db034e8caa6d4766d817219cc2b43f112b9df5b0 100644 (file)
@@ -39,8 +39,6 @@ import org.apache.poi.sl.draw.EmbeddedExtractor.EmbeddedPart;
 import org.apache.poi.util.Dimension2DDouble;
 import org.apache.poi.util.GenericRecordJsonWriter;
 import org.apache.poi.util.LocaleUtil;
-import org.apache.poi.xslf.util.OutputFormat.BitmapFormat;
-import org.apache.poi.xslf.util.OutputFormat.SVGFormat;
 
 /**
  * An utility to convert slides of a .pptx slide show to a PNG image
@@ -62,7 +60,7 @@ public final class PPTX2PNG {
             "    -scale <float>    scale factor\n" +
             "    -fixSide <side>   specify side (long,short,width,height) to fix - use <scale> as amount of pixels\n" +
             "    -slide <integer>  1-based index of a slide to render\n" +
-            "    -format <type>    png,gif,jpg,svg (,null for testing)\n" +
+            "    -format <type>    png,gif,jpg,svg,pdf (,null for testing)\n" +
             "    -outdir <dir>     output directory, defaults to origin of the ppt/pptx file\n" +
             "    -outfile <file>   output filename, defaults to '"+OUTPUT_PAT_REGEX+"'\n" +
             "    -outpat <pattern> output filename pattern, defaults to '"+OUTPUT_PAT_REGEX+"'\n" +
@@ -207,7 +205,7 @@ public final class PPTX2PNG {
             return false;
         }
 
-        if (format == null || !format.matches("^(png|gif|jpg|null|svg)$")) {
+        if (format == null || !format.matches("^(png|gif|jpg|null|svg|pdf)$")) {
             usage("Invalid format given");
             return false;
         }
@@ -262,19 +260,19 @@ public final class PPTX2PNG {
             final int width = Math.max((int)Math.rint(dim.getWidth()),1);
             final int height = Math.max((int)Math.rint(dim.getHeight()),1);
 
-            for (int slideNo : slidenum) {
-                proxy.setSlideNo(slideNo);
-                if (!quiet) {
-                    String title = proxy.getTitle();
-                    System.out.println("Rendering slide " + slideNo + (title == null ? "" : ": " + title.trim()));
-                }
+            try (OutputFormat outputFormat = getOutput()) {
+                for (int slideNo : slidenum) {
+                    proxy.setSlideNo(slideNo);
+                    if (!quiet) {
+                        String title = proxy.getTitle();
+                        System.out.println("Rendering slide " + slideNo + (title == null ? "" : ": " + title.trim()));
+                    }
 
-                dumpRecords(proxy);
+                    dumpRecords(proxy);
 
-                extractEmbedded(proxy, slideNo);
+                    extractEmbedded(proxy, slideNo);
 
-                try (OutputFormat outputFormat = ("svg".equals(format)) ? new SVGFormat(textAsShapes) : new BitmapFormat(format)) {
-                    Graphics2D graphics = outputFormat.getGraphics2D(width, height);
+                    Graphics2D graphics = outputFormat.addSlide(width, height);
 
                     // default rendering options
                     graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
@@ -294,9 +292,12 @@ public final class PPTX2PNG {
                     // draw stuff
                     proxy.draw(graphics);
 
-                    outputFormat.writeOut(proxy, new File(outdir, calcOutFile(proxy, slideNo)));
+                    outputFormat.writeSlide(proxy, new File(outdir, calcOutFile(proxy, slideNo)));
                 }
+
+                outputFormat.writeDocument(proxy, new File(outdir, calcOutFile(proxy, 0)));
             }
+
         } catch (NoScratchpadException e) {
             usage("'"+file.getName()+"': Format not supported - try to include poi-scratchpad.jar into the CLASSPATH.");
             return;
@@ -307,6 +308,17 @@ public final class PPTX2PNG {
         }
     }
 
+    private OutputFormat getOutput() {
+        switch (format) {
+            case "svg":
+                return new SVGFormat(textAsShapes);
+            case "pdf":
+                return new PDFFormat();
+            default:
+                return new BitmapFormat(format);
+        }
+    }
+
     private double getDimensions(MFProxy proxy, Dimension2D dim) {
         final Dimension2D pgsize = proxy.getSize();
 
@@ -413,7 +425,7 @@ public final class PPTX2PNG {
             return outfile;
         }
         String inname = String.format(Locale.ROOT, "%04d|%s|%s", slideNo, format, file.getName());
-        String outpat = (proxy.getSlideCount() > 1 ? outPattern : outPattern.replaceAll("-?\\$\\{slideno}", ""));
+        String outpat = (proxy.getSlideCount() > 1 && slideNo > 0 ? outPattern : outPattern.replaceAll("-?\\$\\{slideno}", ""));
         return INPUT_PATTERN.matcher(inname).replaceAll(outpat);
     }
 
diff --git a/src/ooxml/java/org/apache/poi/xslf/util/SVGFormat.java b/src/ooxml/java/org/apache/poi/xslf/util/SVGFormat.java
new file mode 100644 (file)
index 0000000..3dd791f
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ *  ====================================================================
+ *    Licensed to the Apache Software Foundation (ASF) under one or more
+ *    contributor license agreements.  See the NOTICE file distributed with
+ *    this work for additional information regarding copyright ownership.
+ *    The ASF licenses this file to You under the Apache License, Version 2.0
+ *    (the "License"); you may not use this file except in compliance with
+ *    the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ * ====================================================================
+ */
+
+package org.apache.poi.xslf.util;
+
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.batik.dom.GenericDOMImplementation;
+import org.apache.batik.svggen.SVGGraphics2D;
+import org.apache.poi.util.Internal;
+import org.apache.poi.xslf.draw.SVGPOIGraphics2D;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+
+@Internal
+public class SVGFormat implements OutputFormat {
+    static final String svgNS = "http://www.w3.org/2000/svg";
+    private SVGGraphics2D svgGenerator;
+    private final boolean textAsShapes;
+
+    public SVGFormat(boolean textAsShapes) {
+        this.textAsShapes = textAsShapes;
+    }
+
+    @Override
+    public Graphics2D addSlide(double width, double height) {
+        // Get a DOMImplementation.
+        DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();
+
+        // Create an instance of org.w3c.dom.Document.
+        Document document = domImpl.createDocument(svgNS, "svg", null);
+        svgGenerator = new SVGPOIGraphics2D(document, textAsShapes);
+        svgGenerator.setSVGCanvasSize(new Dimension((int)width, (int)height));
+        return svgGenerator;
+    }
+
+    @Override
+    public void writeSlide(MFProxy proxy, File outFile) throws IOException {
+        svgGenerator.stream(outFile.getCanonicalPath(), true);
+    }
+
+    @Override
+    public void close() throws IOException {
+        svgGenerator.dispose();
+    }
+}