diff options
author | Andreas Beeker <kiwiwings@apache.org> | 2019-11-01 17:18:13 +0000 |
---|---|---|
committer | Andreas Beeker <kiwiwings@apache.org> | 2019-11-01 17:18:13 +0000 |
commit | f7c28ad08f34de47bdc1f6315ccd858570a363e5 (patch) | |
tree | 311676fd07e6784e7fc892cf8324765ce36979fc | |
parent | 3f0a01ae7cf30a8c0da4c3af84c3e05da79a7d63 (diff) | |
download | poi-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
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")) { |