You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@poi.apache.org by ki...@apache.org on 2019/05/04 23:01:54 UTC

svn commit: r1858625 [2/2] - in /poi/trunk: src/java/org/apache/poi/poifs/filesystem/ src/java/org/apache/poi/util/ src/scratchpad/src/org/apache/poi/hemf/record/emf/ src/scratchpad/src/org/apache/poi/hemf/record/emfplus/ src/scratchpad/src/org/apache/...

Added: poi/trunk/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfEmbeddedIterator.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfEmbeddedIterator.java?rev=1858625&view=auto
==============================================================================
--- poi/trunk/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfEmbeddedIterator.java (added)
+++ poi/trunk/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfEmbeddedIterator.java Sat May  4 23:01:53 2019
@@ -0,0 +1,342 @@
+/* ====================================================================
+   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.hemf.usermodel;
+
+import java.awt.Transparency;
+import java.awt.color.ColorSpace;
+import java.awt.image.BufferedImage;
+import java.awt.image.ComponentColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.PixelInterleavedSampleModel;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
+
+import javax.imageio.ImageIO;
+
+import org.apache.poi.hemf.record.emf.HemfComment;
+import org.apache.poi.hemf.record.emf.HemfRecord;
+import org.apache.poi.hemf.record.emfplus.HemfPlusObject;
+import org.apache.poi.hwmf.record.HwmfBitmapDib;
+import org.apache.poi.hwmf.record.HwmfFill;
+import org.apache.poi.hwmf.usermodel.HwmfEmbedded;
+import org.apache.poi.hwmf.usermodel.HwmfEmbeddedType;
+import org.apache.poi.poifs.filesystem.FileMagic;
+
+public class HemfEmbeddedIterator implements Iterator<HwmfEmbedded> {
+
+    private final Deque<Iterator<?>> iterStack = new ArrayDeque<>();
+    private Object current;
+
+    public HemfEmbeddedIterator(HemfPicture emf) {
+        this(emf.getRecords().iterator());
+    }
+
+    public HemfEmbeddedIterator(Iterator<HemfRecord> recordIterator) {
+        iterStack.add(recordIterator);
+    }
+
+    @Override
+    public boolean hasNext() {
+        if (iterStack.isEmpty()) {
+            return false;
+        }
+
+        if (current != null) {
+            // don't search twice and potentially skip items
+            return true;
+        }
+
+        Iterator<?> iter;
+        do {
+            iter = iterStack.peek();
+            while (iter.hasNext()) {
+                Object obj = iter.next();
+                if (obj instanceof HemfComment.EmfComment) {
+                    HemfComment.EmfCommentData cd = ((HemfComment.EmfComment)obj).getCommentData();
+                    if (
+                        cd instanceof HemfComment.EmfCommentDataWMF ||
+                        cd instanceof HemfComment.EmfCommentDataGeneric
+                    ) {
+                        current = obj;
+                        return true;
+                    }
+
+                    if (cd instanceof HemfComment.EmfCommentDataMultiformats) {
+                        Iterator<?> iter2 = ((HemfComment.EmfCommentDataMultiformats)cd).getFormats().iterator();
+                        if (iter2.hasNext()) {
+                            iterStack.push(iter2);
+                            continue;
+                        }
+                    }
+
+                    if (cd instanceof HemfComment.EmfCommentDataPlus) {
+                        Iterator<?> iter2 = ((HemfComment.EmfCommentDataPlus)cd).getRecords().iterator();
+                        if (iter2.hasNext()) {
+                            iter = iter2;
+                            iterStack.push(iter2);
+                            continue;
+                        }
+                    }
+                }
+
+                if (obj instanceof HemfComment.EmfCommentDataFormat) {
+                    current = obj;
+                    return true;
+                }
+
+                if (obj instanceof HemfPlusObject.EmfPlusObject && ((HemfPlusObject.EmfPlusObject)obj).getObjectType() == HemfPlusObject.EmfPlusObjectType.IMAGE) {
+                    current = obj;
+                    return true;
+                }
+
+                if (obj instanceof HwmfFill.WmfStretchDib) {
+                    HwmfBitmapDib bitmap = ((HwmfFill.WmfStretchDib) obj).getBitmap();
+                    if (bitmap.isValid()) {
+                        current = obj;
+                        return true;
+                    }
+                }
+            }
+            iterStack.pop();
+        } while (!iterStack.isEmpty());
+
+        return false;
+    }
+
+    @Override
+    public HwmfEmbedded next() {
+        HwmfEmbedded emb;
+        if ((emb = checkEmfCommentDataWMF()) != null) {
+            return emb;
+        }
+        if ((emb = checkEmfCommentDataGeneric()) != null) {
+            return emb;
+        }
+        if ((emb = checkEmfCommentDataFormat()) != null) {
+            return emb;
+        }
+        if ((emb = checkEmfPlusObject()) != null) {
+            return emb;
+        }
+        if ((emb = checkWmfStretchDib()) != null) {
+            return emb;
+        }
+
+        return null;
+    }
+
+    private HwmfEmbedded checkEmfCommentDataWMF() {
+        if (!(current instanceof HemfComment.EmfCommentDataWMF && ((HemfComment.EmfComment)current).getCommentData() instanceof HemfComment.EmfCommentDataWMF)) {
+            return null;
+        }
+
+        HemfComment.EmfCommentDataWMF wmf = (HemfComment.EmfCommentDataWMF)((HemfComment.EmfComment)current).getCommentData();
+        HwmfEmbedded emb = new HwmfEmbedded();
+        emb.setEmbeddedType(HwmfEmbeddedType.WMF);
+        emb.setData(wmf.getWMFData());
+        current = null;
+        return emb;
+    }
+
+    private HwmfEmbedded checkEmfCommentDataGeneric() {
+        if (!(current instanceof HemfComment.EmfComment && ((HemfComment.EmfComment)current).getCommentData() instanceof HemfComment.EmfCommentDataGeneric)) {
+            return null;
+        }
+        HemfComment.EmfCommentDataGeneric cdg = (HemfComment.EmfCommentDataGeneric)((HemfComment.EmfComment)current).getCommentData();
+        HwmfEmbedded emb = new HwmfEmbedded();
+        emb.setEmbeddedType(HwmfEmbeddedType.UNKNOWN);
+        emb.setData(cdg.getPrivateData());
+        current = null;
+        return emb;
+    }
+
+    private HwmfEmbedded checkEmfCommentDataFormat() {
+        if (!(current instanceof HemfComment.EmfCommentDataFormat)) {
+            return null;
+        }
+        HemfComment.EmfCommentDataFormat cdf = (HemfComment.EmfCommentDataFormat)current;
+        HwmfEmbedded emb = new HwmfEmbedded();
+        boolean isEmf = (cdf.getSignature() == HemfComment.EmfFormatSignature.ENHMETA_SIGNATURE);
+        emb.setEmbeddedType(isEmf ? HwmfEmbeddedType.EMF : HwmfEmbeddedType.EPS);
+        emb.setData(cdf.getRawData());
+        current = null;
+        return emb;
+    }
+
+    private HwmfEmbedded checkWmfStretchDib() {
+        if (!(current instanceof HwmfFill.WmfStretchDib)) {
+            return null;
+        }
+        HwmfEmbedded emb = new HwmfEmbedded();
+        emb.setData(((HwmfFill.WmfStretchDib) current).getBitmap().getBMPData());
+        emb.setEmbeddedType(HwmfEmbeddedType.BMP);
+        current = null;
+        return emb;
+    }
+
+    private HwmfEmbedded checkEmfPlusObject() {
+        if (!(current instanceof HemfPlusObject.EmfPlusObject)) {
+            return null;
+        }
+
+        HemfPlusObject.EmfPlusObject epo = (HemfPlusObject.EmfPlusObject)current;
+        assert(epo.getObjectType() == HemfPlusObject.EmfPlusObjectType.IMAGE);
+        HemfPlusObject.EmfPlusImage img = epo.getObjectData();
+        assert(img.getImageDataType() != null);
+
+        HwmfEmbedded emb = getEmfPlusImageData();
+
+        HwmfEmbeddedType et;
+        switch (img.getImageDataType()) {
+            case BITMAP:
+                if (img.getBitmapType() == HemfPlusObject.EmfPlusBitmapDataType.COMPRESSED) {
+                    switch (FileMagic.valueOf(emb.getRawData())) {
+                        case JPEG:
+                            et = HwmfEmbeddedType.JPEG;
+                            break;
+                        case GIF:
+                            et = HwmfEmbeddedType.GIF;
+                            break;
+                        case PNG:
+                            et = HwmfEmbeddedType.PNG;
+                            break;
+                        case TIFF:
+                            et = HwmfEmbeddedType.TIFF;
+                            break;
+                        default:
+                            et = HwmfEmbeddedType.BITMAP;
+                            break;
+                    }
+                } else {
+                    et = HwmfEmbeddedType.PNG;
+                    compressGDIBitmap(img, emb, et);
+                }
+                break;
+            case METAFILE:
+                assert(img.getMetafileType() != null);
+                switch (img.getMetafileType()) {
+                    case Wmf:
+                    case WmfPlaceable:
+                        et = HwmfEmbeddedType.WMF;
+                        break;
+                    case Emf:
+                    case EmfPlusDual:
+                    case EmfPlusOnly:
+                        et = HwmfEmbeddedType.EMF;
+                        break;
+                    default:
+                        et = HwmfEmbeddedType.UNKNOWN;
+                        break;
+                }
+                break;
+            default:
+                et = HwmfEmbeddedType.UNKNOWN;
+                break;
+        }
+        emb.setEmbeddedType(et);
+
+        return emb;
+    }
+
+    /**
+     * Compress GDIs internal format to something useful
+     */
+    private void compressGDIBitmap(HemfPlusObject.EmfPlusImage img, HwmfEmbedded emb, HwmfEmbeddedType et) {
+        final int width = img.getBitmapWidth();
+        final int height = img.getBitmapHeight();
+        final int stride = img.getBitmapStride();
+        final HemfPlusObject.EmfPlusPixelFormat pf = img.getPixelFormat();
+
+        int[] nBits, bOffs;
+        switch (pf) {
+            case ARGB_32BPP:
+                nBits = new int[]{8, 8, 8, 8};
+                bOffs = new int[]{2, 1, 0, 3};
+                break;
+            case RGB_24BPP:
+                nBits = new int[]{8, 8, 8};
+                bOffs = new int[]{2, 1, 0};
+                break;
+            default:
+                throw new RuntimeException("not yet implemented");
+        }
+
+        ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
+        ComponentColorModel cm = new ComponentColorModel
+                (cs, nBits, pf.isAlpha(), pf.isPreMultiplied(), Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
+        PixelInterleavedSampleModel csm =
+                new PixelInterleavedSampleModel(cm.getTransferType(), width, height, cm.getNumColorComponents(), stride, bOffs);
+
+        byte d[] = emb.getRawData();
+        WritableRaster raster = (WritableRaster) Raster.createRaster(csm, new DataBufferByte(d, d.length), null);
+
+        BufferedImage bi = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
+
+        try {
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            // use HwmfEmbeddedType literal for conversion
+            ImageIO.write(bi, et.toString(), bos);
+            emb.setData(bos.toByteArray());
+        } catch (IOException e) {
+            // TODO: throw appropriate exception
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    private HwmfEmbedded getEmfPlusImageData() {
+        HemfPlusObject.EmfPlusObject epo = (HemfPlusObject.EmfPlusObject)current;
+        assert(epo.getObjectType() == HemfPlusObject.EmfPlusObjectType.IMAGE);
+
+        final int objectId = epo.getObjectId();
+
+        HwmfEmbedded emb = new HwmfEmbedded();
+
+        HemfPlusObject.EmfPlusImage img = (HemfPlusObject.EmfPlusImage)epo.getObjectData();
+        assert(img.getImageDataType() != null);
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        try {
+            for (;;) {
+                bos.write(img.getImageData());
+
+                current = null;
+                //noinspection ConstantConditions
+                if (hasNext() &&
+                    (current instanceof HemfPlusObject.EmfPlusObject) &&
+                    ((epo = (HemfPlusObject.EmfPlusObject) current).getObjectId() == objectId)
+                ) {
+                    img = (HemfPlusObject.EmfPlusImage)epo.getObjectData();
+                } else {
+                    return emb;
+                }
+            }
+        } catch (IOException e) {
+            // ByteArrayOutputStream doesn't throw IOException
+            return null;
+        } finally {
+            emb.setData(bos.toByteArray());
+        }
+    }
+}

Propchange: poi/trunk/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfEmbeddedIterator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: poi/trunk/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java?rev=1858625&r1=1858624&r2=1858625&view=diff
==============================================================================
--- poi/trunk/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java (original)
+++ poi/trunk/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java Sat May  4 23:01:53 2019
@@ -35,6 +35,7 @@ import org.apache.poi.hemf.record.emf.He
 import org.apache.poi.hemf.record.emf.HemfRecord;
 import org.apache.poi.hemf.record.emf.HemfRecordIterator;
 import org.apache.poi.hemf.record.emf.HemfWindowing;
+import org.apache.poi.hwmf.usermodel.HwmfEmbedded;
 import org.apache.poi.util.Dimension2DDouble;
 import org.apache.poi.util.Internal;
 import org.apache.poi.util.LittleEndianInputStream;
@@ -158,4 +159,7 @@ public class HemfPicture implements Iter
         }
     }
 
+    public Iterable<HwmfEmbedded> getEmbeddings() {
+        return () -> new HemfEmbeddedIterator(HemfPicture.this);
+    }
 }

Modified: poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java?rev=1858625&r1=1858624&r2=1858625&view=diff
==============================================================================
--- poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java (original)
+++ poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java Sat May  4 23:01:53 2019
@@ -142,7 +142,7 @@ public class HwmfGraphics {
             graphicsCtx.fill(shape);
         }
 
-//        draw(shape);
+        draw(shape);
     }
 
     protected BasicStroke getStroke() {

Modified: poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java?rev=1858625&r1=1858624&r2=1858625&view=diff
==============================================================================
--- poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java (original)
+++ poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java Sat May  4 23:01:53 2019
@@ -25,6 +25,7 @@ import java.awt.LinearGradientPaint;
 import java.awt.MultipleGradientPaint;
 import java.awt.RenderingHints;
 import java.awt.image.BufferedImage;
+import java.awt.image.IndexColorModel;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -460,12 +461,29 @@ public class HwmfBitmapDib {
     }
     
     public BufferedImage getImage() {
+        return getImage(null, null, false);
+    }
+
+    public BufferedImage getImage(Color foreground, Color background, boolean hasAlpha) {
+        BufferedImage bi;
         try {
-            return ImageIO.read(getBMPStream());
+            bi = ImageIO.read(getBMPStream());
         } catch (IOException|RuntimeException e) {
             logger.log(POILogger.ERROR, "invalid bitmap data - returning placeholder image");
             return getPlaceholder();
         }
+
+        if (foreground != null && background != null && headerBitCount == HwmfBitmapDib.BitCount.BI_BITCOUNT_1) {
+            IndexColorModel cmOld = (IndexColorModel)bi.getColorModel();
+            int transPixel = hasAlpha ? (((cmOld.getRGB(0) & 0xFFFFFF) == 0) ? 0 : 1) : -1;
+            int transferType = bi.getData().getTransferType();
+            int fg = foreground.getRGB(), bg = background.getRGB();
+            int[] cmap = { (transPixel == 0 ? bg : fg), (transPixel == 1 ? bg : fg) };
+            IndexColorModel cmNew = new IndexColorModel(1, cmap.length, cmap, 0, hasAlpha, transPixel, transferType);
+            bi = new BufferedImage(cmNew, bi.getRaster(), false, null);
+        }
+
+        return bi;
     }
 
     @Override

Modified: poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfEscape.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfEscape.java?rev=1858625&r1=1858624&r2=1858625&view=diff
==============================================================================
--- poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfEscape.java (original)
+++ poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfEscape.java Sat May  4 23:01:53 2019
@@ -18,10 +18,13 @@
 package org.apache.poi.hwmf.record;
 
 import java.io.IOException;
+import java.util.function.Supplier;
 
 import org.apache.poi.hwmf.draw.HwmfGraphics;
 import org.apache.poi.util.HexDump;
 import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianCP950Reader;
 import org.apache.poi.util.LittleEndianConsts;
 import org.apache.poi.util.LittleEndianInputStream;
 
@@ -30,135 +33,140 @@ import org.apache.poi.util.LittleEndianI
  * might not be directly accessible through WMF records
  */
 public class HwmfEscape implements HwmfRecord {
-    
+    private static final int MAX_OBJECT_SIZE = 0xFFFF;
+
     public enum EscapeFunction {
         /** Notifies the printer driver that the application has finished writing to a page. */
-        NEWFRAME(0x0001),
+        NEWFRAME(0x0001, WmfEscapeUnknownData::new),
         /** Stops processing the current document. */
-        ABORTDOC(0x0002),
+        ABORTDOC(0x0002, WmfEscapeUnknownData::new),
         /** Notifies the printer driver that the application has finished writing to a band. */
-        NEXTBAND(0x0003),
+        NEXTBAND(0x0003, WmfEscapeUnknownData::new),
         /** Sets color table values. */
-        SETCOLORTABLE(0x0004),
+        SETCOLORTABLE(0x0004, WmfEscapeUnknownData::new),
         /** Gets color table values. */
-        GETCOLORTABLE(0x0005),
+        GETCOLORTABLE(0x0005, WmfEscapeUnknownData::new),
         /** Causes all pending output to be flushed to the output device. */
-        FLUSHOUT(0x0006),
+        FLUSHOUT(0x0006, WmfEscapeUnknownData::new),
         /** Indicates that the printer driver SHOULD print text only, and no graphics. */
-        DRAFTMODE(0x0007),
+        DRAFTMODE(0x0007, WmfEscapeUnknownData::new),
         /** Queries a printer driver to determine whether a specific escape function is supported on the output device it drives. */
-        QUERYESCSUPPORT(0x0008),
+        QUERYESCSUPPORT(0x0008, WmfEscapeUnknownData::new),
         /** Sets the application-defined function that allows a print job to be canceled during printing. */
-        SETABORTPROC(0x0009),
+        SETABORTPROC(0x0009, WmfEscapeUnknownData::new),
         /** Notifies the printer driver that a new print job is starting. */
-        STARTDOC(0x000A),
+        STARTDOC(0x000A, WmfEscapeUnknownData::new),
         /** Notifies the printer driver that the current print job is ending. */
-        ENDDOC(0x000B),
+        ENDDOC(0x000B, WmfEscapeUnknownData::new),
         /** Retrieves the physical page size currently selected on an output device. */
-        GETPHYSPAGESIZE(0x000C),
+        GETPHYSPAGESIZE(0x000C, WmfEscapeUnknownData::new),
         /** Retrieves the offset from the upper-left corner of the physical page where the actual printing or drawing begins. */
-        GETPRINTINGOFFSET(0x000D),
+        GETPRINTINGOFFSET(0x000D, WmfEscapeUnknownData::new),
         /** Retrieves the scaling factors for the x-axis and the y-axis of a printer. */
-        GETSCALINGFACTOR(0x000E),
+        GETSCALINGFACTOR(0x000E, WmfEscapeUnknownData::new),
         /** Used to embed an enhanced metafile format (EMF) metafile within a WMF metafile. */
-        META_ESCAPE_ENHANCED_METAFILE(0x000F),
+        META_ESCAPE_ENHANCED_METAFILE(0x000F, WmfEscapeEMF::new),
         /** Sets the width of a pen in pixels. */
-        SETPENWIDTH(0x0010),
+        SETPENWIDTH(0x0010, WmfEscapeUnknownData::new),
         /** Sets the number of copies. */
-        SETCOPYCOUNT(0x0011),
+        SETCOPYCOUNT(0x0011, WmfEscapeUnknownData::new),
         /** Sets the source, such as a particular paper tray or bin on a printer, for output forms. */
-        SETPAPERSOURCE(0x0012),
+        SETPAPERSOURCE(0x0012, WmfEscapeUnknownData::new),
         /** This record passes through arbitrary data. */
-        PASSTHROUGH(0x0013),
+        PASSTHROUGH(0x0013, WmfEscapeUnknownData::new),
         /** Gets information concerning graphics technology that is supported on a device. */
-        GETTECHNOLOGY(0x0014),
+        GETTECHNOLOGY(0x0014, WmfEscapeUnknownData::new),
         /** Specifies the line-drawing mode to use in output to a device. */
-        SETLINECAP(0x0015),
+        SETLINECAP(0x0015, WmfEscapeUnknownData::new),
         /** Specifies the line-joining mode to use in output to a device. */
-        SETLINEJOIN(0x0016),
+        SETLINEJOIN(0x0016, WmfEscapeUnknownData::new),
         /** Sets the limit for the length of miter joins to use in output to a device. */
-        SETMITERLIMIT(0x0017),
+        SETMITERLIMIT(0x0017, WmfEscapeUnknownData::new),
         /** Retrieves or specifies settings concerning banding on a device, such as the number of bands. */
-        BANDINFO(0x0018),
+        BANDINFO(0x0018, WmfEscapeUnknownData::new),
         /** Draws a rectangle with a defined pattern. */
-        DRAWPATTERNRECT(0x0019),
+        DRAWPATTERNRECT(0x0019, WmfEscapeUnknownData::new),
         /** Retrieves the physical pen size currently defined on a device. */
-        GETVECTORPENSIZE(0x001A),
+        GETVECTORPENSIZE(0x001A, WmfEscapeUnknownData::new),
         /** Retrieves the physical brush size currently defined on a device. */
-        GETVECTORBRUSHSIZE(0x001B),
+        GETVECTORBRUSHSIZE(0x001B, WmfEscapeUnknownData::new),
         /** Enables or disables double-sided (duplex) printing on a device. */
-        ENABLEDUPLEX(0x001C),
+        ENABLEDUPLEX(0x001C, WmfEscapeUnknownData::new),
         /** Retrieves or specifies the source of output forms on a device. */
-        GETSETPAPERBINS(0x001D),
+        GETSETPAPERBINS(0x001D, WmfEscapeUnknownData::new),
         /** Retrieves or specifies the paper orientation on a device. */
-        GETSETPRINTORIENT(0x001E),
+        GETSETPRINTORIENT(0x001E, WmfEscapeUnknownData::new),
         /** Retrieves information concerning the sources of different forms on an output device. */
-        ENUMPAPERBINS(0x001F),
+        ENUMPAPERBINS(0x001F, WmfEscapeUnknownData::new),
         /** Specifies the scaling of device-independent bitmaps (DIBs). */
-        SETDIBSCALING(0x0020),
+        SETDIBSCALING(0x0020, WmfEscapeUnknownData::new),
         /** Indicates the start and end of an encapsulated PostScript (EPS) section. */
-        EPSPRINTING(0x0021),
+        EPSPRINTING(0x0021, WmfEscapeUnknownData::new),
         /** Queries a printer driver for paper dimensions and other forms data. */
-        ENUMPAPERMETRICS(0x0022),
+        ENUMPAPERMETRICS(0x0022, WmfEscapeUnknownData::new),
         /** Retrieves or specifies paper dimensions and other forms data on an output device. */
-        GETSETPAPERMETRICS(0x0023),
+        GETSETPAPERMETRICS(0x0023, WmfEscapeUnknownData::new),
         /** Sends arbitrary PostScript data to an output device. */
-        POSTSCRIPT_DATA(0x0025),
+        POSTSCRIPT_DATA(0x0025, WmfEscapeUnknownData::new),
         /** Notifies an output device to ignore PostScript data. */
-        POSTSCRIPT_IGNORE(0x0026),
+        POSTSCRIPT_IGNORE(0x0026, WmfEscapeUnknownData::new),
         /** Gets the device units currently configured on an output device. */
-        GETDEVICEUNITS(0x002A),
+        GETDEVICEUNITS(0x002A, WmfEscapeUnknownData::new),
         /** Gets extended text metrics currently configured on an output device. */
-        GETEXTENDEDTEXTMETRICS(0x0100),
+        GETEXTENDEDTEXTMETRICS(0x0100, WmfEscapeUnknownData::new),
         /** Gets the font kern table currently defined on an output device. */
-        GETPAIRKERNTABLE(0x0102),
+        GETPAIRKERNTABLE(0x0102, WmfEscapeUnknownData::new),
         /** Draws text using the currently selected font, background color, and text color. */
-        EXTTEXTOUT(0x0200),
+        EXTTEXTOUT(0x0200, WmfEscapeUnknownData::new),
         /** Gets the font face name currently configured on a device. */
-        GETFACENAME(0x0201),
+        GETFACENAME(0x0201, WmfEscapeUnknownData::new),
         /** Sets the font face name on a device. */
-        DOWNLOADFACE(0x0202),
+        DOWNLOADFACE(0x0202, WmfEscapeUnknownData::new),
         /** Queries a printer driver about the support for metafiles on an output device. */
-        METAFILE_DRIVER(0x0801),
+        METAFILE_DRIVER(0x0801, WmfEscapeUnknownData::new),
         /** Queries the printer driver about its support for DIBs on an output device. */
-        QUERYDIBSUPPORT(0x0C01),
+        QUERYDIBSUPPORT(0x0C01, WmfEscapeUnknownData::new),
         /** Opens a path. */
-        BEGIN_PATH(0x1000),
+        BEGIN_PATH(0x1000, WmfEscapeUnknownData::new),
         /** Defines a clip region that is bounded by a path. The input MUST be a 16-bit quantity that defines the action to take. */
-        CLIP_TO_PATH(0x1001),
+        CLIP_TO_PATH(0x1001, WmfEscapeUnknownData::new),
         /** Ends a path. */
-        END_PATH(0x1002),
+        END_PATH(0x1002, WmfEscapeUnknownData::new),
         /** The same as STARTDOC specified with a NULL document and output filename, data in raw mode, and a type of zero. */
-        OPEN_CHANNEL(0x100E),
+        OPEN_CHANNEL(0x100E, WmfEscapeUnknownData::new),
         /** Instructs the printer driver to download sets of PostScript procedures. */
-        DOWNLOADHEADER(0x100F),
+        DOWNLOADHEADER(0x100F, WmfEscapeUnknownData::new),
         /** The same as ENDDOC. See OPEN_CHANNEL. */
-        CLOSE_CHANNEL(0x1010),
+        CLOSE_CHANNEL(0x1010, WmfEscapeUnknownData::new),
         /** Sends arbitrary data directly to a printer driver, which is expected to process this data only when in PostScript mode. */
-        POSTSCRIPT_PASSTHROUGH(0x1013),
+        POSTSCRIPT_PASSTHROUGH(0x1013, WmfEscapeUnknownData::new),
         /** Sends arbitrary data directly to the printer driver. */
-        ENCAPSULATED_POSTSCRIPT(0x1014),
+        ENCAPSULATED_POSTSCRIPT(0x1014, WmfEscapeUnknownData::new),
         /** Sets the printer driver to either PostScript or GDI mode. */
-        POSTSCRIPT_IDENTIFY(0x1015),
+        POSTSCRIPT_IDENTIFY(0x1015, WmfEscapeUnknownData::new),
         /** Inserts a block of raw data into a PostScript stream. The input MUST be
         a 32-bit quantity specifying the number of bytes to inject, a 16-bit quantity specifying the
         injection point, and a 16-bit quantity specifying the page number, followed by the bytes to
         inject. */
-        POSTSCRIPT_INJECTION(0x1016),
+        POSTSCRIPT_INJECTION(0x1016, WmfEscapeUnknownData::new),
         /** Checks whether the printer supports a JPEG image. */
-        CHECKJPEGFORMAT(0x1017),
+        CHECKJPEGFORMAT(0x1017, WmfEscapeUnknownData::new),
         /** Checks whether the printer supports a PNG image */
-        CHECKPNGFORMAT(0x1018),
+        CHECKPNGFORMAT(0x1018, WmfEscapeUnknownData::new),
         /** Gets information on a specified feature setting for a PostScript printer driver. */
-        GET_PS_FEATURESETTING(0x1019),
+        GET_PS_FEATURESETTING(0x1019, WmfEscapeUnknownData::new),
         /** Enables applications to write documents to a file or to a printer in XML Paper Specification (XPS) format. */
-        MXDC_ESCAPE(0x101A),
+        MXDC_ESCAPE(0x101A, WmfEscapeUnknownData::new),
         /** Enables applications to include private procedures and other arbitrary data in documents. */
-        SPCLPASSTHROUGH2(0x11D8);
+        SPCLPASSTHROUGH2(0x11D8, WmfEscapeUnknownData::new);
         
-        int flag;
-        EscapeFunction(int flag) {
+        public int flag;
+        public final Supplier<? extends HwmfEscape.HwmfEscapeData> constructor;
+
+
+        EscapeFunction(int flag, Supplier<? extends HwmfEscape.HwmfEscapeData> constructor) {
             this.flag = flag;
+            this.constructor = constructor;
         }
 
         static EscapeFunction valueOf(int flag) {
@@ -169,21 +177,18 @@ public class HwmfEscape implements HwmfR
         }
     }
     
+    public interface HwmfEscapeData {
+        public int init(LittleEndianInputStream leis, long recordSize, EscapeFunction escapeFunction) throws IOException;
+    }
+
+
     /**
      * A 16-bit unsigned integer that defines the escape function. The 
      * value MUST be from the MetafileEscapes enumeration.
      */
     private EscapeFunction escapeFunction;
-    /**
-     * A 16-bit unsigned integer that specifies the size, in bytes, of the 
-     * EscapeData field.
-     */
-    private int byteCount;
-    /**
-     * An array of bytes of size ByteCount.
-     */
-    private byte[] escapeData;
-    
+    private HwmfEscapeData escapeData;
+
     @Override
     public HwmfRecordType getWmfRecordType() {
         return HwmfRecordType.escape;
@@ -192,10 +197,22 @@ public class HwmfEscape implements HwmfR
     @Override
     public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
         escapeFunction = EscapeFunction.valueOf(leis.readUShort());
-        byteCount = leis.readUShort();
-        escapeData = IOUtils.toByteArray(leis,byteCount);
+        // A 16-bit unsigned integer that specifies the size, in bytes, of the EscapeData field.
+        int byteCount = leis.readUShort();
+        int size = 2*LittleEndianConsts.SHORT_SIZE;
+
+        escapeData = escapeFunction.constructor.get();
+        size += escapeData.init(leis, byteCount, escapeFunction);
+
+        return size;
+    }
+
+    public EscapeFunction getEscapeFunction() {
+        return escapeFunction;
+    }
 
-        return 2*LittleEndianConsts.SHORT_SIZE+byteCount;
+    public <T extends HwmfEscapeData> T getEscapeData() {
+        return (T)escapeData;
     }
 
     @Override
@@ -206,7 +223,126 @@ public class HwmfEscape implements HwmfR
     public String toString() {
         StringBuilder sb = new StringBuilder();
         sb.append("escape - function: "+escapeFunction+"\n");
-        sb.append(HexDump.dump(escapeData, 0, 0));
+        sb.append(escapeData.toString());
         return sb.toString();
     }
+
+    public static class WmfEscapeUnknownData implements HwmfEscapeData {
+        EscapeFunction escapeFunction;
+        private byte[] escapeDataBytes;
+
+        public byte[] getEscapeDataBytes() {
+            return escapeDataBytes;
+        }
+
+        @Override
+        public int init(LittleEndianInputStream leis, long recordSize, EscapeFunction escapeFunction) throws IOException {
+            this.escapeFunction = escapeFunction;
+            escapeDataBytes = IOUtils.toByteArray(leis,recordSize,MAX_OBJECT_SIZE);
+            return 0;
+        }
+
+        @Override
+        public String toString() {
+            return HexDump.dump(escapeDataBytes, 0, 0);
+        }
+    }
+
+    public static class WmfEscapeEMF implements HwmfEscapeData {
+        // The magic for EMF parts, i.e. the byte sequence for "WMFC"
+        private static final int EMF_COMMENT_IDENTIFIER = 0x43464D57;
+
+        int commentIdentifier;
+        int commentType;
+        int version;
+        int checksum;
+        int flags;
+        int commentRecordCount;
+        int currentRecordSize;
+        int remainingBytes;
+        int emfRecordSize;
+        byte[] emfData;
+
+
+        @Override
+        public int init(LittleEndianInputStream leis, long recordSize, EscapeFunction escapeFunction) throws IOException {
+            if (recordSize < LittleEndianConsts.INT_SIZE) {
+                return 0;
+            }
+
+            // A 32-bit unsigned integer that defines this record as a WMF Comment record.
+            int commentIdentifier = leis.readInt();
+
+            if (commentIdentifier != EMF_COMMENT_IDENTIFIER) {
+                // there are some WMF implementation using this record as a MFCOMMENT or similar
+                // if the commentIdentifier doesn't match, then return immediately
+                return LittleEndianConsts.INT_SIZE;
+            }
+
+            // A 32-bit unsigned integer that identifies the type of comment in this record.
+            // This value MUST be 0x00000001.
+            commentType = leis.readInt();
+            assert(commentType == 0x00000001);
+
+            // A 32-bit unsigned integer that specifies EMF metafile interoperability. This SHOULD be 0x00010000.
+            version = leis.readInt();
+
+            // A 16-bit unsigned integer used to validate the correctness of the embedded EMF stream.
+            // This value MUST be the one's-complement of the result of applying an XOR operation to all WORDs in the EMF stream.
+            checksum = leis.readUShort();
+
+            // This 32-bit unsigned integer is unused and MUST be set to zero.
+            flags = leis.readInt();
+            assert(flags == 0);
+
+            // A 32-bit unsigned integer that specifies the total number of consecutive META_ESCAPE_ENHANCED_METAFILE
+            // records that contain the embedded EMF metafile.
+            commentRecordCount = leis.readInt();
+
+            // A 32-bit unsigned integer that specifies the size, in bytes, of the EnhancedMetafileData field.
+            // This value MUST be less than or equal to 8,192.
+            currentRecordSize = leis.readInt();
+            assert(0 <= currentRecordSize && currentRecordSize <= 0x2000);
+
+            // A 32-bit unsigned integer that specifies the number of bytes in the EMF stream that remain to be
+            // processed after this record. Those additional EMF bytes MUST follow in the EnhancedMetafileData
+            // fields of subsequent META_ESCAPE_ENHANDED_METAFILE escape records.
+            remainingBytes = leis.readInt();
+
+            // A 32-bit unsigned integer that specifies the total size of the EMF stream embedded in this
+            // sequence of META_ESCAPE_ENHANCED_METAFILE records.
+            emfRecordSize = leis.readInt();
+
+
+            // A segment of an EMF file. The bytes in consecutive META_ESCAPE_ENHANCED_METAFILE records
+            // MUST be concatenated to represent the entire embedded EMF file.
+            emfData = IOUtils.toByteArray(leis, currentRecordSize, MAX_OBJECT_SIZE);
+
+            return LittleEndianConsts.INT_SIZE*8+ LittleEndianConsts.SHORT_SIZE+emfData.length;
+        }
+
+        public boolean isValid() {
+            return commentIdentifier == EMF_COMMENT_IDENTIFIER;
+        }
+
+        public int getCommentRecordCount() {
+            return commentRecordCount;
+        }
+
+        public int getCurrentRecordSize() {
+            return currentRecordSize;
+        }
+
+        public int getRemainingBytes() {
+            return remainingBytes;
+        }
+
+        public int getEmfRecordSize() {
+            return emfRecordSize;
+        }
+
+        public byte[] getEmfData() {
+            return emfData;
+        }
+    }
 }

Modified: poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java?rev=1858625&r1=1858624&r2=1858625&view=diff
==============================================================================
--- poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java (original)
+++ poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java Sat May  4 23:01:53 2019
@@ -20,6 +20,7 @@ package org.apache.poi.hwmf.record;
 import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
 import static org.apache.poi.hwmf.record.HwmfDraw.readPointS;
 
+import java.awt.Color;
 import java.awt.Shape;
 import java.awt.geom.Path2D;
 import java.awt.geom.Point2D;
@@ -29,6 +30,7 @@ import java.io.IOException;
 
 import org.apache.poi.hwmf.draw.HwmfDrawProperties;
 import org.apache.poi.hwmf.draw.HwmfGraphics;
+import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode;
 import org.apache.poi.util.LittleEndianConsts;
 import org.apache.poi.util.LittleEndianInputStream;
 
@@ -37,7 +39,29 @@ public class HwmfFill {
      * A record which contains an image (to be extracted)
      */
     public interface HwmfImageRecord {
-        BufferedImage getImage();
+
+        default BufferedImage getImage() {
+            return getImage(Color.BLACK, new Color(0x00FFFFFF, true), true);
+        }
+
+        /**
+         * Provide an image using the fore-/background color, in case of a 1-bit pattern
+         * @param foreground the foreground color
+         * @param background the background color
+         * @param hasAlpha if true, the background color is rendered transparent - see {@link HwmfMisc.WmfSetBkMode.HwmfBkMode}
+         * @return the image
+         *
+         * @since POI 4.1.1
+         */
+        BufferedImage getImage(Color foreground, Color background, boolean hasAlpha);
+
+        /**
+         * @return the raw BMP data
+         *
+         * @see <a href="https://en.wikipedia.org/wiki/BMP_file_format">BMP format</a>
+         * @since POI 4.1.1
+         */
+        byte[] getBMPData();
     }
     
     /**
@@ -497,7 +521,9 @@ public class HwmfFill {
             HwmfDrawProperties prop = ctx.getProperties();
             prop.setRasterOp(rasterOperation);
             if (bitmap.isValid()) {
-                ctx.drawImage(getImage(), srcBounds, dstBounds);
+                BufferedImage bi = bitmap.getImage(prop.getPenColor().getColor(), prop.getBackgroundColor().getColor(),
+                                                   prop.getBkMode() == HwmfBkMode.TRANSPARENT);
+                ctx.drawImage(bi, srcBounds, dstBounds);
             } else if (!dstBounds.isEmpty()) {
                 BufferedImage bi = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
                 ctx.drawImage(bi, new Rectangle2D.Double(0,0,100,100), dstBounds);
@@ -505,8 +531,17 @@ public class HwmfFill {
         }
 
         @Override
-        public BufferedImage getImage() {
-            return bitmap.getImage();
+        public BufferedImage getImage(Color foreground, Color background, boolean hasAlpha) {
+            return bitmap.getImage(foreground,background,hasAlpha);
+        }
+
+        public HwmfBitmapDib getBitmap() {
+            return bitmap;
+        }
+
+        @Override
+        public byte[] getBMPData() {
+            return bitmap.getBMPData();
         }
 
         @Override
@@ -631,8 +666,13 @@ public class HwmfFill {
         }
 
         @Override
-        public BufferedImage getImage() {
-            return dib.getImage();
+        public BufferedImage getImage(Color foreground, Color background, boolean hasAlpha) {
+            return dib.getImage(foreground,background,hasAlpha);
+        }
+
+        @Override
+        public byte[] getBMPData() {
+            return dib.getBMPData();
         }
     }
 
@@ -738,8 +778,13 @@ public class HwmfFill {
         }
 
         @Override
-        public BufferedImage getImage() {
-            return (target != null && target.isValid()) ? target.getImage() : null;
+        public BufferedImage getImage(Color foreground, Color background, boolean hasAlpha) {
+            return (target != null && target.isValid()) ? target.getImage(foreground,background,hasAlpha) : null;
+        }
+
+        @Override
+        public byte[] getBMPData() {
+            return (target != null && target.isValid()) ? target.getBMPData() : null;
         }
     }
 

Modified: poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java?rev=1858625&r1=1858624&r2=1858625&view=diff
==============================================================================
--- poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java (original)
+++ poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java Sat May  4 23:01:53 2019
@@ -17,6 +17,7 @@
 
 package org.apache.poi.hwmf.record;
 
+import java.awt.Color;
 import java.awt.geom.Dimension2D;
 import java.awt.image.BufferedImage;
 import java.io.IOException;
@@ -25,6 +26,7 @@ import org.apache.poi.hwmf.draw.HwmfDraw
 import org.apache.poi.hwmf.draw.HwmfGraphics;
 import org.apache.poi.hwmf.record.HwmfFill.ColorUsage;
 import org.apache.poi.hwmf.record.HwmfFill.HwmfImageRecord;
+import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode;
 import org.apache.poi.util.Dimension2DDouble;
 import org.apache.poi.util.LittleEndianConsts;
 import org.apache.poi.util.LittleEndianInputStream;
@@ -459,18 +461,30 @@ public class HwmfMisc {
             }
             HwmfDrawProperties prop = ctx.getProperties();
             prop.setBrushStyle(style);
-            prop.setBrushBitmap(getImage());
+            prop.setBrushBitmap(getImage(prop.getBrushColor().getColor(), prop.getBackgroundColor().getColor(),
+                                         prop.getBkMode() == HwmfBkMode.TRANSPARENT));
         }
 
         @Override
-        public BufferedImage getImage() {
+        public BufferedImage getImage(Color foreground, Color background, boolean hasAlpha) {
             if (patternDib != null && patternDib.isValid()) {
-                return patternDib.getImage();
+                return patternDib.getImage(foreground, background, hasAlpha);
             } else if (pattern16 != null) {
                 return pattern16.getImage();
             } else {
                 return null;
             }
+        }
+
+        @Override
+        public byte[] getBMPData() {
+            if (patternDib != null && patternDib.isValid()) {
+                return patternDib.getBMPData();
+            } else if (pattern16 != null) {
+                return null;
+            } else {
+                return null;
+            }
         }
     }
 

Modified: poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPlaceableHeader.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPlaceableHeader.java?rev=1858625&r1=1858624&r2=1858625&view=diff
==============================================================================
--- poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPlaceableHeader.java (original)
+++ poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPlaceableHeader.java Sat May  4 23:01:53 2019
@@ -66,7 +66,13 @@ public class HwmfPlaceableHeader {
          * This value can be used to determine whether the metafile has become corrupted.
          */
         leis.readShort();
-        
+
+        // sometimes the placeable header is filled/aligned to dwords.
+        // check for padding 0 bytes.
+        leis.mark(LittleEndianConsts.INT_SIZE);
+        if (leis.readShort() != 0) {
+            leis.reset();
+        }
     }
     
     public static HwmfPlaceableHeader readHeader(LittleEndianInputStream leis) throws IOException {

Added: poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfEmbedded.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfEmbedded.java?rev=1858625&view=auto
==============================================================================
--- poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfEmbedded.java (added)
+++ poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfEmbedded.java Sat May  4 23:01:53 2019
@@ -0,0 +1,49 @@
+/* ====================================================================
+   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.hwmf.usermodel;
+
+import org.apache.poi.util.Beta;
+
+/**
+ * An embedded resource - this class hides the logic of chained emf+ object records and other internals.
+ * Consider its API as unstable for now, i.e. there's no guarantee for backward compatibility
+ */
+@Beta
+public class HwmfEmbedded {
+
+    private HwmfEmbeddedType embeddedType;
+    private byte[] data;
+
+    public HwmfEmbeddedType getEmbeddedType() {
+        return embeddedType;
+    }
+
+    public byte[] getRawData() {
+        return data;
+    }
+
+    public void setEmbeddedType(HwmfEmbeddedType embeddedType) {
+        this.embeddedType = embeddedType;
+    }
+
+    public void setData(byte[] data) {
+        this.data = data;
+    }
+}
+
+

Propchange: poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfEmbedded.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfEmbeddedIterator.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfEmbeddedIterator.java?rev=1858625&view=auto
==============================================================================
--- poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfEmbeddedIterator.java (added)
+++ poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfEmbeddedIterator.java Sat May  4 23:01:53 2019
@@ -0,0 +1,140 @@
+/* ====================================================================
+   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.hwmf.usermodel;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
+
+import org.apache.poi.hwmf.record.HwmfEscape;
+import org.apache.poi.hwmf.record.HwmfEscape.EscapeFunction;
+import org.apache.poi.hwmf.record.HwmfEscape.WmfEscapeEMF;
+import org.apache.poi.hwmf.record.HwmfFill.HwmfImageRecord;
+import org.apache.poi.hwmf.record.HwmfRecord;
+
+public class HwmfEmbeddedIterator implements Iterator<HwmfEmbedded> {
+
+    private final Deque<Iterator<?>> iterStack = new ArrayDeque<>();
+    private Object current;
+
+    public HwmfEmbeddedIterator(HwmfPicture wmf) {
+        this(wmf.getRecords().iterator());
+    }
+
+    public HwmfEmbeddedIterator(Iterator<HwmfRecord> recordIterator) {
+        iterStack.add(recordIterator);
+    }
+
+    @Override
+    public boolean hasNext() {
+        if (iterStack.isEmpty()) {
+            return false;
+        }
+
+        if (current != null) {
+            // don't search twice and potentially skip items
+            return true;
+        }
+
+        Iterator<?> iter;
+        do {
+            iter = iterStack.peek();
+            while (iter.hasNext()) {
+                Object obj = iter.next();
+                if (obj instanceof HwmfImageRecord) {
+                    current = obj;
+                    return true;
+                }
+                if (obj instanceof HwmfEscape && ((HwmfEscape)obj).getEscapeFunction() == EscapeFunction.META_ESCAPE_ENHANCED_METAFILE) {
+                    WmfEscapeEMF emfData = ((HwmfEscape)obj).getEscapeData();
+                    if (emfData.isValid()) {
+                        current = obj;
+                        return true;
+                    }
+                }
+            }
+            iterStack.pop();
+        } while (!iterStack.isEmpty());
+
+        return false;
+    }
+
+    @Override
+    public HwmfEmbedded next() {
+        HwmfEmbedded emb;
+        if ((emb = checkHwmfImageRecord()) != null) {
+            return emb;
+        }
+        if ((emb = checkHwmfEscapeRecord()) != null) {
+            return emb;
+        }
+        return null;
+    }
+
+    private HwmfEmbedded checkHwmfImageRecord() {
+        if (!(current instanceof HwmfImageRecord)) {
+            return null;
+        }
+
+        HwmfImageRecord hir = (HwmfImageRecord)current;
+        current = null;
+
+        HwmfEmbedded emb = new HwmfEmbedded();
+        emb.setEmbeddedType(HwmfEmbeddedType.BMP);
+        emb.setData(hir.getBMPData());
+
+        return emb;
+    }
+
+
+    private HwmfEmbedded checkHwmfEscapeRecord() {
+        if (!(current instanceof HwmfEscape)) {
+            return null;
+        }
+        final HwmfEscape esc = (HwmfEscape)current;
+        assert(esc.getEscapeFunction() == EscapeFunction.META_ESCAPE_ENHANCED_METAFILE);
+
+        WmfEscapeEMF img = esc.getEscapeData();
+        assert(img.isValid());
+        current = null;
+
+        final HwmfEmbedded emb = new HwmfEmbedded();
+        emb.setEmbeddedType(HwmfEmbeddedType.EMF);
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        try {
+            for (;;) {
+                bos.write(img.getEmfData());
+
+                current = null;
+                if (img.getRemainingBytes() > 0 && hasNext() && (current instanceof HwmfEscape)) {
+                    img = ((HwmfEscape)current).getEscapeData();
+                } else {
+                    return emb;
+                }
+            }
+        } catch (IOException e) {
+            // ByteArrayOutputStream doesn't throw IOException
+            return null;
+        } finally {
+            emb.setData(bos.toByteArray());
+        }
+    }
+}

Propchange: poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfEmbeddedIterator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfEmbeddedType.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfEmbeddedType.java?rev=1858625&view=auto
==============================================================================
--- poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfEmbeddedType.java (added)
+++ poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfEmbeddedType.java Sat May  4 23:01:53 2019
@@ -0,0 +1,37 @@
+/* ====================================================================
+   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.hwmf.usermodel;
+
+public enum HwmfEmbeddedType {
+    BITMAP(".bitmap"),
+    WMF(".wmf"),
+    EMF(".emf"),
+    EPS(".eps"),
+    JPEG(".jpg"),
+    GIF(".gif"),
+    TIFF(".tiff"),
+    PNG(".png"),
+    BMP(".bmp"),
+    UNKNOWN(".dat");
+
+    public final String extension;
+
+    HwmfEmbeddedType(String extension) {
+        this.extension = extension;
+    }
+}

Propchange: poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfEmbeddedType.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java?rev=1858625&r1=1858624&r2=1858625&view=diff
==============================================================================
--- poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java (original)
+++ poi/trunk/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java Sat May  4 23:01:53 2019
@@ -127,8 +127,10 @@ public class HwmfPicture {
             ctx.scale(graphicsBounds.getWidth()/wmfBounds.getWidth(), graphicsBounds.getHeight()/wmfBounds.getHeight());
             
             HwmfGraphics g = new HwmfGraphics(ctx, wmfBounds);
+            int idx = 0;
             for (HwmfRecord r : records) {
                 r.draw(g);
+                idx++;
             }
         } finally {
             ctx.setTransform(at);
@@ -184,4 +186,8 @@ public class HwmfPicture {
         double coeff = Units.POINT_DPI/inch;
         return new Dimension((int)Math.round(bounds.getWidth()*coeff), (int)Math.round(bounds.getHeight()*coeff));
     }
+
+    public Iterable<HwmfEmbedded> getEmbeddings() {
+        return () -> new HwmfEmbeddedIterator(HwmfPicture.this);
+    }
 }

Modified: poi/trunk/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java?rev=1858625&r1=1858624&r2=1858625&view=diff
==============================================================================
--- poi/trunk/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java (original)
+++ poi/trunk/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java Sat May  4 23:01:53 2019
@@ -35,6 +35,7 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -51,6 +52,8 @@ import org.apache.poi.hemf.record.emf.He
 import org.apache.poi.hemf.record.emf.HemfText;
 import org.apache.poi.hwmf.record.HwmfRecord;
 import org.apache.poi.hwmf.record.HwmfText;
+import org.apache.poi.hwmf.usermodel.HwmfEmbedded;
+import org.apache.poi.hwmf.usermodel.HwmfEmbeddedType;
 import org.apache.poi.hwmf.usermodel.HwmfPicture;
 import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.RecordFormatException;
@@ -73,16 +76,17 @@ public class HemfPictureTest {
         // emfs/govdocs1/844/844795.ppt_2.emf
         // emfs/commoncrawl2/TO/TOYZSTNUSW5OFCFUQ6T5FBLIDLCRF3NH_0.emf
 
-        final boolean writeLog = true;
+        final boolean writeLog = false;
         final boolean dumpRecords = false;
-        final boolean savePng = true;
+        final boolean savePng = false;
+        final boolean dumpEmbedded = true;
 
         Set<String> passed = new HashSet<>();
 
         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/render_emf.7z"))) {
+             SevenZFile sevenZFile = new SevenZFile(new File("tmp/plus_emf.7z"))) {
             for (int idx=0;;idx++) {
                 SevenZArchiveEntry entry = sevenZFile.getNextEntry();
                 if (entry == null) break;
@@ -90,6 +94,11 @@ public class HemfPictureTest {
 
                 if (entry.isDirectory() || !etName.endsWith(".emf") || passed.contains(etName)) continue;
 
+                if (!etName.equals("emfs/commoncrawl2/2S/2SYMYPLNJURGCXJKLNZCJQGIBHVMQTRS_0.emf")) continue;
+
+                // emfs/commoncrawl2/ZJ/ZJT2BZPLQR7DKSKYLYL6GRDEUM2KIO5F_4.emf
+                // emfs/govdocs1/005/005203.ppt_3.emf
+
                 System.out.println(etName);
 
                 int size = sevenZFile.read(buf);
@@ -116,6 +125,18 @@ public class HemfPictureTest {
                     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();
@@ -194,7 +215,7 @@ public class HemfPictureTest {
         if (Files.exists(log)) {
             soo = StandardOpenOption.APPEND;
             try (Stream<String> stream = Files.lines(log)) {
-                stream.forEach((s) -> passed.add(s.split("\\s")[0]));
+                stream.filter(s -> !s.startsWith("#")).forEach((s) -> passed.add(s.split("\\s")[0]));
             }
         } else {
             soo = StandardOpenOption.CREATE;
@@ -380,7 +401,28 @@ public class HemfPictureTest {
         }
     }
 
-     /*
-        govdocs1 064213.doc-0.emf contains an example of extextouta
-     */
+    @Test
+    public void nestedWmfEmf() throws Exception {
+        try (InputStream is = sl_samples.openResourceAsStream("nested_wmf.emf")) {
+            HemfPicture emf1 = new HemfPicture(is);
+            List<HwmfEmbedded> embeds = new ArrayList<>();
+            emf1.getEmbeddings().forEach(embeds::add);
+            assertEquals(1, embeds.size());
+            assertEquals(HwmfEmbeddedType.WMF, embeds.get(0).getEmbeddedType());
+
+            HwmfPicture wmf = new HwmfPicture(new ByteArrayInputStream(embeds.get(0).getRawData()));
+            embeds.clear();
+            wmf.getEmbeddings().forEach(embeds::add);
+            assertEquals(3, embeds.size());
+            assertEquals(HwmfEmbeddedType.EMF, embeds.get(0).getEmbeddedType());
+
+            HemfPicture emf2 = new HemfPicture(new ByteArrayInputStream(embeds.get(0).getRawData()));
+            embeds.clear();
+            emf2.getEmbeddings().forEach(embeds::add);
+            assertTrue(embeds.isEmpty());
+        }
+    }
+
+
+    /* govdocs1 064213.doc-0.emf contains an example of extextouta */
 }
\ No newline at end of file

Modified: poi/trunk/src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java?rev=1858625&r1=1858624&r2=1858625&view=diff
==============================================================================
--- poi/trunk/src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java (original)
+++ poi/trunk/src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java Sat May  4 23:01:53 2019
@@ -47,6 +47,7 @@ import org.apache.poi.hwmf.record.HwmfFo
 import org.apache.poi.hwmf.record.HwmfRecord;
 import org.apache.poi.hwmf.record.HwmfRecordType;
 import org.apache.poi.hwmf.record.HwmfText;
+import org.apache.poi.hwmf.usermodel.HwmfEmbedded;
 import org.apache.poi.hwmf.usermodel.HwmfPicture;
 import org.apache.poi.sl.usermodel.PictureData;
 import org.apache.poi.sl.usermodel.PictureData.PictureType;
@@ -82,8 +83,10 @@ public class TestHwmfParsing {
     @Test
     @Ignore("This is work-in-progress and not a real unit test ...")
     public void paint() throws IOException {
-        File f = samples.getFile("santa.wmf");
-        // File f = new File("bla.wmf");
+        boolean dumpEmbedded = true;
+
+//        File f = samples.getFile("santa.wmf");
+         File f = new File("testme.wmf");
         FileInputStream fis = new FileInputStream(f);
         HwmfPicture wmf = new HwmfPicture(fis);
         fis.close();
@@ -92,12 +95,10 @@ public class TestHwmfParsing {
         int width = Units.pointsToPixel(dim.getWidth());
         // keep aspect ratio for height
         int height = Units.pointsToPixel(dim.getHeight());
-        double max = Math.max(width, height);
-        if (max > 1500) {
-            width *= 1500/max;
-            height *= 1500/max;
-        }
-        
+        double scale = (width > height) ? 1500 / width : 1500 / width;
+        width *= scale;
+        height *= scale;
+
         BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
         Graphics2D g = bufImg.createGraphics();
         g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
@@ -110,6 +111,17 @@ public class TestHwmfParsing {
         g.dispose();
         
         ImageIO.write(bufImg, "PNG", new File("bla.png"));
+
+        if (dumpEmbedded) {
+            int embIdx = 0;
+            for (HwmfEmbedded emb : wmf.getEmbeddings()) {
+                final File embName = new File("build/tmp", "emb_"+embIdx + emb.getEmbeddedType().extension);
+                try (FileOutputStream fos = new FileOutputStream(embName)) {
+                    fos.write(emb.getRawData());
+                }
+                embIdx++;
+            }
+        }
     }
 
     @Test
@@ -190,7 +202,7 @@ public class TestHwmfParsing {
                     int width = Units.pointsToPixel(dim.getWidth());
                     // keep aspect ratio for height
                     int height = Units.pointsToPixel(dim.getHeight());
-                    
+
                     BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
                     Graphics2D g = bufImg.createGraphics();
                     g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

Added: poi/trunk/test-data/slideshow/nested_wmf.emf
URL: http://svn.apache.org/viewvc/poi/trunk/test-data/slideshow/nested_wmf.emf?rev=1858625&view=auto
==============================================================================
Binary file - no diff available.

Propchange: poi/trunk/test-data/slideshow/nested_wmf.emf
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@poi.apache.org
For additional commands, e-mail: commits-help@poi.apache.org