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 2018/10/19 22:53:33 UTC

svn commit: r1844380 - in /poi/branches/hemf/src/scratchpad: src/org/apache/poi/hemf/draw/ src/org/apache/poi/hemf/record/emf/ src/org/apache/poi/hemf/usermodel/ src/org/apache/poi/hwmf/draw/ src/org/apache/poi/hwmf/record/ src/org/apache/poi/hwmf/user...

Author: kiwiwings
Date: Fri Oct 19 22:53:33 2018
New Revision: 1844380

URL: http://svn.apache.org/viewvc?rev=1844380&view=rev
Log:
#60656 - Support export file that contains emf and render it correctly

Modified:
    poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java
    poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java
    poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java
    poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java
    poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java
    poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java
    poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java
    poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java
    poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java
    poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java
    poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java
    poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java
    poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java
    poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java
    poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java
    poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java
    poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java
    poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java
    poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java
    poi/branches/hemf/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java

Modified: poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java
URL: http://svn.apache.org/viewvc/poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java?rev=1844380&r1=1844379&r2=1844380&view=diff
==============================================================================
--- poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java (original)
+++ poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java Fri Oct 19 22:53:33 2018
@@ -52,7 +52,7 @@ public class HemfGraphics extends HwmfGr
     public HemfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) {
         super(graphicsCtx,bbox);
         // add dummy entry for object index 0, as emf is 1-based
-        addObjectTableEntry((ctx)->{});
+        objectIndexes.set(0);
     }
 
     @Override
@@ -105,6 +105,12 @@ public class HemfGraphics extends HwmfGr
         final Path2D path;
         if (useBracket) {
             path = prop.getPath();
+            if (path.getCurrentPoint() == null) {
+                // workaround if a path has been started and no MoveTo command
+                // has been specified before the first lineTo/splineTo
+                final Point2D loc = prop.getLocation();
+                path.moveTo(loc.getX(), loc.getY());
+            }
         } else {
             path = new Path2D.Double();
             Point2D pnt = prop.getLocation();
@@ -135,19 +141,11 @@ public class HemfGraphics extends HwmfGr
      */
     public void addObjectTableEntry(HwmfObjectTableEntry entry, int index) {
         if (index < 1) {
-            super.addObjectTableEntry(entry);
-            return;
-        }
-
-        if (index > objectTable.size()) {
-            throw new IllegalStateException("object table hasn't grown to this index yet");
+            throw new IndexOutOfBoundsException("Object table entry index in EMF must be > 0 - invalid index: "+index);
         }
 
-        if (index == objectTable.size()) {
-            objectTable.add(entry);
-        } else {
-            objectTable.set(index, entry);
-        }
+        objectIndexes.set(index);
+        objectTable.put(index, entry);
     }
 
     @Override

Modified: poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java
URL: http://svn.apache.org/viewvc/poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java?rev=1844380&r1=1844379&r2=1844380&view=diff
==============================================================================
--- poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java (original)
+++ poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java Fri Oct 19 22:53:33 2018
@@ -28,6 +28,7 @@ import java.util.function.Supplier;
 
 import org.apache.poi.hemf.record.emfplus.HemfPlusRecord;
 import org.apache.poi.hemf.record.emfplus.HemfPlusRecordIterator;
+import org.apache.poi.hwmf.usermodel.HwmfPicture;
 import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.Internal;
 import org.apache.poi.util.LittleEndianConsts;
@@ -40,7 +41,7 @@ import org.apache.poi.util.RecordFormatE
  */
 @Internal
 public class HemfComment {
-    private static final int MAX_RECORD_LENGTH = 2_000_000;
+    private static final int MAX_RECORD_LENGTH = HwmfPicture.MAX_RECORD_LENGTH;
 
     public enum HemfCommentRecordType {
         emfGeneric(-1, EmfCommentDataGeneric::new, false),
@@ -145,7 +146,12 @@ public class HemfComment {
             } else {
                 // A 32-bit unsigned integer from the RecordType enumeration that identifies this record
                 // as a comment record. This value MUST be 0x00000046.
-                type = leis.readUInt();
+                try {
+                    type = leis.readUInt();
+                } catch (RuntimeException e) {
+                    // EOF
+                    return null;
+                }
                 assert(type == HemfRecordType.comment.id);
                 // A 32-bit unsigned integer that specifies the size in bytes of this record in the
                 // metafile. This value MUST be a multiple of 4 bytes.
@@ -327,8 +333,12 @@ public class HemfComment {
             for (EmfCommentDataFormat fmt : formats) {
                 int skip = fmt.offData-(leis.getReadIndex()-startIdx);
                 leis.skipFully(skip);
-                fmt.rawData = new byte[fmt.sizeData];
-                leis.readFully(fmt.rawData);
+                fmt.rawData = IOUtils.safelyAllocate(fmt.sizeData, MAX_RECORD_LENGTH);
+                int readBytes = leis.read(fmt.rawData);
+                if (readBytes < fmt.sizeData) {
+                    // EOF
+                    break;
+                }
             }
 
             return leis.getReadIndex()-startIdx;
@@ -405,8 +415,7 @@ public class HemfComment {
         }
 
         @Override
-        public long init(final LittleEndianInputStream leis, final long dataSize)
-                throws IOException {
+        public long init(final LittleEndianInputStream leis, final long dataSize) throws IOException {
             final int startIdx = leis.getReadIndex();
             final int commentIdentifier = (int)leis.readUInt();
             assert(commentIdentifier == HemfCommentRecordType.emfPublic.id);
@@ -431,7 +440,8 @@ public class HemfComment {
             int winMetafileSize = (int)leis.readUInt();
 
             byte[] winMetafile = IOUtils.safelyAllocate(winMetafileSize, MAX_RECORD_LENGTH);
-            leis.readFully(winMetafile);
+            // some emf comments are truncated, so we don't use readFully here
+            leis.read(winMetafile);
 
             return leis.getReadIndex()-startIdx;
         }

Modified: poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java
URL: http://svn.apache.org/viewvc/poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java?rev=1844380&r1=1844379&r2=1844380&view=diff
==============================================================================
--- poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java (original)
+++ poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java Fri Oct 19 22:53:33 2018
@@ -934,7 +934,7 @@ public class HemfDraw {
         public void draw(HemfGraphics ctx) {
             final HemfDrawProperties prop = ctx.getProperties();
             final Path2D path = prop.getPath();
-            if (path != null) {
+            if (path != null && path.getCurrentPoint() != null) {
                 path.closePath();
                 prop.setLocation(path.getCurrentPoint());
             }
@@ -1041,15 +1041,16 @@ public class HemfDraw {
     }
 
     private static void polyTo(final HemfGraphics ctx, final Path2D poly) {
-        final PathIterator pi = poly.getPathIterator(null);
-        if (pi.isDone()) {
-            // ignore empty polys
+        if (poly.getCurrentPoint() == null) {
             return;
         }
 
-        // ignore dummy start point (moveTo)
+        final PathIterator pi = poly.getPathIterator(null);
+        // ignore empty polys and dummy start point (moveTo)
         pi.next();
-        assert (!pi.isDone());
+        if (pi.isDone()) {
+            return;
+        }
 
         ctx.draw((path) -> path.append(pi, true));
     }

Modified: poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java
URL: http://svn.apache.org/viewvc/poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java?rev=1844380&r1=1844379&r2=1844380&view=diff
==============================================================================
--- poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java (original)
+++ poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java Fri Oct 19 22:53:33 2018
@@ -624,19 +624,19 @@ public class HemfFill {
             return undefinedSpace1;
         }
 
-        final LittleEndianInputStream leisDib;
+        final int dibSize = cbBmi+cbBits;
         if (undefinedSpace2 == 0) {
-            leisDib = leis;
-        } else {
-            final ByteArrayOutputStream bos = new ByteArrayOutputStream(cbBmi+cbBits);
-            final long cbBmiSrcAct = IOUtils.copy(leis, bos, cbBmi);
-            assert (cbBmiSrcAct == cbBmi);
-            leis.skipFully(undefinedSpace2);
-            final long cbBitsSrcAct = IOUtils.copy(leis, bos, cbBits);
-            assert (cbBitsSrcAct == cbBits);
-            leisDib = new LittleEndianInputStream(new ByteArrayInputStream(bos.toByteArray()));
+            return undefinedSpace1 + bitmap.init(leis, dibSize);
         }
-        final int dibSize = cbBmi+cbBits;
+
+        final ByteArrayOutputStream bos = new ByteArrayOutputStream(cbBmi+cbBits);
+        final long cbBmiSrcAct = IOUtils.copy(leis, bos, cbBmi);
+        assert (cbBmiSrcAct == cbBmi);
+        leis.skipFully(undefinedSpace2);
+        final long cbBitsSrcAct = IOUtils.copy(leis, bos, cbBits);
+        assert (cbBitsSrcAct == cbBits);
+
+        final LittleEndianInputStream leisDib = new LittleEndianInputStream(new ByteArrayInputStream(bos.toByteArray()));
         final int dibSizeAct = bitmap.init(leisDib, dibSize);
         assert (dibSizeAct <= dibSize);
         return undefinedSpace1 + cbBmi + undefinedSpace2 + cbBits;
@@ -646,8 +646,8 @@ public class HemfFill {
     static long readRgnData(final LittleEndianInputStream leis, final List<Rectangle2D> rgnRects) {
         // *** RegionDataHeader ***
         // A 32-bit unsigned integer that specifies the size of this object in bytes. This MUST be 0x00000020.
-        long rgnHdrSiez = leis.readUInt();
-        assert(rgnHdrSiez == 0x20);
+        long rgnHdrSize = leis.readUInt();
+        assert(rgnHdrSize == 0x20);
         // A 32-bit unsigned integer that specifies the region type. This SHOULD be RDH_RECTANGLES (0x00000001)
         long rgnHdrType = leis.readUInt();
         assert(rgnHdrType == 1);
@@ -729,7 +729,7 @@ public class HemfFill {
         @Override
         public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
             // A 128-bit WMF RectL object, which specifies bounding rectangle, in device units
-            return readRectL(leis, bounds);
+            return (recordSize == 0) ? 0 : readRectL(leis, bounds);
         }
 
         @Override

Modified: poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java
URL: http://svn.apache.org/viewvc/poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java?rev=1844380&r1=1844379&r2=1844380&view=diff
==============================================================================
--- poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java (original)
+++ poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java Fri Oct 19 22:53:33 2018
@@ -17,17 +17,17 @@
 
 package org.apache.poi.hemf.record.emf;
 
-import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionFloat;
 import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionInt;
 import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL;
+import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
 
 import java.awt.geom.Dimension2D;
 import java.awt.geom.Rectangle2D;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 
 import org.apache.poi.util.Dimension2DDouble;
 import org.apache.poi.util.Internal;
-import org.apache.poi.util.LittleEndian;
 import org.apache.poi.util.LittleEndianConsts;
 import org.apache.poi.util.LittleEndianInputStream;
 
@@ -46,8 +46,7 @@ public class HemfHeader implements HemfR
     private long bytes;
     private long records;
     private int handles;
-    private long nDescription;
-    private long offDescription;
+    private String description;
     private long nPalEntries;
     private boolean hasExtension1;
     private long cbPixelFormat;
@@ -79,13 +78,7 @@ public class HemfHeader implements HemfR
         return handles;
     }
 
-    public long getnDescription() {
-        return nDescription;
-    }
-
-    public long getOffDescription() {
-        return offDescription;
-    }
+    public String getDescription() { return description; }
 
     public long getnPalEntries() {
         return nPalEntries;
@@ -131,8 +124,7 @@ public class HemfHeader implements HemfR
                 ", bytes=" + bytes +
                 ", records=" + records +
                 ", handles=" + handles +
-                ", nDescription=" + nDescription +
-                ", offDescription=" + offDescription +
+                ", description=" + description +
                 ", nPalEntries=" + nPalEntries +
                 ", hasExtension1=" + hasExtension1 +
                 ", cbPixelFormat=" + cbPixelFormat +
@@ -156,6 +148,8 @@ public class HemfHeader implements HemfR
             throw new IOException("Not a valid EMF header. Record type:"+recordId);
         }
 
+        int startIdx = leis.getReadIndex();
+
         //bounds
         long size = readRectL(leis, boundsRectangle);
         size += readRectL(leis, frameRectangle);
@@ -174,8 +168,8 @@ public class HemfHeader implements HemfR
         //reserved
         leis.skipFully(LittleEndianConsts.SHORT_SIZE);
 
-        nDescription = leis.readUInt();
-        offDescription = leis.readUInt();
+        int nDescription = (int)leis.readUInt();
+        int offDescription = (int)leis.readUInt();
         nPalEntries = leis.readUInt();
 
         size += 8*LittleEndianConsts.INT_SIZE;
@@ -183,6 +177,16 @@ public class HemfHeader implements HemfR
         size += readDimensionInt(leis, deviceDimension);
         size += readDimensionInt(leis, milliDimension);
 
+        if (nDescription > 0 && offDescription > 0) {
+            int skip = (int)(offDescription - (size + HEADER_SIZE));
+            leis.mark(skip+nDescription*2);
+            leis.skipFully(skip);
+            byte[] buf = new byte[(nDescription-1)*2];
+            leis.readFully(buf);
+            description = new String(buf, StandardCharsets.UTF_16LE).replace((char)0, ' ').trim();
+            leis.reset();
+        }
+
         if (size+12 <= recordSize) {
             hasExtension1 = true;
             cbPixelFormat =  leis.readUInt();
@@ -195,6 +199,7 @@ public class HemfHeader implements HemfR
             hasExtension2 = true;
             size += readDimensionInt(leis, microDimension);
         }
+
         return size;
     }
 }

Modified: poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java
URL: http://svn.apache.org/viewvc/poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java?rev=1844380&r1=1844379&r2=1844380&view=diff
==============================================================================
--- poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java (original)
+++ poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java Fri Oct 19 22:53:33 2018
@@ -45,8 +45,6 @@ import org.apache.poi.util.LittleEndianC
 import org.apache.poi.util.LittleEndianInputStream;
 
 public class HemfMisc {
-    private static final int MAX_RECORD_LENGTH = 10_000_000;
-
     public static class EmfEof implements HemfRecord {
         protected final List<PaletteEntry> palette = new ArrayList<>();
 
@@ -535,6 +533,11 @@ public class HemfMisc {
         public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
             return readPointL(leis, origin);
         }
+
+        @Override
+        public String toString() {
+            return "{ x: "+origin.getX()+", y: "+origin.getY()+" }";
+        }
     }
 
     public static class EmfSetWorldTransform implements HemfRecord {
@@ -581,4 +584,47 @@ public class HemfMisc {
                 ", modifyWorldTransformMode: "+modifyWorldTransformMode+" }";
         }
     }
+
+    public static class EmfCreateMonoBrush16 extends EmfCreatePen {
+        protected HwmfFill.ColorUsage colorUsage;
+        protected final HwmfBitmapDib bitmap = new HwmfBitmapDib();
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.createMonoBrush16;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            final int startIdx = leis.getReadIndex();
+
+            penIndex = (int) leis.readUInt();
+
+            // A 32-bit unsigned integer that specifies how to interpret values in the color
+            // table in the DIB header. This value MUST be in the DIBColors enumeration
+            colorUsage = HwmfFill.ColorUsage.valueOf((int) leis.readUInt());
+
+            // A 32-bit unsigned integer that specifies the offset from the start of this
+            // record to the DIB header, if the record contains a DIB.
+            int offBmi = (int) leis.readUInt();
+
+            // A 32-bit unsigned integer that specifies the size of the DIB header, if the
+            // record contains a DIB.
+            int cbBmi = (int) leis.readUInt();
+
+            // A 32-bit unsigned integer that specifies the offset from the start of this
+            // record to the DIB bits, if the record contains a DIB.
+            int offBits = (int) leis.readUInt();
+
+            // A 32-bit unsigned integer that specifies the size of the DIB bits, if the record
+            // contains a DIB.
+            int cbBits = (int) leis.readUInt();
+
+            int size = 6 * LittleEndianConsts.INT_SIZE;
+
+            size += readBitmap(leis, bitmap, startIdx, offBmi, cbBmi, offBits, cbBits);
+
+            return size;
+        }
+    }
 }

Modified: poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java
URL: http://svn.apache.org/viewvc/poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java?rev=1844380&r1=1844379&r2=1844380&view=diff
==============================================================================
--- poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java (original)
+++ poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java Fri Oct 19 22:53:33 2018
@@ -53,8 +53,15 @@ public class HemfRecordIterator implemen
         if (currentRecord != null && HemfRecordType.eof == currentRecord.getEmfRecordType()) {
             return null;
         }
-        long recordId = stream.readUInt();
-        long recordSize = stream.readUInt();
+
+        final long recordId, recordSize;
+        try {
+            recordId = stream.readUInt();
+            recordSize = stream.readUInt();
+        } catch (RuntimeException e) {
+            // EOF
+            return null;
+        }
 
         HemfRecordType type = HemfRecordType.getById(recordId);
         if (type == null) {

Modified: poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java
URL: http://svn.apache.org/viewvc/poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java?rev=1844380&r1=1844379&r2=1844380&view=diff
==============================================================================
--- poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java (original)
+++ poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java Fri Oct 19 22:53:33 2018
@@ -116,7 +116,7 @@ public enum HemfRecordType {
     polyPolyline16(0x0000005A, HemfDraw.EmfPolyPolyline16::new),
     polyPolygon16(0x0000005B, HemfDraw.EmfPolyPolygon16::new),
     polyDraw16(0x0000005C, HemfDraw.EmfPolyDraw16::new),
-    createmonobrush16(0x0000005D, UnimplementedHemfRecord::new),
+    createMonoBrush16(0x0000005D, HemfMisc.EmfCreateMonoBrush16::new),
     createDibPatternBrushPt(0x0000005E, HemfMisc.EmfCreateDibPatternBrushPt::new),
     extCreatePen(0x0000005F, HemfMisc.EmfExtCreatePen::new),
     polytextouta(0x00000060, HemfText.PolyTextOutA::new),

Modified: poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java
URL: http://svn.apache.org/viewvc/poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java?rev=1844380&r1=1844379&r2=1844380&view=diff
==============================================================================
--- poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java (original)
+++ poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java Fri Oct 19 22:53:33 2018
@@ -113,36 +113,53 @@ public class HemfText {
             int offDx = (int)leis.readUInt();
             size += LittleEndianConsts.INT_SIZE;
 
-            int undefinedSpace1 = (int)(offString - (size + HEADER_SIZE));
-            assert (undefinedSpace1 >= 0);
-            leis.skipFully(undefinedSpace1);
-            size += undefinedSpace1;
+            // handle dx before string and other way round
+            for (char op : ((offDx < offString) ? "ds" : "sd").toCharArray()) {
+                switch (op) {
+                    case 'd': {
+                        dx.clear();
+                        int undefinedSpace2 = (int) (offDx - (size + HEADER_SIZE));
+                        if (offDx > 0 && undefinedSpace2 >= 0 && offDx-HEADER_SIZE < recordSize) {
+                            leis.skipFully(undefinedSpace2);
+                            size += undefinedSpace2;
 
-            rawTextBytes = IOUtils.safelyAllocate(stringLength*(isUnicode()?2:1), MAX_RECORD_LENGTH);
-            leis.readFully(rawTextBytes);
-            size += rawTextBytes.length;
+                            // An array of 32-bit unsigned integers that specify the output spacing between the origins of adjacent
+                            // character cells in logical units. The location of this field is specified by the value of offDx
+                            // in bytes from the start of this record. If spacing is defined, this field contains the same number
+                            // of values as characters in the output string.
+                            //
+                            // If the Options field of the EmrText object contains the ETO_PDY flag, then this buffer
+                            // contains twice as many values as there are characters in the output string, one
+                            // horizontal and one vertical offset for each, in that order.
+                            //
+                            // If ETO_RTLREADING is specified, characters are laid right to left instead of left to right.
+                            // No other options affect the interpretation of this field.
+                            final int maxSize = (int)Math.min((offDx < offString) ? (offString-HEADER_SIZE) : recordSize, recordSize);
+                            while (size <= maxSize-LittleEndianConsts.INT_SIZE) {
+                                dx.add((int) leis.readUInt());
+                                size += LittleEndianConsts.INT_SIZE;
+                            }
+                        }
+                        if (dx.size() < stringLength) {
+                            // invalid dx array
+                            dx.clear();
+                        }
+                        break;
+                    }
+                    default:
+                    case 's': {
+                        int undefinedSpace1 = (int)(offString - (size + HEADER_SIZE));
+                        if (offString > 0 && undefinedSpace1 >= 0 && offString-HEADER_SIZE < recordSize) {
+                            leis.skipFully(undefinedSpace1);
+                            size += undefinedSpace1;
 
-            dx.clear();
-            if (offDx > 0) {
-                int undefinedSpace2 = (int) (offDx - (size + HEADER_SIZE));
-                assert (undefinedSpace2 >= 0);
-                leis.skipFully(undefinedSpace2);
-                size += undefinedSpace2;
-
-                // An array of 32-bit unsigned integers that specify the output spacing between the origins of adjacent
-                // character cells in logical units. The location of this field is specified by the value of offDx
-                // in bytes from the start of this record. If spacing is defined, this field contains the same number
-                // of values as characters in the output string.
-                //
-                // If the Options field of the EmrText object contains the ETO_PDY flag, then this buffer
-                // contains twice as many values as there are characters in the output string, one
-                // horizontal and one vertical offset for each, in that order.
-                //
-                // If ETO_RTLREADING is specified, characters are laid right to left instead of left to right.
-                // No other options affect the interpretation of this field.
-                while (size < recordSize) {
-                    dx.add((int) leis.readUInt());
-                    size += LittleEndianConsts.INT_SIZE;
+                            final int maxSize = (int)Math.min(recordSize-size, stringLength * (isUnicode() ? 2 : 1));
+                            rawTextBytes = IOUtils.safelyAllocate(maxSize, MAX_RECORD_LENGTH);
+                            leis.readFully(rawTextBytes);
+                            size += maxSize;
+                            break;
+                        }
+                    }
                 }
             }
 

Modified: poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java
URL: http://svn.apache.org/viewvc/poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java?rev=1844380&r1=1844379&r2=1844380&view=diff
==============================================================================
--- poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java (original)
+++ poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java Fri Oct 19 22:53:33 2018
@@ -38,9 +38,10 @@ public class HemfWindowing {
         @Override
         public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
             // cx (4 bytes): A 32-bit unsigned integer that defines the x-coordinate of the point.
-            width = (int)leis.readUInt();
+            int width = (int)leis.readUInt();
             // cy (4 bytes): A 32-bit unsigned integer that defines the y-coordinate of the point.
-            height = (int)leis.readUInt();
+            int height = (int)leis.readUInt();
+            size.setSize(width, height);
 
             return 2*LittleEndianConsts.INT_SIZE;
         }

Modified: poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java
URL: http://svn.apache.org/viewvc/poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java?rev=1844380&r1=1844379&r2=1844380&view=diff
==============================================================================
--- poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java (original)
+++ poi/branches/hemf/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java Fri Oct 19 22:53:33 2018
@@ -34,6 +34,7 @@ import org.apache.poi.hemf.draw.HemfGrap
 import org.apache.poi.hemf.record.emf.HemfHeader;
 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.util.Dimension2DDouble;
 import org.apache.poi.util.Internal;
 import org.apache.poi.util.LittleEndianInputStream;
@@ -47,6 +48,7 @@ public class HemfPicture implements Iter
 
     private final LittleEndianInputStream stream;
     private final List<HemfRecord> records = new ArrayList<>();
+    private boolean isParsed = false;
 
     public HemfPicture(InputStream is) throws IOException {
         this(new LittleEndianInputStream(is));
@@ -61,7 +63,10 @@ public class HemfPicture implements Iter
     }
 
     public List<HemfRecord> getRecords() {
-        if (records.isEmpty()) {
+        if (!isParsed) {
+            // in case the (first) parsing throws an exception, we can provide the
+            // records up to that point
+            isParsed = true;
             new HemfRecordIterator(stream).forEachRemaining(records::add);
         }
         return records;
@@ -89,10 +94,26 @@ public class HemfPicture implements Iter
      */
     public Dimension2D getSize() {
         HemfHeader header = (HemfHeader)getRecords().get(0);
+        final double coeff = (double) Units.EMU_PER_CENTIMETER / Units.EMU_PER_POINT / 10.;
         Rectangle2D dim = header.getFrameRectangle();
+        double width = dim.getWidth(), height = dim.getHeight();
+        if (dim.isEmpty() || Math.rint(width*coeff) == 0 || Math.rint(height*coeff) == 0) {
+            for (HemfRecord r : getRecords()) {
+                if (r instanceof HemfWindowing.EmfSetWindowExtEx) {
+                    Dimension2D d = ((HemfWindowing.EmfSetWindowExtEx)r).getSize();
+                    width = d.getWidth();
+                    height = d.getHeight();
+                    // keep searching - sometimes there's another record
+                }
+            }
+        }
+
+        if (Math.rint(width*coeff) == 0 || Math.rint(height*coeff) == 0) {
+            width = 100;
+            height = 100;
+        }
 
-        double coeff = (double)Units.EMU_PER_CENTIMETER/Units.EMU_PER_POINT/10.;
-        return new Dimension2DDouble(dim.getWidth()*coeff, dim.getHeight()*coeff);
+        return new Dimension2DDouble(width*coeff, height*coeff);
     }
 
     public void draw(Graphics2D ctx, Rectangle2D graphicsBounds) {

Modified: poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java
URL: http://svn.apache.org/viewvc/poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java?rev=1844380&r1=1844379&r2=1844380&view=diff
==============================================================================
--- poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java (original)
+++ poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java Fri Oct 19 22:53:33 2018
@@ -37,6 +37,7 @@ import org.apache.poi.hwmf.record.HwmfMa
 import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode;
 import org.apache.poi.hwmf.record.HwmfPalette.PaletteEntry;
 import org.apache.poi.hwmf.record.HwmfPenStyle;
+import org.apache.poi.hwmf.record.HwmfTernaryRasterOp;
 import org.apache.poi.hwmf.record.HwmfText.HwmfTextAlignment;
 import org.apache.poi.hwmf.record.HwmfText.HwmfTextVerticalAlignment;
 
@@ -65,6 +66,7 @@ public class HwmfDrawProperties {
     private HwmfTextVerticalAlignment textVAlignLatin;
     private HwmfTextAlignment textAlignAsian;
     private HwmfTextVerticalAlignment textVAlignAsian;
+    private HwmfTernaryRasterOp rasterOp;
 
     public HwmfDrawProperties() {
         window = new Rectangle2D.Double(0, 0, 1, 1);
@@ -86,6 +88,7 @@ public class HwmfDrawProperties {
         textVAlignLatin = HwmfTextVerticalAlignment.TOP;
         textAlignAsian = HwmfTextAlignment.RIGHT;
         textVAlignAsian = HwmfTextVerticalAlignment.TOP;
+        rasterOp = HwmfTernaryRasterOp.PATCOPY;
     }
     
     public HwmfDrawProperties(HwmfDrawProperties other) {
@@ -122,6 +125,7 @@ public class HwmfDrawProperties {
         this.textVAlignLatin = other.textVAlignLatin;
         this.textAlignAsian = other.textAlignAsian;
         this.textVAlignAsian = other.textVAlignAsian;
+        this.rasterOp = other.rasterOp;
     }
     
     public void setViewportExt(double width, double height) {
@@ -375,4 +379,12 @@ public class HwmfDrawProperties {
     public int getWindingRule() {
         return getPolyfillMode().awtFlag;
     }
+
+    public HwmfTernaryRasterOp getRasterOp() {
+        return rasterOp;
+    }
+
+    public void setRasterOp(HwmfTernaryRasterOp rasterOp) {
+        this.rasterOp = rasterOp;
+    }
 }

Modified: poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java
URL: http://svn.apache.org/viewvc/poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java?rev=1844380&r1=1844379&r2=1844380&view=diff
==============================================================================
--- poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java (original)
+++ poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java Fri Oct 19 22:53:33 2018
@@ -31,11 +31,11 @@ import java.awt.geom.Rectangle2D;
 import java.awt.image.BufferedImage;
 import java.nio.charset.Charset;
 import java.text.AttributedString;
-import java.util.ArrayList;
+import java.util.BitSet;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.ListIterator;
 import java.util.NoSuchElementException;
+import java.util.TreeMap;
 
 import org.apache.commons.codec.Charsets;
 import org.apache.poi.common.usermodel.fonts.FontInfo;
@@ -56,7 +56,8 @@ public class HwmfGraphics {
     protected final List<HwmfDrawProperties> propStack = new LinkedList<>();
     protected HwmfDrawProperties prop;
     protected final Graphics2D graphicsCtx;
-    protected final List<HwmfObjectTableEntry> objectTable = new ArrayList<>();
+    protected final BitSet objectIndexes = new BitSet();
+    protected final TreeMap<Integer,HwmfObjectTableEntry> objectTable = new TreeMap<>();
 
     private static final Charset DEFAULT_CHARSET = LocaleUtil.CHARSET_1252;
     /** Bounding box from the placeable header */
@@ -83,7 +84,11 @@ public class HwmfGraphics {
     }
 
     public void draw(Shape shape) {
-        HwmfLineDash lineDash = getProperties().getPenStyle().getLineDash();
+        HwmfPenStyle ps = getProperties().getPenStyle();
+        if (ps == null) {
+            return;
+        }
+        HwmfLineDash lineDash = ps.getLineDash();
         if (lineDash == HwmfLineDash.NULL) {
             // line is not drawn
             return;
@@ -201,15 +206,9 @@ public class HwmfGraphics {
      * @param entry
      */
     public void addObjectTableEntry(HwmfObjectTableEntry entry) {
-        ListIterator<HwmfObjectTableEntry> oIter = objectTable.listIterator();
-        while (oIter.hasNext()) {
-            HwmfObjectTableEntry tableEntry = oIter.next();
-            if (tableEntry == null) {
-                oIter.set(entry);
-                return;
-            }
-        }
-        objectTable.add(entry);
+        int objIdx = objectIndexes.nextClearBit(0);
+        objectIndexes.set(objIdx);
+        objectTable.put(objIdx, entry);
     }
 
     /**
@@ -242,7 +241,13 @@ public class HwmfGraphics {
      * @throws IndexOutOfBoundsException if the index is out of range
      */
     public void unsetObjectTableEntry(int index) {
-        objectTable.set(index, null);
+        if (index < 0) {
+            throw new IndexOutOfBoundsException("Invalid index: "+index);
+        }
+        // sometime emfs remove object table entries before they set them
+        // so ignore requests, if the table entry doesn't exist
+        objectTable.remove(index);
+        objectIndexes.clear(index);
     }
     
     /**
@@ -353,6 +358,9 @@ public class HwmfGraphics {
         }
         AttributedString as = new AttributedString(textString);
         addAttributes(as, font);
+
+        // disabled for the time being, as the results aren't promising
+        /*
         if (dx != null && !dx.isEmpty()) {
             //for multi-byte encodings (e.g. Shift_JIS), the byte length
             //might not equal the string length().
@@ -371,22 +379,23 @@ public class HwmfGraphics {
 
             final int cps = textString.codePointCount(0, textString.length());
             final int unicodeSteps = Math.max(dx.size()/cps, 1);
-            int dxPosition = 0;
+            int dxPosition = 0, lastDxPosition = 0;
             int beginIndex = 0;
-            int[] chars = {0};
             while (beginIndex < textString.length() && dxPosition < dx.size()) {
                 int endIndex = textString.offsetByCodePoints(beginIndex, 1);
                 if (beginIndex > 0) {
                     // Tracking works as a prefix/advance space on characters whereas
                     // dx[...] is the complete width of the current char
                     // therefore we need to add the additional/suffix width to the next char
-                    as.addAttribute(TextAttribute.TRACKING, (float)((dx.get(dxPosition) - fontW) / fontH), beginIndex, endIndex);
+
+                    as.addAttribute(TextAttribute.TRACKING, (float)((dx.get(lastDxPosition) - fontW) / fontH), beginIndex, endIndex);
                 }
+                lastDxPosition = dxPosition;
                 dxPosition += (isUnicode) ? unicodeSteps : (endIndex-beginIndex);
                 beginIndex = endIndex;
             }
         }
-        
+        */
         
         double angle = Math.toRadians(-font.getEscapement()/10.);
 
@@ -439,4 +448,20 @@ public class HwmfGraphics {
             return fontHeight*3/4;
         }
     }
+
+    public void drawImage(BufferedImage img, Rectangle2D srcBounds, Rectangle2D dstBounds) {
+        // prop.getRasterOp();
+        graphicsCtx.drawImage(img,
+            (int)dstBounds.getX(),
+            (int)dstBounds.getY(),
+            (int)(dstBounds.getX()+dstBounds.getWidth()),
+            (int)(dstBounds.getY()+dstBounds.getHeight()),
+            (int)srcBounds.getX(),
+            (int)srcBounds.getY(),
+            (int)(srcBounds.getX()+srcBounds.getWidth()),
+            (int)(srcBounds.getY()+srcBounds.getHeight()),
+            null, // getProperties().getBackgroundColor().getColor(),
+            null
+        );
+    }
 }

Modified: poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java
URL: http://svn.apache.org/viewvc/poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java?rev=1844380&r1=1844379&r2=1844380&view=diff
==============================================================================
--- poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java (original)
+++ poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java Fri Oct 19 22:53:33 2018
@@ -17,14 +17,21 @@
 
 package org.apache.poi.hwmf.record;
 
-import javax.imageio.ImageIO;
+import java.awt.AlphaComposite;
+import java.awt.BasicStroke;
 import java.awt.Color;
 import java.awt.Graphics2D;
+import java.awt.LinearGradientPaint;
+import java.awt.MultipleGradientPaint;
+import java.awt.RenderingHints;
 import java.awt.image.BufferedImage;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 
+import javax.imageio.ImageIO;
+
+import org.apache.poi.hwmf.usermodel.HwmfPicture;
 import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.LittleEndian;
 import org.apache.poi.util.LittleEndianConsts;
@@ -38,7 +45,7 @@ import org.apache.poi.util.RecordFormatE
  */
 public class HwmfBitmapDib {
 
-    private static final int MAX_RECORD_LENGTH = 10000000;
+    private static final int MAX_RECORD_LENGTH = HwmfPicture.MAX_RECORD_LENGTH;
 
     public static enum BitCount {
         /**
@@ -225,14 +232,23 @@ public class HwmfBitmapDib {
         introSize += readColors(leis);
         assert(introSize < 10000);
 
-        int fileSize = (headerImageSize < headerSize) ? recordSize : (int)Math.min(introSize+headerImageSize,recordSize);
-        
         leis.reset();
-        imageData = IOUtils.toByteArray(leis, fileSize);
-        
+
         assert( headerSize != 0x0C || ((((headerWidth * headerPlanes * headerBitCount.flag + 31) & ~31) / 8) * Math.abs(headerHeight)) == headerImageSize);
 
-        return fileSize;
+        if (headerImageSize < headerSize) {
+            imageData = IOUtils.safelyAllocate(recordSize, MAX_RECORD_LENGTH);
+            leis.readFully(imageData);
+            return recordSize;
+        } else {
+            int fileSize = (int)Math.min(introSize+headerImageSize,recordSize);
+            imageData = IOUtils.safelyAllocate(fileSize, MAX_RECORD_LENGTH);
+            leis.readFully(imageData, 0, introSize);
+            leis.skipFully(recordSize-fileSize);
+            // emfs are sometimes truncated, read as much as possible
+            int readBytes = leis.read(imageData, introSize, fileSize-introSize);
+            return introSize+(recordSize-fileSize)+readBytes;
+        }
     }
 
     protected int readHeader(LittleEndianInputStream leis) throws IOException {
@@ -262,6 +278,9 @@ public class HwmfBitmapDib {
             headerBitCount = BitCount.valueOf(leis.readUShort());
             size += 4*LittleEndianConsts.SHORT_SIZE;
         } else {
+            // fix header size, sometimes this is invalid
+            headerSize = 40;
+
             // BitmapInfoHeader
             // A 32-bit signed integer that defines the width of the DIB, in pixels.
             // This value MUST be positive.
@@ -306,7 +325,6 @@ public class HwmfBitmapDib {
             headerColorImportant = leis.readUInt();
             size += 8*LittleEndianConsts.INT_SIZE+2*LittleEndianConsts.SHORT_SIZE;
         }
-        assert(size == headerSize);
         return size;
     }
 
@@ -374,6 +392,10 @@ public class HwmfBitmapDib {
         return size;
     }
 
+    public boolean isValid() {
+        return (imageData != null);
+    }
+
     public InputStream getBMPStream() {
         return new ByteArrayInputStream(getBMPData());
     }
@@ -407,14 +429,38 @@ public class HwmfBitmapDib {
     public BufferedImage getImage() {
         try {
             return ImageIO.read(getBMPStream());
-        } catch (IOException e) {
-            logger.log(POILogger.ERROR, "invalid bitmap data - returning black opaque image");
-            BufferedImage bi = new BufferedImage(headerWidth, headerHeight, BufferedImage.TYPE_INT_ARGB);
-            Graphics2D g = bi.createGraphics();
-            g.setPaint(Color.black);
-            g.fillRect(0, 0, headerWidth, headerHeight);
-            g.dispose();
-            return bi;
+        } catch (IOException|RuntimeException e) {
+            logger.log(POILogger.ERROR, "invalid bitmap data - returning placeholder image");
+            return getPlaceholder();
         }
     }
+
+    protected BufferedImage getPlaceholder() {
+        BufferedImage bi = new BufferedImage(headerWidth, headerHeight, BufferedImage.TYPE_INT_ARGB);
+        Graphics2D g = bi.createGraphics();
+        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+        g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+
+        g.setComposite(AlphaComposite.Clear);
+        g.fillRect(0, 0, headerWidth, headerHeight);
+
+        final int arcs = Math.min(headerWidth, headerHeight) / 7;
+
+        Color bg = Color.LIGHT_GRAY;
+        Color fg = Color.GRAY;
+        LinearGradientPaint lgp = new LinearGradientPaint(0f, 0f, 5, 5,
+                new float[] {0,.1f,.1001f}, new Color[] {fg,fg,bg}, MultipleGradientPaint.CycleMethod.REFLECT);
+        g.setComposite(AlphaComposite.SrcOver.derive(0.4f));
+        g.setPaint(lgp);
+        g.fillRoundRect(0, 0, headerWidth-1, headerHeight-1, arcs, arcs);
+
+        g.setColor(Color.DARK_GRAY);
+        g.setComposite(AlphaComposite.Src);
+        g.setStroke(new BasicStroke(2));
+        g.drawRoundRect(0, 0, headerWidth-1, headerHeight-1, arcs, arcs);
+        g.dispose();
+        return bi;
+    }
 }

Modified: poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java
URL: http://svn.apache.org/viewvc/poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java?rev=1844380&r1=1844379&r2=1844380&view=diff
==============================================================================
--- poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java (original)
+++ poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java Fri Oct 19 22:53:33 2018
@@ -433,7 +433,7 @@ public class HwmfFill {
      * The source of the color data is a DIB, and the destination of the transfer is
      * the current output region in the playback device context.
      */
-    public static class WmfStretchDib implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry {
+    public static class WmfStretchDib implements HwmfRecord, HwmfImageRecord {
         /**
          * A 32-bit unsigned integer that defines how the source pixels, the current brush in
          * the playback device context, and the destination pixels are to be combined to
@@ -487,12 +487,10 @@ public class HwmfFill {
 
         @Override
         public void draw(HwmfGraphics ctx) {
-            ctx.addObjectTableEntry(this);
-        }
-        
-        @Override
-        public void applyObject(HwmfGraphics ctx) {
-            
+            if (dib.isValid()) {
+                ctx.getProperties().setRasterOp(rasterOperation);
+                ctx.drawImage(getImage(), srcBounds, dstBounds);
+            }
         }
 
         @Override
@@ -720,7 +718,7 @@ public class HwmfFill {
 
         @Override
         public BufferedImage getImage() {
-            return (target == null) ? null : target.getImage();
+            return (target != null && target.isValid()) ? target.getImage() : null;
         }
     }
 

Modified: poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java
URL: http://svn.apache.org/viewvc/poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java?rev=1844380&r1=1844379&r2=1844380&view=diff
==============================================================================
--- poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java (original)
+++ poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java Fri Oct 19 22:53:33 2018
@@ -179,6 +179,11 @@ public class HwmfMisc {
         public void draw(HwmfGraphics ctx) {
             ctx.getProperties().setBkMode(bkMode);
         }
+
+        @Override
+        public String toString() {
+            return "{ bkMode: '"+bkMode+"' }";
+        }
     }
 
     /**
@@ -436,7 +441,7 @@ public class HwmfMisc {
 
         @Override
         public BufferedImage getImage() {
-            if (patternDib != null) {
+            if (patternDib != null && patternDib.isValid()) {
                 return patternDib.getImage();
             } else if (pattern16 != null) {
                 return pattern16.getImage();

Modified: poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java
URL: http://svn.apache.org/viewvc/poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java?rev=1844380&r1=1844379&r2=1844380&view=diff
==============================================================================
--- poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java (original)
+++ poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java Fri Oct 19 22:53:33 2018
@@ -601,6 +601,54 @@ public class HwmfText {
                 props.setTextVAlignAsian(HwmfTextVerticalAlignment.TOP);
             }
         }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("{ align: '");
+
+            if (TA_CENTER.isSet(textAlignmentMode)) {
+                sb.append("center");
+            } else if (TA_RIGHT.isSet(textAlignmentMode)) {
+                sb.append("right");
+            } else {
+                sb.append("left");
+            }
+
+            sb.append("', align-asian: '");
+
+            if (VTA_CENTER.isSet(textAlignmentMode)) {
+                sb.append("center");
+            } else if (VTA_LEFT.isSet(textAlignmentMode)) {
+                sb.append("left");
+            } else {
+                sb.append("right");
+            }
+
+            sb.append("', valign: '");
+
+            if (TA_BASELINE.isSet(textAlignmentMode)) {
+                sb.append("baseline");
+            } else if (TA_BOTTOM.isSet(textAlignmentMode)) {
+                sb.append("bottom");
+            } else {
+                sb.append("top");
+            }
+
+            sb.append("', valign-asian: '");
+
+            if (VTA_BASELINE.isSet(textAlignmentMode)) {
+                sb.append("baseline");
+            } else if (VTA_BOTTOM.isSet(textAlignmentMode)) {
+                sb.append("bottom");
+            } else {
+                sb.append("top");
+            }
+
+            sb.append("' }");
+
+            return sb.toString();
+        }
     }
     
     public static class WmfCreateFontIndirect implements HwmfRecord, HwmfObjectTableEntry {

Modified: poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java
URL: http://svn.apache.org/viewvc/poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java?rev=1844380&r1=1844379&r2=1844380&view=diff
==============================================================================
--- poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java (original)
+++ poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java Fri Oct 19 22:53:33 2018
@@ -21,10 +21,12 @@ import static org.apache.poi.hwmf.record
 
 import java.awt.Shape;
 import java.awt.geom.Area;
+import java.awt.geom.Dimension2D;
 import java.awt.geom.Rectangle2D;
 import java.io.IOException;
 
 import org.apache.poi.hwmf.draw.HwmfGraphics;
+import org.apache.poi.util.Dimension2DDouble;
 import org.apache.poi.util.LittleEndianConsts;
 import org.apache.poi.util.LittleEndianInputStream;
 
@@ -170,11 +172,7 @@ public class HwmfWindowing {
      */
     public static class WmfSetWindowExt implements HwmfRecord {
 
-        /** A signed integer that defines the vertical extent of the window in logical units. */
-        protected int height;
-
-        /** A signed integer that defines the horizontal extent of the window in logical units. */
-        protected int width;
+        protected final Dimension2D size = new Dimension2DDouble();
 
         @Override
         public HwmfRecordType getWmfRecordType() {
@@ -183,23 +181,22 @@ public class HwmfWindowing {
 
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
-            height = leis.readShort();
-            width = leis.readShort();
+            // A signed integer that defines the vertical extent of the window in logical units.
+            int height = leis.readShort();
+            // A signed integer that defines the horizontal extent of the window in logical units.
+            int width = leis.readShort();
+            size.setSize(width, height);
             return 2*LittleEndianConsts.SHORT_SIZE;
         }
 
         @Override
         public void draw(HwmfGraphics ctx) {
-            ctx.getProperties().setWindowExt(width, height);
+            ctx.getProperties().setWindowExt(size.getWidth(), size.getHeight());
             ctx.updateWindowMapMode();
         }
 
-        public int getHeight() {
-            return height;
-        }
-
-        public int getWidth() {
-            return width;
+        public Dimension2D getSize() {
+            return size;
         }
     }
 

Modified: poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java
URL: http://svn.apache.org/viewvc/poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java?rev=1844380&r1=1844379&r2=1844380&view=diff
==============================================================================
--- poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java (original)
+++ poi/branches/hemf/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java Fri Oct 19 22:53:33 2018
@@ -43,6 +43,9 @@ import org.apache.poi.util.RecordFormatE
 import org.apache.poi.util.Units;
 
 public class HwmfPicture {
+    /** Max. record length - processing longer records will throw an exception */
+    public static final int MAX_RECORD_LENGTH = 50_000_000;
+
     private static final POILogger logger = POILogFactory.getLogger(HwmfPicture.class);
     
     final List<HwmfRecord> records = new ArrayList<>();
@@ -156,7 +159,7 @@ public class HwmfPicture {
             if (wOrg == null || wExt == null) {
                 throw new RuntimeException("invalid wmf file - window records are incomplete.");
             }
-            return new Rectangle2D.Double(wOrg.getX(), wOrg.getY(), wExt.getWidth(), wExt.getHeight());
+            return new Rectangle2D.Double(wOrg.getX(), wOrg.getY(), wExt.getSize().getWidth(), wExt.getSize().getHeight());
         }        
     }
     

Modified: poi/branches/hemf/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java
URL: http://svn.apache.org/viewvc/poi/branches/hemf/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java?rev=1844380&r1=1844379&r2=1844380&view=diff
==============================================================================
--- poi/branches/hemf/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java (original)
+++ poi/branches/hemf/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java Fri Oct 19 22:53:33 2018
@@ -22,26 +22,22 @@ import static org.apache.poi.POITestCase
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import java.awt.Graphics2D;
-import java.awt.RenderingHints;
-import java.awt.geom.Dimension2D;
 import java.awt.geom.Point2D;
-import java.awt.geom.Rectangle2D;
-import java.awt.image.BufferedImage;
+import java.io.BufferedWriter;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-import javax.imageio.ImageIO;
+import java.util.stream.Stream;
 
 import org.apache.poi.POIDataSamples;
 import org.apache.poi.hemf.record.emf.HemfComment;
@@ -54,8 +50,6 @@ import org.apache.poi.hemf.record.emf.He
 import org.apache.poi.hemf.record.emf.HemfText;
 import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.RecordFormatException;
-import org.apache.poi.util.Units;
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class HemfPictureTest {
@@ -63,69 +57,130 @@ public class HemfPictureTest {
     private static final POIDataSamples ss_samples = POIDataSamples.getSpreadSheetInstance();
     private static final POIDataSamples sl_samples = POIDataSamples.getSlideShowInstance();
 
+    /*
     @Test
-    @Ignore("Only for manual tests")
+    @Ignore("Only for manual tests - need to add org.tukaani:xz:1.8 for this to work")
     public void paint() throws IOException {
-//        File f = new File("picture_14.emf"); // sl_samples.getFile("wrench.emf");
-//        try (FileInputStream fis = new FileInputStream(f)) {
+        byte buf[] = new byte[50_000_000];
 
-        try (ZipInputStream zis = new ZipInputStream(new FileInputStream("tmp/emf.zip"))) {
-            for (;;) {
-                ZipEntry ze = zis.getNextEntry();
-                if (ze == null) {
-                    break;
-                }
-                final File pngName = new File("build/tmp",ze.getName().replaceFirst( ".*/","").replace(".emf", ".png"));
-                if (pngName.exists()) {
-                    continue;
-                }
+        final boolean writeLog = true;
+        final boolean savePng = true;
 
+        Set<String> passed = new HashSet<>();
 
-                // 263/263282_000.emf
-//                if (!ze.getName().contains("298/298837_000.emf")) continue;
-                HemfPicture emf = new HemfPicture(zis);
-                System.out.println(ze.getName());
-
-                Dimension2D dim = emf.getSize();
-                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;
+        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"))) {
+            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;
+
+                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;
+                    }
                 }
 
-                BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
-                Graphics2D g = bufImg.createGraphics();
-                g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-                g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
-                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
-                g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
-
-                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");
+                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;
                     }
-                    i++;
-                }
-                fw.close();
 
-                emf.draw(g, new Rectangle2D.Double(0, 0, width, height));
+                    BufferedImage bufImg = new BufferedImage((int)Math.ceil(width), (int)Math.ceil(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_QUALITY);
+                    g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+                    g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+
+                    emf.draw(g, new Rectangle2D.Double(0, 0, width, height));
+
+                    final File pngName = new File("build/tmp", etName.replaceFirst(".*"+"/", "").replace(".emf", ".png"));
+                    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();
+                }
 
-                g.dispose();
+                if (writeLog) {
+                    sucWrite.write(etName + "\n");
+                    sucWrite.flush();
+                }
+            }
+        }
+    } */
 
-                ImageIO.write(bufImg, "PNG", pngName);
+    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();
+    }
 
-//                break;
+    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.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 {



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