aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndreas Beeker <kiwiwings@apache.org>2019-11-01 17:18:13 +0000
committerAndreas Beeker <kiwiwings@apache.org>2019-11-01 17:18:13 +0000
commitf7c28ad08f34de47bdc1f6315ccd858570a363e5 (patch)
tree311676fd07e6784e7fc892cf8324765ce36979fc
parent3f0a01ae7cf30a8c0da4c3af84c3e05da79a7d63 (diff)
downloadpoi-f7c28ad08f34de47bdc1f6315ccd858570a363e5.tar.gz
poi-f7c28ad08f34de47bdc1f6315ccd858570a363e5.zip
#60656 - EMF image support in slideshows
- extract option for embedded element in PPTX2PNG - minor GenericRecordJsonWriter fixes - fix EMF+ world transformations - fix initialization emf pictures, which were partly unbounded -> excessive memory consumption - change EMF+ brushes to continueable record git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1869272 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--src/examples/src/org/apache/poi/hslf/examples/PPT2PNG.java46
-rw-r--r--src/java/org/apache/poi/poifs/filesystem/FileMagic.java11
-rw-r--r--src/java/org/apache/poi/sl/draw/EmbeddedExtractor.java54
-rw-r--r--src/java/org/apache/poi/util/GenericRecordJsonWriter.java10
-rw-r--r--src/java/org/apache/poi/util/GenericRecordUtil.java14
-rw-r--r--src/ooxml/java/org/apache/poi/xslf/util/EMFHandler.java114
-rw-r--r--src/ooxml/java/org/apache/poi/xslf/util/MFProxy.java67
-rw-r--r--src/ooxml/java/org/apache/poi/xslf/util/PPTHandler.java180
-rw-r--r--src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java431
-rw-r--r--src/ooxml/java/org/apache/poi/xslf/util/WMFHandler.java31
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java31
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java26
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java9
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java20
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java4
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java42
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java3
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusBrush.java63
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusMisc.java20
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusObject.java3
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java2
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java16
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java12
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java35
-rw-r--r--src/scratchpad/testcases/org/apache/poi/hemf/usermodel/TestHemfPicture.java174
25 files changed, 895 insertions, 523 deletions
diff --git a/src/examples/src/org/apache/poi/hslf/examples/PPT2PNG.java b/src/examples/src/org/apache/poi/hslf/examples/PPT2PNG.java
deleted file mode 100644
index 2a97610256..0000000000
--- a/src/examples/src/org/apache/poi/hslf/examples/PPT2PNG.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/* ====================================================================
- 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.hslf.examples;
-
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.Graphics2D;
-import java.awt.RenderingHints;
-import java.awt.geom.Rectangle2D;
-import java.awt.image.BufferedImage;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.lang.ref.WeakReference;
-
-import javax.imageio.ImageIO;
-
-import org.apache.poi.hslf.usermodel.HSLFSlide;
-import org.apache.poi.hslf.usermodel.HSLFSlideShow;
-import org.apache.poi.sl.draw.Drawable;
-import org.apache.poi.xslf.util.PPTX2PNG;
-
-/**
- * Demonstrates how you can use HSLF to convert each slide into a PNG image
- */
-public final class PPT2PNG extends PPTX2PNG {
-
- private static void usage(){
- System.out.println("Usage: PPT2PNG [-scale <scale> -slide <num>] ppt");
- }
-}
diff --git a/src/java/org/apache/poi/poifs/filesystem/FileMagic.java b/src/java/org/apache/poi/poifs/filesystem/FileMagic.java
index 16312f5897..4e4ffa9d44 100644
--- a/src/java/org/apache/poi/poifs/filesystem/FileMagic.java
+++ b/src/java/org/apache/poi/poifs/filesystem/FileMagic.java
@@ -94,12 +94,21 @@ public enum FileMagic {
PNG(new byte[]{ (byte)0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A }),
/** TIFF Image */
TIFF("II*\u0000", "MM\u0000*" ),
+ /** WMF image with a placeable header */
+ WMF(new byte[]{ (byte)0xD7, (byte)0xCD, (byte)0xC6, (byte)0x9A }),
+ /** EMF image */
+ EMF(new byte[]{
+ 1, 0, 0, 0,
+ '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?',
+ '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?',
+ ' ', 'E', 'M', 'F'
+ }),
// keep UNKNOWN always as last enum!
/** UNKNOWN magic */
UNKNOWN(new byte[0]);
// update this if a longer pattern is added
- final static int MAX_PATTERN_LENGTH = 12;
+ final static int MAX_PATTERN_LENGTH = 44;
final byte[][] magic;
diff --git a/src/java/org/apache/poi/sl/draw/EmbeddedExtractor.java b/src/java/org/apache/poi/sl/draw/EmbeddedExtractor.java
new file mode 100644
index 0000000000..5fcdba52d7
--- /dev/null
+++ b/src/java/org/apache/poi/sl/draw/EmbeddedExtractor.java
@@ -0,0 +1,54 @@
+/*
+ * ====================================================================
+ * 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.sl.draw;
+
+import java.util.Collections;
+import java.util.function.Supplier;
+
+import org.apache.poi.util.Beta;
+
+@Beta
+public interface EmbeddedExtractor {
+ class EmbeddedPart {
+ private String name;
+ private Supplier<byte[]> data;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Supplier<byte[]> getData() {
+ return data;
+ }
+
+ public void setData(Supplier<byte[]> data) {
+ this.data = data;
+ }
+ }
+
+
+ default Iterable<EmbeddedPart> getEmbeddings() {
+ return Collections.emptyList();
+ }
+} \ No newline at end of file
diff --git a/src/java/org/apache/poi/util/GenericRecordJsonWriter.java b/src/java/org/apache/poi/util/GenericRecordJsonWriter.java
index 08dc3c371a..43e23f31ea 100644
--- a/src/java/org/apache/poi/util/GenericRecordJsonWriter.java
+++ b/src/java/org/apache/poi/util/GenericRecordJsonWriter.java
@@ -368,19 +368,19 @@ public class GenericRecordJsonWriter implements Closeable {
fw.append("{ \"type\": ");
switch (segType) {
case PathIterator.SEG_MOVETO:
- fw.write("'move', \"x\": "+pnts[0]+", \"y\": "+pnts[1]);
+ fw.write("\"move\", \"x\": "+pnts[0]+", \"y\": "+pnts[1]);
break;
case PathIterator.SEG_LINETO:
- fw.write("'lineto', \"x\": "+pnts[0]+", \"y\": "+pnts[1]);
+ fw.write("\"lineto\", \"x\": "+pnts[0]+", \"y\": "+pnts[1]);
break;
case PathIterator.SEG_QUADTO:
- fw.write("'quad', \"x1\": "+pnts[0]+", \"y1\": "+pnts[1]+", \"x2\": "+pnts[2]+", \"y2\": "+pnts[3]);
+ fw.write("\"quad\", \"x1\": "+pnts[0]+", \"y1\": "+pnts[1]+", \"x2\": "+pnts[2]+", \"y2\": "+pnts[3]);
break;
case PathIterator.SEG_CUBICTO:
- fw.write("'cubic', \"x1\": "+pnts[0]+", \"y1\": "+pnts[1]+", \"x2\": "+pnts[2]+", \"y2\": "+pnts[3]+", \"x3\": "+pnts[4]+", \"y3\": "+pnts[5]);
+ fw.write("\"cubic\", \"x1\": "+pnts[0]+", \"y1\": "+pnts[1]+", \"x2\": "+pnts[2]+", \"y2\": "+pnts[3]+", \"x3\": "+pnts[4]+", \"y3\": "+pnts[5]);
break;
case PathIterator.SEG_CLOSE:
- fw.write("'close'");
+ fw.write("\"close\"");
break;
}
fw.append(" }");
diff --git a/src/java/org/apache/poi/util/GenericRecordUtil.java b/src/java/org/apache/poi/util/GenericRecordUtil.java
index 00c46ced8c..8a97d7cdf1 100644
--- a/src/java/org/apache/poi/util/GenericRecordUtil.java
+++ b/src/java/org/apache/poi/util/GenericRecordUtil.java
@@ -107,17 +107,23 @@ public final class GenericRecordUtil {
}
public static Supplier<AnnotatedFlag> getBitsAsString(Supplier<Number> flags, final int[] masks, final String[] names) {
- return () -> new AnnotatedFlag(flags, masks, names);
+ return () -> new AnnotatedFlag(flags, masks, names, false);
+ }
+
+ public static Supplier<AnnotatedFlag> getEnumBitsAsString(Supplier<Number> flags, final int[] masks, final String[] names) {
+ return () -> new AnnotatedFlag(flags, masks, names, true);
}
public static class AnnotatedFlag {
private final Supplier<Number> value;
private final Map<Integer,String> masks = new LinkedHashMap<>();
+ private final boolean exactMatch;
- AnnotatedFlag(Supplier<Number> value, int[] masks, String[] names) {
+ AnnotatedFlag(Supplier<Number> value, int[] masks, String[] names, boolean exactMatch) {
assert(masks.length == names.length);
this.value = value;
+ this.exactMatch = exactMatch;
for (int i=0; i<masks.length; i++) {
this.masks.put(masks[i], names[i]);
}
@@ -135,8 +141,8 @@ public final class GenericRecordUtil {
collect(Collectors.joining(" | "));
}
- private static boolean match(final int val, int mask) {
- return (val & mask) == mask;
+ private boolean match(final int val, int mask) {
+ return exactMatch ? (val == mask) : ((val & mask) == mask);
}
}
}
diff --git a/src/ooxml/java/org/apache/poi/xslf/util/EMFHandler.java b/src/ooxml/java/org/apache/poi/xslf/util/EMFHandler.java
new file mode 100644
index 0000000000..94fb64447e
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/xslf/util/EMFHandler.java
@@ -0,0 +1,114 @@
+/*
+ * ====================================================================
+ * 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.geom.Dimension2D;
+import java.awt.geom.Rectangle2D;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+
+import org.apache.poi.common.usermodel.GenericRecord;
+import org.apache.poi.sl.draw.BitmapImageRenderer;
+import org.apache.poi.sl.draw.DrawPictureShape;
+import org.apache.poi.sl.draw.EmbeddedExtractor;
+import org.apache.poi.sl.draw.EmbeddedExtractor.EmbeddedPart;
+import org.apache.poi.sl.draw.ImageRenderer;
+import org.apache.poi.sl.usermodel.PictureData;
+import org.apache.poi.util.Internal;
+
+@Internal
+class EMFHandler extends MFProxy {
+ private ImageRenderer imgr = null;
+ private InputStream is;
+
+ @Override
+ public void parse(File file) throws IOException {
+ // stream needs to be kept open
+ parse(file.toURI().toURL().openStream());
+ }
+
+ @Override
+ public void parse(InputStream is) throws IOException {
+ imgr = DrawPictureShape.getImageRenderer(null, getContentType());
+ if (imgr instanceof BitmapImageRenderer) {
+ throw new PPTX2PNG.NoScratchpadException();
+ }
+
+ // stream needs to be kept open
+ imgr.loadImage(is, getContentType());
+
+ if (ignoreParse) {
+ try {
+ imgr.getDimension();
+ } catch (Exception e) {
+// if (!quite) {
+// e.printStackTrace(System.err);
+// }
+ }
+ }
+
+ }
+
+ protected String getContentType() {
+ return PictureData.PictureType.EMF.contentType;
+ }
+
+ @Override
+ public Dimension2D getSize() {
+ return imgr.getDimension();
+ }
+
+ @Override
+ public String getTitle() {
+ return "";
+ }
+
+ @Override
+ public void draw(Graphics2D ctx) {
+ Dimension2D dim = getSize();
+ imgr.drawImage(ctx, new Rectangle2D.Double(0, 0, dim.getWidth(), dim.getHeight()));
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (is != null) {
+ try {
+ is.close();
+ } finally {
+ is = null;
+ }
+ }
+ }
+
+ @Override
+ public GenericRecord getRoot() {
+ return imgr.getGenericRecord();
+ }
+
+ @Override
+ public Iterable<EmbeddedPart> getEmbeddings(int slideNo) {
+ return (imgr instanceof EmbeddedExtractor)
+ ? ((EmbeddedExtractor) imgr).getEmbeddings()
+ : Collections.emptyList();
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/xslf/util/MFProxy.java b/src/ooxml/java/org/apache/poi/xslf/util/MFProxy.java
new file mode 100644
index 0000000000..435fdb3e78
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/xslf/util/MFProxy.java
@@ -0,0 +1,67 @@
+/*
+ * ====================================================================
+ * 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.geom.Dimension2D;
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.Set;
+
+import org.apache.poi.common.usermodel.GenericRecord;
+import org.apache.poi.sl.draw.EmbeddedExtractor.EmbeddedPart;
+import org.apache.poi.util.Internal;
+
+@Internal
+abstract class MFProxy implements Closeable {
+ boolean ignoreParse;
+ boolean quite;
+
+ void setIgnoreParse(boolean ignoreParse) {
+ this.ignoreParse = ignoreParse;
+ }
+
+ void setQuite(boolean quite) {
+ this.quite = quite;
+ }
+
+ abstract void parse(File file) throws IOException;
+ abstract void parse(InputStream is) throws IOException;
+
+ abstract Dimension2D getSize();
+
+ void setSlideNo(int slideNo) {}
+
+ abstract String getTitle();
+ abstract void draw(Graphics2D ctx);
+
+ int getSlideCount() { return 1; }
+
+ Set<Integer> slideIndexes(String range) {
+ return Collections.singleton(1);
+ }
+
+ abstract GenericRecord getRoot();
+
+ abstract Iterable<EmbeddedPart> getEmbeddings(int slideNo);
+}
diff --git a/src/ooxml/java/org/apache/poi/xslf/util/PPTHandler.java b/src/ooxml/java/org/apache/poi/xslf/util/PPTHandler.java
new file mode 100644
index 0000000000..debb34f005
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/xslf/util/PPTHandler.java
@@ -0,0 +1,180 @@
+/*
+ * ====================================================================
+ * 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 static java.util.Spliterator.NONNULL;
+import static java.util.Spliterator.ORDERED;
+
+import java.awt.Graphics2D;
+import java.awt.geom.Dimension2D;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Set;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.TreeSet;
+import java.util.function.Consumer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import org.apache.poi.common.usermodel.GenericRecord;
+import org.apache.poi.sl.draw.EmbeddedExtractor.EmbeddedPart;
+import org.apache.poi.sl.usermodel.ObjectData;
+import org.apache.poi.sl.usermodel.ObjectShape;
+import org.apache.poi.sl.usermodel.Shape;
+import org.apache.poi.sl.usermodel.Slide;
+import org.apache.poi.sl.usermodel.SlideShow;
+import org.apache.poi.sl.usermodel.SlideShowFactory;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.Internal;
+
+/** Handler for ppt and pptx files */
+@Internal
+class PPTHandler extends MFProxy {
+ private SlideShow<?,?> ppt;
+ private Slide<?,?> slide;
+
+ @Override
+ public void parse(File file) throws IOException {
+ try {
+ ppt = SlideShowFactory.create(file, null, true);
+ } catch (IOException e) {
+ if (e.getMessage().contains("scratchpad")) {
+ throw new PPTX2PNG.NoScratchpadException(e);
+ } else {
+ throw e;
+ }
+ }
+ slide = ppt.getSlides().get(0);
+ }
+
+ @Override
+ public void parse(InputStream is) throws IOException {
+ try {
+ ppt = SlideShowFactory.create(is, null);
+ } catch (IOException e) {
+ if (e.getMessage().contains("scratchpad")) {
+ throw new PPTX2PNG.NoScratchpadException(e);
+ } else {
+ throw e;
+ }
+ }
+ slide = ppt.getSlides().get(0);
+ }
+
+ @Override
+ public Dimension2D getSize() {
+ return ppt.getPageSize();
+ }
+
+ @Override
+ public int getSlideCount() {
+ return ppt.getSlides().size();
+ }
+
+ @Override
+ public void setSlideNo(int slideNo) {
+ slide = ppt.getSlides().get(slideNo-1);
+ }
+
+ @Override
+ public String getTitle() {
+ return slide.getTitle();
+ }
+
+ private static final String RANGE_PATTERN = "(^|,)(?<from>\\d+)?(-(?<to>\\d+))?";
+
+ @Override
+ public Set<Integer> slideIndexes(String range) {
+ final Matcher matcher = Pattern.compile(RANGE_PATTERN).matcher(range);
+ Spliterator<Matcher> sp = new Spliterators.AbstractSpliterator<Matcher>(range.length(), ORDERED|NONNULL){
+ @Override
+ public boolean tryAdvance(Consumer<? super Matcher> action) {
+ boolean b = matcher.find();
+ if (b) {
+ action.accept(matcher);
+ }
+ return b;
+ }
+ };
+
+ return StreamSupport.stream(sp, false).
+ flatMap(this::range).
+ collect(Collectors.toCollection(TreeSet::new));
+ }
+
+ @Override
+ public void draw(Graphics2D ctx) {
+ slide.draw(ctx);
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (ppt != null) {
+ ppt.close();
+ }
+ }
+
+ @Override
+ public GenericRecord getRoot() {
+ return (ppt instanceof GenericRecord) ? (GenericRecord)ppt : null;
+ }
+
+ private Stream<Integer> range(Matcher m) {
+ final int slideCount = ppt.getSlides().size();
+ String fromStr = m.group("from");
+ String toStr = m.group("to");
+ int from = (fromStr == null || fromStr.isEmpty() ? 1 : Integer.parseInt(fromStr));
+ int to = (toStr == null) ? from
+ : (toStr.isEmpty() || ((fromStr == null || fromStr.isEmpty()) && "1".equals(toStr))) ? slideCount
+ : Integer.parseInt(toStr);
+ return IntStream.rangeClosed(from, to).filter(i -> i <= slideCount).boxed();
+ }
+
+ @Override
+ public Iterable<EmbeddedPart> getEmbeddings(int slideNo) {
+ return () -> ppt.getSlides().get(slideNo).getShapes().stream().
+ filter(s -> s instanceof ObjectShape).
+ map(PPTHandler::fromObjectShape).
+ iterator()
+ ;
+ }
+
+ private static EmbeddedPart fromObjectShape(Shape s) {
+ final ObjectShape os = (ObjectShape)s;
+ final ObjectData od = os.getObjectData();
+ EmbeddedPart embed = new EmbeddedPart();
+ embed.setName(od.getFileName());
+ embed.setData(() -> {
+ try (InputStream is = od.getInputStream()) {
+ return IOUtils.toByteArray(is);
+ } catch (IOException e) {
+ // TODO: change to custom runtime exception
+ throw new RuntimeException(e);
+ }
+ });
+ return embed;
+ }
+}
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 5a2b8dbe7e..5de49370b3 100644
--- a/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java
+++ b/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java
@@ -19,51 +19,33 @@
package org.apache.poi.xslf.util;
-import static java.util.Spliterator.NONNULL;
-import static java.util.Spliterator.ORDERED;
-
import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Dimension2D;
-import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
-import java.io.Closeable;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
-import java.util.Collections;
import java.util.Locale;
import java.util.Set;
-import java.util.Spliterator;
-import java.util.Spliterators.AbstractSpliterator;
-import java.util.TreeSet;
-import java.util.function.Consumer;
-import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
import javax.imageio.ImageIO;
import org.apache.poi.common.usermodel.GenericRecord;
-import org.apache.poi.sl.draw.BitmapImageRenderer;
-import org.apache.poi.sl.draw.DrawPictureShape;
+import org.apache.poi.poifs.filesystem.FileMagic;
import org.apache.poi.sl.draw.Drawable;
-import org.apache.poi.sl.draw.ImageRenderer;
-import org.apache.poi.sl.usermodel.PictureData;
-import org.apache.poi.sl.usermodel.Slide;
-import org.apache.poi.sl.usermodel.SlideShow;
-import org.apache.poi.sl.usermodel.SlideShowFactory;
+import org.apache.poi.sl.draw.EmbeddedExtractor.EmbeddedPart;
+import org.apache.poi.util.Dimension2DDouble;
import org.apache.poi.util.GenericRecordJsonWriter;
/**
* An utility to convert slides of a .pptx slide show to a PNG image
*/
-public class PPTX2PNG {
+public final class PPTX2PNG {
private static final String INPUT_PAT_REGEX =
"(?<slideno>[^|]+)\\|(?<format>[^|]+)\\|(?<basename>.+)\\.(?<ext>[^.]++)";
@@ -72,10 +54,9 @@ public class PPTX2PNG {
private static final String OUTPUT_PAT_REGEX = "${basename}-${slideno}.${format}";
-
private static void usage(String error){
String msg =
- "Usage: PPTX2PNG [options] <ppt or pptx file>\n" +
+ "Usage: PPTX2PNG [options] <ppt or pptx file or 'stdin'>\n" +
(error == null ? "" : ("Error: "+error+"\n")) +
"Options:\n" +
" -scale <float> scale factor\n" +
@@ -87,36 +68,52 @@ public class PPTX2PNG {
" -outpat <pattern> output filename pattern, defaults to '"+OUTPUT_PAT_REGEX+"'\n" +
" patterns: basename, slideno, format, ext\n" +
" -dump <file> dump the annotated records to a file\n" +
- " -quiet do not write to console (for normal processing)";
+ " -quiet do not write to console (for normal processing)\n" +
+ " -ignoreParse ignore parsing error and continue with the records read until the error\n" +
+ " -extractEmbedded extract embedded parts";
System.out.println(msg);
// no System.exit here, as we also run in junit tests!
}
public static void main(String[] args) throws Exception {
- if (args.length == 0) {
- usage(null);
- return;
+ PPTX2PNG p2p = new PPTX2PNG();
+
+ if (p2p.parseCommandLine(args)) {
+ p2p.processFile();
}
+ }
- String slidenumStr = "-1";
- float scale = 1;
- File file = null;
- String format = "png";
- File outdir = null;
- String outfile = null;
- boolean quiet = false;
- String outPattern = OUTPUT_PAT_REGEX;
- File dumpfile = null;
- String fixSide = "scale";
+ private String slidenumStr = "-1";
+ private float scale = 1;
+ private File file = null;
+ private String format = "png";
+ private File outdir = null;
+ private String outfile = null;
+ private boolean quiet = false;
+ private String outPattern = OUTPUT_PAT_REGEX;
+ private File dumpfile = null;
+ private String fixSide = "scale";
+ private boolean ignoreParse = false;
+ private boolean extractEmbedded = false;
+
+ private PPTX2PNG() {
+ }
+ private boolean parseCommandLine(String[] args) {
+ if (args.length == 0) {
+ usage(null);
+ return false;
+ }
for (int i = 0; i < args.length; i++) {
String opt = (i+1 < args.length) ? args[i+1] : null;
switch (args[i]) {
case "-scale":
- scale = Float.parseFloat(opt);
- i++;
+ if (opt != null) {
+ scale = Float.parseFloat(opt);
+ i++;
+ }
break;
case "-slide":
slidenumStr = opt;
@@ -127,8 +124,10 @@ public class PPTX2PNG {
i++;
break;
case "-outdir":
- outdir = new File(opt);
- i++;
+ if (opt != null) {
+ outdir = new File(opt);
+ i++;
+ }
break;
case "-outfile":
outfile = opt;
@@ -142,12 +141,26 @@ public class PPTX2PNG {
quiet = true;
break;
case "-dump":
- dumpfile = new File(opt);
- i++;
+ if (opt != null) {
+ dumpfile = new File(opt);
+ i++;
+ } else {
+ dumpfile = new File("pptx2png.dump");
+ }
break;
case "-fixside":
- fixSide = opt.toLowerCase(Locale.ROOT);
- i++;
+ if (opt != null) {
+ fixSide = opt.toLowerCase(Locale.ROOT);
+ i++;
+ } else {
+ fixSide = "long";
+ }
+ break;
+ case "-ignoreParse":
+ ignoreParse = true;
+ break;
+ case "-extractEmbedded":
+ extractEmbedded = true;
break;
default:
file = new File(args[i]);
@@ -155,40 +168,54 @@ public class PPTX2PNG {
}
}
- if (file == null || !file.exists()) {
+ final boolean isStdin = file != null && "stdin".equalsIgnoreCase(file.getName());
+
+ if (!isStdin && (file == null || !file.exists())) {
usage("File not specified or it doesn't exist");
- return;
+ return false;
}
if (format == null || !format.matches("^(png|gif|jpg|null)$")) {
usage("Invalid format given");
- return;
+ return false;
}
if (outdir == null) {
- outdir = file.getParentFile();
+ if (isStdin) {
+ usage("When reading from STDIN, you need to specify an outdir.");
+ return false;
+ } else {
+ outdir = file.getParentFile();
+ }
+ }
+ if (!outdir.exists()) {
+ usage("Outdir doesn't exist");
+ return false;
}
if (!"null".equals(format) && (outdir == null || !outdir.exists() || !outdir.isDirectory())) {
usage("Output directory doesn't exist");
- return;
+ return false;
}
if (scale < 0) {
usage("Invalid scale given");
- return;
+ return false;
}
if (!"long,short,width,height,scale".contains(fixSide)) {
usage("<fixside> must be one of long / short / width / height");
- return;
+ return false;
}
+ return true;
+ }
+
+ private void processFile() throws IOException {
if (!quiet) {
System.out.println("Processing " + file);
}
-
try (MFProxy proxy = initProxy(file)) {
final Set<Integer> slidenum = proxy.slideIndexes(slidenumStr);
if (slidenum.isEmpty()) {
@@ -196,30 +223,10 @@ public class PPTX2PNG {
return;
}
- final Dimension2D pgsize = proxy.getSize();
-
- final double lenSide;
- switch (fixSide) {
- default:
- case "scale":
- lenSide = 1;
- break;
- case "long":
- lenSide = Math.max(pgsize.getWidth(), pgsize.getHeight());
- break;
- case "short":
- lenSide = Math.min(pgsize.getWidth(), pgsize.getHeight());
- break;
- case "width":
- lenSide = pgsize.getWidth();
- break;
- case "height":
- lenSide = pgsize.getHeight();
- break;
- }
-
- final int width = (int) Math.rint(pgsize.getWidth() * scale / lenSide);
- final int height = (int) Math.rint(pgsize.getHeight() * scale / lenSide);
+ final Dimension2D dim = new Dimension2DDouble();
+ final double lenSide = getDimensions(proxy, dim);
+ final int width = (int)Math.rint(dim.getWidth());
+ final int height = (int)Math.rint(dim.getHeight());
for (int slideNo : slidenum) {
proxy.setSlideNo(slideNo);
@@ -228,16 +235,9 @@ public class PPTX2PNG {
System.out.println("Rendering slide " + slideNo + (title == null ? "" : ": " + title.trim()));
}
- GenericRecord gr = proxy.getRoot();
- if (dumpfile != null) {
- try (GenericRecordJsonWriter fw = new GenericRecordJsonWriter(dumpfile)) {
- if (gr == null) {
- fw.writeError(file.getName()+" doesn't support GenericRecord interface and can't be dumped to a file.");
- } else {
- fw.write(gr);
- }
- }
- }
+ dumpRecords(proxy);
+
+ extractEmbedded(proxy, slideNo);
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = img.createGraphics();
@@ -253,7 +253,7 @@ public class PPTX2PNG {
graphics.scale(scale / lenSide, scale / lenSide);
graphics.setComposite(AlphaComposite.Clear);
- graphics.fillRect(0, 0, (int)width, (int)height);
+ graphics.fillRect(0, 0, width, height);
graphics.setComposite(AlphaComposite.SrcOver);
// draw stuff
@@ -261,10 +261,7 @@ public class PPTX2PNG {
// save the result
if (!"null".equals(format)) {
- String inname = String.format(Locale.ROOT, "%04d|%s|%s", slideNo, format, file.getName());
- String outpat = (proxy.getSlideCount() > 1 ? outPattern : outPattern.replaceAll("-?\\$\\{slideno\\}", ""));
- String outname = (outfile != null) ? outfile : INPUT_PATTERN.matcher(inname).replaceAll(outpat);
- ImageIO.write(img, format, new File(outdir, outname));
+ ImageIO.write(img, format, new File(outdir, calcOutFile(proxy, slideNo)));
}
graphics.dispose();
@@ -280,198 +277,114 @@ public class PPTX2PNG {
}
}
- private static MFProxy initProxy(File file) throws IOException {
- MFProxy proxy;
- final String fileName = file.getName().toLowerCase(Locale.ROOT);
- switch (fileName.contains(".") ? fileName.substring(fileName.lastIndexOf('.')) : "") {
- case ".emf":
- proxy = new EMFHandler();
+ private double getDimensions(MFProxy proxy, Dimension2D dim) {
+ final Dimension2D pgsize = proxy.getSize();
+
+ final double lenSide;
+ switch (fixSide) {
+ default:
+ case "scale":
+ lenSide = 1;
break;
- case ".wmf":
- proxy = new WMFHandler();
+ case "long":
+ lenSide = Math.max(pgsize.getWidth(), pgsize.getHeight());
break;
- default:
- proxy = new PPTHandler();
+ case "short":
+ lenSide = Math.min(pgsize.getWidth(), pgsize.getHeight());
+ break;
+ case "width":
+ lenSide = pgsize.getWidth();
+ break;
+ case "height":
+ lenSide = pgsize.getHeight();
break;
}
- proxy.parse(file);
- return proxy;
+ dim.setSize(pgsize.getWidth() * scale / lenSide, pgsize.getHeight() * scale / lenSide);
+ return lenSide;
}
- private interface MFProxy extends Closeable {
- void parse(File file) throws IOException;
-// Iterable<HwmfEmbedded> getEmbeddings();
- Dimension2D getSize();
-
- default void setSlideNo(int slideNo) {}
-
- String getTitle();
- void draw(Graphics2D ctx);
-
- default int getSlideCount() { return 1; }
-
- default Set<Integer> slideIndexes(String range) {
- return Collections.singleton(1);
+ private void dumpRecords(MFProxy proxy) throws IOException {
+ if (dumpfile == null) {
+ return;
}
-
- GenericRecord getRoot();
- }
-
- /** Handler for ppt and pptx files */
- private static class PPTHandler implements MFProxy {
- SlideShow<?,?> ppt;
- Slide<?,?> slide;
-
- @Override
- public void parse(File file) throws IOException {
- try {
- ppt = SlideShowFactory.create(file, null, true);
- } catch (IOException e) {
- if (e.getMessage().contains("scratchpad")) {
- throw new NoScratchpadException(e);
- } else {
- throw e;
- }
+ GenericRecord gr = proxy.getRoot();
+ try (GenericRecordJsonWriter fw = new GenericRecordJsonWriter(dumpfile)) {
+ if (gr == null) {
+ fw.writeError(file.getName()+" doesn't support GenericRecord interface and can't be dumped to a file.");
+ } else {
+ fw.write(gr);
}
- slide = ppt.getSlides().get(0);
- }
-
- @Override
- public Dimension2D getSize() {
- return ppt.getPageSize();
- }
-
- @Override
- public int getSlideCount() {
- return ppt.getSlides().size();
- }
-
- @Override
- public void setSlideNo(int slideNo) {
- slide = ppt.getSlides().get(slideNo-1);
- }
-
- @Override
- public String getTitle() {
- return slide.getTitle();
- }
-
- private static final String RANGE_PATTERN = "(^|,)(?<from>\\d+)?(-(?<to>\\d+))?";
-
- @Override
- public Set<Integer> slideIndexes(String range) {
- final Matcher matcher = Pattern.compile(RANGE_PATTERN).matcher(range);
- Spliterator<Matcher> sp = new AbstractSpliterator<Matcher>(range.length(), ORDERED|NONNULL){
- @Override
- public boolean tryAdvance(Consumer<? super Matcher> action) {
- boolean b = matcher.find();
- if (b) {
- action.accept(matcher);
- }
- return b;
- }
- };
-
- return StreamSupport.stream(sp, false).
- flatMap(this::range).
- collect(Collectors.toCollection(TreeSet::new));
}
+ }
- @Override
- public void draw(Graphics2D ctx) {
- slide.draw(ctx);
+ private void extractEmbedded(MFProxy proxy, int slideNo) throws IOException {
+ if (!extractEmbedded) {
+ return;
}
-
- @Override
- public void close() throws IOException {
- if (ppt != null) {
- ppt.close();
+ for (EmbeddedPart ep : proxy.getEmbeddings(slideNo)) {
+ String filename = ep.getName();
+ // do some sanitizing for creative filenames ...
+ filename = new File(filename == null ? "dummy.dat" : filename).getName();
+ filename = calcOutFile(proxy, slideNo).replaceFirst("\\.\\w+$", "")+"_"+filename;
+ try (FileOutputStream fos = new FileOutputStream(new File(outdir, filename))) {
+ fos.write(ep.getData().get());
}
}
-
- @Override
- public GenericRecord getRoot() {
- return (ppt instanceof GenericRecord) ? (GenericRecord)ppt : null;
- }
-
- private Stream<Integer> range(Matcher m) {
- final int slideCount = ppt.getSlides().size();
- String fromStr = m.group("from");
- String toStr = m.group("to");
- int from = (fromStr == null || fromStr.isEmpty() ? 1 : Integer.parseInt(fromStr));
- int to = (toStr == null) ? from
- : (toStr.isEmpty() || ((fromStr == null || fromStr.isEmpty()) && "1".equals(toStr))) ? slideCount
- : Integer.parseInt(toStr);
- return IntStream.rangeClosed(from, to).filter(i -> i <= slideCount).boxed();
- }
}
- private static class EMFHandler implements MFProxy {
- private ImageRenderer imgr = null;
- private InputStream is;
-
- @Override
- public void parse(File file) throws IOException {
- imgr = DrawPictureShape.getImageRenderer(null, getContentType());
- if (imgr instanceof BitmapImageRenderer) {
- throw new NoScratchpadException();
+ private MFProxy initProxy(File file) throws IOException {
+ MFProxy proxy;
+ final String fileName = file.getName().toLowerCase(Locale.ROOT);
+ if ("stdin".equals(fileName)) {
+ InputStream bis = FileMagic.prepareToCheckMagic(System.in);
+ FileMagic fm = FileMagic.valueOf(bis);
+ switch (fm) {
+ case EMF:
+ proxy = new EMFHandler();
+ break;
+ case WMF:
+ proxy = new WMFHandler();
+ break;
+ default:
+ proxy = new PPTHandler();
+ break;
}
-
- // stream needs to be kept open
- is = file.toURI().toURL().openStream();
- imgr.loadImage(is, getContentType());
- }
-
- protected String getContentType() {
- return PictureData.PictureType.EMF.contentType;
- }
-
- @Override
- public Dimension2D getSize() {
- return imgr.getDimension();
- }
-
- @Override
- public String getTitle() {
- return "";
- }
-
- @Override
- public void draw(Graphics2D ctx) {
- Dimension2D dim = getSize();
- imgr.drawImage(ctx, new Rectangle2D.Double(0, 0, dim.getWidth(), dim.getHeight()));
- }
-
- @Override
- public void close() throws IOException {
- if (is != null) {
- try {
- is.close();
- } finally {
- is = null;
- }
+ proxy.setIgnoreParse(ignoreParse);
+ proxy.setQuite(quiet);
+ proxy.parse(bis);
+ } else {
+ switch (fileName.contains(".") ? fileName.substring(fileName.lastIndexOf('.')) : "") {
+ case ".emf":
+ proxy = new EMFHandler();
+ break;
+ case ".wmf":
+ proxy = new WMFHandler();
+ break;
+ default:
+ proxy = new PPTHandler();
+ break;
}
+ proxy.parse(file);
}
- @Override
- public GenericRecord getRoot() {
- return imgr.getGenericRecord();
- }
+ return proxy;
}
- private static class WMFHandler extends EMFHandler {
- @Override
- protected String getContentType() {
- return PictureData.PictureType.WMF.contentType;
+ private String calcOutFile(MFProxy proxy, int slideNo) {
+ if (outfile != null) {
+ return outfile;
}
+ String inname = String.format(Locale.ROOT, "%04d|%s|%s", slideNo, format, file.getName());
+ String outpat = (proxy.getSlideCount() > 1 ? outPattern : outPattern.replaceAll("-?\\$\\{slideno}", ""));
+ return INPUT_PATTERN.matcher(inname).replaceAll(outpat);
}
- private static class NoScratchpadException extends IOException {
- public NoScratchpadException() {
+ static class NoScratchpadException extends IOException {
+ NoScratchpadException() {
}
- public NoScratchpadException(Throwable cause) {
+ NoScratchpadException(Throwable cause) {
super(cause);
}
}
diff --git a/src/ooxml/java/org/apache/poi/xslf/util/WMFHandler.java b/src/ooxml/java/org/apache/poi/xslf/util/WMFHandler.java
new file mode 100644
index 0000000000..b97a9e8931
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/xslf/util/WMFHandler.java
@@ -0,0 +1,31 @@
+/*
+ * ====================================================================
+ * 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 org.apache.poi.sl.usermodel.PictureData;
+import org.apache.poi.util.Internal;
+
+@Internal
+class WMFHandler extends EMFHandler {
+ @Override
+ protected String getContentType() {
+ return PictureData.PictureType.WMF.contentType;
+ }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java
index 2a05b16080..67092958f7 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java
@@ -17,13 +17,17 @@
package org.apache.poi.hemf.draw;
+import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.List;
import org.apache.poi.hemf.record.emfplus.HemfPlusBrush.EmfPlusHatchStyle;
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
public class HemfDrawProperties extends HwmfDrawProperties {
+ enum TransOperand { left, right }
/** Path for path bracket operations */
protected Path2D path = null;
@@ -31,6 +35,8 @@ public class HemfDrawProperties extends HwmfDrawProperties {
private EmfPlusHatchStyle emfPlusBrushHatch;
private BufferedImage emfPlusImage;
+ private final List<AffineTransform> transXForm = new ArrayList<>();
+ private final List<TransOperand> transOper = new ArrayList<>();
public HemfDrawProperties() {
}
@@ -43,6 +49,8 @@ public class HemfDrawProperties extends HwmfDrawProperties {
// TODO: check how to clone
clip = other.clip;
emfPlusImage = other.emfPlusImage;
+ transXForm.addAll(other.transXForm);
+ transOper.addAll(other.transOper);
}
/**
@@ -88,4 +96,27 @@ public class HemfDrawProperties extends HwmfDrawProperties {
public void setEmfPlusImage(BufferedImage emfPlusImage) {
this.emfPlusImage = emfPlusImage;
}
+
+ public void addLeftTransform(AffineTransform transform) {
+ transXForm.add(transform);
+ transOper.add(TransOperand.left);
+ }
+
+ public void addRightTransform(AffineTransform transform) {
+ transXForm.add(transform);
+ transOper.add(TransOperand.right);
+ }
+
+ public void clearTransform() {
+ transXForm.clear();
+ transOper.clear();
+ }
+
+ List<AffineTransform> getTransXForm() {
+ return transXForm;
+ }
+
+ List<TransOperand> getTransOper() {
+ return transOper;
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java
index 10e4276533..aa6f6e998d 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java
@@ -23,11 +23,14 @@ import static org.apache.poi.hwmf.record.HwmfBrushStyle.BS_SOLID;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Paint;
+import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
+import java.util.List;
import java.util.function.Consumer;
+import org.apache.poi.hemf.draw.HemfDrawProperties.TransOperand;
import org.apache.poi.hemf.record.emf.HemfComment.EmfComment;
import org.apache.poi.hemf.record.emf.HemfRecord;
import org.apache.poi.hemf.record.emfplus.HemfPlusRecord;
@@ -336,4 +339,27 @@ public class HemfGraphics extends HwmfGraphics {
// TODO: use EmfPlusHatchBrushData
return super.getHatchedFill();
}
+
+ @Override
+ public void updateWindowMapMode() {
+ super.updateWindowMapMode();
+ HemfDrawProperties prop = getProperties();
+
+ List<AffineTransform> transXform = prop.getTransXForm();
+ List<TransOperand> transOper = prop.getTransOper();
+ assert(transXform.size() == transOper.size());
+
+ AffineTransform tx = graphicsCtx.getTransform();
+ for (int i=0; i<transXform.size(); i++) {
+ AffineTransform tx2 = transXform.get(i);
+ if (transOper.get(i) == TransOperand.left) {
+ tx.concatenate(tx2);
+ } else {
+
+ tx.preConcatenate(tx2);
+ }
+ }
+
+ graphicsCtx.setTransform(tx);
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java
index 9d32e2d053..aaf3e31021 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java
@@ -30,13 +30,15 @@ import java.io.InputStream;
import org.apache.poi.common.usermodel.GenericRecord;
import org.apache.poi.hemf.usermodel.HemfPicture;
+import org.apache.poi.hwmf.draw.HwmfImageRenderer;
import org.apache.poi.sl.draw.BitmapImageRenderer;
+import org.apache.poi.sl.draw.EmbeddedExtractor;
import org.apache.poi.sl.draw.ImageRenderer;
import org.apache.poi.sl.usermodel.PictureData;
import org.apache.poi.util.Units;
@SuppressWarnings("unused")
-public class HemfImageRenderer implements ImageRenderer {
+public class HemfImageRenderer implements ImageRenderer, EmbeddedExtractor {
HemfPicture image;
double alpha;
@@ -113,4 +115,9 @@ public class HemfImageRenderer implements ImageRenderer {
public GenericRecord getGenericRecord() {
return image;
}
+
+ @Override
+ public Iterable<EmbeddedPart> getEmbeddings() {
+ return HwmfImageRenderer.getEmbeddings(image.getEmbeddings());
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java
index 3dd5d189e3..165d8dc096 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java
@@ -90,6 +90,13 @@ public class HemfComment {
long init(LittleEndianInputStream leis, long dataSize) throws IOException;
+ /**
+ * Apply the record settings to the graphics context
+ *
+ * @param ctx the graphics context to modify
+ */
+ default void draw(HemfGraphics ctx) {};
+
@Override
default Enum getGenericRecordType() {
return getCommentRecordType();
@@ -117,13 +124,7 @@ public class HemfComment {
@Override
public void draw(HemfGraphics ctx) {
- if (data instanceof EmfCommentDataPlus) {
- if (ctx.getRenderState() == HemfGraphics.EmfRenderState.INITIAL) {
- ctx.setRenderState(HemfGraphics.EmfRenderState.EMFPLUS_ONLY);
- }
-
- ((EmfCommentDataPlus)data).draw(ctx);
- }
+ data.draw(ctx);
}
@Override
@@ -297,7 +298,12 @@ public class HemfComment {
return Collections.unmodifiableList(records);
}
+ @Override
public void draw(HemfGraphics ctx) {
+ if (ctx.getRenderState() == HemfGraphics.EmfRenderState.INITIAL) {
+ ctx.setRenderState(HemfGraphics.EmfRenderState.EMFPLUS_ONLY);
+ }
+
records.forEach(ctx::draw);
}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java
index 77f2b0d4b6..71a2dad577 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java
@@ -18,7 +18,7 @@
package org.apache.poi.hemf.record.emf;
import static org.apache.poi.hwmf.record.HwmfDraw.normalizeBounds;
-import static org.apache.poi.util.GenericRecordUtil.getBitsAsString;
+import static org.apache.poi.util.GenericRecordUtil.getEnumBitsAsString;
import java.awt.Shape;
import java.awt.geom.Arc2D;
@@ -97,7 +97,7 @@ public class HemfDraw {
@Override
public Map<String, Supplier<?>> getGenericProperties() {
return GenericRecordUtil.getGenericProperties(
- "objectIndex", getBitsAsString(this::getObjectIndex, IDX_MASKS, IDX_NAMES)
+ "objectIndex", getEnumBitsAsString(this::getObjectIndex, IDX_MASKS, IDX_NAMES)
);
}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java
index e32fde84e9..5f294e8244 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java
@@ -29,7 +29,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.function.Function;
import java.util.function.Supplier;
import org.apache.poi.hemf.draw.HemfDrawProperties;
@@ -754,10 +753,10 @@ public class HemfMisc {
@Override
public void draw(HemfGraphics ctx) {
+ HemfDrawProperties prop = ctx.getProperties();
+ prop.clearTransform();
+ prop.addLeftTransform(xForm);
ctx.updateWindowMapMode();
- AffineTransform tx = ctx.getTransform();
- tx.concatenate(xForm);
- ctx.setTransform(tx);
}
@Override
@@ -812,28 +811,23 @@ public class HemfMisc {
final HemfDrawProperties prop = ctx.getProperties();
- final AffineTransform tx;
switch (modifyWorldTransformMode) {
case MWT_LEFTMULTIPLY:
- tx = ctx.getTransform();
- tx.concatenate(adaptXForm(xForm, ctx.getTransform()));
+ prop.addLeftTransform(xForm);
break;
case MWT_RIGHTMULTIPLY:
- tx = ctx.getTransform();
- tx.preConcatenate(adaptXForm(xForm, tx));
+ prop.addRightTransform(xForm);
break;
case MWT_IDENTITY:
- ctx.updateWindowMapMode();
- tx = ctx.getTransform();
+ prop.clearTransform();
break;
default:
case MWT_SET:
- ctx.updateWindowMapMode();
- tx = ctx.getTransform();
- tx.concatenate(adaptXForm(xForm, tx));
+ prop.clearTransform();
+ prop.addLeftTransform(xForm);
break;
}
- ctx.setTransform(tx);
+ ctx.updateWindowMapMode();
}
@Override
@@ -950,22 +944,4 @@ public class HemfMisc {
}
}
-
- /**
- * adapt xform depending on the base transformation (... experimental ...)
- */
- public static AffineTransform adaptXForm(AffineTransform xForm, AffineTransform other) {
- // normalize signed zero
- Function<Double,Double> nn = (d) -> (d == 0. ? 0. : d);
- double yDiff = Math.signum(nn.apply(xForm.getTranslateY())) == Math.signum(nn.apply(other.getTranslateY())) ? 1. : -1.;
- double xDiff = Math.signum(nn.apply(xForm.getTranslateX())) == Math.signum(nn.apply(other.getTranslateX())) ? 1. : -1.;
- return new AffineTransform(
- xForm.getScaleX() == 0 ? 1. : xForm.getScaleX(),
- yDiff * xForm.getShearY(),
- xDiff * xForm.getShearX(),
- xForm.getScaleY() == 0. ? 1. : xForm.getScaleY(),
- xForm.getTranslateX(),
- xForm.getTranslateY()
- );
- }
}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java
index f806185d25..3c8e906e10 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java
@@ -219,7 +219,8 @@ public class HemfText {
return GenericRecordUtil.getGenericProperties(
"base", super::getGenericProperties,
"boundsIgnored", () -> boundsIgnored,
- "graphicsMode", this::getGraphicsMode
+ "graphicsMode", this::getGraphicsMode,
+ "scale", this::getScale
);
}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusBrush.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusBrush.java
index 5a2a1ee254..e8e2f4262d 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusBrush.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusBrush.java
@@ -26,6 +26,8 @@ import java.awt.Color;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
@@ -49,6 +51,7 @@ import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.GenericRecordJsonWriter;
import org.apache.poi.util.GenericRecordUtil;
+import org.apache.poi.util.IOUtils;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;
@@ -312,25 +315,35 @@ public class HemfPlusBrush {
/** The EmfPlusBrush object specifies a graphics brush for filling regions. */
public static class EmfPlusBrush implements EmfPlusObjectData {
+ private static final int MAX_OBJECT_SIZE = 1_000_000;
+
private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
private EmfPlusBrushType brushType;
- private EmfPlusBrushData brushData;
+ private byte[] brushBytes;
@Override
public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
+ leis.mark(LittleEndianConsts.INT_SIZE);
long size = graphicsVersion.init(leis);
- brushType = EmfPlusBrushType.valueOf(leis.readInt());
- size += LittleEndianConsts.INT_SIZE;
- assert(brushType != null);
+ if (isContinuedRecord()) {
+ leis.reset();
+ size = 0;
+ } else {
+ int brushInt = leis.readInt();
+ brushType = EmfPlusBrushType.valueOf(brushInt);
+ assert(brushType != null);
+ size += LittleEndianConsts.INT_SIZE;
+ }
- size += (brushData = brushType.constructor.get()).init(leis, dataSize-size);
+ brushBytes = IOUtils.toByteArray(leis, dataSize-size, MAX_OBJECT_SIZE);
- return size;
+ return dataSize;
}
@Override
public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
+ EmfPlusBrushData brushData = getBrushData(continuedObjectData);
brushData.applyObject(ctx, continuedObjectData);
}
@@ -344,10 +357,38 @@ public class HemfPlusBrush {
return GenericRecordJsonWriter.marshal(this);
}
- public EmfPlusBrushData getBrushData() {
+ public byte[] getBrushBytes() {
+ return brushBytes;
+ }
+
+ public EmfPlusBrushData getBrushData(List<? extends EmfPlusObjectData> continuedObjectData) {
+ EmfPlusBrushData brushData = brushType.constructor.get();
+ byte[] buf = getRawData(continuedObjectData);
+ try {
+ brushData.init(new LittleEndianInputStream(new ByteArrayInputStream(buf)), buf.length);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
return brushData;
}
+
+ public byte[] getRawData(List<? extends EmfPlusObjectData> continuedObjectData) {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ try {
+ bos.write(getBrushBytes());
+ if (continuedObjectData != null) {
+ for (EmfPlusObjectData od : continuedObjectData) {
+ bos.write(((EmfPlusBrush)od).getBrushBytes());
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return bos.toByteArray();
+ }
+
+
@Override
public EmfPlusBrushType getGenericRecordType() {
return brushType;
@@ -357,7 +398,8 @@ public class HemfPlusBrush {
public Map<String, Supplier<?>> getGenericProperties() {
return GenericRecordUtil.getGenericProperties(
"graphicsVersion", this::getGraphicsVersion,
- "brushData", this::getBrushData
+ /* only return the first object data ... enough for now */
+ "brushData", () -> getBrushData(null)
);
}
}
@@ -375,6 +417,8 @@ public class HemfPlusBrush {
public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
HemfDrawProperties prop = ctx.getProperties();
prop.setBackgroundColor(new HwmfColorRef(solidColor));
+ prop.setBrushTransform(null);
+ prop.setBrushStyle(HwmfBrushStyle.BS_SOLID);
}
@Override
@@ -696,10 +740,11 @@ public class HemfPlusBrush {
@Override
public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
- image.applyObject(ctx, continuedObjectData);
HemfDrawProperties prop = ctx.getProperties();
+ image.applyObject(ctx, null);
prop.setBrushBitmap(prop.getEmfPlusImage());
prop.setBrushStyle(HwmfBrushStyle.BS_PATTERN);
+ prop.setBrushTransform(transform);
}
@Override
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusMisc.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusMisc.java
index 94566a92b1..04af92e643 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusMisc.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusMisc.java
@@ -17,7 +17,6 @@
package org.apache.poi.hemf.record.emfplus;
-import static org.apache.poi.hemf.record.emf.HemfMisc.adaptXForm;
import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readRectF;
import static org.apache.poi.util.GenericRecordUtil.getBitsAsString;
@@ -28,6 +27,7 @@ import java.io.IOException;
import java.util.Map;
import java.util.function.Supplier;
+import org.apache.poi.hemf.draw.HemfDrawProperties;
import org.apache.poi.hemf.draw.HemfGraphics;
import org.apache.poi.hemf.record.emf.HemfFill;
import org.apache.poi.util.BitField;
@@ -174,6 +174,12 @@ public class HemfPlusMisc {
* The EmfPlusResetWorldTransform record resets the current world space transform to the identify matrix.
*/
public static class EmfPlusResetWorldTransform extends EmfPlusFlagOnly {
+ @Override
+ public void draw(HemfGraphics ctx) {
+ HemfDrawProperties prop = ctx.getProperties();
+ prop.clearTransform();
+ ctx.updateWindowMapMode();
+ }
}
@@ -208,10 +214,10 @@ public class HemfPlusMisc {
@Override
public void draw(HemfGraphics ctx) {
+ HemfDrawProperties prop = ctx.getProperties();
+ prop.clearTransform();
+ prop.addLeftTransform(getMatrixData());
ctx.updateWindowMapMode();
- AffineTransform tx = ctx.getTransform();
- tx.concatenate(getMatrixData());
- ctx.setTransform(tx);
}
@Override
@@ -235,11 +241,9 @@ public class HemfPlusMisc {
@Override
public void draw(HemfGraphics ctx) {
+ HemfDrawProperties prop = ctx.getProperties();
+ prop.addLeftTransform(getMatrixData());
ctx.updateWindowMapMode();
- AffineTransform tx = ctx.getTransform();
- tx.preConcatenate(adaptXForm(getMatrixData(), tx));
- tx.concatenate(getMatrixData());
- ctx.setTransform(tx);
}
}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusObject.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusObject.java
index 6b0c754788..2aabc20f18 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusObject.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusObject.java
@@ -224,7 +224,8 @@ public class HemfPlusObject {
return GenericRecordUtil.getGenericProperties(
"flags", getBitsAsString(this::getFlags, FLAGS_MASKS, FLAGS_NAMES),
"objectId", this::getObjectId,
- "continuedObjectData", this::getContinuedObject,
+ "objectData", () -> objectData.isContinuedRecord() ? null : getObjectData(),
+ "continuedObject", objectData::isContinuedRecord,
"totalObjectSize", () -> totalObjectSize
);
}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java
index 92030fa02c..ff34fb8213 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java
@@ -161,6 +161,8 @@ public class HemfPicture implements Iterable<HemfRecord>, GenericRecord {
HemfGraphics g = new HemfGraphics(ctx, emfBounds);
HemfDrawProperties prop = g.getProperties();
+ prop.setWindowOrg(emfBounds.getX(), emfBounds.getY());
+ prop.setWindowExt(emfBounds.getWidth(), emfBounds.getHeight());
prop.setViewportOrg(emfBounds.getX(), emfBounds.getY());
prop.setViewportExt(emfBounds.getWidth(), emfBounds.getHeight());
diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java
index 8b8358c30f..3e3266be03 100644
--- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java
+++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java
@@ -31,8 +31,8 @@ import java.util.List;
import org.apache.poi.hwmf.record.HwmfBrushStyle;
import org.apache.poi.hwmf.record.HwmfColorRef;
-import org.apache.poi.hwmf.record.HwmfFont;
import org.apache.poi.hwmf.record.HwmfFill.WmfSetPolyfillMode.HwmfPolyfillMode;
+import org.apache.poi.hwmf.record.HwmfFont;
import org.apache.poi.hwmf.record.HwmfHatchStyle;
import org.apache.poi.hwmf.record.HwmfMapMode;
import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode;
@@ -52,6 +52,7 @@ public class HwmfDrawProperties {
private HwmfColorRef brushColor;
private HwmfHatchStyle brushHatch;
private BufferedImage brushBitmap;
+ private final AffineTransform brushTransform = new AffineTransform();
private double penWidth;
private HwmfPenStyle penStyle;
private HwmfColorRef penColor;
@@ -112,6 +113,7 @@ public class HwmfDrawProperties {
WritableRaster raster = other.brushBitmap.copyData(null);
this.brushBitmap = new BufferedImage(cm, raster, isAlphaPremultiplied, null);
}
+ this.brushTransform.setTransform(other.brushTransform);
this.penWidth = other.penWidth;
this.penStyle = (other.penStyle == null) ? null : other.penStyle.clone();
this.penColor = (other.penColor == null) ? null : other.penColor.clone();
@@ -411,4 +413,16 @@ public class HwmfDrawProperties {
public void setClip(Shape clip) {
this.clip = clip;
}
+
+ public AffineTransform getBrushTransform() {
+ return brushTransform;
+ }
+
+ public void setBrushTransform(AffineTransform brushTransform) {
+ if (brushTransform == null) {
+ this.brushTransform.setToIdentity();
+ } else {
+ this.brushTransform.setTransform(brushTransform);
+ }
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java
index e17811086c..5d9365d0cf 100644
--- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java
+++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java
@@ -238,9 +238,11 @@ public class HwmfGraphics {
}
protected Paint getPatternPaint() {
- BufferedImage bi = getProperties().getBrushBitmap();
- return (bi == null) ? null
- : new TexturePaint(bi, new Rectangle(0,0,bi.getWidth(),bi.getHeight()));
+ HwmfDrawProperties prop = getProperties();
+ BufferedImage bi = prop.getBrushBitmap();
+ Rectangle2D rect = new Rectangle2D.Double(0, 0, bi.getWidth(), bi.getHeight());
+ rect = prop.getBrushTransform().createTransformedShape(rect).getBounds2D();
+ return (bi == null) ? null : new TexturePaint(bi, rect);
}
/**
@@ -355,7 +357,7 @@ public class HwmfGraphics {
Rectangle2D win = getProperties().getWindow();
Rectangle2D view = getProperties().getViewport();
HwmfMapMode mapMode = getProperties().getMapMode();
- graphicsCtx.setTransform(initialAT);
+ graphicsCtx.setTransform(getInitTransform());
switch (mapMode) {
default:
@@ -673,7 +675,7 @@ public class HwmfGraphics {
public void setClip(Shape clip, HwmfRegionMode regionMode, boolean useInitialAT) {
final AffineTransform at = graphicsCtx.getTransform();
if (useInitialAT) {
- graphicsCtx.setTransform(initialAT);
+ graphicsCtx.setTransform(getInitTransform());
}
final Shape oldClip = graphicsCtx.getClip();
diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java
index ed34e355a9..076913ddf3 100644
--- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java
+++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java
@@ -27,20 +27,24 @@ import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.util.Iterator;
import org.apache.poi.common.usermodel.GenericRecord;
+import org.apache.poi.hwmf.usermodel.HwmfEmbedded;
import org.apache.poi.hwmf.usermodel.HwmfPicture;
import org.apache.poi.sl.draw.BitmapImageRenderer;
import org.apache.poi.sl.draw.DrawPictureShape;
+import org.apache.poi.sl.draw.EmbeddedExtractor;
import org.apache.poi.sl.draw.ImageRenderer;
import org.apache.poi.sl.usermodel.PictureData.PictureType;
+import org.apache.poi.util.Internal;
import org.apache.poi.util.Units;
/**
* Helper class which is instantiated by {@link DrawPictureShape}
* via reflection
*/
-public class HwmfImageRenderer implements ImageRenderer {
+public class HwmfImageRenderer implements ImageRenderer, EmbeddedExtractor {
HwmfPicture image;
double alpha;
@@ -117,4 +121,33 @@ public class HwmfImageRenderer implements ImageRenderer {
public GenericRecord getGenericRecord() {
return image;
}
+
+
+ @Override
+ public Iterable<EmbeddedExtractor.EmbeddedPart> getEmbeddings() {
+ return getEmbeddings(image.getEmbeddings());
+ }
+
+ @Internal
+ public static Iterable<EmbeddedPart> getEmbeddings(Iterable<HwmfEmbedded> embs) {
+ return () -> {
+ final Iterator<HwmfEmbedded> embit = embs.iterator();
+ final int[] idx = { 1 };
+ return new Iterator<EmbeddedExtractor.EmbeddedPart>() {
+ @Override
+ public boolean hasNext() {
+ return embit.hasNext();
+ }
+
+ @Override
+ public EmbeddedExtractor.EmbeddedPart next() {
+ EmbeddedExtractor.EmbeddedPart ep = new EmbeddedExtractor.EmbeddedPart();
+ HwmfEmbedded emb = embit.next();
+ ep.setData(emb::getRawData);
+ ep.setName("embed_"+(idx[0]++)+emb.getEmbeddedType().extension);
+ return ep;
+ }
+ };
+ };
+ }
}
diff --git a/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/TestHemfPicture.java b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/TestHemfPicture.java
index 35480d1cbc..124e136b13 100644
--- a/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/TestHemfPicture.java
+++ b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/TestHemfPicture.java
@@ -56,9 +56,10 @@ public class TestHemfPicture {
private static final POIDataSamples ss_samples = POIDataSamples.getSpreadSheetInstance();
private static final POIDataSamples sl_samples = POIDataSamples.getSlideShowInstance();
-/* @Test
+ /*
+ @Test
@Ignore("Only for manual tests - need to add org.tukaani:xz:1.8 for this to work")
- public void paint() throws IOException {
+ public void paint() throws Exception {
final byte buf[] = new byte[50_000_000];
// good test samples to validate rendering:
@@ -67,161 +68,56 @@ public class TestHemfPicture {
// emfs/govdocs1/844/844795.ppt_2.emf
// emfs/commoncrawl2/TO/TOYZSTNUSW5OFCFUQ6T5FBLIDLCRF3NH_0.emf
- final boolean writeLog = false;
- final boolean dumpRecords = false;
- final boolean savePng = true;
- final boolean dumpEmbedded = false;
+ // ISS3ANIX2PL4PXR7SZSJSPBZI7YQQE3U_6 - map of italy - stroke problem
+ // 3QKAPISTXYHSFCTV6QTKTYLK6JTWJHQU_2 - text misplaced
+ // KEEDHN6XES4EKK52E3AJHKCARNTQF7PO_0 - dito
+ // KWG4VAU5GM3POSA4BPG6RSVQVS44SXOL_1.emf - processing freezes
- Set<String> passed = new HashSet<>();
+ // F7GK5XOLERFURVTQALOCX3GJ6FH45LNQ strange colors
+ // ISS3ANIX2PL4PXR7SZSJSPBZI7YQQE3U stroke wrong
+ // KWG4VAU5GM3POSA4BPG6RSVQVS44SXOL_1
- try (BufferedWriter sucWrite = parseEmfLog(passed, "emf-success.txt")
- ;BufferedWriter parseError = parseEmfLog(passed, "emf-parse.txt")
- ;BufferedWriter renderError = parseEmfLog(passed, "emf-render.txt")
- ;SevenZFile sevenZFile = new SevenZFile(new File("tmp/plus_emf.7z"))
+ try (SevenZFile sevenZFile = new SevenZFile(new File("tmp/plus_emf.7z"))
) {
for (int idx=0;;idx++) {
SevenZArchiveEntry entry = sevenZFile.getNextEntry();
if (entry == null) break;
final String etName = entry.getName();
- if (entry.isDirectory() || !etName.endsWith(".emf") || passed.contains(etName)) continue;
-
+ if (entry.isDirectory() || !etName.endsWith(".emf")) continue;
- // KEEDHN6XES4EKK52E3AJHKCARNTQF7PO_0.emf takes ages, time is spent while drawing paths
-// if (!etName.contains("KEEDHN6XES4EKK52E3AJHKCARNTQF7PO_0.emf")) continue;
+ if (!(etName.contains("3QKAPISTXYHSFCTV6QTKTYLK6JTWJHQU_2")
+ )) continue;
- // F7GK5XOLERFURVTQALOCX3GJ6FH45LNQ strange colors
- // ISS3ANIX2PL4PXR7SZSJSPBZI7YQQE3U stroke wrong
-// if (!etName.contains("ISS3ANIX2PL4PXR7SZSJSPBZI7YQQE3U")) continue;
+// || etName.contains("ISS3ANIX2PL4PXR7SZSJSPBZI7YQQE3U_6")
+// || etName.contains("KWG4VAU5GM3POSA4BPG6RSVQVS44SXOL_1")
System.out.println(etName);
int size = sevenZFile.read(buf);
-
- HemfPicture emf = null;
- try {
- emf = new HemfPicture(new ByteArrayInputStream(buf, 0, size));
-
- // initialize parsing
- emf.getRecords();
- } catch (Exception|AssertionError e) {
- if (writeLog) {
- parseError.write(etName+" "+hashException(e)+"\n");
- parseError.flush();
- }
- System.out.println("parse error");
- // continue with the read records up to the error anyway
- if (emf.getRecords().isEmpty()) {
- continue;
- }
- }
-
- if (dumpRecords) {
- dumpRecords(emf);
- }
-
- if (dumpEmbedded) {
- int embIdx = 0;
- for (HwmfEmbedded emb : emf.getEmbeddings()) {
- final File embName = new File("build/tmp", "emb_"+etName.replaceFirst(".+/", "").replace(".emf", "_"+embIdx + emb.getEmbeddedType().extension) );
- try (FileOutputStream fos = new FileOutputStream(embName)) {
- fos.write(emb.getRawData());
- }
- embIdx++;
- }
- }
-
-
- Graphics2D g = null;
- try {
- Dimension2D dim = emf.getSize();
- double width = Units.pointsToPixel(dim.getWidth());
- // keep aspect ratio for height
- double height = Units.pointsToPixel(dim.getHeight());
- double max = Math.max(width, height);
- if (max > 1500.) {
- width *= 1500. / max;
- height *= 1500. / max;
- }
- width = Math.ceil(width);
- height = Math.ceil(height);
-
- BufferedImage bufImg = new BufferedImage((int)width, (int)height, BufferedImage.TYPE_INT_ARGB);
- g = bufImg.createGraphics();
- g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
- g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
- g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_SPEED);
- g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
- g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
-
- g.setComposite(AlphaComposite.Clear);
- g.fillRect(0, 0, (int)width, (int)height);
- g.setComposite(AlphaComposite.Src);
-
- final File pngName = new File("build/tmp", etName.replaceFirst(".+/", "").replace(".emf", ".png"));
-
- emf.draw(g, new Rectangle2D.Double(0, 0, width, height));
-
- if (savePng) {
- ImageIO.write(bufImg, "PNG", pngName);
- }
- } catch (Exception|AssertionError e) {
- System.out.println("render error");
- if (writeLog) {
- // dumpRecords(emf.getRecords());
- renderError.write(etName+" "+hashException(e)+"\n");
- renderError.flush();
- }
- continue;
- } finally {
- if (g != null) g.dispose();
- }
-
- if (writeLog) {
- sucWrite.write(etName + "\n");
- sucWrite.flush();
- }
+ ByteArrayInputStream bis = new ByteArrayInputStream(buf, 0, size);
+ System.setIn(bis);
+
+ String lastName = etName.replaceFirst(".+/", "");
+
+ String[] args = {
+ "-format", "png", // png,gif,jpg or null for test
+ "-outdir", new File("build/tmp/").getCanonicalPath(),
+ "-outfile", lastName.replace(".emf", ".png"),
+ "-fixside", "long",
+ "-scale", "800",
+ "-ignoreParse",
+ "-dump", new File("build/tmp/", lastName.replace(".emf",".json")).getCanonicalPath(),
+ // "-quiet",
+ // "-extractEmbedded",
+ "stdin"
+ };
+ PPTX2PNG.main(args);
}
}
}
-
- private static int hashException(Throwable e) {
- StringBuilder sb = new StringBuilder();
- for (StackTraceElement se : e.getStackTrace()) {
- sb.append(se.getClassName()+":"+se.getLineNumber());
- }
- return sb.toString().hashCode();
- }
-
- private static void dumpRecords(HemfPicture emf) throws IOException {
- FileWriter fw = new FileWriter("record-list.txt");
- int i = 0;
- for (HemfRecord r : emf.getRecords()) {
- if (r.getEmfRecordType() != HemfRecordType.comment) {
- fw.write(i + " " + r.getEmfRecordType() + " " + r.toString() + "\n");
- }
- i++;
- }
- fw.close();
- }
-
- private static BufferedWriter parseEmfLog(Set<String> passed, String logFile) throws IOException {
- Path log = Paths.get(logFile);
-
- StandardOpenOption soo;
- if (Files.exists(log)) {
- soo = StandardOpenOption.APPEND;
- try (Stream<String> stream = Files.lines(log)) {
- stream.filter(s -> !s.startsWith("#")).forEach((s) -> passed.add(s.split("\\s")[0]));
- }
- } else {
- soo = StandardOpenOption.CREATE;
- }
-
- return Files.newBufferedWriter(log, StandardCharsets.UTF_8, soo);
- }*/
-
+*/
@Test
public void testBasicWindows() throws Exception {
try (InputStream is = ss_samples.openResourceAsStream("SimpleEMF_windows.emf")) {