#64867 - Provide PDF rendering with PPTX2PNG

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1883074 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2020-11-02 23:56:27 +00:00
parent dae47d5ced
commit b20318b58c
7 changed files with 267 additions and 92 deletions

View File

@ -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">

View 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());
}
}

View File

@ -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();
}
}
}

View File

@ -19,23 +19,12 @@
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);
Graphics2D addSlide(double width, double height) throws IOException;
void writeOut(MFProxy proxy, File outFile) throws IOException;
void writeSlide(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;
default void writeDocument(MFProxy proxy, File outFile) throws IOException {};
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();
}
}
class BitmapFormat implements OutputFormat {
private final String format;
private BufferedImage img;
private Graphics2D graphics;
BitmapFormat(String format) {
this.format = format;
}
@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();
}
}
}
}

View File

@ -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();
}
}

View 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);
}

View File

@ -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();
}
}