diff --git a/build.xml b/build.xml index d998365c5a..673d1bb983 100644 --- a/build.xml +++ b/build.xml @@ -294,10 +294,13 @@ under the License. - + + + + @@ -450,10 +453,17 @@ under the License. + + + + + + + @@ -712,6 +722,9 @@ under the License. + + + @@ -730,6 +743,9 @@ under the License. + + + diff --git a/src/java/org/apache/poi/sl/draw/DrawTextFragment.java b/src/java/org/apache/poi/sl/draw/DrawTextFragment.java index cb2ef66df9..7bc23cfa7c 100644 --- a/src/java/org/apache/poi/sl/draw/DrawTextFragment.java +++ b/src/java/org/apache/poi/sl/draw/DrawTextFragment.java @@ -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 index 0000000000..093eb87f52 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/util/BitmapFormat.java @@ -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(); + } + } +} diff --git a/src/ooxml/java/org/apache/poi/xslf/util/OutputFormat.java b/src/ooxml/java/org/apache/poi/xslf/util/OutputFormat.java index 23c71b0d76..cfa8df2c83 100644 --- a/src/ooxml/java/org/apache/poi/xslf/util/OutputFormat.java +++ b/src/ooxml/java/org/apache/poi/xslf/util/OutputFormat.java @@ -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(); - } - } - } } 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 index 0000000000..2ada1c738b --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/util/PDFFormat.java @@ -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(); + } +} diff --git a/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java b/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java index f4c57325da..db034e8caa 100644 --- a/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java +++ b/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java @@ -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 scale factor\n" + " -fixSide specify side (long,short,width,height) to fix - use as amount of pixels\n" + " -slide 1-based index of a slide to render\n" + - " -format png,gif,jpg,svg (,null for testing)\n" + + " -format png,gif,jpg,svg,pdf (,null for testing)\n" + " -outdir output directory, defaults to origin of the ppt/pptx file\n" + " -outfile output filename, defaults to '"+OUTPUT_PAT_REGEX+"'\n" + " -outpat 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 index 0000000000..3dd791f7da --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/util/SVGFormat.java @@ -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(); + } +}