]> source.dussan.org Git - poi.git/commitdiff
#60656 - Support export file that contains emf and render it correctly
authorAndreas Beeker <kiwiwings@apache.org>
Fri, 14 Sep 2018 21:37:37 +0000 (21:37 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Fri, 14 Sep 2018 21:37:37 +0000 (21:37 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1840956 13f79535-47bb-0310-9956-ffa450edef68

61 files changed:
src/java/org/apache/poi/util/Dimension2DDouble.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/geom/Dimension2dDouble.java [deleted file]
src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFPage.java
src/ooxml/java/org/apache/poi/xdgf/util/VsdxToPng.java
src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/extractor/HemfExtractor.java [deleted file]
src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusHeader.java [deleted file]
src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecord.java [deleted file]
src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecordType.java [deleted file]
src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/UnimplementedHemfPlusRecord.java [deleted file]
src/scratchpad/src/org/apache/poi/hemf/record/AbstractHemfComment.java [deleted file]
src/scratchpad/src/org/apache/poi/hemf/record/HemfComment.java [deleted file]
src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFPlus.java [deleted file]
src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFSpool.java [deleted file]
src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentPublic.java [deleted file]
src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentRecord.java [deleted file]
src/scratchpad/src/org/apache/poi/hemf/record/HemfHeader.java [deleted file]
src/scratchpad/src/org/apache/poi/hemf/record/HemfRecord.java [deleted file]
src/scratchpad/src/org/apache/poi/hemf/record/HemfRecordType.java [deleted file]
src/scratchpad/src/org/apache/poi/hemf/record/HemfText.java [deleted file]
src/scratchpad/src/org/apache/poi/hemf/record/UnimplementedHemfRecord.java [deleted file]
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFont.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPalette.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/record/emf/UnimplementedHemfRecord.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecord.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordIterator.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordType.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/UnimplementedHemfPlusRecord.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBrushStyle.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfEscape.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFont.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMapMode.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPalette.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPenStyle.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRecord.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRecordType.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java
src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java
src/scratchpad/testcases/org/apache/poi/hemf/extractor/HemfExtractorTest.java [deleted file]
src/scratchpad/testcases/org/apache/poi/hemf/hemfplus/extractor/HemfPlusExtractorTest.java
src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java [new file with mode: 0644]
src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java

diff --git a/src/java/org/apache/poi/util/Dimension2DDouble.java b/src/java/org/apache/poi/util/Dimension2DDouble.java
new file mode 100644 (file)
index 0000000..d089064
--- /dev/null
@@ -0,0 +1,73 @@
+/* ====================================================================
+   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.util;
+
+import java.awt.geom.Dimension2D;
+
+public class Dimension2DDouble extends Dimension2D {
+
+    double width;
+    double height;
+
+    public Dimension2DDouble() {
+        width = 0d;
+        height = 0d;
+    }
+
+    public Dimension2DDouble(double width, double height) {
+        this.width = width;
+        this.height = height;
+    }
+
+    @Override
+    public double getWidth() {
+        return width;
+    }
+
+    @Override
+    public double getHeight() {
+        return height;
+    }
+
+    @Override
+    public void setSize(double width, double height) {
+        this.width = width;
+        this.height = height;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof Dimension2DDouble) {
+            Dimension2DDouble other = (Dimension2DDouble) obj;
+            return width == other.width && height == other.height;
+        }
+
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        double sum = width + height;
+        return (int) Math.ceil(sum * (sum + 1) / 2 + width);
+    }
+
+    @Override
+    public String toString() {
+        return "Dimension2DDouble[" + width + ", " + height + "]";
+    }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/geom/Dimension2dDouble.java b/src/ooxml/java/org/apache/poi/xdgf/geom/Dimension2dDouble.java
deleted file mode 100644 (file)
index 94b887e..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-/* ====================================================================
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================== */
-
-package org.apache.poi.xdgf.geom;
-
-import java.awt.geom.Dimension2D;
-
-public class Dimension2dDouble extends Dimension2D {
-
-    double width;
-    double height;
-
-    public Dimension2dDouble() {
-        width = 0d;
-        height = 0d;
-    }
-
-    public Dimension2dDouble(double width, double height) {
-        this.width = width;
-        this.height = height;
-    }
-
-    @Override
-    public double getWidth() {
-        return width;
-    }
-
-    @Override
-    public double getHeight() {
-        return height;
-    }
-
-    @Override
-    public void setSize(double width, double height) {
-        this.width = width;
-        this.height = height;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj instanceof Dimension2dDouble) {
-            Dimension2dDouble other = (Dimension2dDouble) obj;
-            return width == other.width && height == other.height;
-        }
-
-        return false;
-    }
-
-    @Override
-    public int hashCode() {
-        double sum = width + height;
-        return (int) Math.ceil(sum * (sum + 1) / 2 + width);
-    }
-
-    @Override
-    public String toString() {
-        return "Dimension2dDouble[" + width + ", " + height + "]";
-    }
-}
index 43559203fe3384e96f5f18f5a19e543b5859e273..c094a6edbf2bc87d11515d9b0e49fa27946690fb 100644 (file)
@@ -22,7 +22,7 @@ import java.awt.geom.Rectangle2D;
 
 import org.apache.poi.ooxml.POIXMLException;
 import org.apache.poi.util.Internal;
-import org.apache.poi.xdgf.geom.Dimension2dDouble;
+import org.apache.poi.util.Dimension2DDouble;
 
 import com.microsoft.schemas.office.visio.x2012.main.PageType;
 
@@ -75,14 +75,14 @@ public class XDGFPage {
     /**
      * @return width/height of page
      */
-    public Dimension2dDouble getPageSize() {
+    public Dimension2DDouble getPageSize() {
         XDGFCell w = _pageSheet.getCell("PageWidth");
         XDGFCell h = _pageSheet.getCell("PageHeight");
 
         if (w == null || h == null)
             throw new POIXMLException("Cannot determine page size");
 
-        return new Dimension2dDouble(Double.parseDouble(w.getValue()),
+        return new Dimension2DDouble(Double.parseDouble(w.getValue()),
                 Double.parseDouble(h.getValue()));
     }
 
@@ -109,7 +109,7 @@ public class XDGFPage {
      * @return bounding box of page
      */
     public Rectangle2D getBoundingBox() {
-        Dimension2dDouble sz = getPageSize();
+        Dimension2DDouble sz = getPageSize();
         Point2D.Double offset = getPageOffset();
 
         return new Rectangle2D.Double(-offset.getX(), -offset.getY(),
index 7706ca19e34befdb6ae2da3c88c7c5318a14035b..c0ddf5fb0b7c8af8f8b5ee39ff625344cec8524f 100644 (file)
@@ -25,7 +25,7 @@ import java.io.*;
 
 import javax.imageio.ImageIO;
 
-import org.apache.poi.xdgf.geom.Dimension2dDouble;
+import org.apache.poi.util.Dimension2DDouble;
 import org.apache.poi.xdgf.usermodel.XDGFPage;
 import org.apache.poi.xdgf.usermodel.XmlVisioDocument;
 import org.apache.poi.xdgf.usermodel.shape.ShapeDebuggerRenderer;
@@ -58,7 +58,7 @@ public class VsdxToPng {
     public static void renderToPng(XDGFPage page, File outFile, double scale,
             ShapeRenderer renderer) throws IOException {
 
-        Dimension2dDouble sz = page.getPageSize();
+        Dimension2DDouble sz = page.getPageSize();
 
         int width = (int) (scale * sz.getWidth());
         int height = (int) (scale * sz.getHeight());
diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java
new file mode 100644 (file)
index 0000000..f2e1957
--- /dev/null
@@ -0,0 +1,30 @@
+/* ====================================================================
+   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.draw;
+
+import org.apache.poi.hwmf.draw.HwmfDrawProperties;
+
+public class HemfDrawProperties extends HwmfDrawProperties {
+
+    public HemfDrawProperties() {
+    }
+
+    public HemfDrawProperties(HemfDrawProperties other) {
+        super(other);
+    }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java
new file mode 100644 (file)
index 0000000..89a764a
--- /dev/null
@@ -0,0 +1,29 @@
+/* ====================================================================
+   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.draw;
+
+import java.awt.Graphics2D;
+import java.awt.geom.Rectangle2D;
+
+import org.apache.poi.hwmf.draw.HwmfGraphics;
+
+public class HemfGraphics extends HwmfGraphics {
+    public HemfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) {
+        super(graphicsCtx,bbox);
+    }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/extractor/HemfExtractor.java b/src/scratchpad/src/org/apache/poi/hemf/extractor/HemfExtractor.java
deleted file mode 100644 (file)
index ab4add4..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-/* ====================================================================
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================== */
-
-package org.apache.poi.hemf.extractor;
-
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Iterator;
-
-import org.apache.poi.hemf.record.HemfHeader;
-import org.apache.poi.hemf.record.HemfRecord;
-import org.apache.poi.hemf.record.HemfRecordType;
-import org.apache.poi.util.Internal;
-import org.apache.poi.util.LittleEndianInputStream;
-import org.apache.poi.util.RecordFormatException;
-
-/**
- * Read-only EMF extractor.  Lots remain
- */
-@Internal
-public class HemfExtractor implements Iterable<HemfRecord> {
-
-    private HemfHeader header;
-    private final LittleEndianInputStream stream;
-
-    public HemfExtractor(InputStream is) throws IOException {
-        stream = new LittleEndianInputStream(is);
-        header = new HemfHeader();
-        long recordId = stream.readUInt();
-        long recordSize = stream.readUInt();
-
-        header = new HemfHeader();
-        header.init(stream, recordId, recordSize-8);
-    }
-
-    @Override
-    public Iterator<HemfRecord> iterator() {
-        return new HemfRecordIterator();
-    }
-
-    public HemfHeader getHeader() {
-        return header;
-    }
-
-    private class HemfRecordIterator implements Iterator<HemfRecord> {
-
-        private HemfRecord currentRecord;
-
-        HemfRecordIterator() {
-            //queue the first non-header record
-            currentRecord = _next();
-        }
-
-        @Override
-        public boolean hasNext() {
-            return currentRecord != null;
-        }
-
-        @Override
-        public HemfRecord next() {
-            HemfRecord toReturn = currentRecord;
-            currentRecord = _next();
-            return toReturn;
-        }
-
-        private HemfRecord _next() {
-            if (currentRecord != null && currentRecord.getRecordType().equals(HemfRecordType.eof)) {
-                return null;
-            }
-            long recordId = stream.readUInt();
-            long recordSize = stream.readUInt();
-
-            HemfRecord record = null;
-            HemfRecordType type = HemfRecordType.getById(recordId);
-            if (type == null) {
-                throw new RuntimeException("Undefined record of type:"+recordId);
-            }
-            try {
-                record = type.clazz.newInstance();
-            } catch (InstantiationException e) {
-                throw new RuntimeException(e);
-            } catch (IllegalAccessException e) {
-                throw new RuntimeException(e);
-            }
-            try {
-                record.init(stream, recordId, recordSize-8);
-            } catch (IOException e) {
-                throw new RecordFormatException(e);
-            }
-
-            return record;
-        }
-
-        @Override
-        public void remove() {
-            throw new UnsupportedOperationException("Remove not supported");
-        }
-
-    }
-}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusHeader.java b/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusHeader.java
deleted file mode 100644 (file)
index 2594793..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-/* ====================================================================
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================== */
-
-package org.apache.poi.hemf.hemfplus.record;
-
-
-import java.io.IOException;
-
-import org.apache.poi.util.Internal;
-import org.apache.poi.util.LittleEndian;
-
-@Internal
-public class HemfPlusHeader implements HemfPlusRecord {
-
-    private int flags;
-    private long version; //hack for now; replace with EmfPlusGraphicsVersion object
-    private long emfPlusFlags;
-    private long logicalDpiX;
-    private long logicalDpiY;
-
-    @Override
-    public HemfPlusRecordType getRecordType() {
-        return HemfPlusRecordType.header;
-    }
-
-    public int getFlags() {
-        return flags;
-    }
-
-    @Override
-    public void init(byte[] dataBytes, int recordId, int flags) throws IOException {
-        //assert record id == header
-        this.flags = flags;
-        int offset = 0;
-        this.version = LittleEndian.getUInt(dataBytes, offset); offset += LittleEndian.INT_SIZE;
-        this.emfPlusFlags = LittleEndian.getUInt(dataBytes, offset); offset += LittleEndian.INT_SIZE;
-        this.logicalDpiX = LittleEndian.getUInt(dataBytes, offset); offset += LittleEndian.INT_SIZE;
-        this.logicalDpiY = LittleEndian.getUInt(dataBytes, offset);
-
-    }
-
-    public long getVersion() {
-        return version;
-    }
-
-    public long getEmfPlusFlags() {
-        return emfPlusFlags;
-    }
-
-    public long getLogicalDpiX() {
-        return logicalDpiX;
-    }
-
-    public long getLogicalDpiY() {
-        return logicalDpiY;
-    }
-
-    @Override
-    public String toString() {
-        return "HemfPlusHeader{" +
-                "flags=" + flags +
-                ", version=" + version +
-                ", emfPlusFlags=" + emfPlusFlags +
-                ", logicalDpiX=" + logicalDpiX +
-                ", logicalDpiY=" + logicalDpiY +
-                '}';
-    }
-}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecord.java b/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecord.java
deleted file mode 100644 (file)
index 09af3d5..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/* ====================================================================
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================== */
-
-package org.apache.poi.hemf.hemfplus.record;
-
-
-import java.io.IOException;
-
-import org.apache.poi.util.Internal;
-
-@Internal
-public interface HemfPlusRecord {
-
-    HemfPlusRecordType getRecordType();
-
-    int getFlags();
-
-    /**
-     *
-     * @param dataBytes these are the bytes that start after the id, flags, record size
-     *                    and go to the end of the record; they do not include any required padding
-     *                    at the end.
-     * @param recordId record type id
-     * @param flags flags
-     * @throws IOException, RecordFormatException
-     */
-    void init(byte[] dataBytes, int recordId, int flags) throws IOException;
-
-
-}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecordType.java b/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecordType.java
deleted file mode 100644 (file)
index 7083762..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-/* ====================================================================
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================== */
-
-package org.apache.poi.hemf.hemfplus.record;
-
-import org.apache.poi.util.Internal;
-
-@Internal
-public enum HemfPlusRecordType {
-    header(0x4001, HemfPlusHeader.class),
-    endOfFile(0x4002, UnimplementedHemfPlusRecord.class),
-    comment(0x4003, UnimplementedHemfPlusRecord.class),
-    getDC(0x4004, UnimplementedHemfPlusRecord.class),
-    multiFormatStart(0x4005, UnimplementedHemfPlusRecord.class),
-    multiFormatSection(0x4006, UnimplementedHemfPlusRecord.class),
-    multiFormatEnd(0x4007, UnimplementedHemfPlusRecord.class),
-    object(0x4008, UnimplementedHemfPlusRecord.class),
-    clear(0x4009, UnimplementedHemfPlusRecord.class),
-    fillRects(0x400A, UnimplementedHemfPlusRecord.class),
-    drawRects(0x400B, UnimplementedHemfPlusRecord.class),
-    fillPolygon(0x400C, UnimplementedHemfPlusRecord.class),
-    drawLines(0x400D, UnimplementedHemfPlusRecord.class),
-    fillEllipse(0x400E, UnimplementedHemfPlusRecord.class),
-    drawEllipse(0x400F, UnimplementedHemfPlusRecord.class),
-    fillPie(0x4010, UnimplementedHemfPlusRecord.class),
-    drawPie(0x4011, UnimplementedHemfPlusRecord.class),
-    drawArc(0x4012, UnimplementedHemfPlusRecord.class),
-    fillRegion(0x4013, UnimplementedHemfPlusRecord.class),
-    fillPath(0x4014, UnimplementedHemfPlusRecord.class),
-    drawPath(0x4015, UnimplementedHemfPlusRecord.class),
-    fillClosedCurve(0x4016, UnimplementedHemfPlusRecord.class),
-    drawClosedCurve(0x4017, UnimplementedHemfPlusRecord.class),
-    drawCurve(0x4018, UnimplementedHemfPlusRecord.class),
-    drawBeziers(0x4019, UnimplementedHemfPlusRecord.class),
-    drawImage(0x401A, UnimplementedHemfPlusRecord.class),
-    drawImagePoints(0x401B, UnimplementedHemfPlusRecord.class),
-    drawString(0x401C, UnimplementedHemfPlusRecord.class),
-    setRenderingOrigin(0x401D, UnimplementedHemfPlusRecord.class),
-    setAntiAliasMode(0x401E, UnimplementedHemfPlusRecord.class),
-    setTextRenderingHint(0x401F, UnimplementedHemfPlusRecord.class),
-    setTextContrast(0x4020, UnimplementedHemfPlusRecord.class),
-    setInterpolationMode(0x4021, UnimplementedHemfPlusRecord.class),
-    setPixelOffsetMode(0x4022, UnimplementedHemfPlusRecord.class),
-    setComositingMode(0x4023, UnimplementedHemfPlusRecord.class),
-    setCompositingQuality(0x4024, UnimplementedHemfPlusRecord.class),
-    save(0x4025, UnimplementedHemfPlusRecord.class),
-    restore(0x4026, UnimplementedHemfPlusRecord.class),
-    beginContainer(0x4027, UnimplementedHemfPlusRecord.class),
-    beginContainerNoParams(0x428, UnimplementedHemfPlusRecord.class),
-    endContainer(0x4029, UnimplementedHemfPlusRecord.class),
-    setWorldTransform(0x402A, UnimplementedHemfPlusRecord.class),
-    resetWorldTransform(0x402B, UnimplementedHemfPlusRecord.class),
-    multiplyWorldTransform(0x402C, UnimplementedHemfPlusRecord.class),
-    translateWorldTransform(0x402D, UnimplementedHemfPlusRecord.class),
-    scaleWorldTransform(0x402E, UnimplementedHemfPlusRecord.class),
-    rotateWorldTransform(0x402F, UnimplementedHemfPlusRecord.class),
-    setPageTransform(0x4030, UnimplementedHemfPlusRecord.class),
-    resetClip(0x4031, UnimplementedHemfPlusRecord.class),
-    setClipRect(0x4032, UnimplementedHemfPlusRecord.class),
-    setClipRegion(0x4033, UnimplementedHemfPlusRecord.class),
-    setClipPath(0x4034, UnimplementedHemfPlusRecord.class),
-    offsetClip(0x4035, UnimplementedHemfPlusRecord.class),
-    drawDriverstring(0x4036, UnimplementedHemfPlusRecord.class),
-    strokeFillPath(0x4037, UnimplementedHemfPlusRecord.class),
-    serializableObject(0x4038, UnimplementedHemfPlusRecord.class),
-    setTSGraphics(0x4039, UnimplementedHemfPlusRecord.class),
-    setTSClip(0x403A, UnimplementedHemfPlusRecord.class);
-
-    public final long id;
-    public final Class<? extends HemfPlusRecord> clazz;
-
-    HemfPlusRecordType(long id, Class<? extends HemfPlusRecord> clazz) {
-        this.id = id;
-        this.clazz = clazz;
-    }
-
-    public static HemfPlusRecordType getById(long id) {
-        for (HemfPlusRecordType wrt : values()) {
-            if (wrt.id == id) return wrt;
-        }
-        return null;
-    }
-}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/UnimplementedHemfPlusRecord.java b/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/UnimplementedHemfPlusRecord.java
deleted file mode 100644 (file)
index 7e3cbcf..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-/* ====================================================================
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================== */
-
-package org.apache.poi.hemf.hemfplus.record;
-
-
-import java.io.IOException;
-
-import org.apache.poi.util.Internal;
-
-@Internal
-public class UnimplementedHemfPlusRecord implements HemfPlusRecord {
-
-    private int recordId;
-    private int flags;
-    private byte[] recordBytes;
-
-    @Override
-    public HemfPlusRecordType getRecordType() {
-        return HemfPlusRecordType.getById(recordId);
-    }
-
-    @Override
-    public int getFlags() {
-        return flags;
-    }
-
-    @Override
-    public void init(byte[] recordBytes, int recordId, int flags) throws IOException {
-        this.recordId = recordId;
-        this.flags = flags;
-        this.recordBytes = recordBytes;
-    }
-
-    public byte[] getRecordBytes() {
-        //should probably defensively return a copy.
-        return recordBytes;
-    }
-}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/AbstractHemfComment.java b/src/scratchpad/src/org/apache/poi/hemf/record/AbstractHemfComment.java
deleted file mode 100644 (file)
index 7ffff6b..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/* ====================================================================
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================== */
-
-package org.apache.poi.hemf.record;
-
-import org.apache.poi.util.Internal;
-
-/**
- * Syntactic utility to allow for four different
- * comment classes
- */
-@Internal
-public abstract class AbstractHemfComment {
-
-    private final byte[] rawBytes;
-
-    public AbstractHemfComment(byte[] rawBytes) {
-        this.rawBytes = rawBytes;
-    }
-
-    public byte[] getRawBytes() {
-        return rawBytes;
-    }
-
-}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfComment.java b/src/scratchpad/src/org/apache/poi/hemf/record/HemfComment.java
deleted file mode 100644 (file)
index 5d45927..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-/* ====================================================================
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================== */
-
-package org.apache.poi.hemf.record;
-
-import org.apache.poi.util.Internal;
-
-/**
- * Contains arbitrary data
- */
-@Internal
-public class HemfComment extends  AbstractHemfComment {
-
-    public HemfComment(byte[] rawBytes) {
-        super(rawBytes);
-    }
-}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFPlus.java b/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFPlus.java
deleted file mode 100644 (file)
index d5d4c10..0000000
+++ /dev/null
@@ -1,111 +0,0 @@
-/* ====================================================================
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================== */
-
-package org.apache.poi.hemf.record;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.poi.hemf.hemfplus.record.HemfPlusRecord;
-import org.apache.poi.hemf.hemfplus.record.HemfPlusRecordType;
-import org.apache.poi.util.IOUtils;
-import org.apache.poi.util.Internal;
-import org.apache.poi.util.LittleEndian;
-import org.apache.poi.util.RecordFormatException;
-
-/**
- * An HemfCommentEMFPlus may contain one or more EMFPlus records
- */
-@Internal
-public class HemfCommentEMFPlus extends AbstractHemfComment {
-
-    private static final int MAX_RECORD_LENGTH = 1_000_000;
-
-
-    long dataSize;
-    public HemfCommentEMFPlus(byte[] rawBytes) {
-        //these rawBytes contain only the EMFPlusRecord(s?)
-        //the EmfComment type, size, datasize and comment identifier have all been stripped.
-        //The EmfPlus type, flags, size, data size should start at rawBytes[0]
-        super(rawBytes);
-
-    }
-
-    public List<HemfPlusRecord> getRecords() {
-        return HemfPlusParser.parse(getRawBytes());
-    }
-
-    private static class HemfPlusParser {
-
-        public static List<HemfPlusRecord> parse(byte[] bytes) {
-            List<HemfPlusRecord> records = new ArrayList<>();
-            int offset = 0;
-            while (offset < bytes.length) {
-                if (offset + 12 > bytes.length) {
-                    //if header will go beyond bytes, stop now
-                    //TODO: log or throw
-                    break;
-                }
-                int type = LittleEndian.getUShort(bytes, offset); offset += LittleEndian.SHORT_SIZE;
-                int flags = LittleEndian.getUShort(bytes, offset); offset += LittleEndian.SHORT_SIZE;
-                long sizeLong = LittleEndian.getUInt(bytes, offset); offset += LittleEndian.INT_SIZE;
-                if (sizeLong >= Integer.MAX_VALUE) {
-                    throw new RecordFormatException("size of emf record >= Integer.MAX_VALUE");
-                }
-                int size = (int)sizeLong;
-                long dataSizeLong = LittleEndian.getUInt(bytes, offset); offset += LittleEndian.INT_SIZE;
-                if (dataSizeLong >= Integer.MAX_VALUE) {
-                    throw new RuntimeException("data size of emfplus record cannot be >= Integer.MAX_VALUE");
-                }
-                int dataSize = (int)dataSizeLong;
-                if (dataSize + offset > bytes.length) {
-                    //TODO: log or throw?
-                    break;
-                }
-                HemfPlusRecord record = buildRecord(type, flags, dataSize, offset, bytes);
-                records.add(record);
-                offset += dataSize;
-            }
-            return records;
-        }
-
-        private static HemfPlusRecord buildRecord(int recordId, int flags, int size, int offset, byte[] bytes) {
-            HemfPlusRecord record = null;
-            HemfPlusRecordType type = HemfPlusRecordType.getById(recordId);
-            if (type == null) {
-                throw new RuntimeException("Undefined record of type:"+recordId);
-            }
-            try {
-                record = type.clazz.newInstance();
-            } catch (InstantiationException e) {
-                throw new RuntimeException(e);
-            } catch (IllegalAccessException e) {
-                throw new RuntimeException(e);
-            }
-            byte[] dataBytes = IOUtils.safelyAllocate(size, MAX_RECORD_LENGTH);
-            System.arraycopy(bytes, offset, dataBytes, 0, size);
-            try {
-                record.init(dataBytes, recordId, flags);
-            } catch (IOException e) {
-                throw new RuntimeException(e);
-            }
-            return record;
-
-        }
-    }
-}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFSpool.java b/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFSpool.java
deleted file mode 100644 (file)
index 009974d..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-/* ====================================================================
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================== */
-
-package org.apache.poi.hemf.record;
-
-import org.apache.poi.util.Internal;
-
-/**
- * Not yet implemented
- */
-@Internal
-public class HemfCommentEMFSpool extends AbstractHemfComment {
-
-    public HemfCommentEMFSpool(byte[] rawBytes) {
-        super(rawBytes);
-    }
-}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentPublic.java b/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentPublic.java
deleted file mode 100644 (file)
index 5ca5c37..0000000
+++ /dev/null
@@ -1,177 +0,0 @@
-/* ====================================================================
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================== */
-
-package org.apache.poi.hemf.record;
-
-
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.poi.util.IOUtils;
-import org.apache.poi.util.Internal;
-import org.apache.poi.util.LittleEndian;
-import org.apache.poi.util.LittleEndianConsts;
-import org.apache.poi.util.RecordFormatException;
-
-/**
- * Container class for four subtypes of HemfCommentPublic: BeginGroup, EndGroup, MultiFormats
- * and Windows Metafile.
- */
-@Internal
-public class HemfCommentPublic  {
-
-    private static final int MAX_RECORD_LENGTH = 1_000_000;
-
-
-    /**
-     * Stub, to be implemented
-     */
-    public static class BeginGroup extends AbstractHemfComment {
-
-        public BeginGroup(byte[] rawBytes) {
-            super(rawBytes);
-        }
-
-    }
-
-    /**
-     * Stub, to be implemented
-     */
-    public static class EndGroup extends AbstractHemfComment {
-
-        public EndGroup(byte[] rawBytes) {
-            super(rawBytes);
-        }
-    }
-
-    public static class MultiFormats extends AbstractHemfComment {
-
-        public MultiFormats(byte[] rawBytes) {
-            super(rawBytes);
-        }
-
-        /**
-         *
-         * @return a list of HemfMultFormatsData
-         */
-        public List<HemfMultiFormatsData> getData() {
-
-            byte[] rawBytes = getRawBytes();
-            //note that raw bytes includes the public comment identifier
-            int currentOffset = 4 + 16;//4 public comment identifier, 16 for outputrect
-            long countFormats = LittleEndian.getUInt(rawBytes, currentOffset);
-            currentOffset += LittleEndianConsts.INT_SIZE;
-            List<EmrFormat> emrFormatList = new ArrayList<>();
-            for (long i = 0; i < countFormats; i++) {
-                emrFormatList.add(new EmrFormat(rawBytes, currentOffset));
-                currentOffset += 4 * LittleEndianConsts.INT_SIZE;
-            }
-            List<HemfMultiFormatsData> list = new ArrayList<>();
-            for (EmrFormat emrFormat : emrFormatList) {
-                byte[] data = IOUtils.safelyAllocate(emrFormat.size, MAX_RECORD_LENGTH);
-                System.arraycopy(rawBytes, emrFormat.offset-4, data, 0, emrFormat.size);
-                list.add(new HemfMultiFormatsData(emrFormat.signature, emrFormat.version, data));
-            }
-            return list;
-        }
-
-        private static class EmrFormat {
-            long signature;
-            long version;
-            int size;
-            int offset;
-
-            public EmrFormat(byte[] rawBytes, int currentOffset) {
-                signature = LittleEndian.getUInt(rawBytes, currentOffset); currentOffset += LittleEndianConsts.INT_SIZE;
-                version = LittleEndian.getUInt(rawBytes, currentOffset); currentOffset += LittleEndianConsts.INT_SIZE;
-                //spec says this must be a 32bit "aligned" typo for "signed"?
-                //realistically, this has to be an int...
-                size = LittleEndian.getInt(rawBytes, currentOffset); currentOffset += LittleEndianConsts.INT_SIZE;
-                //y, can be long, but realistically?
-                offset = LittleEndian.getInt(rawBytes, currentOffset); currentOffset += LittleEndianConsts.INT_SIZE;
-                if (size < 0) {
-                    throw new RecordFormatException("size for emrformat must be > 0");
-                }
-                if (offset < 0) {
-                    throw new RecordFormatException("offset for emrformat must be > 0");
-                }
-            }
-        }
-    }
-
-    /**
-     * Stub, to be implemented
-     */
-    public static class WindowsMetafile extends AbstractHemfComment {
-
-        private final byte[] wmfBytes;
-        public WindowsMetafile(byte[] rawBytes) {
-            super(rawBytes);
-            int offset = LittleEndianConsts.INT_SIZE;//public comment identifier
-            int version = LittleEndian.getUShort(rawBytes, offset); offset += LittleEndianConsts.SHORT_SIZE;
-            int reserved = LittleEndian.getUShort(rawBytes, offset); offset += LittleEndianConsts.SHORT_SIZE;
-            offset += LittleEndianConsts.INT_SIZE; //checksum
-            offset += LittleEndianConsts.INT_SIZE; //flags
-            long winMetafileSizeLong = LittleEndian.getUInt(rawBytes, offset); offset += LittleEndianConsts.INT_SIZE;
-            if (winMetafileSizeLong == 0L) {
-                wmfBytes = new byte[0];
-                return;
-            }
-            wmfBytes = IOUtils.safelyAllocate(winMetafileSizeLong, MAX_RECORD_LENGTH);
-            System.arraycopy(rawBytes, offset, wmfBytes, 0, wmfBytes.length);
-        }
-
-        /**
-         *
-         * @return an InputStream for the embedded WMF file
-         */
-        public InputStream getWmfInputStream() {
-            return new ByteArrayInputStream(wmfBytes);
-        }
-    }
-
-    /**
-     * This encapulates a single record stored within
-     * a HemfCommentPublic.MultiFormats record.
-     */
-    public static class HemfMultiFormatsData {
-
-        long signature;
-        long version;
-        byte[] data;
-
-        public HemfMultiFormatsData(long signature, long version, byte[] data) {
-            this.signature = signature;
-            this.version = version;
-            this.data = data;
-        }
-
-        public long getSignature() {
-            return signature;
-        }
-
-        public long getVersion() {
-            return version;
-        }
-
-        public byte[] getData() {
-            return data;
-        }
-    }
-}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentRecord.java
deleted file mode 100644 (file)
index af8aa28..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
-/* ====================================================================
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================== */
-
-package org.apache.poi.hemf.record;
-
-
-import java.io.IOException;
-
-import org.apache.poi.util.IOUtils;
-import org.apache.poi.util.Internal;
-import org.apache.poi.util.LittleEndian;
-import org.apache.poi.util.LittleEndianInputStream;
-import org.apache.poi.util.RecordFormatException;
-
-/**
- * This is the outer comment record that is recognized
- * by the initial parse by {@link HemfRecordType#comment}.
- * However, there are four types of comment: EMR_COMMENT,
- * EMR_COMMENT_EMFPLUS, EMR_COMMENT_EMFSPOOL, and EMF_COMMENT_PUBLIC.
- * To get the underlying comment, call {@link #getComment()}.
- *
- */
-@Internal
-public class HemfCommentRecord implements HemfRecord {
-    private static final int MAX_RECORD_LENGTH = 1_000_000;
-
-    public final static long COMMENT_EMFSPOOL = 0x00000000;
-    public final static long COMMENT_EMFPLUS = 0x2B464D45;
-    public final static long COMMENT_PUBLIC = 0x43494447;
-
-
-    private AbstractHemfComment comment;
-    @Override
-    public HemfRecordType getRecordType() {
-        return HemfRecordType.comment;
-    }
-
-    @Override
-    public long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException {
-        long dataSize = leis.readUInt();  recordSize -= LittleEndian.INT_SIZE;
-
-        byte[] optionalCommentIndentifierBuffer = new byte[4];
-        leis.readFully(optionalCommentIndentifierBuffer);
-        dataSize = dataSize-LittleEndian.INT_SIZE; //size minus the first int which could be a comment identifier
-        recordSize -= LittleEndian.INT_SIZE;
-        long optionalCommentIdentifier = LittleEndian.getInt(optionalCommentIndentifierBuffer) & 0x00FFFFFFFFL;
-        if (optionalCommentIdentifier == COMMENT_EMFSPOOL) {
-            comment = new HemfCommentEMFSpool(readToByteArray(leis, dataSize, recordSize));
-        } else if (optionalCommentIdentifier == COMMENT_EMFPLUS) {
-            comment = new HemfCommentEMFPlus(readToByteArray(leis, dataSize, recordSize));
-        } else if (optionalCommentIdentifier == COMMENT_PUBLIC) {
-            comment = CommentPublicParser.parse(readToByteArray(leis, dataSize, recordSize));
-        } else {
-            comment = new HemfComment(readToByteArray(optionalCommentIndentifierBuffer, leis, dataSize, recordSize));
-        }
-
-        return recordSize;
-    }
-
-    //this prepends the initial "int" which turned out NOT to be
-    //a signifier of emfplus, spool, public.
-    private byte[] readToByteArray(byte[] initialBytes, LittleEndianInputStream leis,
-                                   long remainingDataSize, long remainingRecordSize) throws IOException {
-        if (remainingDataSize > Integer.MAX_VALUE) {
-            throw new RecordFormatException("Data size can't be > Integer.MAX_VALUE");
-        }
-
-        if (remainingRecordSize > Integer.MAX_VALUE) {
-            throw new RecordFormatException("Record size can't be > Integer.MAX_VALUE");
-        }
-        if (remainingRecordSize == 0) {
-            return new byte[0];
-        }
-
-        int dataSize = (int)remainingDataSize;
-        int recordSize = (int)remainingRecordSize;
-        byte[] arr = IOUtils.safelyAllocate(dataSize+initialBytes.length, MAX_RECORD_LENGTH);
-        System.arraycopy(initialBytes,0,arr, 0, initialBytes.length);
-        long read = IOUtils.readFully(leis, arr, initialBytes.length, dataSize);
-        if (read != dataSize) {
-            throw new RecordFormatException("InputStream ended before full record could be read");
-        }
-        long toSkip = recordSize-dataSize;
-        long skipped = IOUtils.skipFully(leis, toSkip);
-        if (toSkip != skipped) {
-            throw new RecordFormatException("InputStream ended before full record could be read");
-        }
-
-        return arr;
-    }
-
-    private byte[] readToByteArray(LittleEndianInputStream leis, long dataSize, long recordSize) throws IOException {
-
-        if (recordSize == 0) {
-            return new byte[0];
-        }
-
-        byte[] arr = IOUtils.safelyAllocate(dataSize, MAX_RECORD_LENGTH);
-
-        long read = IOUtils.readFully(leis, arr);
-        if (read != dataSize) {
-            throw new RecordFormatException("InputStream ended before full record could be read");
-        }
-        long toSkip = recordSize-dataSize;
-        long skipped = IOUtils.skipFully(leis, recordSize-dataSize);
-        if (toSkip != skipped) {
-            throw new RecordFormatException("InputStream ended before full record could be read");
-        }
-        return arr;
-    }
-
-    public AbstractHemfComment getComment() {
-        return comment;
-    }
-
-    private static class CommentPublicParser {
-        private static final long WINDOWS_METAFILE = 0x80000001L; //wmf
-        private static final long BEGINGROUP = 0x00000002; //beginning of a group of drawing records
-        private static final long ENDGROUP = 0x00000003; //end of a group of drawing records
-        private static final long MULTIFORMATS = 0x40000004; //allows multiple definitions of an image, including encapsulated postscript
-        private static final long UNICODE_STRING = 0x00000040; //reserved. must not be used
-        private static final long UNICODE_END = 0x00000080; //reserved, must not be used
-
-        private static AbstractHemfComment parse(byte[] bytes) {
-            long publicCommentIdentifier = LittleEndian.getUInt(bytes, 0);
-            if (publicCommentIdentifier == WINDOWS_METAFILE) {
-                return new HemfCommentPublic.WindowsMetafile(bytes);
-            } else if (publicCommentIdentifier == BEGINGROUP) {
-                return new HemfCommentPublic.BeginGroup(bytes);
-            } else if (publicCommentIdentifier == ENDGROUP) {
-                return new HemfCommentPublic.EndGroup(bytes);
-            } else if (publicCommentIdentifier == MULTIFORMATS) {
-                return new HemfCommentPublic.MultiFormats(bytes);
-            } else if (publicCommentIdentifier == UNICODE_STRING || publicCommentIdentifier == UNICODE_END) {
-                throw new RuntimeException("UNICODE_STRING/UNICODE_END values are reserved in CommentPublic records");
-            }
-            throw new RuntimeException("Unrecognized public comment type:" +publicCommentIdentifier + " ; " + WINDOWS_METAFILE);
-        }
-    }
-}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfHeader.java b/src/scratchpad/src/org/apache/poi/hemf/record/HemfHeader.java
deleted file mode 100644 (file)
index 7a6f876..0000000
+++ /dev/null
@@ -1,201 +0,0 @@
-/* ====================================================================
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================== */
-
-package org.apache.poi.hemf.record;
-
-import java.awt.Rectangle;
-import java.io.IOException;
-
-import org.apache.poi.util.IOUtils;
-import org.apache.poi.util.Internal;
-import org.apache.poi.util.LittleEndian;
-import org.apache.poi.util.LittleEndianInputStream;
-
-/**
- * Extracts the full header from EMF files.
- * @see org.apache.poi.sl.image.ImageHeaderEMF
- */
-@Internal
-public class HemfHeader implements HemfRecord {
-
-    private static final int MAX_RECORD_LENGTH = 1_000_000;
-
-
-    private Rectangle boundsRectangle;
-    private Rectangle frameRectangle;
-    private long bytes;
-    private long records;
-    private int handles;
-    private long nDescription;
-    private long offDescription;
-    private long nPalEntries;
-    private boolean hasExtension1;
-    private long cbPixelFormat;
-    private long offPixelFormat;
-    private long bOpenGL;
-    private boolean hasExtension2;
-    private long micrometersX;
-    private long micrometersY;
-
-    public Rectangle getBoundsRectangle() {
-        return boundsRectangle;
-    }
-
-    public Rectangle getFrameRectangle() {
-        return frameRectangle;
-    }
-
-    public long getBytes() {
-        return bytes;
-    }
-
-    public long getRecords() {
-        return records;
-    }
-
-    public int getHandles() {
-        return handles;
-    }
-
-    public long getnDescription() {
-        return nDescription;
-    }
-
-    public long getOffDescription() {
-        return offDescription;
-    }
-
-    public long getnPalEntries() {
-        return nPalEntries;
-    }
-
-    public boolean isHasExtension1() {
-        return hasExtension1;
-    }
-
-    public long getCbPixelFormat() {
-        return cbPixelFormat;
-    }
-
-    public long getOffPixelFormat() {
-        return offPixelFormat;
-    }
-
-    public long getbOpenGL() {
-        return bOpenGL;
-    }
-
-    public boolean isHasExtension2() {
-        return hasExtension2;
-    }
-
-    public long getMicrometersX() {
-        return micrometersX;
-    }
-
-    public long getMicrometersY() {
-        return micrometersY;
-    }
-
-    @Override
-    public String toString() {
-        return "HemfHeader{" +
-                "boundsRectangle=" + boundsRectangle +
-                ", frameRectangle=" + frameRectangle +
-                ", bytes=" + bytes +
-                ", records=" + records +
-                ", handles=" + handles +
-                ", nDescription=" + nDescription +
-                ", offDescription=" + offDescription +
-                ", nPalEntries=" + nPalEntries +
-                ", hasExtension1=" + hasExtension1 +
-                ", cbPixelFormat=" + cbPixelFormat +
-                ", offPixelFormat=" + offPixelFormat +
-                ", bOpenGL=" + bOpenGL +
-                ", hasExtension2=" + hasExtension2 +
-                ", micrometersX=" + micrometersX +
-                ", micrometersY=" + micrometersY +
-                '}';
-    }
-
-    @Override
-    public HemfRecordType getRecordType() {
-        return HemfRecordType.header;
-    }
-
-    @Override
-    public long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException {
-        if (recordId != 1L) {
-            throw new IOException("Not a valid EMF header. Record type:"+recordId);
-        }
-        //read the record--id and size (2 bytes) have already been read
-        byte[] data = IOUtils.safelyAllocate(recordSize, MAX_RECORD_LENGTH);
-        IOUtils.readFully(leis, data);
-
-        int offset = 0;
-
-        //bounds
-        int boundsLeft = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
-        int boundsTop = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
-        int boundsRight = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
-        int boundsBottom = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
-        boundsRectangle = new Rectangle(boundsLeft, boundsTop,
-                boundsRight - boundsLeft, boundsBottom - boundsTop);
-
-        int frameLeft = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
-        int frameTop = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
-        int frameRight = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
-        int frameBottom = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
-        frameRectangle = new Rectangle(frameLeft, frameTop,
-                frameRight - frameLeft, frameBottom - frameTop);
-
-        long recordSignature = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
-        if (recordSignature != 0x464D4520) {
-            throw new IOException("bad record signature: " + recordSignature);
-        }
-
-        long version = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
-        //According to the spec, MSOffice doesn't pay attention to this value.
-        //It _should_ be 0x00010000
-        bytes = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
-        records = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
-        handles = LittleEndian.getUShort(data, offset);offset += LittleEndian.SHORT_SIZE;
-        offset += LittleEndian.SHORT_SIZE;//reserved
-        nDescription = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
-        offDescription = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
-        nPalEntries = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
-
-        //should be skips
-        offset += 8;//device
-        offset += 8;//millimeters
-
-
-        if (recordSize+8 >= 100) {
-            hasExtension1 = true;
-            cbPixelFormat = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
-            offPixelFormat = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
-            bOpenGL= LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
-        }
-
-        if (recordSize+8 >= 108) {
-            hasExtension2 = true;
-            micrometersX = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
-            micrometersY = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
-        }
-        return recordSize;
-    }
-}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/HemfRecord.java
deleted file mode 100644 (file)
index de1271e..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/* ====================================================================
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================== */
-
-package org.apache.poi.hemf.record;
-
-
-import java.io.IOException;
-
-import org.apache.poi.util.Internal;
-import org.apache.poi.util.LittleEndianInputStream;
-
-@Internal
-public interface HemfRecord {
-
-    HemfRecordType getRecordType();
-
-    /**
-     * Init record from stream
-     *
-     * @param leis the little endian input stream
-     * @return count of processed bytes
-     * @throws IOException
-     */
-    long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException;
-
-
-}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfRecordType.java b/src/scratchpad/src/org/apache/poi/hemf/record/HemfRecordType.java
deleted file mode 100644 (file)
index b1c5857..0000000
+++ /dev/null
@@ -1,159 +0,0 @@
-/* ====================================================================
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================== */
-
-package org.apache.poi.hemf.record;
-
-import org.apache.poi.util.Internal;
-
-@Internal
-public enum HemfRecordType {
-
-    header(0x00000001, UnimplementedHemfRecord.class),
-    polybeizer(0x00000002, UnimplementedHemfRecord.class),
-    polygon(0x00000003, UnimplementedHemfRecord.class),
-    polyline(0x00000004, UnimplementedHemfRecord.class),
-    polybezierto(0x00000005, UnimplementedHemfRecord.class),
-    polylineto(0x00000006, UnimplementedHemfRecord.class),
-    polypolyline(0x00000007, UnimplementedHemfRecord.class),
-    polypolygon(0x00000008, UnimplementedHemfRecord.class),
-    setwindowextex(0x00000009, UnimplementedHemfRecord.class),
-    setwindoworgex(0x0000000A, UnimplementedHemfRecord.class),
-    setviewportextex(0x0000000B, UnimplementedHemfRecord.class),
-    setviewportorgex(0x0000000C, UnimplementedHemfRecord.class),
-    setbrushorgex(0x0000000D, UnimplementedHemfRecord.class),
-    eof(0x0000000E, UnimplementedHemfRecord.class),
-    setpixelv(0x0000000F, UnimplementedHemfRecord.class),
-    setmapperflags(0x00000010, UnimplementedHemfRecord.class),
-    setmapmode(0x00000011, UnimplementedHemfRecord.class),
-    setbkmode(0x00000012, UnimplementedHemfRecord.class),
-    setpolyfillmode(0x00000013, UnimplementedHemfRecord.class),
-    setrop2(0x00000014, UnimplementedHemfRecord.class),
-    setstretchbltmode(0x00000015, UnimplementedHemfRecord.class),
-    settextalign(0x00000016, HemfText.SetTextAlign.class),
-    setcoloradjustment(0x00000017, UnimplementedHemfRecord.class),
-    settextcolor(0x00000018, HemfText.SetTextColor.class),
-    setbkcolor(0x00000019, UnimplementedHemfRecord.class),
-    setoffsetcliprgn(0x0000001A, UnimplementedHemfRecord.class),
-    setmovetoex(0x0000001B, UnimplementedHemfRecord.class),
-    setmetargn(0x0000001C, UnimplementedHemfRecord.class),
-    setexcludecliprect(0x0000001D, UnimplementedHemfRecord.class),
-    setintersectcliprect(0x0000001E, UnimplementedHemfRecord.class),
-    scaleviewportextex(0x0000001F, UnimplementedHemfRecord.class),
-    scalewindowextex(0x00000020, UnimplementedHemfRecord.class),
-    savedc(0x00000021, UnimplementedHemfRecord.class),
-    restoredc(0x00000022, UnimplementedHemfRecord.class),
-    setworldtransform(0x00000023, UnimplementedHemfRecord.class),
-    modifyworldtransform(0x00000024, UnimplementedHemfRecord.class),
-    selectobject(0x00000025, UnimplementedHemfRecord.class),
-    createpen(0x00000026, UnimplementedHemfRecord.class),
-    createbrushindirect(0x00000027, UnimplementedHemfRecord.class),
-    deleteobject(0x00000028, UnimplementedHemfRecord.class),
-    anglearc(0x00000029, UnimplementedHemfRecord.class),
-    ellipse(0x0000002A, UnimplementedHemfRecord.class),
-    rectangle(0x0000002B, UnimplementedHemfRecord.class),
-    roundirect(0x0000002C, UnimplementedHemfRecord.class),
-    arc(0x0000002D, UnimplementedHemfRecord.class),
-    chord(0x0000002E, UnimplementedHemfRecord.class),
-    pie(0x0000002F, UnimplementedHemfRecord.class),
-    selectpalette(0x00000030, UnimplementedHemfRecord.class),
-    createpalette(0x00000031, UnimplementedHemfRecord.class),
-    setpaletteentries(0x00000032, UnimplementedHemfRecord.class),
-    resizepalette(0x00000033, UnimplementedHemfRecord.class),
-    realizepalette(0x0000034, UnimplementedHemfRecord.class),
-    extfloodfill(0x00000035, UnimplementedHemfRecord.class),
-    lineto(0x00000036, UnimplementedHemfRecord.class),
-    arcto(0x00000037, UnimplementedHemfRecord.class),
-    polydraw(0x00000038, UnimplementedHemfRecord.class),
-    setarcdirection(0x00000039, UnimplementedHemfRecord.class),
-    setmiterlimit(0x0000003A, UnimplementedHemfRecord.class),
-    beginpath(0x0000003B, UnimplementedHemfRecord.class),
-    endpath(0x0000003C, UnimplementedHemfRecord.class),
-    closefigure(0x0000003D, UnimplementedHemfRecord.class),
-    fillpath(0x0000003E, UnimplementedHemfRecord.class),
-    strokeandfillpath(0x0000003F, UnimplementedHemfRecord.class),
-    strokepath(0x00000040, UnimplementedHemfRecord.class),
-    flattenpath(0x00000041, UnimplementedHemfRecord.class),
-    widenpath(0x00000042, UnimplementedHemfRecord.class),
-    selectclippath(0x00000043, UnimplementedHemfRecord.class),
-    abortpath(0x00000044, UnimplementedHemfRecord.class), //no 45?!
-    comment(0x00000046, HemfCommentRecord.class),
-    fillrgn(0x00000047, UnimplementedHemfRecord.class),
-    framergn(0x00000048, UnimplementedHemfRecord.class),
-    invertrgn(0x00000049, UnimplementedHemfRecord.class),
-    paintrgn(0x0000004A, UnimplementedHemfRecord.class),
-    extselectciprrgn(0x0000004B, UnimplementedHemfRecord.class),
-    bitblt(0x0000004C, UnimplementedHemfRecord.class),
-    stretchblt(0x0000004D, UnimplementedHemfRecord.class),
-    maskblt(0x0000004E, UnimplementedHemfRecord.class),
-    plgblt(0x0000004F, UnimplementedHemfRecord.class),
-    setbitstodevice(0x00000050, UnimplementedHemfRecord.class),
-    stretchdibits(0x00000051, UnimplementedHemfRecord.class),
-    extcreatefontindirectw(0x00000052, HemfText.ExtCreateFontIndirectW.class),
-    exttextouta(0x00000053, HemfText.ExtTextOutA.class),
-    exttextoutw(0x00000054, HemfText.ExtTextOutW.class),
-    polybezier16(0x00000055, UnimplementedHemfRecord.class),
-    polygon16(0x00000056, UnimplementedHemfRecord.class),
-    polyline16(0x00000057, UnimplementedHemfRecord.class),
-    polybezierto16(0x00000058, UnimplementedHemfRecord.class),
-    polylineto16(0x00000059, UnimplementedHemfRecord.class),
-    polypolyline16(0x0000005A, UnimplementedHemfRecord.class),
-    polypolygon16(0x0000005B, UnimplementedHemfRecord.class),
-    polydraw16(0x0000005C, UnimplementedHemfRecord.class),
-    createmonobrush16(0x0000005D, UnimplementedHemfRecord.class),
-    createdibpatternbrushpt(0x0000005E, UnimplementedHemfRecord.class),
-    extcreatepen(0x0000005F, UnimplementedHemfRecord.class),
-    polytextouta(0x00000060, HemfText.PolyTextOutA.class),
-    polytextoutw(0x00000061, HemfText.PolyTextOutW.class),
-    seticmmode(0x00000062, UnimplementedHemfRecord.class),
-    createcolorspace(0x00000063, UnimplementedHemfRecord.class),
-    setcolorspace(0x00000064, UnimplementedHemfRecord.class),
-    deletecolorspace(0x00000065, UnimplementedHemfRecord.class),
-    glsrecord(0x00000066, UnimplementedHemfRecord.class),
-    glsboundedrecord(0x00000067, UnimplementedHemfRecord.class),
-    pixelformat(0x00000068, UnimplementedHemfRecord.class),
-    drawescape(0x00000069, UnimplementedHemfRecord.class),
-    extescape(0x0000006A, UnimplementedHemfRecord.class),//no 6b?!
-    smalltextout(0x0000006C, UnimplementedHemfRecord.class),
-    forceufimapping(0x0000006D, UnimplementedHemfRecord.class),
-    namedescape(0x0000006E, UnimplementedHemfRecord.class),
-    colorcorrectpalette(0x0000006F, UnimplementedHemfRecord.class),
-    seticmprofilea(0x00000070, UnimplementedHemfRecord.class),
-    seticmprofilew(0x00000071, UnimplementedHemfRecord.class),
-    alphablend(0x00000072, UnimplementedHemfRecord.class),
-    setlayout(0x00000073, UnimplementedHemfRecord.class),
-    transparentblt(0x00000074, UnimplementedHemfRecord.class),
-    gradientfill(0x00000076, UnimplementedHemfRecord.class), //no 75?!
-    setlinkdufis(0x00000077, UnimplementedHemfRecord.class),
-    settextjustification(0x00000078, HemfText.SetTextJustification.class),
-    colormatchtargetw(0x00000079, UnimplementedHemfRecord.class),
-    createcolorspacew(0x0000007A, UnimplementedHemfRecord.class);
-
-    public final long id;
-    public final Class<? extends HemfRecord> clazz;
-
-    HemfRecordType(long id, Class<? extends HemfRecord> clazz) {
-        this.id = id;
-        this.clazz = clazz;
-    }
-
-    public static HemfRecordType getById(long id) {
-        for (HemfRecordType wrt : values()) {
-            if (wrt.id == id) return wrt;
-        }
-        return null;
-    }
-}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfText.java b/src/scratchpad/src/org/apache/poi/hemf/record/HemfText.java
deleted file mode 100644 (file)
index 74b8212..0000000
+++ /dev/null
@@ -1,262 +0,0 @@
-/* ====================================================================
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================== */
-
-package org.apache.poi.hemf.record;
-
-import static java.nio.charset.StandardCharsets.UTF_16LE;
-
-import java.io.ByteArrayInputStream;
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.nio.charset.Charset;
-
-import org.apache.poi.util.IOUtils;
-import org.apache.poi.util.Internal;
-import org.apache.poi.util.LittleEndian;
-import org.apache.poi.util.LittleEndianInputStream;
-import org.apache.poi.util.RecordFormatException;
-
-/**
- * Container class to gather all text-related commands
- * This is starting out as read only, and very little is actually
- * implemented at this point!
- */
-@Internal
-public class HemfText {
-
-    private static final int MAX_RECORD_LENGTH = 1_000_000;
-
-    public static class ExtCreateFontIndirectW extends UnimplementedHemfRecord {
-    }
-
-    public static class ExtTextOutA implements HemfRecord {
-
-        private long left,top,right,bottom;
-
-        //TODO: translate this to a graphicsmode enum
-        private long graphicsMode;
-
-        private long exScale;
-        private long eyScale;
-        EmrTextObject textObject;
-
-        @Override
-        public HemfRecordType getRecordType() {
-            return HemfRecordType.exttextouta;
-        }
-
-        @Override
-        public long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException {
-            //note that the first 2 uInts have been read off and the recordsize has
-            //been decreased by 8
-            left = leis.readInt();
-            top = leis.readInt();
-            right = leis.readInt();
-            bottom = leis.readInt();
-            graphicsMode = leis.readUInt();
-            exScale = leis.readUInt();
-            eyScale = leis.readUInt();
-
-            int recordSizeInt = -1;
-            if (recordSize < Integer.MAX_VALUE) {
-                recordSizeInt = (int)recordSize;
-            } else {
-                throw new RecordFormatException("can't have text length > Integer.MAX_VALUE");
-            }
-            //guarantee to read the rest of the EMRTextObjectRecord
-            //emrtextbytes start after 7*4 bytes read above
-            byte[] emrTextBytes = IOUtils.safelyAllocate(recordSizeInt-(7*LittleEndian.INT_SIZE), MAX_RECORD_LENGTH);
-            IOUtils.readFully(leis, emrTextBytes);
-            textObject = new EmrTextObject(emrTextBytes, getEncodingHint(), 20);//should be 28, but recordSizeInt has already subtracted 8
-            return recordSize;
-        }
-
-        protected Charset getEncodingHint() {
-            return null;
-        }
-
-        /**
-         *
-         * To be implemented!  We need to get the current character set
-         * from the current font for {@link ExtTextOutA},
-         * which has to be tracked in the playback device.
-         *
-         * For {@link ExtTextOutW}, the charset is "UTF-16LE"
-         *
-         * @param charset the charset to be used to decode the character bytes
-         * @return text from this text element
-         * @throws IOException
-         */
-        public String getText(Charset charset) throws IOException {
-            return textObject.getText(charset);
-        }
-
-        /**
-         *
-         * @return the x offset for the EmrTextObject
-         */
-        public long getX() {
-            return textObject.x;
-        }
-
-        /**
-         *
-         * @return the y offset for the EmrTextObject
-         */
-        public long getY() {
-            return textObject.y;
-        }
-
-        public long getLeft() {
-            return left;
-        }
-
-        public long getTop() {
-            return top;
-        }
-
-        public long getRight() {
-            return right;
-        }
-
-        public long getBottom() {
-            return bottom;
-        }
-
-        public long getGraphicsMode() {
-            return graphicsMode;
-        }
-
-        public long getExScale() {
-            return exScale;
-        }
-
-        public long getEyScale() {
-            return eyScale;
-        }
-
-    }
-
-    public static class ExtTextOutW extends ExtTextOutA {
-
-        @Override
-        public HemfRecordType getRecordType() {
-            return HemfRecordType.exttextoutw;
-        }
-
-        @Override
-        protected Charset getEncodingHint() {
-            return UTF_16LE;
-        }
-
-        public String getText() throws IOException {
-            return getText(UTF_16LE);
-        }
-    }
-
-    /**
-     * Needs to be implemented.  Couldn't find example.
-     */
-    public static class PolyTextOutA extends UnimplementedHemfRecord {
-
-    }
-
-    /**
-     * Needs to be implemented.  Couldn't find example.
-     */
-    public static class PolyTextOutW extends UnimplementedHemfRecord {
-
-    }
-
-    public static class SetTextAlign extends UnimplementedHemfRecord {
-    }
-
-    public static class SetTextColor extends UnimplementedHemfRecord {
-    }
-
-
-    public static class SetTextJustification extends UnimplementedHemfRecord {
-
-    }
-
-    private static class EmrTextObject {
-        long x;
-        long y;
-        int numChars;
-        byte[] rawTextBytes;//this stores _all_ of the bytes to the end of the EMRTextObject record.
-                            //Because of potential variable length encodings, must
-                            //carefully read only the numChars from this byte array.
-
-        EmrTextObject(byte[] emrTextObjBytes, Charset charsetHint, int readSoFar) throws IOException {
-
-            int offset = 0;
-            x = LittleEndian.getUInt(emrTextObjBytes, offset); offset+= LittleEndian.INT_SIZE;
-            y = LittleEndian.getUInt(emrTextObjBytes, offset); offset+= LittleEndian.INT_SIZE;
-            long numCharsLong = LittleEndian.getUInt(emrTextObjBytes, offset); offset += LittleEndian.INT_SIZE;
-            long offString = LittleEndian.getUInt(emrTextObjBytes, offset); offset += LittleEndian.INT_SIZE;
-            int start = (int)offString-offset-readSoFar;
-
-            if (numCharsLong == 0) {
-                rawTextBytes = new byte[0];
-                numChars = 0;
-                return;
-            }
-            if (numCharsLong > Integer.MAX_VALUE) {
-                throw new RecordFormatException("Number of characters can't be > Integer.MAX_VALUE");
-            } else if (numCharsLong < 0) {
-                throw new RecordFormatException("Number of characters can't be < 0");
-            }
-
-            numChars = (int)numCharsLong;
-            rawTextBytes = IOUtils.safelyAllocate(emrTextObjBytes.length-start, MAX_RECORD_LENGTH);
-            System.arraycopy(emrTextObjBytes, start, rawTextBytes, 0, emrTextObjBytes.length-start);
-        }
-
-        String getText(Charset charset) throws IOException {
-            StringBuilder sb = new StringBuilder();
-            try (Reader r = new InputStreamReader(new ByteArrayInputStream(rawTextBytes), charset)) {
-                for (int i = 0; i < numChars; i++) {
-                    sb.appendCodePoint(readCodePoint(r));
-                }
-            }
-            return sb.toString();
-        }
-
-        //TODO: move this to IOUtils?
-        private int readCodePoint(Reader r) throws IOException {
-            int c1 = r.read();
-            if (c1 == -1) {
-                throw new EOFException("Tried to read beyond byte array");
-            }
-            if (!Character.isHighSurrogate((char)c1)) {
-                return c1;
-            }
-            int c2 = r.read();
-            if (c2 == -1) {
-                throw new EOFException("Tried to read beyond byte array");
-            }
-            if (!Character.isLowSurrogate((char)c2)) {
-                throw new RecordFormatException("Expected low surrogate after high surrogate");
-            }
-            return Character.toCodePoint((char)c1, (char)c2);
-        }
-    }
-
-
-}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/UnimplementedHemfRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/UnimplementedHemfRecord.java
deleted file mode 100644 (file)
index 6f3ded4..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/* ====================================================================
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================== */
-
-package org.apache.poi.hemf.record;
-
-
-import java.io.IOException;
-
-import org.apache.poi.util.IOUtils;
-import org.apache.poi.util.Internal;
-import org.apache.poi.util.LittleEndianInputStream;
-
-@Internal
-public class UnimplementedHemfRecord implements HemfRecord {
-
-    private long recordId;
-    public UnimplementedHemfRecord() {
-
-    }
-
-    @Override
-    public HemfRecordType getRecordType() {
-        return HemfRecordType.getById(recordId);
-    }
-
-    @Override
-    public long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException {
-        this.recordId = recordId;
-        long skipped = IOUtils.skipFully(leis, recordSize);
-        if (skipped < recordSize) {
-            throw new IOException("End of stream reached before record read");
-        }
-        return skipped;
-    }
-}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java
new file mode 100644 (file)
index 0000000..0a459e3
--- /dev/null
@@ -0,0 +1,444 @@
+/* ====================================================================
+   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.record.emf;
+
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+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.util.IOUtils;
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianInputStream;
+import org.apache.poi.util.RecordFormatException;
+
+/**
+ * Contains arbitrary data
+ */
+@Internal
+public class HemfComment {
+    private static final int MAX_RECORD_LENGTH = 1_000_000;
+
+    public enum HemfCommentRecordType {
+        emfGeneric(-1, EmfCommentDataGeneric::new, false),
+        emfSpool(0x00000000, EmfCommentDataGeneric::new, false),
+        emfPlus(0x2B464D45, EmfCommentDataPlus::new, false),
+        emfPublic(0x43494447, null, false),
+        emfBeginGroup(0x00000002, EmfCommentDataBeginGroup::new, true),
+        emfEndGroup(0x00000003, EmfCommentDataEndGroup::new, true),
+        emfMultiFormats(0x40000004, EmfCommentDataMultiformats::new, true),
+        emfWMF(0x80000001, EmfCommentDataWMF::new, true),
+        emfUnicodeString(0x00000040, EmfCommentDataUnicode::new, true),
+        emfUnicodeEnd(0x00000080, EmfCommentDataUnicode::new, true)
+        ;
+
+
+        public final long id;
+        public final Supplier<? extends EmfCommentData> constructor;
+        public final boolean isEmfPublic;
+
+        HemfCommentRecordType(long id, Supplier<? extends EmfCommentData> constructor, boolean isEmfPublic) {
+            this.id = id;
+            this.constructor = constructor;
+            this.isEmfPublic = isEmfPublic;
+        }
+
+        public static HemfCommentRecordType getById(long id, boolean isEmfPublic) {
+            for (HemfCommentRecordType wrt : values()) {
+                if (wrt.id == id && wrt.isEmfPublic == isEmfPublic) return wrt;
+            }
+            return emfGeneric;
+        }
+    }
+
+    public interface EmfCommentData {
+        HemfCommentRecordType getCommentRecordType();
+
+        long init(LittleEndianInputStream leis, long dataSize) throws IOException;
+    }
+
+    public static class EmfComment implements HemfRecord {
+        private EmfCommentData data;
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.comment;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            int startIdx = leis.getReadIndex();
+            data = new EmfCommentDataIterator(leis, (int)recordSize, true).next();
+            return leis.getReadIndex()-startIdx;
+        }
+
+        public EmfCommentData getCommentData() {
+            return data;
+        }
+    }
+
+    public static class EmfCommentDataIterator implements Iterator<EmfCommentData> {
+
+        private final LittleEndianInputStream leis;
+        private final int startIdx;
+        private final int limit;
+        private EmfCommentData currentRecord;
+        /** is the caller the EmfComment */
+        private final boolean emfParent;
+
+        public EmfCommentDataIterator(LittleEndianInputStream leis, int limit, boolean emfParent) {
+            this.leis = leis;
+            this.limit = limit;
+            this.emfParent = emfParent;
+            startIdx = leis.getReadIndex();
+            //queue the first non-header record
+            currentRecord = _next();
+        }
+
+        @Override
+        public boolean hasNext() {
+            return currentRecord != null;
+        }
+
+        @Override
+        public EmfCommentData next() {
+            EmfCommentData toReturn = currentRecord;
+            final boolean isEOF = (limit == -1 || leis.getReadIndex() >= startIdx+limit);
+            // (currentRecord instanceof HemfPlusMisc.EmfEof)
+            currentRecord = isEOF ? null : _next();
+            return toReturn;
+        }
+
+        private EmfCommentData _next() {
+            long type, recordSize;
+            if (currentRecord == null && emfParent) {
+                type = HemfRecordType.comment.id;
+                recordSize = limit;
+            } 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();
+                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.
+                recordSize = leis.readUInt();
+            }
+
+            // A 32-bit unsigned integer that specifies the size, in bytes, of the CommentIdentifier and
+            // CommentRecordParm fields in the RecordBuffer field that follows.
+            // It MUST NOT include the size of itself or the size of the AlignmentPadding field, if present.
+            long dataSize = leis.readUInt();
+
+            try {
+                leis.mark(2*LittleEndianConsts.INT_SIZE);
+                // An optional, 32-bit unsigned integer that identifies the type of comment record.
+                // See the preceding table for descriptions of these record types.
+                // Valid comment identifier values are listed in the following table.
+                //
+                // If this field contains any other value, the comment record MUST be an EMR_COMMENT record
+                final int commentIdentifier = (int)leis.readUInt();
+                // A 32-bit unsigned integer that identifies the type of public comment record.
+                final int publicCommentIdentifier = (int)leis.readUInt();
+
+                final boolean isEmfPublic = (commentIdentifier == HemfCommentRecordType.emfPublic.id);
+                leis.reset();
+
+                final HemfCommentRecordType commentType = HemfCommentRecordType.getById
+                    (isEmfPublic ? publicCommentIdentifier : commentIdentifier, isEmfPublic);
+                assert(commentType != null);
+                final EmfCommentData record = commentType.constructor.get();
+
+                long readBytes = record.init(leis, dataSize);
+                final int skipBytes = (int)(recordSize-4-readBytes);
+                assert (skipBytes >= 0);
+                leis.skipFully(skipBytes);
+
+                return record;
+            } catch (IOException e) {
+                throw new RecordFormatException(e);
+            }
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException("Remove not supported");
+        }
+
+    }
+
+
+
+    /**
+     * Private data is unknown to EMF; it is meaningful only to applications that know the format of the
+     * data and how to use it. EMR_COMMENT private data records MAY be ignored.
+     */
+    public static class EmfCommentDataGeneric implements EmfCommentData {
+        private byte[] privateData;
+
+        @Override
+        public HemfCommentRecordType getCommentRecordType() {
+            return HemfCommentRecordType.emfGeneric;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long dataSize) throws IOException {
+            privateData = IOUtils.safelyAllocate(dataSize, MAX_RECORD_LENGTH);
+            leis.readFully(privateData);
+            return privateData.length;
+        }
+    }
+
+    /** The EMR_COMMENT_EMFPLUS record contains embedded EMF+ records. */
+    public static class EmfCommentDataPlus implements EmfCommentData {
+        private final List<HemfPlusRecord> records = new ArrayList<>();
+
+        @Override
+        public HemfCommentRecordType getCommentRecordType() {
+            return HemfCommentRecordType.emfPlus;
+        }
+
+        @Override
+        public long init(final LittleEndianInputStream leis, final long dataSize)
+        throws IOException {
+            long startIdx = leis.getReadIndex();
+            int commentIdentifier = leis.readInt();
+            assert (commentIdentifier == HemfCommentRecordType.emfPlus.id);
+            new HemfPlusRecordIterator(leis, (int)dataSize-LittleEndianConsts.INT_SIZE).forEachRemaining(records::add);
+            return leis.getReadIndex()-startIdx;
+        }
+
+        public List<HemfPlusRecord> getRecords() {
+            return Collections.unmodifiableList(records);
+        }
+    }
+
+    public static class EmfCommentDataBeginGroup implements EmfCommentData {
+        private final Rectangle2D bounds = new Rectangle2D.Double();
+        private String description;
+
+        @Override
+        public HemfCommentRecordType getCommentRecordType() {
+            return HemfCommentRecordType.emfBeginGroup;
+        }
+
+        @Override
+        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);
+            final int publicCommentIdentifier = (int)leis.readUInt();
+            assert(publicCommentIdentifier == HemfCommentRecordType.emfBeginGroup.id);
+            HemfDraw.readRectL(leis, bounds);
+
+            // The number of Unicode characters in the optional description string that follows.
+            int nDescription = (int)leis.readUInt();
+
+            byte[] buf = IOUtils.safelyAllocate(nDescription*2, MAX_RECORD_LENGTH);
+            leis.readFully(buf);
+            description = new String(buf, StandardCharsets.UTF_16LE);
+
+            return leis.getReadIndex()-startIdx;
+        }
+    }
+
+    public static class EmfCommentDataEndGroup implements EmfCommentData {
+        @Override
+        public HemfCommentRecordType getCommentRecordType() {
+            return HemfCommentRecordType.emfEndGroup;
+        }
+
+        @Override
+        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);
+            final int publicCommentIdentifier = (int)leis.readUInt();
+            assert(publicCommentIdentifier == HemfCommentRecordType.emfEndGroup.id);
+            return leis.getReadIndex()-startIdx;
+        }
+    }
+
+    public static class EmfCommentDataMultiformats implements EmfCommentData {
+        private final Rectangle2D bounds = new Rectangle2D.Double();
+        private final List<EmfCommentDataFormat> formats = new ArrayList<>();
+
+        @Override
+        public HemfCommentRecordType getCommentRecordType() {
+            return HemfCommentRecordType.emfMultiFormats;
+        }
+
+        @Override
+        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);
+            final int publicCommentIdentifier = (int)leis.readUInt();
+            assert(publicCommentIdentifier == HemfCommentRecordType.emfMultiFormats.id);
+            HemfDraw.readRectL(leis, bounds);
+
+            // A 32-bit unsigned integer that specifies the number of graphics formats contained in this record.
+            int countFormats = (int)leis.readUInt();
+            for (int i=0; i<countFormats; i++) {
+                EmfCommentDataFormat fmt = new EmfCommentDataFormat();
+                long readBytes = fmt.init(leis, dataSize, startIdx);
+                formats.add(fmt);
+                if (readBytes == 0) {
+                    // binary data is appended without DataFormat header
+                    break;
+                }
+            }
+
+            for (EmfCommentDataFormat fmt : formats) {
+                int skip = fmt.offData-(leis.getReadIndex()-startIdx);
+                leis.skipFully(skip);
+                fmt.rawData = new byte[fmt.sizeData];
+                leis.readFully(fmt.rawData);
+            }
+
+            return leis.getReadIndex()-startIdx;
+        }
+
+        public List<EmfCommentDataFormat> getFormats() {
+            return Collections.unmodifiableList(formats);
+        }
+    }
+
+    public enum EmfFormatSignature {
+        ENHMETA_SIGNATURE(0x464D4520),
+        EPS_SIGNATURE(0x46535045);
+
+        int id;
+
+        EmfFormatSignature(int flag) {
+            this.id = id;
+        }
+
+        public static EmfFormatSignature getById(int id) {
+            for (EmfFormatSignature wrt : values()) {
+                if (wrt.id == id) return wrt;
+            }
+            return null;
+        }
+
+    }
+
+    public static class EmfCommentDataFormat {
+        private EmfFormatSignature signature;
+        private int version;
+        private int sizeData;
+        private int offData;
+        private byte[] rawData;
+
+        public long init(final LittleEndianInputStream leis, final long dataSize, long startIdx) throws IOException {
+            // A 32-bit unsigned integer that specifies the format of the image data.
+            signature = EmfFormatSignature.getById(leis.readInt());
+
+            // A 32-bit unsigned integer that specifies the format version number.
+            // If the Signature field specifies encapsulated PostScript (EPS), this value MUST be 0x00000001;
+            // otherwise, this value MUST be ignored.
+            version = leis.readInt();
+
+            // A 32-bit unsigned integer that specifies the size of the data in bytes.
+            sizeData = leis.readInt();
+
+            // A 32-bit unsigned integer that specifies the offset to the data from the start
+            // of the identifier field in an EMR_COMMENT_PUBLIC record. The offset MUST be 32-bit aligned.
+            offData = leis.readInt();
+            if (sizeData < 0) {
+                throw new RecordFormatException("size for emrformat must be > 0");
+            }
+            if (offData < 0) {
+                throw new RecordFormatException("offset for emrformat must be > 0");
+            }
+
+            return 4*LittleEndianConsts.INT_SIZE;
+        }
+
+        public byte[] getRawData() {
+            return rawData;
+        }
+    }
+
+    public static class EmfCommentDataWMF implements EmfCommentData {
+        private final Rectangle2D bounds = new Rectangle2D.Double();
+        private final List<EmfCommentDataFormat> formats = new ArrayList<>();
+
+        @Override
+        public HemfCommentRecordType getCommentRecordType() {
+            return HemfCommentRecordType.emfWMF;
+        }
+
+        @Override
+        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);
+            final int publicCommentIdentifier = (int)leis.readUInt();
+            assert(publicCommentIdentifier == HemfCommentRecordType.emfWMF.id);
+
+            // A 16-bit unsigned integer that specifies the WMF metafile version in terms
+            //of support for device-independent bitmaps (DIBs)
+            int version = leis.readUShort();
+
+            // A 16-bit value that MUST be 0x0000 and MUST be ignored.
+            leis.skipFully(LittleEndianConsts.SHORT_SIZE);
+
+            // A 32-bit unsigned integer that specifies the checksum for this record.
+            int checksum = leis.readInt();
+
+            // A 32-bit value that MUST be 0x00000000 and MUST be ignored.
+            int flags = leis.readInt();
+
+            // A 32-bit unsigned integer that specifies the size, in bytes, of the
+            // WMF metafile in the WinMetafile field.
+            int winMetafileSize = (int)leis.readUInt();
+
+            byte[] winMetafile = IOUtils.safelyAllocate(winMetafileSize, MAX_RECORD_LENGTH);
+            leis.readFully(winMetafile);
+
+            return leis.getReadIndex()-startIdx;
+        }
+    }
+
+    public static class EmfCommentDataUnicode implements EmfCommentData {
+        private final Rectangle2D bounds = new Rectangle2D.Double();
+        private final List<EmfCommentDataFormat> formats = new ArrayList<>();
+
+        @Override
+        public HemfCommentRecordType getCommentRecordType() {
+            return HemfCommentRecordType.emfUnicodeString;
+        }
+
+        @Override
+        public long init(final LittleEndianInputStream leis, final long dataSize)
+                throws IOException {
+            throw new RecordFormatException("UNICODE_STRING/UNICODE_END values are reserved in CommentPublic records");
+        }
+    }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java
new file mode 100644 (file)
index 0000000..688a9ff
--- /dev/null
@@ -0,0 +1,783 @@
+/* ====================================================================
+   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.record.emf;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Dimension2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+
+import org.apache.poi.hemf.draw.HemfGraphics;
+import org.apache.poi.hwmf.draw.HwmfGraphics;
+import org.apache.poi.hwmf.record.HwmfDraw;
+import org.apache.poi.hwmf.record.HwmfDraw.WmfSelectObject;
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianInputStream;
+
+public class HemfDraw {
+    /**
+     * The EMR_SELECTOBJECT record adds a graphics object to the current metafile playback device
+     * context. The object is specified either by its index in the EMF Object Table or by its
+     * value from the StockObject enumeration.
+     */
+    public static class EmfSelectObject extends WmfSelectObject implements HemfRecord {
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.selectObject;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            // A 32-bit unsigned integer that specifies either the index of a graphics object in the
+            // EMF Object Table or the index of a stock object from the StockObject enumeration.
+            objectIndex = leis.readInt();
+            return LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+
+    /** The EMR_POLYBEZIER record specifies one or more Bezier curves. */
+    public static class EmfPolyBezier extends HwmfDraw.WmfPolygon implements HemfRecord {
+        private final Rectangle2D bounds = new Rectangle2D.Double();
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.polyBezier;
+        }
+
+        protected long readPoint(LittleEndianInputStream leis, Point2D point) {
+            return readPointL(leis, point);
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            long size = readRectL(leis, bounds);
+
+            /* A 32-bit unsigned integer that specifies the number of points in the points
+             * array. This value MUST be one more than three times the number of curves to
+             * be drawn, because each Bezier curve requires two control points and an
+             * endpoint, and the initial curve requires an additional starting point.
+             *
+             * Line width | Device supports wideline | Maximum points allowed
+             *    1       |            n/a           |          16K
+             *   > 1      |            yes           |          16K
+             *   > 1      |             no           |         1360
+             *
+             * Any extra points MUST be ignored.
+             */
+            final int count = (int)leis.readUInt();
+            final int points = Math.min(count, 16384);
+            size += LittleEndianConsts.INT_SIZE;
+
+            poly.reset();
+
+            /* Cubic Bezier curves are defined using the endpoints and control points
+             * specified by the points field. The first curve is drawn from the first
+             * point to the fourth point, using the second and third points as control
+             * points. Each subsequent curve in the sequence needs exactly three more points:
+             * the ending point of the previous curve is used as the starting point,
+             * the next two points in the sequence are control points,
+             * and the third is the ending point.
+             * The cubic Bezier curves SHOULD be drawn using the current pen.
+             */
+
+            Point2D pnt[] = { new Point2D.Double(), new Point2D.Double(), new Point2D.Double() };
+
+            // points-1 because of the first point
+            final int pointCnt = hasStartPoint() ? points-1 : points;
+            for (int i=0; i+3<pointCnt; i+=3) {
+                // x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point.
+                // y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point.
+                if (i==0 && hasStartPoint()) {
+                    size += readPoint(leis, pnt[0]);
+                    poly.moveTo(pnt[0].getX(),pnt[0].getY());
+                }
+
+                size += readPoint(leis, pnt[0]);
+                size += readPoint(leis, pnt[1]);
+                size += readPoint(leis, pnt[2]);
+
+                poly.curveTo(
+                    pnt[0].getX(),pnt[0].getY(),
+                    pnt[1].getX(),pnt[1].getY(),
+                    pnt[2].getX(),pnt[2].getY()
+                );
+            }
+
+            return size;
+        }
+
+        /**
+         * @return true, if start point is in the list of points. false, if start point is taken from the context
+         */
+        protected boolean hasStartPoint() {
+            return true;
+        }
+    }
+
+    /**
+     * The EMR_POLYBEZIER16 record specifies one or more Bezier curves.
+     * The curves are drawn using the current pen.
+     */
+    public static class EmfPolyBezier16 extends EmfPolyBezier {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.polyBezier16;
+        }
+
+        protected long readPoint(LittleEndianInputStream leis, Point2D point) {
+            return readPointS(leis, point);
+        }
+    }
+
+
+    /**
+     * The EMR_POLYGON record specifies a polygon consisting of two or more vertexes connected by
+     * straight lines.
+     */
+    public static class EmfPolygon extends HwmfDraw.WmfPolygon implements HemfRecord {
+        private final Rectangle2D bounds = new Rectangle2D.Double();
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.polygon;
+        }
+
+        protected long readPoint(LittleEndianInputStream leis, Point2D point) {
+            return readPointL(leis, point);
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            long size = readRectL(leis, bounds);
+
+            // see PolyBezier about limits
+            final int count = (int)leis.readUInt();
+            final int points = Math.min(count, 16384);
+            size += LittleEndianConsts.INT_SIZE;
+
+            Point2D pnt = new Point2D.Double();
+            for (int i=0; i<points; i++) {
+                size += readPoint(leis, pnt);
+                if (i==0) {
+                    poly.moveTo(pnt.getX(), pnt.getY());
+
+                    if (hasStartPoint()) {
+                        continue;
+                    }
+
+                    // if this path is connected to the current position (= has no start point)
+                    // the first entry is a dummy entry and will be skipped later
+                }
+                poly.lineTo(pnt.getX(), pnt.getY());
+            }
+
+            return size;
+        }
+
+        /**
+         * @return true, if start point is in the list of points. false, if start point is taken from the context
+         */
+        protected boolean hasStartPoint() {
+            return true;
+        }
+    }
+
+    /**
+     * The EMR_POLYGON16 record specifies a polygon consisting of two or more vertexes connected by straight lines.
+     * The polygon is outlined by using the current pen and filled by using the current brush and polygon fill mode.
+     * The polygon is closed automatically by drawing a line from the last vertex to the first
+     */
+    public static class EmfPolygon16 extends EmfPolygon {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.polygon16;
+        }
+
+        @Override
+        protected long readPoint(LittleEndianInputStream leis, Point2D point) {
+            return readPointS(leis, point);
+        }
+    }
+
+    /**
+     * The EMR_POLYLINE record specifies a series of line segments by connecting the points in the
+     * specified array.
+     */
+    public static class EmfPolyline extends EmfPolygon {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.polyline;
+        }
+
+        @Override
+        protected boolean isFill() {
+            return false;
+        }
+    }
+
+    /**
+     * The EMR_POLYLINE16 record specifies a series of line segments by connecting the points in the
+     * specified array.
+     */
+    public static class EmfPolyline16 extends EmfPolyline {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.polyline16;
+        }
+
+        @Override
+        protected long readPoint(LittleEndianInputStream leis, Point2D point) {
+            return readPointS(leis, point);
+        }
+    }
+
+    /**
+     * The EMR_POLYBEZIERTO record specifies one or more Bezier curves based upon the current
+     * position.
+     */
+    public static class EmfPolyBezierTo extends EmfPolyBezier {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.polyBezierTo;
+        }
+
+        @Override
+        protected boolean hasStartPoint() {
+            return false;
+        }
+
+        @Override
+        protected Path2D getShape(HwmfGraphics ctx) {
+            return polyTo(ctx, poly);
+        }
+    }
+
+    /**
+     * The EMR_POLYBEZIERTO16 record specifies one or more Bezier curves based on the current
+     * position.
+     */
+    public static class EmfPolyBezierTo16 extends EmfPolyBezierTo {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.polyBezierTo16;
+        }
+
+        @Override
+        protected long readPoint(LittleEndianInputStream leis, Point2D point) {
+            return readPointS(leis, point);
+        }
+    }
+
+    /** The EMR_POLYLINETO record specifies one or more straight lines based upon the current position. */
+    public static class EmfPolylineTo extends EmfPolyline {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.polylineTo;
+        }
+
+        @Override
+        protected boolean hasStartPoint() {
+            return false;
+        }
+
+        @Override
+        protected Path2D getShape(HwmfGraphics ctx) {
+            return polyTo(ctx, poly);
+        }
+    }
+
+    /**
+     * The EMR_POLYLINETO16 record specifies one or more straight lines based upon the current position.
+     * A line is drawn from the current position to the first point specified by the points field by using the
+     * current pen. For each additional line, drawing is performed from the ending point of the previous
+     * line to the next point specified by points.
+     */
+    public static class EmfPolylineTo16 extends EmfPolylineTo {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.polylineTo16;
+        }
+
+        @Override
+        protected long readPoint(LittleEndianInputStream leis, Point2D point) {
+            return readPointS(leis, point);
+        }
+    }
+
+    /**
+     * The EMR_POLYPOLYGON record specifies a series of closed polygons.
+     */
+    public static class EmfPolyPolygon extends HwmfDraw.WmfPolyPolygon implements HemfRecord {
+        private final Rectangle2D bounds = new Rectangle2D.Double();
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.polyPolygon;
+        }
+
+        protected long readPoint(LittleEndianInputStream leis, Point2D point) {
+            return readPointL(leis, point);
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            long size = readRectL(leis, bounds);
+
+            // A 32-bit unsigned integer that specifies the number of polygons.
+            long numberOfPolygons = leis.readUInt();
+            // A 32-bit unsigned integer that specifies the total number of points in all polygons.
+            long count = Math.min(16384, leis.readUInt());
+
+            size += 2 * LittleEndianConsts.INT_SIZE;
+
+            // An array of 32-bit unsigned integers that specifies the point count for each polygon.
+            long[] polygonPointCount = new long[(int)numberOfPolygons];
+
+            size += numberOfPolygons * LittleEndianConsts.INT_SIZE;
+
+            for (int i=0; i<numberOfPolygons; i++) {
+                polygonPointCount[i] = leis.readUInt();
+            }
+
+            Point2D pnt = new Point2D.Double();
+            for (long nPoints : polygonPointCount) {
+                /**
+                 * An array of WMF PointL objects that specifies the points for all polygons in logical units.
+                 * The number of points is specified by the Count field value.
+                 */
+                Path2D poly = new Path2D.Double();
+                for (int i=0; i<nPoints; i++) {
+                    size += readPoint(leis, pnt);
+                    if (i == 0) {
+                        poly.moveTo(pnt.getX(), pnt.getY());
+                    } else {
+                        poly.lineTo(pnt.getX(), pnt.getY());
+                    }
+                }
+                if (isClosed()) {
+                    poly.closePath();
+                }
+                polyList.add(poly);
+            }
+            return size;
+        }
+
+        /**
+         * @return true, if a polyline should be closed, i.e. is a polygon
+         */
+        protected boolean isClosed() {
+            return true;
+        }
+    }
+
+    /**
+     * The EMR_POLYPOLYGON16 record specifies a series of closed polygons. Each polygon is outlined
+     * using the current pen, and filled using the current brush and polygon fill mode.
+     * The polygons drawn by this record can overlap.
+     */
+    public static class EmfPolyPolygon16 extends EmfPolyPolygon {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.polyPolygon16;
+        }
+
+        @Override
+        protected long readPoint(LittleEndianInputStream leis, Point2D point) {
+            return readPointS(leis, point);
+        }
+    }
+
+    /**
+     * The EMR_POLYPOLYLINE record specifies multiple series of connected line segments.
+     */
+    public static class EmfPolyPolyline extends EmfPolyPolygon {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.polyPolyline;
+        }
+
+        @Override
+        protected boolean isClosed() {
+            return false;
+        }
+
+        @Override
+        protected boolean isFill() {
+            return false;
+        }
+    }
+
+    /** The EMR_POLYPOLYLINE16 record specifies multiple series of connected line segments. */
+    public static class EmfPolyPolyline16 extends EmfPolyPolyline {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.polyPolyline16;
+        }
+
+        @Override
+        protected long readPoint(LittleEndianInputStream leis, Point2D point) {
+            return readPointS(leis, point);
+        }
+    }
+
+    /**
+     * The EMR_SETPIXELV record defines the color of the pixel at the specified logical coordinates.
+     */
+    public static class EmfSetPixelV extends HwmfDraw.WmfSetPixel implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.setPixelV;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            long size = readPointL(leis, point);
+            size += colorRef.init(leis);
+            return size;
+        }
+    }
+
+    /**
+     * The EMR_MOVETOEX record specifies coordinates of the new current position, in logical units.
+     */
+    public static class EmfSetMoveToEx extends HwmfDraw.WmfMoveTo implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.setMoveToEx;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            return readPointL(leis, point);
+        }
+    }
+
+    /**
+     * The EMR_ARCTO record specifies an elliptical arc.
+     * It resets the current position to the end point of the arc.
+     */
+    public static class EmfArc extends HwmfDraw.WmfArc implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.arc;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            long size = readRectL(leis, bounds);
+            size += readPointL(leis, startPoint);
+            size += readPointL(leis, endPoint);
+            return size;
+        }
+
+        @Override
+        public void draw(HemfGraphics ctx) {
+            super.draw(ctx);
+            ctx.getProperties().setLocation(endPoint);
+        }
+    }
+
+    /**
+     * The EMR_CHORD record specifies a chord, which is a region bounded by the intersection of an
+     * ellipse and a line segment, called a secant. The chord is outlined by using the current pen
+     * and filled by using the current brush.
+     */
+    public static class EmfChord extends HwmfDraw.WmfChord implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.chord;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            long size = readRectL(leis, bounds);
+            size += readPointL(leis, startPoint);
+            size += readPointL(leis, endPoint);
+            return size;
+        }
+    }
+
+    /**
+     * The EMR_PIE record specifies a pie-shaped wedge bounded by the intersection of an ellipse and two
+     * radials. The pie is outlined by using the current pen and filled by using the current brush.
+     */
+    public static class EmfPie extends HwmfDraw.WmfPie implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.pie;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            long size = readRectL(leis, bounds);
+            size += readPointL(leis, startPoint);
+            size += readPointL(leis, endPoint);
+            return size;
+        }
+    }
+
+    /**
+     * The EMR_ELLIPSE record specifies an ellipse. The center of the ellipse is the center of the specified
+     * bounding rectangle. The ellipse is outlined by using the current pen and is filled by using the current
+     * brush.
+     */
+    public static class EmfEllipse extends HwmfDraw.WmfEllipse implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.ellipse;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            return readRectL(leis, bounds);
+        }
+    }
+
+    /**
+     * The EMR_RECTANGLE record draws a rectangle. The rectangle is outlined by using the current pen
+     * and filled by using the current brush.
+     */
+    public static class EmfRectangle extends HwmfDraw.WmfRectangle implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.rectangle;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            return readRectL(leis, bounds);
+        }
+    }
+
+    /**
+     * The EMR_ROUNDRECT record specifies a rectangle with rounded corners. The rectangle is outlined
+     * by using the current pen and filled by using the current brush.
+     */
+    public static class EmfRoundRect extends HwmfDraw.WmfRoundRect implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.roundRect;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            long size = readRectL(leis, bounds);
+
+            // A 32-bit unsigned integer that defines the x-coordinate of the point.
+            width = (int)leis.readUInt();
+            height = (int)leis.readUInt();
+
+            return size + 2*LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+    /**
+     * The EMR_LINETO record specifies a line from the current position up to, but not including, the
+     * specified point. It resets the current position to the specified point.
+     */
+    public static class EmfLineTo extends HwmfDraw.WmfLineTo implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.lineTo;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            return readPointL(leis, point);
+        }
+    }
+
+    /**
+     * The EMR_ARCTO record specifies an elliptical arc.
+     * It resets the current position to the end point of the arc.
+     */
+    public static class EmfArcTo extends HwmfDraw.WmfArc implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.arcTo;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            long size = readRectL(leis, bounds);
+            size += readPointL(leis, startPoint);
+            size += readPointL(leis, endPoint);
+            return size;
+        }
+
+        @Override
+        public void draw(HemfGraphics ctx) {
+            super.draw(ctx);
+            ctx.getProperties().setLocation(endPoint);
+        }
+    }
+
+    /** The EMR_POLYDRAW record specifies a set of line segments and Bezier curves. */
+    public static class EmfPolyDraw extends HwmfDraw.WmfPolygon implements HemfRecord {
+        private final Rectangle2D bounds = new Rectangle2D.Double();
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.polyDraw;
+        }
+
+        protected long readPoint(LittleEndianInputStream leis, Point2D point) {
+            return readPointL(leis, point);
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            long size = readRectL(leis, bounds);
+            int count = (int)leis.readUInt();
+            size += LittleEndianConsts.INT_SIZE;
+            Point2D points[] = new Point2D[count];
+            for (int i=0; i<count; i++) {
+                 size += readPoint(leis, points[i]);
+            }
+
+            poly.reset();
+
+            for (int i=0; i<count; i++) {
+                int mode = leis.readUByte();
+                switch (mode & 0x06) {
+                    // PT_LINETO
+                    // Specifies that a line is to be drawn from the current position to this point, which
+                    // then becomes the new current position.
+                    case 0x02:
+                        poly.lineTo(points[i].getX(), points[i].getY());
+                        break;
+                    // PT_BEZIERTO
+                    // Specifies that this point is a control point or ending point for a Bezier curve.
+                    // PT_BEZIERTO types always occur in sets of three.
+                    // The current position defines the starting point for the Bezier curve.
+                    // The first two PT_BEZIERTO points are the control points,
+                    // and the third PT_BEZIERTO point is the ending point.
+                    // The ending point becomes the new current position.
+                    // If there are not three consecutive PT_BEZIERTO points, an error results.
+                    case 0x04:
+                        int mode2 = leis.readUByte();
+                        int mode3 = leis.readUByte();
+                        assert(mode2 == 0x04 && mode3 == 0x04);
+                        poly.curveTo(
+                            points[i].getX(), points[i].getY(),
+                            points[i+1].getX(), points[i+1].getY(),
+                            points[i+2].getX(), points[i+2].getY()
+                        );
+                        i+=2;
+                        break;
+                    // PT_MOVETO
+                    // Specifies that this point starts a disjoint figure. This point becomes the new current position.
+                    case 0x06:
+                        poly.moveTo(points[i].getX(), points[i].getY());
+                        break;
+                    default:
+                        // TODO: log error
+                        break;
+                }
+
+                // PT_CLOSEFIGURE
+                // A PT_LINETO or PT_BEZIERTO type can be combined with this value by using the bitwise operator OR
+                // to indicate that the corresponding point is the last point in a figure and the figure is closed.
+                // The current position is set to the ending point of the closing line.
+                if ((mode & 0x01) == 0x01) {
+                    this.poly.closePath();
+                }
+            }
+            size += count;
+            return size;
+        }
+    }
+
+    public static class EmfPolyDraw16 extends EmfPolyDraw {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.polyDraw16;
+        }
+
+        protected long readPoint(LittleEndianInputStream leis, Point2D point) {
+            return readPointS(leis, point);
+        }
+
+    }
+    static long readRectL(LittleEndianInputStream leis, Rectangle2D bounds) {
+        /* A 32-bit signed integer that defines the x coordinate, in logical coordinates,
+         * of the ... corner of the rectangle.
+         */
+        final int left = leis.readInt();
+        final int top = leis.readInt();
+        final int right = leis.readInt();
+        final int bottom = leis.readInt();
+        bounds.setRect(left, top, right-left, bottom-top);
+
+        return 4 * LittleEndianConsts.INT_SIZE;
+    }
+
+    static long readPointS(LittleEndianInputStream leis, Point2D point) {
+        // x (2 bytes): A 16-bit signed integer that defines the horizontal (x) coordinate of the point.
+        final int x = leis.readShort();
+        // y (2 bytes): A 16-bit signed integer that defines the vertical (y) coordinate of the point.
+        final int y = leis.readShort();
+        point.setLocation(x, y);
+
+        return 2*LittleEndianConsts.SHORT_SIZE;
+
+    }
+    static long readPointL(LittleEndianInputStream leis, Point2D point) {
+        // x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point.
+        final int x = leis.readInt();
+        // y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point.
+        final int y = leis.readInt();
+        point.setLocation(x, y);
+
+        return 2*LittleEndianConsts.INT_SIZE;
+
+    }
+
+    static long readDimensionFloat(LittleEndianInputStream leis, Dimension2D dimension) {
+        final double width = leis.readFloat();
+        final double height = leis.readFloat();
+        dimension.setSize(width, height);
+        return 2*LittleEndianConsts.INT_SIZE;
+    }
+
+    static long readDimensionInt(LittleEndianInputStream leis, Dimension2D dimension) {
+        final double width = leis.readUInt();
+        final double height = leis.readUInt();
+        dimension.setSize(width, height);
+        return 2*LittleEndianConsts.INT_SIZE;
+    }
+
+    private static Path2D polyTo(HwmfGraphics ctx, Path2D poly) {
+        Path2D polyCopy = new Path2D.Double();
+        Point2D start = ctx.getProperties().getLocation();
+        polyCopy.moveTo(start.getX(), start.getY());
+
+        PathIterator iter = poly.getPathIterator(null);
+        iter.next();
+
+        polyCopy.append(iter, true);
+        return polyCopy;
+    }
+
+
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java
new file mode 100644 (file)
index 0000000..5e4b972
--- /dev/null
@@ -0,0 +1,620 @@
+/* ====================================================================
+   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.record.emf;
+
+import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL;
+import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL;
+import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Area;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.poi.hwmf.draw.HwmfGraphics;
+import org.apache.poi.hwmf.record.HwmfBitmapDib;
+import org.apache.poi.hwmf.record.HwmfColorRef;
+import org.apache.poi.hwmf.record.HwmfDraw;
+import org.apache.poi.hwmf.record.HwmfFill;
+import org.apache.poi.hwmf.record.HwmfFill.ColorUsage;
+import org.apache.poi.hwmf.record.HwmfTernaryRasterOp;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianInputStream;
+
+public class HemfFill {
+    private static final int MAX_RECORD_LENGTH = 10_000_000;
+
+    public enum HemfRegionMode {
+        RGN_AND(0x01),
+        RGN_OR(0x02),
+        RGN_XOR(0x03),
+        RGN_DIFF(0x04),
+        RGN_COPY(0x05);
+
+        int flag;
+        HemfRegionMode(int flag) {
+            this.flag = flag;
+        }
+
+        public static HemfRegionMode valueOf(int flag) {
+            for (HemfRegionMode rm : values()) {
+                if (rm.flag == flag) return rm;
+            }
+            return null;
+        }
+
+    }
+
+
+    /**
+     * The EMR_SETPOLYFILLMODE record defines polygon fill mode.
+     */
+    public static class EmfSetPolyfillMode extends HwmfFill.WmfSetPolyfillMode implements HemfRecord {
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.setPolyfillMode;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            // A 32-bit unsigned integer that specifies the polygon fill mode and
+            // MUST be in the PolygonFillMode enumeration.
+            polyfillMode = HwmfPolyfillMode.valueOf((int)leis.readUInt());
+            return LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+    public static class EmfExtFloodFill extends HwmfFill.WmfExtFloodFill implements HemfRecord {
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.extFloodFill;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            long size = readPointL(leis, start);
+            size = colorRef.init(leis);
+            // A 32-bit unsigned integer that specifies how to use the Color value to determine the area for
+            // the flood fill operation. The value MUST be in the FloodFill enumeration
+            mode = (int)leis.readUInt();
+            return size + LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+    /**
+     * The EMR_STRETCHBLT record specifies a block transfer of pixels from a source bitmap to a destination rectangle,
+     * optionally in combination with a brush pattern, according to a specified raster operation, stretching or
+     * compressing the output to fit the dimensions of the destination, if necessary.
+     */
+    public static class EmfStretchBlt extends HwmfFill.WmfBitBlt implements HemfRecord {
+        protected final Rectangle2D bounds = new Rectangle2D.Double();
+
+        /** An XForm object that specifies a world-space to page-space transform to apply to the source bitmap. */
+        protected final byte[] xformSrc = new byte[24];
+
+        /** A WMF ColorRef object that specifies the background color of the source bitmap. */
+        protected final HwmfColorRef bkColorSrc = new HwmfColorRef();
+
+        /**
+         * A 32-bit unsigned integer that specifies how to interpret values in the color table in
+         * the source bitmap header. This value MUST be in the DIBColors enumeration
+         */
+        protected int usageSrc;
+
+        /** The source bitmap header. */
+        protected byte[] bmiSrc;
+
+        /** The source bitmap bits. */
+        protected byte[] bitsSrc;
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.stretchBlt;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            long size = readRectL(leis, bounds);
+
+            size += readBounds2(leis, this.dstBounds);
+
+            // A 32-bit unsigned integer that specifies the raster operation code. This code defines how the
+            // color data of the source rectangle is to be combined with the color data of the destination
+            // rectangle and optionally a brush pattern, to achieve the final color.
+            int rasterOpIndex = (int)leis.readUInt();
+
+            rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex);
+
+            size += LittleEndianConsts.INT_SIZE;
+
+            final Point2D srcPnt = new Point2D.Double();
+            size += readPointL(leis, srcPnt);
+
+            leis.readFully(xformSrc);
+            size += 24;
+
+            size += bkColorSrc.init(leis);
+
+            usageSrc = (int)leis.readUInt();
+
+            // A 32-bit unsigned integer that specifies the offset, in bytes, from the
+            // start of this record to the source bitmap header in the BitmapBuffer field.
+            final int offBmiSrc = (int)leis.readUInt();
+
+            // A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap header.
+            final int cbBmiSrc = (int)leis.readUInt();
+
+            // A 32-bit unsigned integer that specifies the offset, in bytes, from the
+            // start of this record to the source bitmap bits in the BitmapBuffer field.
+            final int offBitsSrc = (int)leis.readUInt();
+
+            // A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap bits.
+            final int cbBitsSrc = (int)leis.readUInt();
+
+            size += 5*LittleEndianConsts.INT_SIZE;
+
+            if (srcEqualsDstDimension()) {
+                srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight());
+            } else {
+                int srcWidth = leis.readInt();
+                int srcHeight = leis.readInt();
+                size += 2 * LittleEndianConsts.INT_SIZE;
+                srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), srcWidth, srcHeight);
+            }
+
+            // size + type and size field
+            final int undefinedSpace1 = (int)(offBmiSrc - size - HEADER_SIZE);
+            assert(undefinedSpace1 >= 0);
+            leis.skipFully(undefinedSpace1);
+            size += undefinedSpace1;
+
+            bmiSrc = IOUtils.safelyAllocate(cbBmiSrc, MAX_RECORD_LENGTH);
+            leis.readFully(bmiSrc);
+            size += cbBmiSrc;
+
+            final int undefinedSpace2 = (int)(offBitsSrc - size - HEADER_SIZE);
+            assert(undefinedSpace2 >= 0);
+            leis.skipFully(undefinedSpace2);
+            size += undefinedSpace2;
+
+            bitsSrc = IOUtils.safelyAllocate(cbBitsSrc, MAX_RECORD_LENGTH);
+            leis.readFully(bitsSrc);
+            size += cbBitsSrc;
+
+            return size;
+        }
+
+        protected boolean srcEqualsDstDimension() {
+            return false;
+        }
+    }
+
+    /**
+     * The EMR_BITBLT record specifies a block transfer of pixels from a source bitmap to a destination rectangle,
+     * optionally in combination with a brush pattern, according to a specified raster operation.
+     */
+    public static class EmfBitBlt extends EmfStretchBlt {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.bitBlt;
+        }
+
+        @Override
+        protected boolean srcEqualsDstDimension() {
+            return false;
+        }
+    }
+
+
+    /** The EMR_FRAMERGN record draws a border around the specified region using the specified brush. */
+    public static class EmfFrameRgn extends HwmfDraw.WmfFrameRegion implements HemfRecord {
+        private final Rectangle2D bounds = new Rectangle2D.Double();
+        private final List<Rectangle2D> rgnRects = new ArrayList<>();
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.frameRgn;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            long size = readRectL(leis, bounds);
+            // A 32-bit unsigned integer that specifies the size of region data, in bytes.
+            long rgnDataSize = leis.readUInt();
+            // A 32-bit unsigned integer that specifies the brush EMF Object Table index.
+            brushIndex = (int)leis.readUInt();
+            // A 32-bit signed integer that specifies the width of the vertical brush stroke, in logical units.
+            width = leis.readInt();
+            // A 32-bit signed integer that specifies the height of the horizontal brush stroke, in logical units.
+            height = leis.readInt();
+            size += 4*LittleEndianConsts.INT_SIZE;
+            size += readRgnData(leis, rgnRects);
+            return size;
+        }
+
+        @Override
+        public void draw(HwmfGraphics ctx) {
+            ctx.applyObjectTableEntry(brushIndex);
+
+            Area frame = new Area();
+            for (Rectangle2D rct : rgnRects) {
+                frame.add(new Area(rct));
+            }
+            Rectangle2D frameBounds = frame.getBounds2D();
+            AffineTransform at = new AffineTransform();
+            at.translate(bounds.getX()-frameBounds.getX(), bounds.getY()-frameBounds.getY());
+            at.scale(bounds.getWidth()/frameBounds.getWidth(), bounds.getHeight()/frameBounds.getHeight());
+            frame.transform(at);
+
+            ctx.fill(frame);
+        }
+    }
+
+    /** The EMR_INVERTRGN record inverts the colors in the specified region. */
+    public static class EmfInvertRgn implements HemfRecord {
+        protected final Rectangle2D bounds = new Rectangle2D.Double();
+        protected final List<Rectangle2D> rgnRects = new ArrayList<>();
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.invertRgn;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            long size = readRectL(leis, bounds);
+            // A 32-bit unsigned integer that specifies the size of region data, in bytes.
+            long rgnDataSize = leis.readUInt();
+            size += LittleEndianConsts.INT_SIZE;
+            size += readRgnData(leis, rgnRects);
+            return size;
+        }
+    }
+
+    /**
+     * The EMR_PAINTRGN record paints the specified region by using the brush currently selected into the
+     * playback device context.
+     */
+    public static class EmfPaintRgn extends EmfInvertRgn {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.paintRgn;
+        }
+    }
+
+    /** The EMR_FILLRGN record fills the specified region by using the specified brush. */
+    public static class EmfFillRgn extends HwmfFill.WmfFillRegion implements HemfRecord {
+        protected final Rectangle2D bounds = new Rectangle2D.Double();
+        protected final List<Rectangle2D> rgnRects = new ArrayList<>();
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.fillRgn;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            long size = readRectL(leis, bounds);
+            // A 32-bit unsigned integer that specifies the size of region data, in bytes.
+            long rgnDataSize = leis.readUInt();
+            brushIndex = (int)leis.readUInt();
+            size += 2*LittleEndianConsts.INT_SIZE;
+            size += readRgnData(leis, rgnRects);
+            return size;
+        }
+    }
+
+    public static class EmfExtSelectClipRgn implements HemfRecord {
+        protected HemfRegionMode regionMode;
+        protected final List<Rectangle2D> rgnRects = new ArrayList<>();
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.extSelectClipRgn;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            // A 32-bit unsigned integer that specifies the size of region data in bytes
+            long rgnDataSize = leis.readUInt();
+            // A 32-bit unsigned integer that specifies the way to use the region.
+            regionMode = HemfRegionMode.valueOf((int)leis.readUInt());
+            long size = 2* LittleEndianConsts.INT_SIZE;
+
+            // If RegionMode is RGN_COPY, this data can be omitted and the clip region
+            // SHOULD be set to the default (NULL) clip region.
+            if (regionMode != HemfRegionMode.RGN_COPY) {
+                size += readRgnData(leis, rgnRects);
+            }
+            return size;
+        }
+    }
+
+    public static class EmfAlphaBlend implements HemfRecord {
+        /** the destination bounding rectangle in device units */
+        protected final Rectangle2D bounds = new Rectangle2D.Double();
+        /** the destination rectangle */
+        protected final Rectangle2D destRect = new Rectangle2D.Double();
+        /** the source rectangle */
+        protected final Rectangle2D srcRect = new Rectangle2D.Double();
+        /**
+         * The blend operation code. The only source and destination blend operation that has been defined
+         * is 0x00, which specifies that the source bitmap MUST be combined with the destination bitmap based
+         * on the alpha transparency values of the source pixels.
+         */
+        protected byte blendOperation;
+        /** This value MUST be 0x00 and MUST be ignored. */
+        protected byte blendFlags;
+        /**
+         * An 8-bit unsigned integer that specifies alpha transparency, which determines the blend of the source
+         * and destination bitmaps. This value MUST be used on the entire source bitmap. The minimum alpha
+         * transparency value, zero, corresponds to completely transparent; the maximum value, 0xFF, corresponds
+         * to completely opaque. In effect, a value of 0xFF specifies that the per-pixel alpha values determine
+         * the blend of the source and destination bitmaps.
+         */
+        protected int srcConstantAlpha;
+        /**
+         * A byte that specifies how source and destination pixels are interpreted with respect to alpha transparency.
+         *
+         * 0x00:
+         * The pixels in the source bitmap do not specify alpha transparency.
+         * In this case, the SrcConstantAlpha value determines the blend of the source and destination bitmaps.
+         * Note that in the following equations SrcConstantAlpha is divided by 255,
+         * which produces a value in the range 0 to 1.
+         *
+         * 0x01: "AC_SRC_ALPHA"
+         * Indicates that the source bitmap is 32 bits-per-pixel and specifies an alpha transparency value
+         * for each pixel.
+         */
+        protected byte alphaFormat;
+        /** a world-space to page-space transform to apply to the source bitmap. */
+        protected final AffineTransform xFormSrc = new AffineTransform();
+        /** the background color of the source bitmap. */
+        protected final HwmfColorRef bkColorSrc = new HwmfColorRef();
+        /**
+         * A 32-bit unsigned integer that specifies how to interpret values in the
+         * color table in the source bitmap header.
+         */
+        protected ColorUsage usageSrc;
+
+        protected final HwmfBitmapDib bitmap = new HwmfBitmapDib();
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.alphaBlend;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            final int startIdx = leis.getReadIndex();
+
+            long size = readRectL(leis, bounds);
+            size += readBounds2(leis, destRect);
+
+            blendOperation = leis.readByte();
+            assert (blendOperation == 0);
+            blendFlags = leis.readByte();
+            assert (blendOperation == 0);
+            srcConstantAlpha = leis.readUByte();
+            alphaFormat = leis.readByte();
+
+            // A 32-bit signed integer that specifies the logical x-coordinate of the upper-left
+            // corner of the source rectangle.
+            final int xSrc = leis.readInt();
+            // A 32-bit signed integer that specifies the logical y-coordinate of the upper-left
+            // corner of the source rectangle.
+            final int ySrc = leis.readInt();
+
+            size += 3*LittleEndianConsts.INT_SIZE;
+            size += readXForm(leis, xFormSrc);
+            size += bkColorSrc.init(leis);
+
+            usageSrc = ColorUsage.valueOf((int)leis.readUInt());
+
+
+            // A 32-bit unsigned integer that specifies the offset, in bytes, from the
+            // start of this record to the source bitmap header in the BitmapBuffer field.
+            final int offBmiSrc = (int)leis.readUInt();
+
+            // A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap header.
+            final int cbBmiSrc = (int)leis.readUInt();
+            // A 32-bit unsigned integer that specifies the offset, in bytes, from the
+            // start of this record to the source bitmap bits in the BitmapBuffer field.
+            final int offBitsSrc = (int)leis.readUInt();
+            // A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap bits.
+            final int cbBitsSrc = (int)leis.readUInt();
+
+            // A 32-bit signed integer that specifies the logical width of the source rectangle.
+            // This value MUST be greater than zero.
+            final int cxSrc = leis.readInt();
+            // A 32-bit signed integer that specifies the logical height of the source rectangle.
+            // This value MUST be greater than zero.
+            final int cySrc = leis.readInt();
+
+            srcRect.setRect(xSrc, ySrc, cxSrc, cySrc);
+
+            size += 7 * LittleEndianConsts.INT_SIZE;
+
+            size += readBitmap(leis, bitmap, startIdx, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc);
+
+            return size;
+        }
+    }
+
+    public static class EmfSetDiBitsToDevice implements HemfRecord {
+        protected final Rectangle2D bounds = new Rectangle2D.Double();
+        protected final Point2D dest = new Point2D.Double();
+        protected final Rectangle2D src = new Rectangle2D.Double();
+        protected ColorUsage usageSrc;
+        protected final HwmfBitmapDib bitmap = new HwmfBitmapDib();
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.setDiBitsToDevice;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            int startIdx = leis.getReadIndex();
+
+            // A WMF RectL object that defines the destination bounding rectangle in device units.
+            long size = readRectL(leis, bounds);
+            // the logical x/y-coordinate of the upper-left corner of the destination rectangle.
+            size += readPointL(leis, dest);
+            // the source rectangle
+            size += readBounds2(leis, src);
+            // A 32-bit unsigned integer that specifies the offset, in bytes, from the
+            // start of this record to the source bitmap header in the BitmapBuffer field.
+            final int offBmiSrc = (int)leis.readUInt();
+            // A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap header.
+            final int cbBmiSrc = (int)leis.readUInt();
+            // A 32-bit unsigned integer that specifies the offset, in bytes, from the
+            // start of this record to the source bitmap bits in the BitmapBuffer field.
+            final int offBitsSrc = (int)leis.readUInt();
+            // A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap bits.
+            final int cbBitsSrc = (int)leis.readUInt();
+            // A 32-bit unsigned integer that specifies how to interpret values in the color table
+            // in the source bitmap header. This value MUST be in the DIBColors enumeration
+            usageSrc = ColorUsage.valueOf((int)leis.readUInt());
+            // A 32-bit unsigned integer that specifies the first scan line in the array.
+            final int iStartScan = (int)leis.readUInt();
+            // A 32-bit unsigned integer that specifies the number of scan lines.
+            final int cScans = (int)leis.readUInt();
+            size += 7*LittleEndianConsts.INT_SIZE;
+
+            size += readBitmap(leis, bitmap, startIdx, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc);
+
+            return size;
+        }
+    }
+
+    static long readBitmap(final LittleEndianInputStream leis, final HwmfBitmapDib bitmap,
+            final int startIdx, final int offBmiSrc, final int cbBmiSrc, final int offBitsSrc, int cbBitsSrc)
+    throws IOException {
+        final int offCurr = leis.getReadIndex()-(startIdx-HEADER_SIZE);
+        final int undefinedSpace1 = offBmiSrc-offCurr;
+        assert(undefinedSpace1 >= 0);
+
+        final int undefinedSpace2 = offBitsSrc-offCurr-cbBmiSrc-undefinedSpace1;
+        assert(undefinedSpace2 >= 0);
+
+        leis.skipFully(undefinedSpace1);
+
+        if (cbBmiSrc == 0 || cbBitsSrc == 0) {
+            return undefinedSpace1;
+        }
+
+        final LittleEndianInputStream leisDib;
+        if (undefinedSpace2 == 0) {
+            leisDib = leis;
+        } else {
+            final ByteArrayOutputStream bos = new ByteArrayOutputStream(cbBmiSrc+cbBitsSrc);
+            final long cbBmiSrcAct = IOUtils.copy(leis, bos, cbBmiSrc);
+            assert (cbBmiSrcAct == cbBmiSrc);
+            leis.skipFully(undefinedSpace2);
+            final long cbBitsSrcAct = IOUtils.copy(leis, bos, cbBitsSrc);
+            assert (cbBitsSrcAct == cbBitsSrc);
+            leisDib = new LittleEndianInputStream(new ByteArrayInputStream(bos.toByteArray()));
+        }
+        final int dibSize = cbBmiSrc+cbBitsSrc;
+        final int dibSizeAct = bitmap.init(leisDib, dibSize);
+        assert (dibSizeAct <= dibSize);
+        return undefinedSpace1 + cbBmiSrc + undefinedSpace2 + cbBitsSrc;
+    }
+
+
+    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);
+        // A 32-bit unsigned integer that specifies the region type. This SHOULD be RDH_RECTANGLES (0x00000001)
+        long rgnHdrType = leis.readUInt();
+        assert(rgnHdrType == 1);
+        // A 32-bit unsigned integer that specifies the number of rectangles in this region.
+        long rgnCntRect = leis.readUInt();
+        // A 32-bit unsigned integer that specifies the size of the buffer of rectangles in bytes.
+        long rgnCntBytes = leis.readUInt();
+        long size = 4*LittleEndianConsts.INT_SIZE;
+        // A 128-bit WMF RectL object, which specifies the bounds of the region.
+        Rectangle2D rgnBounds = new Rectangle2D.Double();
+        size += readRectL(leis, rgnBounds);
+        for (int i=0; i<rgnCntRect; i++) {
+            Rectangle2D rgnRct = new Rectangle2D.Double();
+            size += readRectL(leis, rgnRct);
+            rgnRects.add(rgnRct);
+        }
+        return size;
+    }
+
+
+    static int readBounds2(LittleEndianInputStream leis, Rectangle2D bounds) {
+        /**
+         * The 32-bit signed integers that defines the corners of the bounding rectangle.
+         */
+        int x = leis.readInt();
+        int y = leis.readInt();
+        int w = leis.readInt();
+        int h = leis.readInt();
+
+        bounds.setRect(x, y, w, h);
+
+        return 4 * LittleEndianConsts.INT_SIZE;
+    }
+
+    static int readXForm(LittleEndianInputStream leis, AffineTransform xform) {
+        // mapping <java AffineTransform> = <xform>:
+        // m00 (scaleX) = eM11 (Horizontal scaling component)
+        // m11 (scaleY) = eM22 (Vertical scaling component)
+        // m01 (shearX) = eM12 (Horizontal proportionality constant)
+        // m10 (shearY) = eM21 (Vertical proportionality constant)
+        // m02 (translateX) = eDx (The horizontal translation component, in logical units.)
+        // m12 (translateY) = eDy (The vertical translation component, in logical units.)
+
+        // A 32-bit floating-point value of the transform matrix.
+        double eM11 = leis.readFloat();
+
+        // A 32-bit floating-point value of the transform matrix.
+        double eM12 = leis.readFloat();
+
+        // A 32-bit floating-point value of the transform matrix.
+        double eM21 = leis.readFloat();
+
+        // A 32-bit floating-point value of the transform matrix.
+        double eM22 = leis.readFloat();
+
+        // A 32-bit floating-point value that contains a horizontal translation component, in logical units.
+        double eDx = leis.readFloat();
+
+        // A 32-bit floating-point value that contains a vertical translation component, in logical units.
+        double eDy = leis.readFloat();
+
+        xform.setTransform(eM11, eM21, eM12, eM22, eDx, eDy);
+
+        return 6 * LittleEndian.INT_SIZE;
+    }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFont.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFont.java
new file mode 100644 (file)
index 0000000..8690855
--- /dev/null
@@ -0,0 +1,464 @@
+/* ====================================================================
+   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.record.emf;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.poi.common.usermodel.fonts.FontCharset;
+import org.apache.poi.hwmf.record.HwmfFont;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianInputStream;
+
+public class HemfFont extends HwmfFont {
+    private static final int LOGFONT_SIZE = 92;
+    private static final int LOGFONTPANOSE_SIZE = 320;
+
+    protected interface LogFontDetails {}
+
+    protected static class LogFontExDv implements LogFontDetails {
+        protected int[] designVector;
+    }
+
+    protected static class LogFontPanose implements LogFontDetails {
+        enum FamilyType {
+            PAN_ANY,
+            PAN_NO_FIT,
+            PAN_FAMILY_TEXT_DISPLAY,
+            PAN_FAMILY_SCRIPT,
+            PAN_FAMILY_DECORATIVE,
+            PAN_FAMILY_PICTORIAL
+        }
+
+        enum SerifType {
+            PAN_ANY,
+            PAN_NO_FIT,
+            PAN_SERIF_COVE,
+            PAN_SERIF_OBTUSE_COVE,
+            PAN_SERIF_SQUARE_COVE,
+            PAN_SERIF_OBTUSE_SQUARE_COVE,
+            PAN_SERIF_SQUARE,
+            PAN_SERIF_THIN,
+            PAN_SERIF_BONE,
+            PAN_SERIF_EXAGGERATED,
+            PAN_SERIF_TRIANGLE,
+            PAN_SERIF_NORMAL_SANS,
+            PAN_SERIF_OBTUSE_SANS,
+            PAN_SERIF_PERP_SANS,
+            PAN_SERIF_FLARED,
+            PAN_SERIF_ROUNDED
+        }
+
+        enum FontWeight {
+            PAN_ANY,
+            PAN_NO_FIT,
+            PAN_WEIGHT_VERY_LIGHT,
+            PAN_WEIGHT_LIGHT,
+            PAN_WEIGHT_THIN,
+            PAN_WEIGHT_BOOK,
+            PAN_WEIGHT_MEDIUM,
+            PAN_WEIGHT_DEMI,
+            PAN_WEIGHT_BOLD,
+            PAN_WEIGHT_HEAVY,
+            PAN_WEIGHT_BLACK,
+            PAN_WEIGHT_NORD
+        }
+
+        enum Proportion {
+            PAN_ANY,
+            PAN_NO_FIT,
+            PAN_PROP_OLD_STYLE,
+            PAN_PROP_MODERN,
+            PAN_PROP_EVEN_WIDTH,
+            PAN_PROP_EXPANDED,
+            PAN_PROP_CONDENSED,
+            PAN_PROP_VERY_EXPANDED,
+            PAN_PROP_VERY_CONDENSED,
+            PAN_PROP_MONOSPACED
+        }
+
+        enum Contrast {
+            PAN_ANY,
+            PAN_NO_FIT,
+            PAN_CONTRAST_NONE,
+            PAN_CONTRAST_VERY_LOW,
+            PAN_CONTRAST_LOW,
+            PAN_CONTRAST_MEDIUM_LOW,
+            PAN_CONTRAST_MEDIUM,
+            PAN_CONTRAST_MEDIUM_HIGH,
+            PAN_CONTRAST_HIGH,
+            PAN_CONTRAST_VERY_HIGH
+        }
+
+        enum StrokeVariation {
+            PAN_ANY,
+            PAN_NO_FIT,
+            PAN_STROKE_GRADUAL_DIAG,
+            PAN_STROKE_GRADUAL_TRAN,
+            PAN_STROKE_GRADUAL_VERT,
+            PAN_STROKE_GRADUAL_HORZ,
+            PAN_STROKE_RAPID_VERT,
+            PAN_STROKE_RAPID_HORZ,
+            PAN_STROKE_INSTANT_VERT
+        }
+
+        enum ArmStyle {
+            PAN_ANY,
+            PAN_NO_FIT,
+            PAN_STRAIGHT_ARMS_HORZ,
+            PAN_STRAIGHT_ARMS_WEDGE,
+            PAN_STRAIGHT_ARMS_VERT,
+            PAN_STRAIGHT_ARMS_SINGLE_SERIF,
+            PAN_STRAIGHT_ARMS_DOUBLE_SERIF,
+            PAN_BENT_ARMS_HORZ,
+            PAN_BENT_ARMS_WEDGE,
+            PAN_BENT_ARMS_VERT,
+            PAN_BENT_ARMS_SINGLE_SERIF,
+            PAN_BENT_ARMS_DOUBLE_SERIF
+        }
+
+        enum Letterform {
+            PAN_ANY,
+            PAN_NO_FIT,
+            PAN_LETT_NORMAL_CONTACT,
+            PAN_LETT_NORMAL_WEIGHTED,
+            PAN_LETT_NORMAL_BOXED,
+            PAN_LETT_NORMAL_FLATTENED,
+            PAN_LETT_NORMAL_ROUNDED,
+            PAN_LETT_NORMAL_OFF_CENTER,
+            PAN_LETT_NORMAL_SQUARE,
+            PAN_LETT_OBLIQUE_CONTACT,
+            PAN_LETT_OBLIQUE_WEIGHTED,
+            PAN_LETT_OBLIQUE_BOXED,
+            PAN_LETT_OBLIQUE_FLATTENED,
+            PAN_LETT_OBLIQUE_ROUNDED,
+            PAN_LETT_OBLIQUE_OFF_CENTER,
+            PAN_LETT_OBLIQUE_SQUARE
+        }
+
+        enum MidLine {
+            PAN_ANY,
+            PAN_NO_FIT,
+            PAN_MIDLINE_STANDARD_TRIMMED,
+            PAN_MIDLINE_STANDARD_POINTED,
+            PAN_MIDLINE_STANDARD_SERIFED,
+            PAN_MIDLINE_HIGH_TRIMMED,
+            PAN_MIDLINE_HIGH_POINTED,
+            PAN_MIDLINE_HIGH_SERIFED,
+            PAN_MIDLINE_CONSTANT_TRIMMED,
+            PAN_MIDLINE_CONSTANT_POINTED,
+            PAN_MIDLINE_CONSTANT_SERIFED,
+            PAN_MIDLINE_LOW_TRIMMED,
+            PAN_MIDLINE_LOW_POINTED,
+            PAN_MIDLINE_LOW_SERIFED
+        }
+
+        enum XHeight {
+            PAN_ANY,
+            PAN_NO_FIT,
+            PAN_XHEIGHT_CONSTANT_SMALL,
+            PAN_XHEIGHT_CONSTANT_STD,
+            PAN_XHEIGHT_CONSTANT_LARGE,
+            PAN_XHEIGHT_DUCKING_SMALL,
+            PAN_XHEIGHT_DUCKING_STD,
+            PAN_XHEIGHT_DUCKING_LARGE
+        }
+
+        protected int styleSize;
+        protected int vendorId;
+        protected int culture;
+        protected FamilyType familyType;
+        protected SerifType serifStyle;
+        protected FontWeight weight;
+        protected Proportion proportion;
+        protected Contrast contrast;
+        protected StrokeVariation strokeVariation;
+        protected ArmStyle armStyle;
+        protected Letterform letterform;
+        protected MidLine midLine;
+        protected XHeight xHeight;
+    }
+
+    protected String fullname;
+    protected String style;
+    protected String script;
+
+    protected LogFontDetails details;
+
+    @Override
+    public int init(LittleEndianInputStream leis, long recordSize) throws IOException {
+        // A 32-bit signed integer that specifies the height, in logical units, of the font's
+        // character cell or character. The character height value, also known as the em size, is the
+        // character cell height value minus the internal leading value. The font mapper SHOULD
+        // interpret the value specified in the Height field in the following manner.
+        //
+        // 0x00000000 < value:
+        // The font mapper transforms this value into device units and matches it against
+        // the cell height of the available fonts.
+        //
+        // 0x00000000
+        // The font mapper uses a default height value when it searches for a match.
+        //
+        // value < 0x00000000:
+        // The font mapper transforms this value into device units and matches its
+        // absolute value against the character height of the available fonts.
+        //
+        // For all height comparisons, the font mapper SHOULD look for the largest font that does not
+        // exceed the requested size.
+        height = leis.readInt();
+
+        // A 32-bit signed integer that specifies the average width, in logical units, of
+        // characters in the font. If the Width field value is zero, an appropriate value SHOULD be
+        // calculated from other LogFont values to find a font that has the typographer's intended
+        // aspect ratio.
+        width = leis.readInt();
+
+        // A 32-bit signed integer that specifies the angle, in tenths of degrees,
+        // between the escapement vector and the x-axis of the device. The escapement vector is
+        // parallel to the baseline of a row of text.
+        //
+        // When the graphics mode is set to GM_ADVANCED, the escapement angle of the string can
+        // be specified independently of the orientation angle of the string's characters.
+        escapement = leis.readInt();
+
+        // A 32-bit signed integer that specifies the angle, in tenths of degrees,
+        // between each character's baseline and the x-axis of the device.
+        orientation = leis.readInt();
+
+        // A 32-bit signed integer that specifies the weight of the font in the range zero through 1000.
+        // For example, 400 is normal and 700 is bold. If this value is zero, a default weight can be used.
+        weight = leis.readInt();
+
+        // An 8-bit unsigned integer that specifies an italic font if set to 0x01;
+        // otherwise, it MUST be set to 0x00.
+        italic = (leis.readUByte() == 0x01);
+
+        // An 8-bit unsigned integer that specifies an underlined font if set to 0x01;
+        // otherwise, it MUST be set to 0x00.
+        underline = (leis.readUByte() == 0x01);
+
+        // An 8-bit unsigned integer that specifies a strikeout font if set to 0x01;
+        // otherwise, it MUST be set to 0x00.
+        strikeOut = (leis.readUByte() == 0x01);
+
+        // An 8-bit unsigned integer that specifies the set of character glyphs.
+        // It MUST be a value in the WMF CharacterSet enumeration.
+        // If the character set is unknown, metafile processing SHOULD NOT attempt
+        // to translate or interpret strings that are rendered with that font.
+        // If a typeface name is specified in the Facename field, the CharSet field
+        // value MUST match the character set of that typeface.
+        charSet = FontCharset.valueOf(leis.readUByte());
+
+        // An 8-bit unsigned integer that specifies the output precision.
+        // The output precision defines how closely the font is required to match the requested height, width,
+        // character orientation, escapement, pitch, and font type.
+        // It MUST be a value from the WMF OutPrecision enumeration.
+        // Applications can use the output precision to control how the font mapper chooses a font when the
+        // operating system contains more than one font with a specified name. For example, if an operating
+        // system contains a font named Symbol in rasterized and TrueType forms, an output precision value
+        // of OUT_TT_PRECIS forces the font mapper to choose the TrueType version.
+        // A value of OUT_TT_ONLY_PRECIS forces the font mapper to choose a TrueType font, even if it is
+        // necessary to substitute a TrueType font with another name.
+        outPrecision = WmfOutPrecision.valueOf(leis.readUByte());
+
+        // An 8-bit unsigned integer that specifies the clipping precision.
+        // The clipping precision defines how to clip characters that are partially outside the clipping region.
+        // It can be one or more of the WMF ClipPrecision Flags
+        clipPrecision.init(leis);
+
+        // An 8-bit unsigned integer that specifies the output quality. The output quality defines how closely
+        // to attempt to match the logical-font attributes to those of an actual physical font.
+        // It MUST be one of the values in the WMF FontQuality enumeration
+        quality = WmfFontQuality.valueOf(leis.readUByte());
+
+        // A WMF PitchAndFamily object that specifies the pitch and family of the font.
+        // Font families describe the look of a font in a general way.
+        // They are intended for specifying a font when the specified typeface is not available.
+        pitchAndFamily = leis.readUByte();
+
+        int size = 5* LittleEndianConsts.INT_SIZE+8*LittleEndianConsts.BYTE_SIZE;
+
+        StringBuilder sb = new StringBuilder();
+
+        // A string of no more than 32 Unicode characters that specifies the typeface name of the font.
+        // If the length of this string is less than 32 characters, a terminating NULL MUST be present,
+        // after which the remainder of this field MUST be ignored.
+        int readBytes = readString(leis, sb, 32);
+        if (readBytes == -1) {
+            throw new IOException("Font facename can't be determined.");
+        }
+        facename = sb.toString();
+        size += readBytes;
+
+        if (recordSize <= LOGFONT_SIZE) {
+            return size;
+        }
+
+        // A string of 64 Unicode characters that contains the font's full name.
+        // Ifthe length of this string is less than 64 characters, a terminating
+        // NULL MUST be present, after which the remainder of this field MUST be ignored.
+        readBytes = readString(leis, sb, 64);
+        if (readBytes == -1) {
+            throw new IOException("Font fullname can't be determined.");
+        }
+        fullname = sb.toString();
+        size += readBytes;
+
+        // A string of 32 Unicode characters that defines the font's style. If the length of
+        // this string is less than 32 characters, a terminating NULL MUST be present,
+        // after which the remainder of this field MUST be ignored.
+        readBytes = readString(leis, sb, 32);
+        if (readBytes == -1) {
+            throw new IOException("Font style can't be determined.");
+        }
+        style = sb.toString();
+        size += readBytes;
+
+        if (recordSize == LOGFONTPANOSE_SIZE) {
+            // LogFontPanose Object
+
+            LogFontPanose logPan = new LogFontPanose();
+            details = logPan;
+
+            int version = leis.readInt();
+
+            // A 32-bit unsigned integer that specifies the point size at which font
+            //hinting is performed. If set to zero, font hinting is performed at the point size corresponding
+            //to the Height field in the LogFont object in the LogFont field.
+            logPan.styleSize = (int)leis.readUInt();
+
+            int match = leis.readInt();
+
+            int reserved = leis.readInt();
+
+            logPan.vendorId = leis.readInt();
+
+            logPan.culture = leis.readInt();
+
+            // An 8-bit unsigned integer that specifies the family type.
+            // The value MUST be in the FamilyType enumeration table.
+            logPan.familyType = LogFontPanose.FamilyType.values()[leis.readUByte()];
+
+            // An 8-bit unsigned integer that specifies the serif style.
+            // The value MUST be in the SerifType enumeration table.
+            logPan.serifStyle = LogFontPanose.SerifType.values()[leis.readUByte()];
+
+            // An 8-bit unsigned integer that specifies the weight of the font.
+            // The value MUST be in the Weight enumeration table.
+            logPan.weight = LogFontPanose.FontWeight.values()[leis.readUByte()];
+
+            // An 8-bit unsigned integer that specifies the proportion of the font.
+            // The value MUST be in the Proportion enumeration table.
+            logPan.proportion = LogFontPanose.Proportion.values()[leis.readUByte()];
+
+            // An 8-bit unsigned integer that specifies the proportion of the font.
+            // The value MUST be in the Proportion enumeration table.
+            logPan.contrast = LogFontPanose.Contrast.values()[leis.readUByte()];
+
+            // An 8-bit unsigned integer that specifies the stroke variation for the font.
+            // The value MUST be in the StrokeVariation enumeration table.
+            logPan.strokeVariation = LogFontPanose.StrokeVariation.values()[leis.readUByte()];
+
+            // An 8-bit unsigned integer that specifies the arm style of the font.
+            // The value MUST be in the ArmStyle enumeration table.
+            logPan.armStyle = LogFontPanose.ArmStyle.values()[leis.readUByte()];
+
+            // An 8-bit unsigned integer that specifies the letterform of the font.
+            // The value MUST be in the Letterform enumeration table.
+            logPan.letterform = LogFontPanose.Letterform.values()[leis.readUByte()];
+
+            // An 8-bit unsigned integer that specifies the midline of the font.
+            // The value MUST be in the MidLine enumeration table.
+            logPan.midLine = LogFontPanose.MidLine.values()[leis.readUByte()];
+
+            // An 8-bit unsigned integer that specifies the x height of the font.
+            // The value MUST be in the XHeight enumeration table.
+            logPan.xHeight = LogFontPanose.XHeight.values()[leis.readUByte()];
+
+            // skip 2 byte to ensure 32-bit alignment of this structure.
+            leis.skip(2);
+
+            size += 6*LittleEndianConsts.INT_SIZE+10* LittleEndianConsts.BYTE_SIZE+2;
+        } else {
+            // LogFontExDv Object
+
+            LogFontExDv logEx = new LogFontExDv();
+            details = logEx;
+
+                    // A string of 32 Unicode characters that defines the character set of the font.
+            // If the length of this string is less than 32 characters, a terminating NULL MUST be present,
+            // after which the remainder of this field MUST be ignored.
+            readBytes = readString(leis, sb, 32);
+            if (readBytes == -1) {
+                throw new IOException("Font script can't be determined.");
+            }
+            script = sb.toString();
+            size += readBytes;
+
+            // Design Vector
+
+            // A 32-bit unsigned integer that MUST be set to the value 0x08007664.
+            int signature = leis.readInt();
+            assert (signature == 0x08007664);
+
+            // A 32-bit unsigned integer that specifies the number of elements in the
+            // Values array. It MUST be in the range 0 to 16, inclusive.
+            int numAxes = leis.readInt();
+            assert (0 <= numAxes && numAxes <= 16);
+
+            // An optional array of 32-bit signed integers that specify the values of the font axes of a
+            // multiple master, OpenType font. The maximum number of values in the array is 16.
+            if (numAxes > 0) {
+                logEx.designVector = new int[numAxes];
+                for (int i=0; i<numAxes; i++) {
+                    logEx.designVector[i] = leis.readInt();
+                }
+            }
+            size += (2+numAxes)*LittleEndianConsts.INT_SIZE;
+        }
+
+
+
+
+        return size;
+    }
+
+    @Override
+    protected int readString(LittleEndianInputStream leis, StringBuilder sb, int limit) throws IOException {
+        sb.setLength(0);
+        byte buf[] = new byte[limit*2];
+        leis.readFully(buf);
+
+        int b1, b2, readBytes = 0;
+        do {
+            if (readBytes == limit*2) {
+                return -1;
+            }
+
+            b1 = buf[readBytes++];
+            b2 = buf[readBytes++];
+        } while ((b1 != 0 || b2 != 0) && b1 != -1 && b2 != -1 && readBytes <= limit*2);
+
+        sb.append(new String(buf, 0, readBytes-2, StandardCharsets.UTF_16LE));
+
+        return limit*2;
+    }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java
new file mode 100644 (file)
index 0000000..0821d36
--- /dev/null
@@ -0,0 +1,195 @@
+/* ====================================================================
+   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.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 java.awt.geom.Dimension2D;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+
+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;
+
+/**
+ * Extracts the full header from EMF files.
+ * @see org.apache.poi.sl.image.ImageHeaderEMF
+ */
+@Internal
+public class HemfHeader implements HemfRecord {
+
+    private static final int MAX_RECORD_LENGTH = 1_000_000;
+
+
+    private final Rectangle2D boundsRectangle = new Rectangle2D.Double();
+    private final Rectangle2D frameRectangle = new Rectangle2D.Double();
+    private long bytes;
+    private long records;
+    private int handles;
+    private long nDescription;
+    private long offDescription;
+    private long nPalEntries;
+    private boolean hasExtension1;
+    private long cbPixelFormat;
+    private long offPixelFormat;
+    private long bOpenGL;
+    private boolean hasExtension2;
+    private final Dimension2D deviceDimension = new Dimension2DDouble();
+    private final Dimension2D milliDimension = new Dimension2DDouble();
+    private final Dimension2D microDimension = new Dimension2DDouble();
+
+
+    public Rectangle2D getBoundsRectangle() {
+        return boundsRectangle;
+    }
+
+    public Rectangle2D getFrameRectangle() {
+        return frameRectangle;
+    }
+
+    public long getBytes() {
+        return bytes;
+    }
+
+    public long getRecords() {
+        return records;
+    }
+
+    public int getHandles() {
+        return handles;
+    }
+
+    public long getnDescription() {
+        return nDescription;
+    }
+
+    public long getOffDescription() {
+        return offDescription;
+    }
+
+    public long getnPalEntries() {
+        return nPalEntries;
+    }
+
+    public boolean isHasExtension1() {
+        return hasExtension1;
+    }
+
+    public long getCbPixelFormat() {
+        return cbPixelFormat;
+    }
+
+    public long getOffPixelFormat() {
+        return offPixelFormat;
+    }
+
+    public long getbOpenGL() {
+        return bOpenGL;
+    }
+
+    public boolean isHasExtension2() {
+        return hasExtension2;
+    }
+
+    public long getMicrometersX() {
+        return (long)microDimension.getWidth();
+    }
+
+    public long getMicrometersY() {
+        return (long)microDimension.getHeight();
+    }
+
+    @Override
+    public String toString() {
+        return "HemfHeader{" +
+                "boundsRectangle=" + boundsRectangle +
+                ", frameRectangle=" + frameRectangle +
+                ", bytes=" + bytes +
+                ", records=" + records +
+                ", handles=" + handles +
+                ", nDescription=" + nDescription +
+                ", offDescription=" + offDescription +
+                ", nPalEntries=" + nPalEntries +
+                ", hasExtension1=" + hasExtension1 +
+                ", cbPixelFormat=" + cbPixelFormat +
+                ", offPixelFormat=" + offPixelFormat +
+                ", bOpenGL=" + bOpenGL +
+                ", hasExtension2=" + hasExtension2 +
+                ", micrometersX=" + getMicrometersX() +
+                ", micrometersY=" + getMicrometersY() +
+                '}';
+    }
+
+    @Override
+    public HemfRecordType getEmfRecordType() {
+        return HemfRecordType.header;
+    }
+
+    @Override
+    public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+        if (recordId != HemfRecordType.header.id) {
+            throw new IOException("Not a valid EMF header. Record type:"+recordId);
+        }
+
+        //bounds
+        long size = readRectL(leis, boundsRectangle);
+        size += readRectL(leis, frameRectangle);
+
+        int recordSignature = leis.readInt();
+        if (recordSignature != 0x464D4520) {
+            throw new IOException("bad record signature: " + recordSignature);
+        }
+
+        long version = leis.readInt();
+        //According to the spec, MSOffice doesn't pay attention to this value.
+        //It _should_ be 0x00010000
+        bytes = leis.readUInt();
+        records = leis.readUInt();
+        handles = leis.readUShort();
+        //reserved
+        leis.skipFully(LittleEndianConsts.SHORT_SIZE);
+
+        nDescription = leis.readUInt();
+        offDescription = leis.readUInt();
+        nPalEntries = leis.readUInt();
+
+        size += 8*LittleEndianConsts.INT_SIZE;
+
+        size += readDimensionInt(leis, deviceDimension);
+        size += readDimensionInt(leis, milliDimension);
+
+        if (size+12 <= recordSize) {
+            hasExtension1 = true;
+            cbPixelFormat =  leis.readUInt();
+            offPixelFormat = leis.readUInt();
+            bOpenGL = leis.readUInt();
+            size += 3*LittleEndianConsts.INT_SIZE;
+        }
+
+        if (size+8 <= recordSize) {
+            hasExtension2 = true;
+            size += readDimensionInt(leis, microDimension);
+        }
+        return size;
+    }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java
new file mode 100644 (file)
index 0000000..ab3d06b
--- /dev/null
@@ -0,0 +1,447 @@
+/* ====================================================================
+   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.record.emf;
+
+import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionInt;
+import static org.apache.poi.hemf.record.emf.HemfFill.readBitmap;
+import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.poi.hemf.draw.HemfGraphics;
+import org.apache.poi.hwmf.record.HwmfBinaryRasterOp;
+import org.apache.poi.hwmf.record.HwmfBitmapDib;
+import org.apache.poi.hwmf.record.HwmfBrushStyle;
+import org.apache.poi.hwmf.record.HwmfColorRef;
+import org.apache.poi.hwmf.record.HwmfHatchStyle;
+import org.apache.poi.hwmf.record.HwmfMapMode;
+import org.apache.poi.hwmf.record.HwmfMisc;
+import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode;
+import org.apache.poi.hwmf.record.HwmfPalette.PaletteEntry;
+import org.apache.poi.hwmf.record.HwmfPenStyle;
+import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineDash;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.LittleEndianConsts;
+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<>();
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.eof;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+
+            // A 32-bit unsigned integer that specifies the number of palette entries.
+            int nPalEntries = (int)leis.readUInt();
+            // A 32-bit unsigned integer that specifies the offset to the palette entries from the start of this record.
+            int offPalEntries = (int)leis.readUInt();
+
+            int size = 2*LittleEndianConsts.INT_SIZE;
+            int undefinedSpace1 = (int)(offPalEntries - size - HEADER_SIZE);
+            assert (undefinedSpace1 >= 0);
+            leis.skipFully(undefinedSpace1);
+            size += undefinedSpace1;
+
+            for (int i=0; i<nPalEntries; i++) {
+                PaletteEntry pe = new PaletteEntry();
+                size += pe.init(leis);
+            }
+
+            int undefinedSpace2 = (int)(recordSize - size - LittleEndianConsts.INT_SIZE);
+            assert (undefinedSpace2 >= 0);
+            leis.skipFully(undefinedSpace2);
+            size += undefinedSpace2;
+
+            // A 32-bit unsigned integer that MUST be the same as Size and MUST be the
+            // last field of the record and hence the metafile.
+            // LogPaletteEntry objects, if they exist, MUST precede this field.
+            long sizeLast = leis.readUInt();
+            size += LittleEndianConsts.INT_SIZE;
+            assert ((sizeLast-HEADER_SIZE) == recordSize && recordSize == size);
+
+            return size;
+        }
+    }
+
+    /**
+     * The EMF_SAVEDC record saves the playback device context for later retrieval.
+     */
+    public static class EmfSaveDc implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.saveDc;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            return 0;
+        }
+
+        @Override
+        public void draw(HemfGraphics ctx) {
+            ctx.saveProperties();
+        }
+    }
+
+    /**
+     * The EMF_RESTOREDC record restores the playback device context from a previously saved device
+     * context.
+     */
+    public static class EmfRestoreDc implements HemfRecord {
+
+        /**
+         * SavedDC (4 bytes): A 32-bit signed integer that specifies the saved state to restore relative to
+         * the current state. This value MUST be negative; â€“1 represents the state that was most
+         * recently saved on the stack, â€“2 the one before that, etc.
+         */
+        private int nSavedDC;
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.restoreDc;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            nSavedDC = leis.readInt();
+            return LittleEndianConsts.INT_SIZE;
+        }
+
+        @Override
+        public void draw(HemfGraphics ctx) {
+            ctx.restoreProperties(nSavedDC);
+        }
+    }
+
+    /**
+     * The META_SETBKCOLOR record sets the background color in the playback device context to a
+     * specified color, or to the nearest physical color if the device cannot represent the specified color.
+     */
+    public static class EmfSetBkColor implements HemfRecord {
+
+        private HwmfColorRef colorRef;
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.setBkColor;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            colorRef = new HwmfColorRef();
+            return colorRef.init(leis);
+        }
+
+        @Override
+        public void draw(HemfGraphics ctx) {
+            ctx.getProperties().setBackgroundColor(colorRef);
+        }
+    }
+
+
+    /**
+     * The EMR_SETBKMODE record specifies the background mix mode of the playback device context.
+     * The background mix mode is used with text, hatched brushes, and pen styles that are not solid
+     * lines.
+     */
+    public static class EmfSetBkMode extends WmfSetBkMode implements HemfRecord {
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.setBkMode;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            /*
+             * A 32-bit unsigned integer that specifies the background mode
+             * and MUST be in the BackgroundMode (section 2.1.4) enumeration
+             */
+            bkMode = HwmfBkMode.valueOf((int)leis.readUInt());
+            return LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+    /**
+     * The EMR_SETMAPPERFLAGS record specifies parameters of the process of matching logical fonts to
+     * physical fonts, which is performed by the font mapper.
+     */
+    public static class EmfSetMapperFlags extends HwmfMisc.WmfSetMapperFlags implements HemfRecord {
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.setMapperFlags;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            return super.init(leis, recordSize, (int)recordId);
+        }
+    }
+
+    /**
+     * The EMR_SETMAPMODE record specifies the mapping mode of the playback device context. The
+     * mapping mode specifies the unit of measure used to transform page space units into device space
+     * units, and also specifies the orientation of the device's x-axis and y-axis.
+     */
+    public static class EmfSetMapMode extends HwmfMisc.WmfSetMapMode implements HemfRecord {
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.setMapMode;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            // A 32-bit unsigned integer whose definition MUST be in the MapMode enumeration
+            mapMode = HwmfMapMode.valueOf((int)leis.readUInt());
+            return LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+    /**
+     * The EMR_SETROP2 record defines a binary raster operation mode.
+     */
+    public static class EmfSetRop2 extends HwmfMisc.WmfSetRop2 implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.setRop2;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            // A 32-bit unsigned integer that specifies the raster operation mode and
+            // MUST be in the WMF Binary Raster Op enumeration
+            drawMode = HwmfBinaryRasterOp.valueOf((int)leis.readUInt());
+            return LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+
+    /**
+     * The EMR_SETSTRETCHBLTMODE record specifies bitmap stretch mode.
+     */
+    public static class EmfSetStretchBltMode extends HwmfMisc.WmfSetStretchBltMode implements HemfRecord {
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.setStretchBltMode;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            // A 32-bit unsigned integer that specifies the stretch mode and MAY be
+            // in the StretchMode enumeration.
+            stretchBltMode = StretchBltMode.valueOf((int)leis.readUInt());
+            return LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+    /** The EMR_CREATEBRUSHINDIRECT record defines a logical brush for graphics operations. */
+    public static class EmfCreateBrushIndirect extends HwmfMisc.WmfCreateBrushIndirect implements HemfRecord {
+        /**
+         * A 32-bit unsigned integer that specifies the index of the logical brush object in the
+         * EMF Object Table. This index MUST be saved so that this object can be reused or modified.
+         */
+        private int brushIdx;
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.createBrushIndirect;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            brushIdx = (int)leis.readUInt();
+
+            brushStyle = HwmfBrushStyle.valueOf((int)leis.readUInt());
+            colorRef = new HwmfColorRef();
+            int size = colorRef.init(leis);
+            brushHatch = HwmfHatchStyle.valueOf((int)leis.readUInt());
+            return size+3*LittleEndianConsts.INT_SIZE;
+
+        }
+    }
+
+    /**
+     * The EMR_DELETEOBJECT record deletes a graphics object, which is specified by its index
+     * in the EMF Object Table
+     */
+    public static class EmfDeleteObject extends HwmfMisc.WmfDeleteObject implements HemfRecord {
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.deleteobject;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            objectIndex = (int)leis.readUInt();
+            return LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+    /** The EMR_CREATEPEN record defines a logical pen for graphics operations. */
+    public static class EmfCreatePen extends HwmfMisc.WmfCreatePenIndirect implements HemfRecord {
+        /**
+         * A 32-bit unsigned integer that specifies the index of the logical palette object
+         * in the EMF Object Table. This index MUST be saved so that this object can be
+         * reused or modified.
+         */
+        protected int penIndex;
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.createPen;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            penIndex = (int)leis.readUInt();
+
+            // A 32-bit unsigned integer that specifies the PenStyle.
+            // The value MUST be defined from the PenStyle enumeration table
+            penStyle = HwmfPenStyle.valueOf((int)leis.readUInt());
+
+            int widthX = leis.readInt();
+            int widthY = leis.readInt();
+            dimension.setSize(widthX, widthY);
+
+            int size = colorRef.init(leis);
+
+            return size + 4*LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+    public static class EmfExtCreatePen extends EmfCreatePen {
+        protected HwmfBrushStyle brushStyle;
+        protected HwmfHatchStyle hatchStyle;
+
+        protected int[] styleEntry;
+
+        protected final HwmfBitmapDib bitmap = new HwmfBitmapDib();
+
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.extCreatePen;
+        }
+
+        @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 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();
+
+            // A 32-bit unsigned integer that specifies the PenStyle.
+            // The value MUST be defined from the PenStyle enumeration table
+            penStyle = HwmfPenStyle.valueOf((int)leis.readUInt());
+
+            // A 32-bit unsigned integer that specifies the width of the line drawn by the pen.
+            // If the pen type in the PenStyle field is PS_GEOMETRIC, this value is the width in logical
+            // units; otherwise, the width is specified in device units. If the pen type in the PenStyle field is
+            // PS_COSMETIC, this value MUST be 0x00000001.
+            long width = leis.readUInt();
+            dimension.setSize(width, 0);
+
+            // A 32-bit unsigned integer that specifies a brush style for the pen from the WMF BrushStyle enumeration
+            //
+            // If the pen type in the PenStyle field is PS_GEOMETRIC, this value MUST be either BS_SOLID or BS_HATCHED.
+            // The value of this field can be BS_NULL, but only if the line style specified in PenStyle is PS_NULL.
+            // The BS_NULL style SHOULD be used to specify a brush that has no effect
+            brushStyle = HwmfBrushStyle.valueOf((int)leis.readUInt());
+
+            int size = 8 * LittleEndianConsts.INT_SIZE;
+
+            size += colorRef.init(leis);
+
+            hatchStyle = HwmfHatchStyle.valueOf(leis.readInt());
+
+            // The number of elements in the array specified in the StyleEntry
+            // field. This value SHOULD be zero if PenStyle does not specify PS_USERSTYLE.
+            final int numStyleEntries = (int)leis.readUInt();
+            size += 2*LittleEndianConsts.INT_SIZE;
+
+            assert(numStyleEntries == 0 || penStyle.getLineDash() == HwmfLineDash.USERSTYLE);
+
+            // An optional array of 32-bit unsigned integers that defines the lengths of
+            // dashes and gaps in the line drawn by this pen, when the value of PenStyle is
+            // PS_USERSTYLE line style for the pen. The array contains a number of entries specified by
+            // NumStyleEntries, but it is used as if it repeated indefinitely.
+            // The first entry in the array specifies the length of the first dash. The second entry specifies
+            // the length of the first gap. Thereafter, lengths of dashes and gaps alternate.
+            // If the pen type in the PenStyle field is PS_GEOMETRIC, the lengths are specified in logical
+            // units; otherwise, the lengths are specified in device units.
+
+            styleEntry = new int[numStyleEntries];
+
+            for (int i=0; i<numStyleEntries; i++) {
+                styleEntry[i] = (int)leis.readUInt();
+            }
+
+            size += numStyleEntries * LittleEndianConsts.INT_SIZE;
+
+            size += readBitmap(leis, bitmap, startIdx, offBmi, cbBmi, offBits, cbBits);
+
+            return size;
+        }
+    }
+
+    /**
+     * The EMR_SETMITERLIMIT record specifies the limit for the length of miter joins for the playback
+     * device context.
+     */
+    public static class EmfSetMiterLimit implements HemfRecord {
+        protected int miterLimit;
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.setMiterLimit;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            miterLimit = (int)leis.readUInt();
+            return LittleEndianConsts.INT_SIZE;
+        }
+
+        @Override
+        public void draw(HemfGraphics ctx) {
+            ctx.getProperties().setPenMiterLimit(miterLimit);
+        }
+    }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPalette.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPalette.java
new file mode 100644 (file)
index 0000000..b9a9099
--- /dev/null
@@ -0,0 +1,138 @@
+/* ====================================================================
+   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.record.emf;
+
+import java.io.IOException;
+
+import org.apache.poi.hwmf.record.HwmfPalette;
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianInputStream;
+
+public class HemfPalette {
+    /** The EMR_SELECTPALETTE record specifies a logical palette for the playback device context. */
+    public static class EmfSelectPalette extends HwmfPalette.WmfSelectPalette implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.selectPalette;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            /*
+             * A 32-bit unsigned integer that specifies either the index of a LogPalette object
+             * in the EMF Object Table or the value DEFAULT_PALETTE, which is the index
+             * of a stock object palette from the StockObject enumeration
+             */
+            paletteIndex = (int)leis.readUInt();
+            return LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+    /** The EMR_CREATEPALETTE record defines a logical palette for graphics operations. */
+    public static class EmfCreatePalette extends HwmfPalette.WmfCreatePalette implements HemfRecord {
+
+        protected int paletteIndex;
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.createPalette;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            start = 0x0300;
+            /* A 32-bit unsigned integer that specifies the index of the logical palette object
+             * in the EMF Object Table. This index MUST be saved so that this object can be
+             * reused or modified.
+             */
+            paletteIndex = (int)leis.readUInt();
+            /* A 16-bit unsigned integer that specifies the version number of the system. This MUST be 0x0300. */
+            int version = leis.readUShort();
+            assert(version == 0x0300);
+            int size = readPaletteEntries(leis, -1);
+            return size + LittleEndianConsts.INT_SIZE + LittleEndianConsts.SHORT_SIZE;
+        }
+    }
+
+    /**
+     * The EMR_SETPALETTEENTRIES record defines RGB color values in a range of entries for an existing
+     * LogPalette object.
+     */
+    public static class EmfSetPaletteEntries extends HwmfPalette.WmfSetPaletteEntries implements HemfRecord {
+        /** specifies the palette EMF Object Table index. */
+        int paletteIndex;
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.setPaletteEntries;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            // A 32-bit unsigned integer that specifies the palette EMF Object Table index.
+            paletteIndex = (int)leis.readUInt();
+            // A 32-bit unsigned integer that specifies the index of the first entry to set.
+            start = (int)leis.readUInt();
+            // A 32-bit unsigned integer that specifies the number of entries.
+            int nbrOfEntries = (int)leis.readUInt();
+            int size = readPaletteEntries(leis, nbrOfEntries);
+            return size + 3*LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+    /**
+     * The EMR_RESIZEPALETTE record increases or decreases the size of an existing LogPalette object
+     */
+    public static class EmfResizePalette extends HwmfPalette.WmfResizePalette implements HemfRecord {
+        /** specifies the palette EMF Object Table index. */
+        int paletteIndex;
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.resizePalette;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            // A 32-bit unsigned integer that specifies the index of the palette object in the EMF Object Table
+            paletteIndex = (int)leis.readUInt();
+
+            // A 32-bit unsigned integer that specifies the number of entries in the palette after resizing.
+            // The value MUST be less than or equal to 0x00000400 and greater than 0x00000000.
+            numberOfEntries = (int)leis.readUInt();
+
+            return 2*LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+    /**
+     * This record maps palette entries from the current LogPalette object to the system_palette.
+     * This EMF record specifies no parameters.
+     */
+    public static class EmfRealizePalette extends HwmfPalette.WmfRealizePalette implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.realizePalette;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            return 0;
+        }
+    }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java
new file mode 100644 (file)
index 0000000..627bd73
--- /dev/null
@@ -0,0 +1,46 @@
+/* ====================================================================
+   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.record.emf;
+
+
+import java.io.IOException;
+
+import org.apache.poi.hemf.draw.HemfGraphics;
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndianInputStream;
+
+@Internal
+public interface HemfRecord {
+
+    HemfRecordType getEmfRecordType();
+
+    /**
+     * Init record from stream
+     *
+     * @param leis the little endian input stream
+     * @param recordSize the size limit for this record
+     * @param recordId the id of the {@link HemfRecordType}
+     *
+     * @return count of processed bytes
+     *
+     * @throws IOException when the inputstream is malformed
+     */
+    long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException;
+
+    default void draw(HemfGraphics ctx) {}
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java
new file mode 100644 (file)
index 0000000..49452f2
--- /dev/null
@@ -0,0 +1,82 @@
+/* ====================================================================
+   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.record.emf;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianInputStream;
+import org.apache.poi.util.RecordFormatException;
+
+public class HemfRecordIterator implements Iterator<HemfRecord> {
+
+    static final int HEADER_SIZE = 2*LittleEndianConsts.INT_SIZE;
+
+    private final LittleEndianInputStream stream;
+    private HemfRecord currentRecord;
+
+    public HemfRecordIterator(LittleEndianInputStream leis) {
+        stream = leis;
+        //queue the first non-header record
+        currentRecord = _next();
+    }
+
+    @Override
+    public boolean hasNext() {
+        return currentRecord != null;
+    }
+
+    @Override
+    public HemfRecord next() {
+        HemfRecord toReturn = currentRecord;
+        currentRecord = (currentRecord instanceof HemfMisc.EmfEof) ? null : _next();
+        return toReturn;
+    }
+
+    private HemfRecord _next() {
+        if (currentRecord != null && HemfRecordType.eof == currentRecord.getEmfRecordType()) {
+            return null;
+        }
+        long recordId = stream.readUInt();
+        long recordSize = stream.readUInt();
+
+        HemfRecordType type = HemfRecordType.getById(recordId);
+        if (type == null) {
+            throw new RecordFormatException("Undefined record of type:"+recordId);
+        }
+        final HemfRecord record = type.constructor.get();
+
+        try {
+            long remBytes = recordSize-HEADER_SIZE;
+            long readBytes = record.init(stream, remBytes, recordId);
+            assert (readBytes <= remBytes);
+            stream.skipFully((int)(remBytes-readBytes));
+        } catch (IOException|RuntimeException e) {
+            throw new RecordFormatException(e);
+        }
+
+        return record;
+    }
+
+    @Override
+    public void remove() {
+        throw new UnsupportedOperationException("Remove not supported");
+    }
+
+}
\ No newline at end of file
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java
new file mode 100644 (file)
index 0000000..0995291
--- /dev/null
@@ -0,0 +1,165 @@
+/* ====================================================================
+   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.record.emf;
+
+import java.util.function.Supplier;
+
+import org.apache.poi.util.Internal;
+
+@Internal
+public enum HemfRecordType {
+
+    header(0x00000001, HemfHeader::new),
+    polyBezier(0x00000002, HemfDraw.EmfPolyBezier::new),
+    polygon(0x00000003, HemfDraw.EmfPolygon::new),
+    polyline(0x00000004, HemfDraw.EmfPolyline::new),
+    polyBezierTo(0x00000005, HemfDraw.EmfPolyBezierTo::new),
+    polylineTo(0x00000006, HemfDraw.EmfPolylineTo::new),
+    polyPolyline(0x00000007, HemfDraw.EmfPolyPolyline::new),
+    polyPolygon(0x00000008, HemfDraw.EmfPolyPolygon::new),
+    setWindowExtEx(0x00000009, HemfWindowing.EmfSetWindowExtEx::new),
+    setWindowOrgEx(0x0000000A, HemfWindowing.EmfSetWindowOrgEx::new),
+    setViewportExtEx(0x0000000B, HemfWindowing.EmfSetViewportExtEx::new),
+    setViewportOrgEx(0x0000000C, HemfWindowing.EmfSetViewportOrgEx::new),
+    setbrushorgex(0x0000000D, UnimplementedHemfRecord::new),
+    eof(0x0000000E, HemfMisc.EmfEof::new),
+    setPixelV(0x0000000F, HemfDraw.EmfSetPixelV::new),
+    setMapperFlags(0x00000010, HemfMisc.EmfSetMapperFlags::new),
+    setMapMode(0x00000011, HemfMisc.EmfSetMapMode::new),
+    setBkMode(0x00000012, HemfMisc.EmfSetBkMode::new),
+    setPolyfillMode(0x00000013, HemfFill.EmfSetPolyfillMode::new),
+    setRop2(0x00000014, HemfMisc.EmfSetRop2::new),
+    setStretchBltMode(0x00000015, HemfMisc.EmfSetStretchBltMode::new),
+    setTextAlign(0x00000016, HemfText.EmfSetTextAlign::new),
+    setcoloradjustment(0x00000017, UnimplementedHemfRecord::new),
+    setTextColor(0x00000018, HemfText.SetTextColor::new),
+    setBkColor(0x00000019, HemfMisc.EmfSetBkColor::new),
+    setOffsetClipRgn(0x0000001A, HemfWindowing.EmfSetOffsetClipRgn::new),
+    setMoveToEx(0x0000001B, HemfDraw.EmfSetMoveToEx::new),
+    setmetargn(0x0000001C, UnimplementedHemfRecord::new),
+    setExcludeClipRect(0x0000001D, HemfWindowing.EmfSetExcludeClipRect::new),
+    setIntersectClipRect(0x0000001E, HemfWindowing.EmfSetIntersectClipRect::new),
+    scaleViewportExtEx(0x0000001F, HemfWindowing.EmfScaleViewportExtEx::new),
+    scaleWindowExtEx(0x00000020, HemfWindowing.EmfScaleWindowExtEx::new),
+    saveDc(0x00000021, HemfMisc.EmfSaveDc::new),
+    restoreDc(0x00000022, HemfMisc.EmfRestoreDc::new),
+    setworldtransform(0x00000023, UnimplementedHemfRecord::new),
+    modifyworldtransform(0x00000024, UnimplementedHemfRecord::new),
+    selectObject(0x00000025, HemfDraw.EmfSelectObject::new),
+    createPen(0x00000026, HemfMisc.EmfCreatePen::new),
+    createBrushIndirect(0x00000027, HemfMisc.EmfCreateBrushIndirect::new),
+    deleteobject(0x00000028, HemfMisc.EmfDeleteObject::new),
+    anglearc(0x00000029, UnimplementedHemfRecord::new),
+    ellipse(0x0000002A, HemfDraw.EmfEllipse::new),
+    rectangle(0x0000002B, HemfDraw.EmfRectangle::new),
+    roundRect(0x0000002C, HemfDraw.EmfRoundRect::new),
+    arc(0x0000002D, HemfDraw.EmfArc::new),
+    chord(0x0000002E, HemfDraw.EmfChord::new),
+    pie(0x0000002F, HemfDraw.EmfPie::new),
+    selectPalette(0x00000030, HemfPalette.EmfSelectPalette::new),
+    createPalette(0x00000031, HemfPalette.EmfCreatePalette::new),
+    setPaletteEntries(0x00000032, HemfPalette.EmfSetPaletteEntries::new),
+    resizePalette(0x00000033, HemfPalette.EmfResizePalette::new),
+    realizePalette(0x0000034, HemfPalette.EmfRealizePalette::new),
+    extFloodFill(0x00000035, HemfFill.EmfExtFloodFill::new),
+    lineTo(0x00000036, HemfDraw.EmfLineTo::new),
+    arcTo(0x00000037, HemfDraw.EmfArcTo::new),
+    polyDraw(0x00000038, HemfDraw.EmfPolyDraw::new),
+    setarcdirection(0x00000039, UnimplementedHemfRecord::new),
+    setMiterLimit(0x0000003A, HemfMisc.EmfSetMiterLimit::new),
+    beginpath(0x0000003B, UnimplementedHemfRecord::new),
+    endpath(0x0000003C, UnimplementedHemfRecord::new),
+    closefigure(0x0000003D, UnimplementedHemfRecord::new),
+    fillpath(0x0000003E, UnimplementedHemfRecord::new),
+    strokeandfillpath(0x0000003F, UnimplementedHemfRecord::new),
+    strokepath(0x00000040, UnimplementedHemfRecord::new),
+    flattenpath(0x00000041, UnimplementedHemfRecord::new),
+    widenpath(0x00000042, UnimplementedHemfRecord::new),
+    selectclippath(0x00000043, UnimplementedHemfRecord::new),
+    abortpath(0x00000044, UnimplementedHemfRecord::new),
+    // no 45 ?!
+    comment(0x00000046, HemfComment.EmfComment::new),
+    fillRgn(0x00000047, HemfFill.EmfFillRgn::new),
+    frameRgn(0x00000048, HemfFill.EmfFrameRgn::new),
+    invertRgn(0x00000049, HemfFill.EmfInvertRgn::new),
+    paintRgn(0x0000004A, HemfFill.EmfPaintRgn::new),
+    extSelectClipRgn(0x0000004B, HemfFill.EmfExtSelectClipRgn::new),
+    bitBlt(0x0000004C, HemfFill.EmfBitBlt::new),
+    stretchBlt(0x0000004D, HemfFill.EmfStretchBlt::new),
+    maskblt(0x0000004E, UnimplementedHemfRecord::new),
+    plgblt(0x0000004F, UnimplementedHemfRecord::new),
+    setDiBitsToDevice(0x00000050, HemfFill.EmfSetDiBitsToDevice::new),
+    stretchdibits(0x00000051, UnimplementedHemfRecord::new),
+    extCreateFontIndirectW(0x00000052, HemfText.ExtCreateFontIndirectW::new),
+    exttextouta(0x00000053, HemfText.ExtTextOutA::new),
+    exttextoutw(0x00000054, HemfText.ExtTextOutW::new),
+    polyBezier16(0x00000055, HemfDraw.EmfPolyBezier16::new),
+    polygon16(0x00000056, HemfDraw.EmfPolygon16::new),
+    polyline16(0x00000057, HemfDraw.EmfPolyline16::new),
+    polyBezierTo16(0x00000058, HemfDraw.EmfPolyBezierTo16::new),
+    polylineTo16(0x00000059, HemfDraw.EmfPolylineTo16::new),
+    polyPolyline16(0x0000005A, HemfDraw.EmfPolyPolyline16::new),
+    polyPolygon16(0x0000005B, HemfDraw.EmfPolyPolygon16::new),
+    polyDraw16(0x0000005C, HemfDraw.EmfPolyDraw16::new),
+    createmonobrush16(0x0000005D, UnimplementedHemfRecord::new),
+    createdibpatternbrushpt(0x0000005E, UnimplementedHemfRecord::new),
+    extCreatePen(0x0000005F, HemfMisc.EmfExtCreatePen::new),
+    polytextouta(0x00000060, HemfText.PolyTextOutA::new),
+    polytextoutw(0x00000061, HemfText.PolyTextOutW::new),
+    seticmmode(0x00000062, UnimplementedHemfRecord::new),
+    createcolorspace(0x00000063, UnimplementedHemfRecord::new),
+    setcolorspace(0x00000064, UnimplementedHemfRecord::new),
+    deletecolorspace(0x00000065, UnimplementedHemfRecord::new),
+    glsrecord(0x00000066, UnimplementedHemfRecord::new),
+    glsboundedrecord(0x00000067, UnimplementedHemfRecord::new),
+    pixelformat(0x00000068, UnimplementedHemfRecord::new),
+    drawescape(0x00000069, UnimplementedHemfRecord::new),
+    extescape(0x0000006A, UnimplementedHemfRecord::new),
+    // no 6b ?!
+    smalltextout(0x0000006C, UnimplementedHemfRecord::new),
+    forceufimapping(0x0000006D, UnimplementedHemfRecord::new),
+    namedescape(0x0000006E, UnimplementedHemfRecord::new),
+    colorcorrectpalette(0x0000006F, UnimplementedHemfRecord::new),
+    seticmprofilea(0x00000070, UnimplementedHemfRecord::new),
+    seticmprofilew(0x00000071, UnimplementedHemfRecord::new),
+    alphaBlend(0x00000072, HemfFill.EmfAlphaBlend::new),
+    setlayout(0x00000073, UnimplementedHemfRecord::new),
+    transparentblt(0x00000074, UnimplementedHemfRecord::new),
+    // no 75 ?!
+    gradientfill(0x00000076, UnimplementedHemfRecord::new),
+    setlinkdufis(0x00000077, UnimplementedHemfRecord::new),
+    settextjustification(0x00000078, HemfText.SetTextJustification::new),
+    colormatchtargetw(0x00000079, UnimplementedHemfRecord::new),
+    createcolorspacew(0x0000007A, UnimplementedHemfRecord::new);
+
+
+    public final long id;
+    public final Supplier<? extends HemfRecord> constructor;
+
+    HemfRecordType(long id, Supplier<? extends HemfRecord> constructor) {
+        this.id = id;
+        this.constructor = constructor;
+    }
+
+    public static HemfRecordType getById(long id) {
+        for (HemfRecordType wrt : values()) {
+            if (wrt.id == id) return wrt;
+        }
+        return null;
+    }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java
new file mode 100644 (file)
index 0000000..95c110d
--- /dev/null
@@ -0,0 +1,317 @@
+/* ====================================================================
+   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.record.emf;
+
+import static java.nio.charset.StandardCharsets.UTF_16LE;
+import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL;
+import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionFloat;
+import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL;
+
+import java.awt.geom.Dimension2D;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.poi.hwmf.record.HwmfColorRef;
+import org.apache.poi.hwmf.record.HwmfText;
+import org.apache.poi.hwmf.record.HwmfText.WmfSetTextAlign;
+import org.apache.poi.util.Dimension2DDouble;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianInputStream;
+import org.apache.poi.util.RecordFormatException;
+
+/**
+ * Container class to gather all text-related commands
+ * This is starting out as read only, and very little is actually
+ * implemented at this point!
+ */
+@Internal
+public class HemfText {
+
+    private static final int MAX_RECORD_LENGTH = 1_000_000;
+
+    public enum EmfGraphicsMode {
+        GM_COMPATIBLE, GM_ADVANCED
+    }
+
+    public static class ExtTextOutA implements HemfRecord {
+
+        protected final Rectangle2D boundsIgnored = new Rectangle2D.Double();
+
+        protected EmfGraphicsMode graphicsMode;
+
+        /**
+         * The scale factor to apply along the X/Y axis to convert from page space units to .01mm units.
+         * This SHOULD be used only if the graphics mode specified by iGraphicsMode is GM_COMPATIBLE.
+         */
+        protected final Dimension2D scale = new Dimension2DDouble();
+
+        protected final EmrTextObject textObject;
+
+        public ExtTextOutA() {
+            this(false);
+        }
+
+        protected ExtTextOutA(boolean isUnicode) {
+            textObject = new EmrTextObject(isUnicode);
+        }
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.exttextouta;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            if (recordSize < 0 || Integer.MAX_VALUE <= recordSize) {
+                throw new RecordFormatException("recordSize must be a positive integer (0-0x7FFFFFFF)");
+            }
+
+            // A WMF RectL object. It is not used and MUST be ignored on receipt.
+            long size = readRectL(leis, boundsIgnored);
+
+            // A 32-bit unsigned integer that specifies the graphics mode from the GraphicsMode enumeration
+            graphicsMode = EmfGraphicsMode.values()[leis.readInt()-1];
+            size += LittleEndianConsts.INT_SIZE;
+
+            size += readDimensionFloat(leis, scale);
+
+            // guarantee to read the rest of the EMRTextObjectRecord
+            size += textObject.init(leis, recordSize, (int)size);
+
+            return size;
+        }
+
+        /**
+         *
+         * To be implemented!  We need to get the current character set
+         * from the current font for {@link ExtTextOutA},
+         * which has to be tracked in the playback device.
+         *
+         * For {@link ExtTextOutW}, the charset is "UTF-16LE"
+         *
+         * @param charset the charset to be used to decode the character bytes
+         * @return text from this text element
+         * @throws IOException
+         */
+        public String getText(Charset charset) throws IOException {
+            return textObject.getText(charset);
+        }
+
+        /**
+         *
+         * @return the x offset for the EmrTextObject
+         */
+        public EmrTextObject getTextObject() {
+            return textObject;
+        }
+
+        public EmfGraphicsMode getGraphicsMode() {
+            return graphicsMode;
+        }
+
+        public Dimension2D getScale() {
+            return scale;
+        }
+    }
+
+    public static class ExtTextOutW extends ExtTextOutA {
+
+        public ExtTextOutW() {
+            super(true);
+        }
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.exttextoutw;
+        }
+
+        public String getText() throws IOException {
+            return getText(UTF_16LE);
+        }
+    }
+
+    /**
+     * The EMR_SETTEXTALIGN record specifies text alignment.
+     */
+    public static class EmfSetTextAlign extends WmfSetTextAlign implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.setTextAlign;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            /**
+             * A 32-bit unsigned integer that specifies text alignment by using a mask of text alignment flags.
+             * These are either WMF TextAlignmentMode Flags for text with a horizontal baseline,
+             * or WMF VerticalTextAlignmentMode Flags for text with a vertical baseline.
+             * Only one value can be chosen from those that affect horizontal and vertical alignment.
+             */
+            textAlignmentMode = (int)leis.readUInt();
+            return LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+    /**
+     * The EMR_SETTEXTCOLOR record defines the current text color.
+     */
+    public static class SetTextColor implements HemfRecord {
+        /** A WMF ColorRef object that specifies the text color value. */
+        private final HwmfColorRef colorRef = new HwmfColorRef();
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.setTextColor;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            return colorRef.init(leis);
+        }
+    }
+
+    public static class EmrTextObject extends HwmfText.WmfExtTextOut {
+        protected final boolean isUnicode;
+        protected final List<Integer> outputDx = new ArrayList<>();
+
+        public EmrTextObject(boolean isUnicode) {
+            super(new EmfExtTextOutOptions());
+            this.isUnicode = isUnicode;
+        }
+
+        @Override
+        public int init(LittleEndianInputStream leis, final long recordSize, final int offset) throws IOException {
+            // A WMF PointL object that specifies the coordinates of the reference point used to position the string.
+            // The reference point is defined by the last EMR_SETTEXTALIGN record.
+            // If no such record has been set, the default alignment is TA_LEFT,TA_TOP.
+            long size = readPointL(leis, reference);
+            // A 32-bit unsigned integer that specifies the number of characters in the string.
+            stringLength = (int)leis.readUInt();
+            // A 32-bit unsigned integer that specifies the offset to the output string, in bytes,
+            // from the start of the record in which this object is contained.
+            // This value MUST be 8- or 16-bit aligned, according to the character format.
+            int offString = (int)leis.readUInt();
+            size += 2*LittleEndianConsts.INT_SIZE;
+
+            size += options.init(leis);
+            // An optional WMF RectL object that defines a clipping and/or opaquing rectangle in logical units.
+            // This rectangle is applied to the text output performed by the containing record.
+            if (options.isClipped() || options.isOpaque()) {
+                size += readRectL(leis, bounds);
+            }
+
+            // A 32-bit unsigned integer that specifies the offset to an intercharacter spacing array, in bytes,
+            // from the start of the record in which this object is contained. This value MUST be 32-bit aligned.
+            int offDx = (int)leis.readUInt();
+            size += LittleEndianConsts.INT_SIZE;
+
+            int undefinedSpace1 = (int)(offString-offset-size-2*LittleEndianConsts.INT_SIZE);
+            assert (undefinedSpace1 >= 0);
+            leis.skipFully(undefinedSpace1);
+            size += undefinedSpace1;
+
+            rawTextBytes = IOUtils.safelyAllocate(stringLength*(isUnicode?2:1), MAX_RECORD_LENGTH);
+            leis.readFully(rawTextBytes);
+            size += rawTextBytes.length;
+
+            outputDx.clear();
+            if (offDx > 0) {
+                int undefinedSpace2 = (int) (offDx - offset - size - 2 * LittleEndianConsts.INT_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) {
+                    outputDx.add((int) leis.readUInt());
+                    size += LittleEndianConsts.INT_SIZE;
+                }
+            }
+
+            return (int)size;
+        }
+    }
+
+
+    public static class ExtCreateFontIndirectW extends HwmfText.WmfCreateFontIndirect
+    implements HemfRecord {
+        int fontIdx;
+
+        public ExtCreateFontIndirectW() {
+            super(new HemfFont());
+        }
+
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.extCreateFontIndirectW;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            // A 32-bit unsigned integer that specifies the index of the logical font object
+            // in the EMF Object Table
+            fontIdx = (int)leis.readUInt();
+            int size = font.init(leis, (int)(recordSize-LittleEndianConsts.INT_SIZE));
+            return size+LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+    public static class EmfExtTextOutOptions extends HwmfText.WmfExtTextOutOptions {
+        @Override
+        public int init(LittleEndianInputStream leis) {
+            // A 32-bit unsigned integer that specifies how to use the rectangle specified in the Rectangle field.
+            // This field can be a combination of more than one ExtTextOutOptions enumeration
+            flag = (int)leis.readUInt();
+            return LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+    public static class SetTextJustification extends UnimplementedHemfRecord {
+
+    }
+
+    /**
+     * Needs to be implemented.  Couldn't find example.
+     */
+    public static class PolyTextOutA extends UnimplementedHemfRecord {
+
+    }
+
+    /**
+     * Needs to be implemented.  Couldn't find example.
+     */
+    public static class PolyTextOutW extends UnimplementedHemfRecord {
+
+    }
+
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java
new file mode 100644 (file)
index 0000000..bd48f1b
--- /dev/null
@@ -0,0 +1,200 @@
+/* ====================================================================
+   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.record.emf;
+
+import java.io.IOException;
+
+import org.apache.poi.hwmf.record.HwmfWindowing;
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianInputStream;
+
+public class HemfWindowing {
+
+    /**
+     * The EMR_SETWINDOWEXTEX record defines the window extent.
+     */
+    public static class EmfSetWindowExtEx extends HwmfWindowing.WmfSetWindowExt implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.setWindowExtEx;
+        }
+
+        @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();
+            // cy (4 bytes): A 32-bit unsigned integer that defines the y-coordinate of the point.
+            height = (int)leis.readUInt();
+
+            return 2*LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+    /**
+     * The EMR_SETWINDOWORGEX record defines the window origin.
+     */
+    public static class EmfSetWindowOrgEx extends HwmfWindowing.WmfSetWindowOrg implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.setWindowOrgEx;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            // x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point.
+            x = leis.readInt();
+            // y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point.
+            y = leis.readInt();
+
+            return 2*LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+    /**
+     * The EMR_SETVIEWPORTEXTEX record defines the viewport extent.
+     */
+    public static class EmfSetViewportExtEx extends HwmfWindowing.WmfSetViewportExt implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.setViewportExtEx;
+        }
+
+        @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();
+            // cy (4 bytes): A 32-bit unsigned integer that defines the y-coordinate of the point.
+            height = (int)leis.readUInt();
+
+            return 2*LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+    /**
+     * The EMR_SETVIEWPORTORGEX record defines the viewport origin.
+     */
+    public static class EmfSetViewportOrgEx extends HwmfWindowing.WmfSetViewportOrg implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.setViewportOrgEx;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            // x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point.
+            x = leis.readInt();
+            // y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point.
+            y = leis.readInt();
+
+            return 2*LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+    /**
+     * The EMR_OFFSETCLIPRGN record moves the current clipping region in the playback device context
+     * by the specified offsets.
+     */
+    public static class EmfSetOffsetClipRgn extends HwmfWindowing.WmfOffsetClipRgn implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.setOffsetClipRgn;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            // x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point.
+            xOffset = leis.readInt();
+            // y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point.
+            yOffset = leis.readInt();
+
+            return 2*LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+    /**
+     * The EMR_EXCLUDECLIPRECT record specifies a new clipping region that consists of the existing
+     * clipping region minus the specified rectangle.
+     */
+    public static class EmfSetExcludeClipRect extends HwmfWindowing.WmfExcludeClipRect implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.setExcludeClipRect;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            return HemfDraw.readRectL(leis, bounds);
+        }
+    }
+
+    /**
+     * The EMR_INTERSECTCLIPRECT record specifies a new clipping region from the intersection of the
+     * current clipping region and the specified rectangle.
+     */
+    public static class EmfSetIntersectClipRect extends HwmfWindowing.WmfIntersectClipRect implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.setIntersectClipRect;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            return HemfDraw.readRectL(leis, bounds);
+        }
+    }
+
+    /**
+     * The EMR_SCALEVIEWPORTEXTEX record respecifies the viewport for a device context by using the
+     * ratios formed by the specified multiplicands and divisors.
+     */
+    public static class EmfScaleViewportExtEx extends HwmfWindowing.WmfScaleViewportExt implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.scaleViewportExtEx;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            xNum = leis.readInt();
+            xDenom = leis.readInt();
+            yNum = leis.readInt();
+            yDenom = leis.readInt();
+            return 4*LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+    /**
+     * The EMR_SCALEWINDOWEXTEX record respecifies the window for a playback device context by
+     * using the ratios formed by the specified multiplicands and divisors.
+     */
+    public static class EmfScaleWindowExtEx extends HwmfWindowing.WmfScaleWindowExt implements HemfRecord {
+        @Override
+        public HemfRecordType getEmfRecordType() {
+            return HemfRecordType.scaleWindowExtEx;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+            xNum = leis.readInt();
+            xDenom = leis.readInt();
+            yNum = leis.readInt();
+            yDenom = leis.readInt();
+            return 4*LittleEndianConsts.INT_SIZE;
+        }
+    }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/UnimplementedHemfRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/UnimplementedHemfRecord.java
new file mode 100644 (file)
index 0000000..e66e2b4
--- /dev/null
@@ -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.hemf.record.emf;
+
+
+import java.io.IOException;
+
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndianInputStream;
+
+@Internal
+public class UnimplementedHemfRecord implements HemfRecord {
+
+    private long recordId;
+    public UnimplementedHemfRecord() {
+
+    }
+
+    @Override
+    public HemfRecordType getEmfRecordType() {
+        return HemfRecordType.getById(recordId);
+    }
+
+    @Override
+    public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+        this.recordId = recordId;
+        long skipped = IOUtils.skipFully(leis, recordSize);
+        if (skipped < recordSize) {
+            throw new IOException("End of stream reached before record read");
+        }
+        return skipped;
+    }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java
new file mode 100644 (file)
index 0000000..72a8d31
--- /dev/null
@@ -0,0 +1,87 @@
+/* ====================================================================
+   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.record.emfplus;
+
+
+import java.io.IOException;
+
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianInputStream;
+
+@Internal
+public class HemfPlusHeader implements HemfPlusRecord {
+
+    private int flags;
+    private long version; //hack for now; replace with EmfPlusGraphicsVersion object
+    private long emfPlusFlags;
+    private long logicalDpiX;
+    private long logicalDpiY;
+
+    @Override
+    public HemfPlusRecordType getRecordType() {
+        return HemfPlusRecordType.header;
+    }
+
+    public int getFlags() {
+        return flags;
+    }
+
+    @Override
+    public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
+        this.flags = flags;
+        version = leis.readUInt();
+
+        // verify MetafileSignature (20 bits) == 0xDBC01 and
+        // GraphicsVersion (12 bits) in (1 or 2)
+        assert((version & 0xFFFFFA00) == 0xDBC01000L && ((version & 0x3FF) == 1 || (version & 0x3FF) == 2));
+
+        emfPlusFlags = leis.readUInt();
+
+        logicalDpiX = leis.readUInt();
+        logicalDpiY = leis.readUInt();
+        return 4* LittleEndianConsts.INT_SIZE;
+    }
+
+    public long getVersion() {
+        return version;
+    }
+
+    public long getEmfPlusFlags() {
+        return emfPlusFlags;
+    }
+
+    public long getLogicalDpiX() {
+        return logicalDpiX;
+    }
+
+    public long getLogicalDpiY() {
+        return logicalDpiY;
+    }
+
+    @Override
+    public String toString() {
+        return "HemfPlusHeader{" +
+                "flags=" + flags +
+                ", version=" + version +
+                ", emfPlusFlags=" + emfPlusFlags +
+                ", logicalDpiX=" + logicalDpiX +
+                ", logicalDpiY=" + logicalDpiY +
+                '}';
+    }
+}
\ No newline at end of file
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecord.java
new file mode 100644 (file)
index 0000000..c53db98
--- /dev/null
@@ -0,0 +1,48 @@
+/* ====================================================================
+   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.record.emfplus;
+
+
+import java.io.IOException;
+
+import org.apache.poi.hemf.record.emf.HemfRecordType;
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndianInputStream;
+
+@Internal
+public interface HemfPlusRecord {
+
+    HemfPlusRecordType getRecordType();
+
+    int getFlags();
+
+    /**
+     * Init record from stream
+     *
+     * @param leis the little endian input stream
+     * @param dataSize the size limit for this record
+     * @param recordId the id of the {@link HemfPlusRecordType}
+     * @param flags the record flags
+     *
+     * @return count of processed bytes
+     *
+     * @throws IOException when the inputstream is malformed
+     */
+    long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException;
+
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordIterator.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordIterator.java
new file mode 100644 (file)
index 0000000..a3b56fa
--- /dev/null
@@ -0,0 +1,98 @@
+/* ====================================================================
+   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.record.emfplus;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import org.apache.poi.util.LittleEndianInputStream;
+import org.apache.poi.util.RecordFormatException;
+
+public class HemfPlusRecordIterator implements Iterator<HemfPlusRecord> {
+
+    private final LittleEndianInputStream leis;
+    private final int startIdx;
+    private final int limit;
+    private HemfPlusRecord currentRecord;
+
+    public HemfPlusRecordIterator(LittleEndianInputStream leis) {
+        this(leis, -1);
+    }
+
+    public HemfPlusRecordIterator(LittleEndianInputStream leis, int limit) {
+        this.leis = leis;
+        this.limit = limit;
+        startIdx = leis.getReadIndex();
+        //queue the first non-header record
+        currentRecord = _next();
+    }
+
+    @Override
+    public boolean hasNext() {
+        return currentRecord != null;
+    }
+
+    @Override
+    public HemfPlusRecord next() {
+        HemfPlusRecord toReturn = currentRecord;
+        final boolean isEOF = (limit == -1 || leis.getReadIndex()-startIdx >= limit);
+        // (currentRecord instanceof HemfPlusMisc.EmfEof)
+        currentRecord = isEOF ? null : _next();
+        return toReturn;
+    }
+
+    private HemfPlusRecord _next() {
+        if (currentRecord != null && HemfPlusRecordType.eof == currentRecord.getRecordType()) {
+            return null;
+        }
+        // A 16-bit unsigned integer that identifies this record type
+        int recordId = leis.readUShort();
+        // A 16-bit unsigned integer that provides information about how the operation is
+        // to be performed, and about the structure of the record.
+        int flags = leis.readUShort();
+        // A 32-bit unsigned integer that specifies the 32-bit-aligned size of the entire
+        // record in bytes, including the 12-byte record header and record-specific data.
+        int recordSize = (int)leis.readUInt();
+        // A 32-bit unsigned integer that specifies the 32-bit-aligned number of bytes of data
+        // in the record-specific data that follows. This number does not include the size of
+        // the invariant part of this record.
+        int dataSize = (int)leis.readUInt();
+
+        HemfPlusRecordType type = HemfPlusRecordType.getById(recordId);
+        if (type == null) {
+            throw new RecordFormatException("Undefined record of type:"+recordId);
+        }
+        final HemfPlusRecord record = type.constructor.get();
+
+        try {
+            long readBytes = record.init(leis, dataSize, recordId, flags);
+            assert (readBytes <= recordSize-12);
+            leis.skipFully((int)(recordSize-12-readBytes));
+        } catch (IOException e) {
+            throw new RecordFormatException(e);
+        }
+
+        return record;
+    }
+
+    @Override
+    public void remove() {
+        throw new UnsupportedOperationException("Remove not supported");
+    }
+
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordType.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordType.java
new file mode 100644 (file)
index 0000000..2fc1926
--- /dev/null
@@ -0,0 +1,100 @@
+/* ====================================================================
+   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.record.emfplus;
+
+import java.util.function.Supplier;
+
+import org.apache.poi.util.Internal;
+
+@Internal
+public enum HemfPlusRecordType {
+    header(0x4001, HemfPlusHeader::new),
+    eof(0x4002, UnimplementedHemfPlusRecord::new),
+    comment(0x4003, UnimplementedHemfPlusRecord::new),
+    getDC(0x4004, UnimplementedHemfPlusRecord::new),
+    multiFormatStart(0x4005, UnimplementedHemfPlusRecord::new),
+    multiFormatSection(0x4006, UnimplementedHemfPlusRecord::new),
+    multiFormatEnd(0x4007, UnimplementedHemfPlusRecord::new),
+    object(0x4008, UnimplementedHemfPlusRecord::new),
+    clear(0x4009, UnimplementedHemfPlusRecord::new),
+    fillRects(0x400A, UnimplementedHemfPlusRecord::new),
+    drawRects(0x400B, UnimplementedHemfPlusRecord::new),
+    fillPolygon(0x400C, UnimplementedHemfPlusRecord::new),
+    drawLines(0x400D, UnimplementedHemfPlusRecord::new),
+    fillEllipse(0x400E, UnimplementedHemfPlusRecord::new),
+    drawEllipse(0x400F, UnimplementedHemfPlusRecord::new),
+    fillPie(0x4010, UnimplementedHemfPlusRecord::new),
+    drawPie(0x4011, UnimplementedHemfPlusRecord::new),
+    drawArc(0x4012, UnimplementedHemfPlusRecord::new),
+    fillRegion(0x4013, UnimplementedHemfPlusRecord::new),
+    fillPath(0x4014, UnimplementedHemfPlusRecord::new),
+    drawPath(0x4015, UnimplementedHemfPlusRecord::new),
+    fillClosedCurve(0x4016, UnimplementedHemfPlusRecord::new),
+    drawClosedCurve(0x4017, UnimplementedHemfPlusRecord::new),
+    drawCurve(0x4018, UnimplementedHemfPlusRecord::new),
+    drawBeziers(0x4019, UnimplementedHemfPlusRecord::new),
+    drawImage(0x401A, UnimplementedHemfPlusRecord::new),
+    drawImagePoints(0x401B, UnimplementedHemfPlusRecord::new),
+    drawString(0x401C, UnimplementedHemfPlusRecord::new),
+    setRenderingOrigin(0x401D, UnimplementedHemfPlusRecord::new),
+    setAntiAliasMode(0x401E, UnimplementedHemfPlusRecord::new),
+    setTextRenderingHint(0x401F, UnimplementedHemfPlusRecord::new),
+    setTextContrast(0x4020, UnimplementedHemfPlusRecord::new),
+    setInterpolationMode(0x4021, UnimplementedHemfPlusRecord::new),
+    setPixelOffsetMode(0x4022, UnimplementedHemfPlusRecord::new),
+    setComositingMode(0x4023, UnimplementedHemfPlusRecord::new),
+    setCompositingQuality(0x4024, UnimplementedHemfPlusRecord::new),
+    save(0x4025, UnimplementedHemfPlusRecord::new),
+    restore(0x4026, UnimplementedHemfPlusRecord::new),
+    beginContainer(0x4027, UnimplementedHemfPlusRecord::new),
+    beginContainerNoParams(0x428, UnimplementedHemfPlusRecord::new),
+    endContainer(0x4029, UnimplementedHemfPlusRecord::new),
+    setWorldTransform(0x402A, UnimplementedHemfPlusRecord::new),
+    resetWorldTransform(0x402B, UnimplementedHemfPlusRecord::new),
+    multiplyWorldTransform(0x402C, UnimplementedHemfPlusRecord::new),
+    translateWorldTransform(0x402D, UnimplementedHemfPlusRecord::new),
+    scaleWorldTransform(0x402E, UnimplementedHemfPlusRecord::new),
+    rotateWorldTransform(0x402F, UnimplementedHemfPlusRecord::new),
+    setPageTransform(0x4030, UnimplementedHemfPlusRecord::new),
+    resetClip(0x4031, UnimplementedHemfPlusRecord::new),
+    setClipRect(0x4032, UnimplementedHemfPlusRecord::new),
+    setClipRegion(0x4033, UnimplementedHemfPlusRecord::new),
+    setClipPath(0x4034, UnimplementedHemfPlusRecord::new),
+    offsetClip(0x4035, UnimplementedHemfPlusRecord::new),
+    drawDriverstring(0x4036, UnimplementedHemfPlusRecord::new),
+    strokeFillPath(0x4037, UnimplementedHemfPlusRecord::new),
+    serializableObject(0x4038, UnimplementedHemfPlusRecord::new),
+    setTSGraphics(0x4039, UnimplementedHemfPlusRecord::new),
+    setTSClip(0x403A, UnimplementedHemfPlusRecord::new);
+
+
+    public final long id;
+    public final Supplier<? extends HemfPlusRecord> constructor;
+
+    HemfPlusRecordType(long id, Supplier<? extends HemfPlusRecord> constructor) {
+        this.id = id;
+        this.constructor = constructor;
+    }
+
+    public static HemfPlusRecordType getById(long id) {
+        for (HemfPlusRecordType wrt : values()) {
+            if (wrt.id == id) return wrt;
+        }
+        return null;
+    }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/UnimplementedHemfPlusRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/UnimplementedHemfPlusRecord.java
new file mode 100644 (file)
index 0000000..21d0e14
--- /dev/null
@@ -0,0 +1,59 @@
+/* ====================================================================
+   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.record.emfplus;
+
+
+import java.io.IOException;
+
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndianInputStream;
+
+@Internal
+public class UnimplementedHemfPlusRecord implements HemfPlusRecord {
+
+    private static final int MAX_RECORD_LENGTH = 1_000_000;
+
+    private long recordId;
+    private int flags;
+    private byte[] recordBytes;
+
+    @Override
+    public HemfPlusRecordType getRecordType() {
+        return HemfPlusRecordType.getById(recordId);
+    }
+
+    @Override
+    public int getFlags() {
+        return flags;
+    }
+
+    @Override
+    public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
+        this.recordId = recordId;
+        this.flags = flags;
+        recordBytes = IOUtils.safelyAllocate(dataSize, MAX_RECORD_LENGTH);
+        leis.readFully(recordBytes);
+        return recordBytes.length;
+    }
+
+    public byte[] getRecordBytes() {
+        //should probably defensively return a copy.
+        return recordBytes;
+    }
+}
\ No newline at end of file
diff --git a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java
new file mode 100644 (file)
index 0000000..40b04d7
--- /dev/null
@@ -0,0 +1,77 @@
+/* ====================================================================
+   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.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Spliterator;
+import java.util.function.Consumer;
+
+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.util.Internal;
+import org.apache.poi.util.LittleEndianInputStream;
+
+/**
+ * Read-only EMF extractor.  Lots remain
+ */
+@Internal
+public class HemfPicture implements Iterable<HemfRecord> {
+
+    private final LittleEndianInputStream stream;
+    private final List<HemfRecord> records = new ArrayList<>();
+
+    public HemfPicture(InputStream is) throws IOException {
+        this(new LittleEndianInputStream(is));
+    }
+
+    public HemfPicture(LittleEndianInputStream is) throws IOException {
+        stream = is;
+    }
+
+    public HemfHeader getHeader() {
+        return (HemfHeader)getRecords().get(0);
+    }
+
+    public List<HemfRecord> getRecords() {
+        if (records.isEmpty()) {
+            new HemfRecordIterator(stream).forEachRemaining(records::add);
+        }
+        return records;
+    }
+
+    @Override
+    public Iterator<HemfRecord> iterator() {
+        return getRecords().iterator();
+    }
+
+    @Override
+    public Spliterator<HemfRecord> spliterator() {
+        return getRecords().spliterator();
+    }
+
+    @Override
+    public void forEach(Consumer<? super HemfRecord> action) {
+        getRecords().forEach(action);
+    }
+}
index bd8c9ff9a9c95ac6332fb6327ddd8e8d66deb233..cdbfefe71e47ab7c06d34e193a16b51ddc069635 100644 (file)
@@ -43,32 +43,48 @@ public class HwmfDrawProperties {
     private final Rectangle2D window;
     private Rectangle2D viewport;
     private final Point2D location;
-    private HwmfMapMode mapMode = HwmfMapMode.MM_ANISOTROPIC;
-    private HwmfColorRef backgroundColor = new HwmfColorRef(Color.BLACK);
-    private HwmfBrushStyle brushStyle = HwmfBrushStyle.BS_SOLID;
-    private HwmfColorRef brushColor = new HwmfColorRef(Color.BLACK);
-    private HwmfHatchStyle brushHatch = HwmfHatchStyle.HS_HORIZONTAL;
+    private HwmfMapMode mapMode;
+    private HwmfColorRef backgroundColor;
+    private HwmfBrushStyle brushStyle;
+    private HwmfColorRef brushColor;
+    private HwmfHatchStyle brushHatch;
     private BufferedImage brushBitmap;
-    private double penWidth = 1;
-    private HwmfPenStyle penStyle = HwmfPenStyle.valueOf(0);
-    private HwmfColorRef penColor = new HwmfColorRef(Color.BLACK);
-    private double penMiterLimit = 10;
-    private HwmfBkMode bkMode = HwmfBkMode.OPAQUE;
-    private HwmfPolyfillMode polyfillMode = HwmfPolyfillMode.WINDING;
+    private double penWidth;
+    private HwmfPenStyle penStyle;
+    private HwmfColorRef penColor;
+    private double penMiterLimit;
+    private HwmfBkMode bkMode;
+    private HwmfPolyfillMode polyfillMode;
     private Shape region;
     private List<PaletteEntry> palette;
     private int paletteOffset;
     private HwmfFont font;
-    private HwmfColorRef textColor = new HwmfColorRef(Color.BLACK);
-    private HwmfTextAlignment textAlignLatin = HwmfTextAlignment.LEFT;
-    private HwmfTextVerticalAlignment textVAlignLatin = HwmfTextVerticalAlignment.TOP;
-    private HwmfTextAlignment textAlignAsian = HwmfTextAlignment.RIGHT;
-    private HwmfTextVerticalAlignment textVAlignAsian = HwmfTextVerticalAlignment.TOP;
+    private HwmfColorRef textColor;
+    private HwmfTextAlignment textAlignLatin;
+    private HwmfTextVerticalAlignment textVAlignLatin;
+    private HwmfTextAlignment textAlignAsian;
+    private HwmfTextVerticalAlignment textVAlignAsian;
 
     public HwmfDrawProperties() {
         window = new Rectangle2D.Double(0, 0, 1, 1);
         viewport = null;
         location = new Point2D.Double(0,0);
+        mapMode = HwmfMapMode.MM_ANISOTROPIC;
+        backgroundColor = new HwmfColorRef(Color.BLACK);
+        brushStyle = HwmfBrushStyle.BS_SOLID;
+        brushColor = new HwmfColorRef(Color.BLACK);
+        brushHatch = HwmfHatchStyle.HS_HORIZONTAL;
+        penWidth = 1;
+        penStyle = HwmfPenStyle.valueOf(0);
+        penColor = new HwmfColorRef(Color.BLACK);
+        penMiterLimit = 10;
+        bkMode = HwmfBkMode.OPAQUE;
+        polyfillMode = HwmfPolyfillMode.WINDING;
+        textColor = new HwmfColorRef(Color.BLACK);
+        textAlignLatin = HwmfTextAlignment.LEFT;
+        textVAlignLatin = HwmfTextVerticalAlignment.TOP;
+        textAlignAsian = HwmfTextAlignment.RIGHT;
+        textVAlignAsian = HwmfTextVerticalAlignment.TOP;
     }
     
     public HwmfDrawProperties(HwmfDrawProperties other) {
@@ -86,7 +102,7 @@ public class HwmfDrawProperties {
             WritableRaster raster = other.brushBitmap.copyData(null);
             this.brushBitmap = new BufferedImage(cm, raster, isAlphaPremultiplied, null);            
         }
-        this.penWidth = 1;
+        this.penWidth = other.penWidth;
         this.penStyle = (other.penStyle == null) ? null : other.penStyle.clone();
         this.penColor = (other.penColor == null) ? null : other.penColor.clone();
         this.penMiterLimit = other.penMiterLimit;
@@ -101,6 +117,10 @@ public class HwmfDrawProperties {
         this.paletteOffset = other.paletteOffset;
         this.font = other.font;
         this.textColor = (other.textColor == null) ? null : other.textColor.clone();
+        this.textAlignLatin = other.textAlignLatin;
+        this.textVAlignLatin = other.textVAlignLatin;
+        this.textAlignAsian = other.textAlignAsian;
+        this.textVAlignAsian = other.textVAlignAsian;
     }
     
     public void setViewportExt(double width, double height) {
@@ -149,6 +169,10 @@ public class HwmfDrawProperties {
         location.setLocation(x, y);
     }
 
+    public void setLocation(Point2D point) {
+        location.setLocation(point);
+    }
+
     public Point2D getLocation() {
         return (Point2D)location.clone();
     }
index 2e8122fb15454c50d1ed3dc9658cb29d397af929..903ba592acc06ed79871df09a2c3e73b87fcac5c 100644 (file)
@@ -71,7 +71,7 @@ public enum HwmfBrushStyle {
         this.flag = flag;
     }
 
-    static HwmfBrushStyle valueOf(int flag) {
+    public static HwmfBrushStyle valueOf(int flag) {
         for (HwmfBrushStyle bs : values()) {
             if (bs.flag == flag) return bs;
         }
index 48bcc60b443d5054386a0966ca00f405001d28aa..fb78158a9d53fae7889ea074709e6aeddeb524f8 100644 (file)
@@ -41,31 +41,21 @@ public class HwmfDraw {
      */
     public static class WmfMoveTo implements HwmfRecord {
 
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units.
-         */
-        private int y;
-
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units.
-         */
-        private int x;
+        protected final Point2D point = new Point2D.Double();
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.moveTo;
         }
 
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
-            y = leis.readShort();
-            x = leis.readShort();
-            return 2*LittleEndianConsts.SHORT_SIZE;
+            return readPointS(leis, point);
         }
 
         @Override
         public void draw(HwmfGraphics ctx) {
-            ctx.getProperties().setLocation(x, y);
+            ctx.getProperties().setLocation(point);
         }
     }
 
@@ -75,36 +65,24 @@ public class HwmfDraw {
      */
     public static class WmfLineTo implements HwmfRecord {
 
-        /**
-         * A 16-bit signed integer that defines the vertical component of the drawing
-         * destination position, in logical units.
-         */
-        private int y;
-
-        /**
-         * A 16-bit signed integer that defines the horizontal component of the drawing
-         * destination position, in logical units.
-         */
-        private int x;
+        protected final Point2D point = new Point2D.Double();
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.lineTo;
         }
 
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
-            y = leis.readShort();
-            x = leis.readShort();
-            return 2*LittleEndianConsts.SHORT_SIZE;
+            return readPointS(leis, point);
         }
 
         @Override
         public void draw(HwmfGraphics ctx) {
             Point2D start = ctx.getProperties().getLocation();
-            Line2D line = new Line2D.Double(start.getX(), start.getY(), x, y);
+            Line2D line = new Line2D.Double(start, point);
             ctx.draw(line);
-            ctx.getProperties().setLocation(x, y);
+            ctx.getProperties().setLocation(point);
         }
     }
 
@@ -115,10 +93,10 @@ public class HwmfDraw {
      */
     public static class WmfPolygon implements HwmfRecord {
 
-        private Path2D poly = new Path2D.Double();
+        protected Path2D poly = new Path2D.Double();
         
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.polygon;
         }
 
@@ -146,16 +124,26 @@ public class HwmfDraw {
 
         @Override
         public void draw(HwmfGraphics ctx) {
-            Path2D shape = getShape();
-//            shape.closePath();
-            Path2D p = (Path2D)shape.clone();
+            Path2D p = getShape(ctx);
+            // don't close the path
             p.setWindingRule(getWindingRule(ctx));
-            ctx.fill(p);
+            if (isFill()) {
+                ctx.fill(p);
+            } else {
+                ctx.draw(p);
+            }
         }
 
-        protected Path2D getShape() {
+        protected Path2D getShape(HwmfGraphics ctx) {
             return (Path2D)poly.clone();
         }
+
+        /**
+         * @return true, if the shape should be filled
+         */
+        protected boolean isFill() {
+            return true;
+        }
     }
 
     /**
@@ -165,16 +153,13 @@ public class HwmfDraw {
     public static class WmfPolyline extends WmfPolygon {
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.polyline;
         }
 
         @Override
-        public void draw(HwmfGraphics ctx) {
-            Path2D shape = getShape();
-            Path2D p = (Path2D)shape.clone();
-            p.setWindingRule(getWindingRule(ctx));
-            ctx.draw(p);
+        protected boolean isFill() {
+            return false;
         }
     }
 
@@ -184,48 +169,21 @@ public class HwmfDraw {
      * are defined in the playback device context.
      */
     public static class WmfEllipse implements HwmfRecord {
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of
-         * the lower-right corner of the bounding rectangle.
-         */
-        private int bottomRect;
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of
-         * the lower-right corner of the bounding rectangle.
-         */
-        private int rightRect;
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of the
-         * upper-left corner of the bounding rectangle.
-         */
-        private int topRect;
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of
-         * the upper-left corner of the bounding rectangle.
-         */
-        private int leftRect;
+        protected final Rectangle2D bounds = new Rectangle2D.Double();
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.ellipse;
         }
 
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
-            bottomRect = leis.readShort();
-            rightRect = leis.readShort();
-            topRect = leis.readShort();
-            leftRect = leis.readShort();
-            return 4*LittleEndianConsts.SHORT_SIZE;
+            return readBounds(leis, bounds);
         }
 
         @Override
         public void draw(HwmfGraphics ctx) {
-            int x = Math.min(leftRect, rightRect);
-            int y = Math.min(topRect, bottomRect);
-            int w = Math.abs(leftRect - rightRect - 1);
-            int h = Math.abs(topRect - bottomRect - 1);
-            Shape s = new Ellipse2D.Double(x, y, w, h);
+            Shape s = new Ellipse2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());
             ctx.fill(s);
         }
     }
@@ -239,25 +197,25 @@ public class HwmfDraw {
          * A 16-bit unsigned integer used to index into the WMF Object Table to get
          * the region to be framed.
          */
-        private int regionIndex;
+        protected int regionIndex;
         /**
          * A 16-bit unsigned integer used to index into the WMF Object Table to get the
          * Brush to use for filling the region.
          */
-        private int brushIndex;
+        protected int brushIndex;
         /**
          * A 16-bit signed integer that defines the height, in logical units, of the
          * region frame.
          */
-        private int height;
+        protected int height;
         /**
          * A 16-bit signed integer that defines the width, in logical units, of the
          * region frame.
          */
-        private int width;
+        protected int width;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.frameRegion;
         }
 
@@ -293,10 +251,10 @@ public class HwmfDraw {
      */
     public static class WmfPolyPolygon implements HwmfRecord {
 
-        private List<Path2D> polyList = new ArrayList<>();
+        protected List<Path2D> polyList = new ArrayList<>();
         
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.polyPolygon;
         }
 
@@ -361,7 +319,20 @@ public class HwmfDraw {
                     area.exclusiveOr(newArea);
                 }
             }
-            ctx.fill(area);
+
+            if (isFill()) {
+                ctx.fill(area);
+            } else {
+                ctx.draw(area);
+            }
+        }
+
+
+        /**
+         * @return true, if the shape should be filled
+         */
+        protected boolean isFill() {
+            return true;
         }
     }
 
@@ -370,92 +341,50 @@ public class HwmfDraw {
      * filled by using the brush that are defined in the playback device context.
      */
     public static class WmfRectangle implements HwmfRecord {
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of
-         * the lower-right corner of the rectangle.
-         */
-        private int bottomRect;
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of
-         * the lower-right corner of the rectangle.
-         */
-        private int rightRect;
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of the
-         * upper-left corner of the rectangle.
-         */
-        private int topRect;
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of
-         * the upper-left corner of the rectangle.
-         */
-        private int leftRect;
+        protected final Rectangle2D bounds = new Rectangle2D.Double();
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.frameRegion;
         }
 
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
-            bottomRect = leis.readShort();
-            rightRect = leis.readShort();
-            topRect = leis.readShort();
-            leftRect = leis.readShort();
-            return 4*LittleEndianConsts.SHORT_SIZE;
+            return readBounds(leis, bounds);
         }
 
         @Override
         public void draw(HwmfGraphics ctx) {
-            int x = Math.min(leftRect, rightRect);
-            int y = Math.min(topRect, bottomRect);
-            int w = Math.abs(leftRect - rightRect - 1);
-            int h = Math.abs(topRect - bottomRect - 1);
-            Shape s = new Rectangle2D.Double(x, y, w, h);
-            ctx.fill(s);
+            ctx.fill(bounds);
         }
     }
 
     /**
-     * The META_RECTANGLE record paints a rectangle. The rectangle is outlined by using the pen and
-     * filled by using the brush that are defined in the playback device context.
+     * The META_SETPIXEL record sets the pixel at the specified coordinates to the specified color.
      */
     public static class WmfSetPixel implements HwmfRecord {
         /**
          * A ColorRef Object that defines the color value.
          */
-        HwmfColorRef colorRef;
+        protected final HwmfColorRef colorRef = new HwmfColorRef();
 
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of the point
-         * to be set.
-         */
-        private int y;
-
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of the point
-         * to be set.
-         */
-        private int x;
+        protected final Point2D point = new Point2D.Double();
 
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.setPixel;
         }
 
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
-            colorRef = new HwmfColorRef();
             int size = colorRef.init(leis);
-            y = leis.readShort();
-            x = leis.readShort();
-            return 2*LittleEndianConsts.SHORT_SIZE+size;
+            return size+ readPointS(leis, point);
         }
 
         @Override
         public void draw(HwmfGraphics ctx) {
-            Shape s = new Rectangle2D.Double(x, y, 1, 1);
+            Shape s = new Rectangle2D.Double(point.getX(), point.getY(), 1, 1);
             ctx.fill(s);
         }
     }
@@ -469,41 +398,19 @@ public class HwmfDraw {
          * A 16-bit signed integer that defines the height, in logical coordinates, of the
          * ellipse used to draw the rounded corners.
          */
-        private int height;
+        protected int height;
 
         /**
          * A 16-bit signed integer that defines the width, in logical coordinates, of the
          * ellipse used to draw the rounded corners.
          */
-        private int width;
+        protected int width;
 
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of
-         * the lower-right corner of the rectangle.
-         */
-        private int bottomRect;
-
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of
-         * the lower-right corner of the rectangle.
-         */
-        private int rightRect;
-
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of the
-         * upper-left corner of the rectangle.
-         */
-        private int topRect;
-
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of
-         * the upper-left corner of the rectangle.
-         */
-        private int leftRect;
+        protected final Rectangle2D bounds = new Rectangle2D.Double();
 
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.roundRect;
         }
 
@@ -511,20 +418,12 @@ public class HwmfDraw {
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
             height = leis.readShort();
             width = leis.readShort();
-            bottomRect = leis.readShort();
-            rightRect = leis.readShort();
-            topRect = leis.readShort();
-            leftRect = leis.readShort();
-            return 6*LittleEndianConsts.SHORT_SIZE;
+            return 2*LittleEndianConsts.SHORT_SIZE+readBounds(leis, bounds);
         }
 
         @Override
         public void draw(HwmfGraphics ctx) {
-            int x = Math.min(leftRect, rightRect);
-            int y = Math.min(topRect, bottomRect);
-            int w = Math.abs(leftRect - rightRect - 1);
-            int h = Math.abs(topRect - bottomRect - 1);
-            Shape s = new RoundRectangle2D.Double(x, y, w, h, width, height);
+            Shape s = new RoundRectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), width, height);
             ctx.fill(s);
         }
     }
@@ -534,73 +433,34 @@ public class HwmfDraw {
      * The META_ARC record draws an elliptical arc.
      */
     public static class WmfArc implements HwmfRecord {
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of
-         * the ending point of the radial line defining the ending point of the arc.
-         */
-        private int yEndArc;
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of
-         * the ending point of the radial line defining the ending point of the arc.
-         */
-        private int xEndArc;
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of
-         * the ending point of the radial line defining the starting point of the arc.
-         */
-        private int yStartArc;
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of
-         * the ending point of the radial line defining the starting point of the arc.
-         */
-        private int xStartArc;
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of
-         * the lower-right corner of the bounding rectangle.
-         */
-        private int bottomRect;
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of
-         * the lower-right corner of the bounding rectangle.
-         */
-        private int rightRect;
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of the
-         * upper-left corner of the bounding rectangle.
-         */
-        private int topRect;
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of
-         * the upper-left corner of the bounding rectangle.
-         */
-        private int leftRect;
+        /** starting point of the arc */
+        protected final Point2D startPoint = new Point2D.Double();
+
+        /** ending point of the arc */
+        protected final Point2D endPoint = new Point2D.Double();
+
+        /** the bounding rectangle */
+        protected final Rectangle2D bounds = new Rectangle2D.Double();
+
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.arc;
         }
 
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
-            yEndArc = leis.readShort();
-            xEndArc = leis.readShort();
-            yStartArc = leis.readShort();
-            xStartArc = leis.readShort();
-            bottomRect = leis.readShort();
-            rightRect = leis.readShort();
-            topRect = leis.readShort();
-            leftRect = leis.readShort();
+            readPointS(leis, endPoint);
+            readPointS(leis, startPoint);
+            readBounds(leis, bounds);
+
             return 8*LittleEndianConsts.SHORT_SIZE;
         }
 
         @Override
         public void draw(HwmfGraphics ctx) {
-            int x = Math.min(leftRect, rightRect);
-            int y = Math.min(topRect, bottomRect);
-            int w = Math.abs(leftRect - rightRect - 1);
-            int h = Math.abs(topRect - bottomRect - 1);
-            double startAngle = Math.toDegrees(Math.atan2(-(yStartArc - (topRect + h / 2.)), xStartArc - (leftRect + w / 2.)));
-            double endAngle =   Math.toDegrees(Math.atan2(-(yEndArc - (topRect + h / 2.)), xEndArc - (leftRect + w / 2.)));
+            double startAngle = Math.toDegrees(Math.atan2(-(startPoint.getY() - bounds.getCenterY()), startPoint.getX() - bounds.getCenterX()));
+            double endAngle =   Math.toDegrees(Math.atan2(-(endPoint.getY() - bounds.getCenterY()), endPoint.getX() - bounds.getCenterX()));
             double arcAngle = (endAngle - startAngle) + (endAngle - startAngle > 0 ? 0 : 360);
             if (startAngle < 0) {
                 startAngle += 360;
@@ -608,7 +468,7 @@ public class HwmfDraw {
 
             boolean fillShape;
             int arcClosure;
-            switch (getRecordType()) {
+            switch (getWmfRecordType()) {
                 default:
                 case arc:
                     arcClosure = Arc2D.OPEN;
@@ -624,7 +484,7 @@ public class HwmfDraw {
                     break;
             }
             
-            Shape s = new Arc2D.Double(x, y, w, h, startAngle, arcAngle, arcClosure);
+            Shape s = new Arc2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), startAngle, arcAngle, arcClosure);
             if (fillShape) {
                 ctx.fill(s);
             } else {
@@ -641,7 +501,7 @@ public class HwmfDraw {
     public static class WmfPie extends WmfArc {
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.pie;
         }
     }
@@ -654,7 +514,7 @@ public class HwmfDraw {
     public static class WmfChord extends WmfArc {
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.chord;
         }
     }
@@ -673,10 +533,10 @@ public class HwmfDraw {
          * A 16-bit unsigned integer used to index into the WMF Object Table to
          * get the object to be selected.
          */
-        private int objectIndex;
+        protected int objectIndex;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.selectObject;
         }
 
@@ -695,4 +555,50 @@ public class HwmfDraw {
     private static int getWindingRule(HwmfGraphics ctx) {
         return ctx.getProperties().getPolyfillMode().awtFlag;
     }
- }
+
+    static int readBounds(LittleEndianInputStream leis, Rectangle2D bounds) {
+        /**
+         * The 16-bit signed integers that defines the corners of the bounding rectangle.
+         */
+        int bottom = leis.readShort();
+        int right = leis.readShort();
+        int top = leis.readShort();
+        int left = leis.readShort();
+
+        int x = Math.min(left, right);
+        int y = Math.min(top, bottom);
+        int w = Math.abs(left - right - 1);
+        int h = Math.abs(top - bottom - 1);
+
+        bounds.setRect(x, y, w, h);
+
+        return 4 * LittleEndianConsts.SHORT_SIZE;
+    }
+
+    static int readRectS(LittleEndianInputStream leis, Rectangle2D bounds) {
+        /**
+         * The 16-bit signed integers that defines the corners of the bounding rectangle.
+         */
+        int left = leis.readShort();
+        int top = leis.readShort();
+        int right = leis.readShort();
+        int bottom = leis.readShort();
+
+        int x = Math.min(left, right);
+        int y = Math.min(top, bottom);
+        int w = Math.abs(left - right - 1);
+        int h = Math.abs(top - bottom - 1);
+
+        bounds.setRect(x, y, w, h);
+
+        return 4 * LittleEndianConsts.SHORT_SIZE;
+    }
+
+    static int readPointS(LittleEndianInputStream leis, Point2D point) {
+        /** a signed integer that defines the x/y-coordinate, in logical units. */
+        int y = leis.readShort();
+        int x = leis.readShort();
+        point.setLocation(x, y);
+        return 2*LittleEndianConsts.SHORT_SIZE;
+    }
+}
index 6c7ef213b0b5d7059f828143f3f7334fa50b1cb5..a1de4dca4616d47082f7259a946ab75f8432ce91 100644 (file)
@@ -185,7 +185,7 @@ public class HwmfEscape implements HwmfRecord {
     private byte escapeData[];
     
     @Override
-    public HwmfRecordType getRecordType() {
+    public HwmfRecordType getWmfRecordType() {
         return HwmfRecordType.escape;
     }
     
index ec4d070540d40f263f1e87df78c6f47bec4d0180..26aa55696b9f92d5450ab2e996820c62bb4641ba 100644 (file)
 
 package org.apache.poi.hwmf.record;
 
+import static org.apache.poi.hwmf.record.HwmfDraw.readPointS;
+
 import java.awt.Shape;
 import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
 import java.awt.image.BufferedImage;
 import java.io.IOException;
 
@@ -62,7 +66,7 @@ public class HwmfFill {
             this.flag = flag;
         }
 
-        static ColorUsage valueOf(int flag) {
+        public static ColorUsage valueOf(int flag) {
             for (ColorUsage bs : values()) {
                 if (bs.flag == flag) return bs;
             }
@@ -80,16 +84,16 @@ public class HwmfFill {
          * A 16-bit unsigned integer used to index into the WMF Object Table to get
          * the region to be filled.
          */
-        private int regionIndex;
+        protected int regionIndex;
 
         /**
          * A 16-bit unsigned integer used to index into the WMF Object Table to get the
          * brush to use for filling the region.
          */
-        private int brushIndex;
+        protected int brushIndex;
         
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.fillRegion;
         }
         
@@ -125,7 +129,7 @@ public class HwmfFill {
          */
         int regionIndex;
 
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.paintRegion;
         }
         
@@ -155,31 +159,21 @@ public class HwmfFill {
         /**
          * A 32-bit ColorRef Object that defines the color value.
          */
-        private HwmfColorRef colorRef;
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of the
-         * point where filling is to start.
-         */
-        private int yStart;
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of the
-         * point where filling is to start.
-         */
-        private int xStart;
-        
-        
+        protected final HwmfColorRef colorRef = new HwmfColorRef();
+
+        /** the point where filling is to start. */
+        protected final Point2D start = new Point2D.Double();
+
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.floodFill;
         }
         
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
-            colorRef = new HwmfColorRef();
             int size = colorRef.init(leis);
-            yStart = leis.readShort();
-            xStart = leis.readShort();
-            return size+2*LittleEndianConsts.SHORT_SIZE;
+            size += readPointS(leis, start);
+            return size;
         }
 
         @Override
@@ -215,22 +209,22 @@ public class HwmfFill {
                 this.awtFlag = awtFlag;
             }
 
-            static HwmfPolyfillMode valueOf(int wmfFlag) {
+            public static HwmfPolyfillMode valueOf(int wmfFlag) {
                 for (HwmfPolyfillMode pm : values()) {
                     if (pm.wmfFlag == wmfFlag) return pm;
                 }
                 return null;
             }
         }
-        
+
         /**
-         * A 16-bit unsigned integer that defines polygon fill mode.
+         * An unsigned integer that defines polygon fill mode.
          * This MUST be one of the values: ALTERNATE = 0x0001, WINDING = 0x0002
          */
-        private HwmfPolyfillMode polyfillMode;
+        protected HwmfPolyfillMode polyfillMode;
         
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.setPolyFillMode;
         }
         
@@ -251,7 +245,7 @@ public class HwmfFill {
      * The META_EXTFLOODFILL record fills an area with the brush that is defined in
      * the playback device context.
      */
-    public static class WmfExtFloodFill implements HwmfRecord {
+    public static class WmfExtFloodFill extends WmfFloodFill {
         
         /**
          * A 16-bit unsigned integer that defines the fill operation to be performed. This
@@ -266,38 +260,17 @@ public class HwmfFill {
          * Filling continues outward in all directions as long as the color is encountered.
          * This style is useful for filling areas with multicolored boundaries.
          */
-        private int mode;
-        
-        /**
-         * A 32-bit ColorRef Object that defines the color value.
-         */
-        private HwmfColorRef colorRef;
-        
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of the point
-         * to be set.
-         */
-        private int y;
-        
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of the point
-         * to be set.
-         */
-        private int x;  
+        protected int mode;
         
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.extFloodFill;
         }
         
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
             mode = leis.readUShort();
-            colorRef = new HwmfColorRef();
-            int size = colorRef.init(leis);
-            y = leis.readShort();
-            x = leis.readShort();
-            return size+3*LittleEndianConsts.SHORT_SIZE;
+            return super.init(leis, recordSize, recordFunction)+LittleEndianConsts.SHORT_SIZE;
         }
 
         @Override
@@ -318,7 +291,7 @@ public class HwmfFill {
         private int region;
         
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.invertRegion;
         }
         
@@ -348,30 +321,10 @@ public class HwmfFill {
          */
         private HwmfTernaryRasterOp rasterOperation;
         
-        /**
-         * A 16-bit signed integer that defines the height, in logical units, of the rectangle.
-         */
-        private int height;
-        
-        /**
-         * A 16-bit signed integer that defines the width, in logical units, of the rectangle.
-         */
-        private int width;
-        
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of the
-         * upper-left corner of the rectangle to be filled.
-         */
-        private int yLeft;
-        
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of the
-         * upper-left corner of the rectangle to be filled.
-         */
-        private int xLeft;
-        
+        private final Rectangle2D bounds = new Rectangle2D.Double();
+
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.patBlt;
         }
         
@@ -383,12 +336,7 @@ public class HwmfFill {
             rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex);
             assert(rasterOpCode == rasterOperation.opCode);
             
-            height = leis.readShort();
-            width = leis.readShort();
-            yLeft = leis.readShort();
-            xLeft = leis.readShort();
-
-            return 6*LittleEndianConsts.SHORT_SIZE;
+            return readBounds2(leis, bounds)+2*LittleEndianConsts.SHORT_SIZE;
         }
 
         @Override
@@ -414,53 +362,22 @@ public class HwmfFill {
          * in the playback device context, and the destination pixels are to be combined to form the new 
          * image. This code MUST be one of the values in the Ternary Raster Operation Enumeration
          */
-        private HwmfTernaryRasterOp rasterOperation;
-        
-        /**
-         * A 16-bit signed integer that defines the height, in logical units, of the source rectangle.
-         */
-        private int srcHeight; 
-        /**
-         * A 16-bit signed integer that defines the width, in logical units, of the source rectangle.
-         */
-        private int srcWidth; 
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left corner 
-         * of the source rectangle.
-         */
-        private int ySrc;
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left corner 
-         * of the source rectangle.
-         */
-        private int xSrc;
-        /**
-         * A 16-bit signed integer that defines the height, in logical units, of the destination rectangle.
-         */
-        private int destHeight;
-        /**
-         * A 16-bit signed integer that defines the width, in logical units, of the destination rectangle.
-         */
-        private int destWidth;
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left 
-         * corner of the destination rectangle.
-         */
-        private int yDest;
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left 
-         * corner of the destination rectangle.
-         */
-        private int xDest;
-        
+        protected HwmfTernaryRasterOp rasterOperation;
+
+        /** the source rectangle */
+        protected final Rectangle2D srcBounds = new Rectangle2D.Double();
+
+        /** the destination rectangle */
+        protected final Rectangle2D dstBounds = new Rectangle2D.Double();
+
         /**
          * A variable-sized Bitmap16 Object that defines source image content.
          * This object MUST be specified, even if the raster operation does not require a source.
          */
-        HwmfBitmap16 target;
+        protected HwmfBitmap16 target;
         
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.stretchBlt;
         }
         
@@ -469,27 +386,23 @@ public class HwmfFill {
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
             boolean hasBitmap = (recordSize > ((recordFunction >> 8) + 3));
 
-            int size = 0;
             int rasterOpCode = leis.readUShort();
             int rasterOpIndex = leis.readUShort();
             
             rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex);
             assert(rasterOpCode == rasterOperation.opCode);
 
-            srcHeight = leis.readShort();
-            srcWidth = leis.readShort();
-            ySrc = leis.readShort();
-            xSrc = leis.readShort();
-            size = 6*LittleEndianConsts.SHORT_SIZE;
+            int size = 2*LittleEndianConsts.SHORT_SIZE;
+
+            size += readBounds2(leis, srcBounds);
+
             if (!hasBitmap) {
                 /*int reserved =*/ leis.readShort();
                 size += LittleEndianConsts.SHORT_SIZE;
             }
-            destHeight = leis.readShort();
-            destWidth = leis.readShort();
-            yDest = leis.readShort();
-            xDest = leis.readShort();
-            size += 4*LittleEndianConsts.SHORT_SIZE;
+
+            size += readBounds2(leis, dstBounds);
+
             if (hasBitmap) {
                 target = new HwmfBitmap16();
                 size += target.init(leis);
@@ -524,46 +437,13 @@ public class HwmfFill {
          * DIB contains explicit RGB values or indexes into a palette.
          */
         private ColorUsage colorUsage;
-        /**
-         * A 16-bit signed integer that defines the height, in logical units, of the
-         * source rectangle.
-         */
-        private int srcHeight;
-        /**
-         * A 16-bit signed integer that defines the width, in logical units, of the
-         * source rectangle.
-         */
-        private int srcWidth; 
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of the
-         * source rectangle.
-         */
-        private int ySrc;
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of the 
-         * source rectangle.
-         */
-        private int xSrc;
-        /**
-         * A 16-bit signed integer that defines the height, in logical units, of the 
-         * destination rectangle.
-         */
-        private int destHeight;
-        /**
-         * A 16-bit signed integer that defines the width, in logical units, of the 
-         * destination rectangle.
-         */
-        private int destWidth;
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of the 
-         * upper-left corner of the destination rectangle.
-         */
-        private int yDst;
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of the 
-         * upper-left corner of the destination rectangle.
-         */
-        private int xDst;
+
+        /** the source rectangle. */
+        protected final Rectangle2D srcBounds = new Rectangle2D.Double();
+
+        /** the destination rectangle. */
+        protected final Rectangle2D dstBounds = new Rectangle2D.Double();
+
         /**
          * A variable-sized DeviceIndependentBitmap Object (section 2.2.2.9) that is the 
          * source of the color data.
@@ -571,7 +451,7 @@ public class HwmfFill {
         private HwmfBitmapDib dib;
         
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.stretchDib;
         }
         
@@ -585,16 +465,12 @@ public class HwmfFill {
             assert(rasterOpCode == rasterOperation.opCode);
 
             colorUsage = ColorUsage.valueOf(leis.readUShort());
-            srcHeight = leis.readShort();
-            srcWidth = leis.readShort();
-            ySrc = leis.readShort();
-            xSrc = leis.readShort();
-            destHeight = leis.readShort();
-            destWidth = leis.readShort();
-            yDst = leis.readShort();
-            xDst = leis.readShort();
-            
-            int size = 11*LittleEndianConsts.SHORT_SIZE;
+
+            int size = 3*LittleEndianConsts.SHORT_SIZE;
+
+            size += readBounds2(leis, srcBounds);
+            size += readBounds2(leis, dstBounds);
+
             dib = new HwmfBitmapDib();
             size += dib.init(leis, (int)(recordSize-6-size));
 
@@ -617,53 +493,10 @@ public class HwmfFill {
         }
     }
     
-    public static class WmfBitBlt implements HwmfRecord {
-
-        /**
-         * 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 form the new image.
-         */
-        private HwmfTernaryRasterOp rasterOperation;
-        
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left corner 
-        of the source rectangle.
-         */
-        private int ySrc; 
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left corner 
-        of the source rectangle.
-         */
-        private int xSrc; 
-        /**
-         * A 16-bit signed integer that defines the height, in logical units, of the source and 
-        destination rectangles.
-         */
-        private int height;
-        /**
-         * A 16-bit signed integer that defines the width, in logical units, of the source and destination 
-        rectangles.
-         */
-        private int width;
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left 
-        corner of the destination rectangle.
-         */
-        private int yDest;
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left 
-        corner of the destination rectangle.
-         */
-        private int xDest;
-        
-        /**
-         * A variable-sized Bitmap16 Object that defines source image content.
-         * This object MUST be specified, even if the raster operation does not require a source.
-         */
-        private HwmfBitmap16 target;
+    public static class WmfBitBlt extends WmfStretchBlt {
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.bitBlt;
         }
         
@@ -671,40 +504,32 @@ public class HwmfFill {
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
             boolean hasBitmap = (recordSize/2 != ((recordFunction >> 8) + 3));
 
-            int size = 0;
             int rasterOpCode = leis.readUShort();
             int rasterOpIndex = leis.readUShort();
             
             rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex);
             assert(rasterOpCode == rasterOperation.opCode);
 
-            ySrc = leis.readShort();
-            xSrc = leis.readShort();
+            int size = 2*LittleEndianConsts.SHORT_SIZE;
+
+            final Point2D srcPnt = new Point2D.Double();
+            size += readPointS(leis, srcPnt);
 
-            size = 4*LittleEndianConsts.SHORT_SIZE;
-            
             if (!hasBitmap) {
                 /*int reserved =*/ leis.readShort();
                 size += LittleEndianConsts.SHORT_SIZE;
             }
-            
-            height = leis.readShort();
-            width = leis.readShort();
-            yDest = leis.readShort();
-            xDest = leis.readShort();
 
-            size += 4*LittleEndianConsts.SHORT_SIZE;
+            size += readBounds2(leis, dstBounds);
+
             if (hasBitmap) {
                 target = new HwmfBitmap16();
                 size += target.init(leis);
             }
-            
-            return size;
-        }
 
-        @Override
-        public void draw(HwmfGraphics ctx) {
+            srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight());
             
+            return size;
         }
     }
 
@@ -729,36 +554,13 @@ public class HwmfFill {
          * A 16-bit unsigned integer that defines the starting scan line in the source.
          */
         private int startScan;  
-        /**
-         * A 16-bit unsigned integer that defines the y-coordinate, in logical units, of the
-         * source rectangle.
-         */
-        private int yDib;  
-        /**
-         * A 16-bit unsigned integer that defines the x-coordinate, in logical units, of the
-         * source rectangle.
-         */
-        private int xDib;  
-        /**
-         * A 16-bit unsigned integer that defines the height, in logical units, of the
-         * source and destination rectangles.
-         */
-        private int height;
-        /**
-         * A 16-bit unsigned integer that defines the width, in logical units, of the
-         * source and destination rectangles.
-         */
-        private int width;
-        /**
-         * A 16-bit unsigned integer that defines the y-coordinate, in logical units, of the
-         * upper-left corner of the destination rectangle.
-         */
-        private int yDest;
-        /**
-         * A 16-bit unsigned integer that defines the x-coordinate, in logical units, of the
-         * upper-left corner of the destination rectangle.
-         */
-        private int xDest;
+
+        /** the source rectangle */
+        protected final Rectangle2D srcBounds = new Rectangle2D.Double();
+
+        /** the destination rectangle, having the same dimension as the source rectangle */
+        protected final Rectangle2D dstBounds = new Rectangle2D.Double();
+
         /**
          * A variable-sized DeviceIndependentBitmap Object that is the source of the color data.
          */
@@ -766,7 +568,7 @@ public class HwmfFill {
         
         
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.setDibToDev;
         }
         
@@ -775,17 +577,19 @@ public class HwmfFill {
             colorUsage = ColorUsage.valueOf(leis.readUShort());
             scanCount = leis.readUShort();
             startScan = leis.readUShort();
-            yDib = leis.readUShort();
-            xDib = leis.readUShort();
-            height = leis.readUShort();
-            width = leis.readUShort();
-            yDest = leis.readUShort();
-            xDest = leis.readUShort();
-            
-            int size = 9*LittleEndianConsts.SHORT_SIZE;
+
+            int size = 3*LittleEndianConsts.SHORT_SIZE;
+
+            final Point2D srcPnt = new Point2D.Double();
+            size += readPointS(leis, srcPnt);
+
+            size += readBounds2(leis, dstBounds);
+
             dib = new HwmfBitmapDib();
             size += dib.init(leis, (int)(recordSize-6-size));
-            
+
+            srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight());
+
             return size;
         }        
 
@@ -806,52 +610,9 @@ public class HwmfFill {
     }
 
 
-    public static class WmfDibBitBlt implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry {
-
-        /**
-         * 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 form the
-         * new image. This code MUST be one of the values in the Ternary Raster Operation Enumeration.
-         */
-        HwmfTernaryRasterOp rasterOperation;  
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of the source rectangle.
-         */
-        private int ySrc;
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of the source rectangle.
-         */
-        private int xSrc;
-        /**
-         * A 16-bit signed integer that defines the height, in logical units, of the source and 
-         * destination rectangles.
-         */
-        private int height;
-        /**
-         * A 16-bit signed integer that defines the width, in logical units, of the source and destination
-         * rectangles.
-         */
-        private int width;
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left
-         * corner of the destination rectangle.
-         */
-        private int yDest;
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left 
-         * corner of the destination rectangle.
-         */
-        private int xDest;
-        
-        /**
-         * A variable-sized DeviceIndependentBitmap Object that defines image content.
-         * This object MUST be specified, even if the raster operation does not require a source.
-         */
-        private HwmfBitmapDib target;
-        
-        
+    public static class WmfDibBitBlt extends WmfDibStretchBlt {
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.dibBitBlt;
         }
         
@@ -859,47 +620,31 @@ public class HwmfFill {
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
             boolean hasBitmap = (recordSize/2 != ((recordFunction >> 8) + 3));
 
-            int size = 0;
             int rasterOpCode = leis.readUShort();
             int rasterOpIndex = leis.readUShort();
             
             rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex);
             assert(rasterOpCode == rasterOperation.opCode);
 
-            ySrc = leis.readShort();
-            xSrc = leis.readShort();
-            size = 4*LittleEndianConsts.SHORT_SIZE;
+            int size = 2*LittleEndianConsts.SHORT_SIZE;
+
+            final Point2D srcPnt = new Point2D.Double();
+            size += readPointS(leis, srcPnt);
             if (!hasBitmap) {
                 /*int reserved =*/ leis.readShort();
                 size += LittleEndianConsts.SHORT_SIZE;
             }
-            height = leis.readShort();
-            width = leis.readShort();
-            yDest = leis.readShort();
-            xDest = leis.readShort();
-            
-            size += 4*LittleEndianConsts.SHORT_SIZE;
+
+            size += readBounds2(leis, dstBounds);
             if (hasBitmap) {
                 target = new HwmfBitmapDib();
                 size += target.init(leis, (int)(recordSize-6-size));
             }
-            
-            return size;
-        }
 
-        @Override
-        public void draw(HwmfGraphics ctx) {
-            ctx.addObjectTableEntry(this);
-        }
-        
-        @Override
-        public void applyObject(HwmfGraphics ctx) {
-            
-        }
+            // the destination rectangle, having the same dimension as the source rectangle
+            srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight());
 
-        @Override
-        public BufferedImage getImage() {
-            return (target == null) ? null : target.getImage();
+            return size;
         }
     }
 
@@ -909,53 +654,22 @@ public class HwmfFill {
          * in the playback device context, and the destination pixels are to be combined to form the
          * new image. This code MUST be one of the values in the Ternary Raster Operation Enumeration.
          */
-        private HwmfTernaryRasterOp rasterOperation;
-        /**
-         * A 16-bit signed integer that defines the height, in logical units, of the source rectangle.
-         */
-        private int srcHeight;
-        /**
-         * A 16-bit signed integer that defines the width, in logical units, of the source rectangle.
-         */
-        private int srcWidth;
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of the
-         * upper-left corner of the source rectangle.
-         */
-        private int ySrc;
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of the
-         * upper-left corner of the source rectangle.
-         */
-        private int xSrc;
-        /**
-         * A 16-bit signed integer that defines the height, in logical units, of the
-         * destination rectangle.
-         */
-        private int destHeight;
-        /**
-         * A 16-bit signed integer that defines the width, in logical units, of the
-         * destination rectangle.
-         */
-        private int destWidth;
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units,
-         * of the upper-left corner of the destination rectangle.
-         */
-        private int yDest;
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units,
-         * of the upper-left corner of the destination rectangle.
-         */
-        private int xDest;
+        protected HwmfTernaryRasterOp rasterOperation;
+
+        /** the source rectangle */
+        protected final Rectangle2D srcBounds = new Rectangle2D.Double();
+
+        /** the destination rectangle */
+        protected final Rectangle2D dstBounds = new Rectangle2D.Double();
+
         /**
          * A variable-sized DeviceIndependentBitmap Object that defines image content.
          * This object MUST be specified, even if the raster operation does not require a source.
          */
-        HwmfBitmapDib target;
-        
+        protected HwmfBitmapDib target;
+
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.dibStretchBlt;
         }
         
@@ -963,27 +677,21 @@ public class HwmfFill {
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
             boolean hasBitmap = (recordSize > ((recordFunction >> 8) + 3));
 
-            int size = 0;
             int rasterOpCode = leis.readUShort();
             int rasterOpIndex = leis.readUShort();
             
             rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex);
             assert(rasterOpCode == rasterOperation.opCode);
 
-            srcHeight = leis.readShort();
-            srcWidth = leis.readShort();
-            ySrc = leis.readShort();
-            xSrc = leis.readShort();
-            size = 6*LittleEndianConsts.SHORT_SIZE;
+            int size = 2*LittleEndianConsts.SHORT_SIZE;
+
+            size += readBounds2(leis, srcBounds);
             if (!hasBitmap) {
                 /*int reserved =*/ leis.readShort();
                 size += LittleEndianConsts.SHORT_SIZE;
             }
-            destHeight = leis.readShort();
-            destWidth = leis.readShort();
-            yDest = leis.readShort();
-            xDest = leis.readShort();
-            size += 4*LittleEndianConsts.SHORT_SIZE;
+
+            size += readBounds2(leis, dstBounds);
             if (hasBitmap) {
                 target = new HwmfBitmapDib();
                 size += target.init(leis, (int)(recordSize-6-size));
@@ -996,15 +704,30 @@ public class HwmfFill {
         public void draw(HwmfGraphics ctx) {
             ctx.addObjectTableEntry(this);
         }
-        
+
         @Override
         public void applyObject(HwmfGraphics ctx) {
-            
+
         }
 
         @Override
         public BufferedImage getImage() {
-            return target.getImage();
+            return (target == null) ? null : target.getImage();
         }
     }
+
+    static int readBounds2(LittleEndianInputStream leis, Rectangle2D bounds) {
+        /**
+         * The 16-bit signed integers that defines the corners of the bounding rectangle.
+         */
+        int h = leis.readShort();
+        int w = leis.readShort();
+        int y = leis.readShort();
+        int x = leis.readShort();
+
+        bounds.setRect(x, y, w, h);
+
+        return 4 * LittleEndianConsts.SHORT_SIZE;
+    }
+
 }
index f094ae1959a147d46dcdeca0e08c64969c748a21..932358740f84652a628afff9db23fd485596cdec 100644 (file)
@@ -24,6 +24,8 @@ import org.apache.poi.common.usermodel.fonts.FontCharset;
 import org.apache.poi.common.usermodel.fonts.FontFamily;
 import org.apache.poi.common.usermodel.fonts.FontInfo;
 import org.apache.poi.common.usermodel.fonts.FontPitch;
+import org.apache.poi.util.BitField;
+import org.apache.poi.util.BitFieldFactory;
 import org.apache.poi.util.LittleEndianConsts;
 import org.apache.poi.util.LittleEndianInputStream;
 
@@ -90,7 +92,7 @@ public class HwmfFont implements FontInfo {
             this.flag = flag;
         }
 
-        static WmfOutPrecision valueOf(int flag) {
+        public static WmfOutPrecision valueOf(int flag) {
             for (WmfOutPrecision op : values()) {
                 if (op.flag == flag) {
                     return op;
@@ -104,22 +106,17 @@ public class HwmfFont implements FontInfo {
      * ClipPrecision Flags specify clipping precision, which defines how to clip characters that are
      * partially outside a clipping region. These flags can be combined to specify multiple options.
      */
-    public enum WmfClipPrecision {
+    public static class WmfClipPrecision {
 
-        /**
-         * Specifies that default clipping MUST be used.
-         */
-        CLIP_DEFAULT_PRECIS (0x00000000),
+        /** Specifies that default clipping MUST be used. */
+        private static final BitField CLIP_DEFAULT_PRECIS = BitFieldFactory.getInstance(0x0000);
 
-        /**
-         * This value SHOULD NOT be used.
-         */
-        CLIP_CHARACTER_PRECIS (0x00000001),
 
-        /**
-         * This value MAY be returned when enumerating rasterized, TrueType and vector fonts.
-         */
-        CLIP_STROKE_PRECIS (0x00000002),
+        /** This value SHOULD NOT be used. */
+        private static final BitField CLIP_CHARACTER_PRECIS = BitFieldFactory.getInstance(0x0001);
+
+        /** This value MAY be returned when enumerating rasterized, TrueType and vector fonts. */
+        private static final BitField CLIP_STROKE_PRECIS = BitFieldFactory.getInstance(0x0002);
 
         /**
          * This value is used to control font rotation, as follows:
@@ -129,37 +126,25 @@ public class HwmfFont implements FontInfo {
          * If clear, device fonts SHOULD rotate counterclockwise, but the rotation of other fonts
          * SHOULD be determined by the orientation of the coordinate system.
          */
-        CLIP_LH_ANGLES (0x00000010),
+        private static final BitField CLIP_LH_ANGLES = BitFieldFactory.getInstance(0x0010);
 
-        /**
-         * This value SHOULD NOT be used.
-         */
-        CLIP_TT_ALWAYS (0x00000020),
+        /** This value SHOULD NOT be used. */
+        private static final BitField CLIP_TT_ALWAYS = BitFieldFactory.getInstance(0x0020);
 
-        /**
-         * This value specifies that font association SHOULD< be turned off.
-         */
-        CLIP_DFA_DISABLE (0x00000040),
+        /** This value specifies that font association SHOULD< be turned off. */
+        private static final BitField CLIP_DFA_DISABLE = BitFieldFactory.getInstance(0x0040);
 
         /**
          * This value specifies that font embedding MUST be used to render document content;
          * embedded fonts are read-only.
          */
-        CLIP_EMBEDDED (0x00000080);
-
+        private static final BitField CLIP_EMBEDDED = BitFieldFactory.getInstance(0x0080);
 
         int flag;
-        WmfClipPrecision(int flag) {
-            this.flag = flag;
-        }
 
-        static WmfClipPrecision valueOf(int flag) {
-            for (WmfClipPrecision cp : values()) {
-                if (cp.flag == flag) {
-                    return cp;
-                }
-            }
-            return null;
+        public int init(LittleEndianInputStream leis) {
+            flag = leis.readUByte();
+            return LittleEndianConsts.BYTE_SIZE;
         }
     }
 
@@ -210,7 +195,7 @@ public class HwmfFont implements FontInfo {
             this.flag = flag;
         }
 
-        static WmfFontQuality valueOf(int flag) {
+        public static WmfFontQuality valueOf(int flag) {
             for (WmfFontQuality fq : values()) {
                 if (fq.flag == flag) {
                     return fq;
@@ -240,7 +225,7 @@ public class HwmfFont implements FontInfo {
      * For all height comparisons, the font mapper SHOULD find the largest physical
      * font that does not exceed the requested size.
      */
-    int height;
+    protected int height;
 
     /**
      * A 16-bit signed integer that defines the average width, in logical units, of
@@ -248,45 +233,45 @@ public class HwmfFont implements FontInfo {
      * against the digitization aspect ratio of the available fonts to find the closest match,
      * determined by the absolute value of the difference.
      */
-    int width;
+    protected int width;
 
     /**
      * A 16-bit signed integer that defines the angle, in tenths of degrees, between the
      * escapement vector and the x-axis of the device. The escapement vector is parallel
      * to the base line of a row of text.
      */
-    int escapement;
+    protected int escapement;
 
     /**
      * A 16-bit signed integer that defines the angle, in tenths of degrees,
      * between each character's base line and the x-axis of the device.
      */
-    int orientation;
+    protected int orientation;
 
     /**
      * A 16-bit signed integer that defines the weight of the font in the range 0
      * through 1000. For example, 400 is normal and 700 is bold. If this value is 0x0000,
      * a default weight SHOULD be used.
      */
-    int weight;
+    protected int weight;
 
     /**
      * A 8-bit Boolean value that specifies the italic attribute of the font.
      * 0 = not italic / 1 = italic.
      */
-    boolean italic;
+    protected boolean italic;
 
     /**
      * An 8-bit Boolean value that specifies the underline attribute of the font.
      * 0 = not underlined / 1 = underlined
      */
-    boolean underline;
+    protected boolean underline;
 
     /**
      * An 8-bit Boolean value that specifies the strike out attribute of the font.
      * 0 = not striked out / 1 = striked out
      */
-    boolean strikeOut;
+    protected boolean strikeOut;
 
     /**
      * An 8-bit unsigned integer that defines the character set.
@@ -299,12 +284,12 @@ public class HwmfFont implements FontInfo {
      * If a typeface name in the FaceName field is specified, the CharSet value MUST match the
      * character set of that typeface.
      */
-    FontCharset charSet;
+    protected FontCharset charSet;
 
     /**
      * An 8-bit unsigned integer that defines the output precision.
      */
-    WmfOutPrecision outPrecision;
+    protected WmfOutPrecision outPrecision;
 
     /**
      * An 8-bit unsigned integer that defines the clipping precision.
@@ -312,40 +297,40 @@ public class HwmfFont implements FontInfo {
      *
      * @see WmfClipPrecision
      */
-    WmfClipPrecision clipPrecision;
+    protected final WmfClipPrecision clipPrecision = new WmfClipPrecision();
 
     /**
      * An 8-bit unsigned integer that defines the output quality.
      */
-    WmfFontQuality quality;
+    protected WmfFontQuality quality;
 
     /**
      * A PitchAndFamily object that defines the pitch and the family of the font.
      * Font families specify the look of fonts in a general way and are intended for
      * specifying fonts when the exact typeface wanted is not available.
      */
-    int pitchAndFamily;
+    protected int pitchAndFamily;
     
     /**
      * Font families specify the look of fonts in a general way and are
      * intended for specifying fonts when the exact typeface wanted is not available.
      * (LSB 4 bits)
      */
-    FontFamily family;
+    protected FontFamily family;
     
     /**
      * A property of a font that describes the pitch (MSB 2 bits)
      */
-    FontPitch pitch;
+    protected FontPitch pitch;
 
     /**
      * A null-terminated string of 8-bit Latin-1 [ISO/IEC-8859-1] ANSI
      * characters that specifies the typeface name of the font. The length of this string MUST NOT
      * exceed 32 8-bit characters, including the terminating null.
      */
-    String facename;
+    protected String facename;
 
-    public int init(LittleEndianInputStream leis) throws IOException {
+    public int init(LittleEndianInputStream leis, long recordSize) throws IOException {
         height = leis.readShort();
         width = leis.readShort();
         escapement = leis.readShort();
@@ -356,21 +341,17 @@ public class HwmfFont implements FontInfo {
         strikeOut = leis.readByte() != 0;
         charSet = FontCharset.valueOf(leis.readUByte());
         outPrecision = WmfOutPrecision.valueOf(leis.readUByte());
-        clipPrecision = WmfClipPrecision.valueOf(leis.readUByte());
+        clipPrecision.init(leis);
         quality = WmfFontQuality.valueOf(leis.readUByte());
         pitchAndFamily = leis.readUByte();
-        
-        byte buf[] = new byte[32], b, readBytes = 0;
-        do {
-            if (readBytes == 32) {
-                throw new IOException("Font facename can't be determined.");
-            }
 
-            buf[readBytes++] = b = leis.readByte();
-        } while (b != 0 && b != -1 && readBytes <= 32);
-        
-        facename = new String(buf, 0, readBytes-1, StandardCharsets.ISO_8859_1);
-        
+        StringBuilder sb = new StringBuilder();
+        int readBytes = readString(leis, sb, 32);
+        if (readBytes == -1) {
+            throw new IOException("Font facename can't be determined.");
+        }
+        facename = sb.toString();
+
         return 5*LittleEndianConsts.SHORT_SIZE+8*LittleEndianConsts.BYTE_SIZE+readBytes;
     }
 
@@ -471,4 +452,19 @@ public class HwmfFont implements FontInfo {
     public void setCharset(FontCharset charset) {
         throw new UnsupportedOperationException("setCharset not supported by HwmfFont.");
     }
+
+    protected int readString(LittleEndianInputStream leis, StringBuilder sb, int limit) throws IOException {
+        byte buf[] = new byte[limit], b, readBytes = 0;
+        do {
+            if (readBytes == limit) {
+                return -1;
+            }
+
+            buf[readBytes++] = b = leis.readByte();
+        } while (b != 0 && b != -1 && readBytes <= limit);
+
+        sb.append(new String(buf, 0, readBytes-1, StandardCharsets.ISO_8859_1));
+
+        return readBytes;
+    }
 }
index 02f896849aa92d90d0a85b5559d645ef3e2d61e3..e364d83b3b729d8f5fcea004da010af6d59aac3d 100644 (file)
@@ -39,7 +39,7 @@ public enum HwmfHatchStyle {
         this.flag = flag;
     }
 
-    static HwmfHatchStyle valueOf(int flag) {
+    public static HwmfHatchStyle valueOf(int flag) {
         for (HwmfHatchStyle hs : values()) {
             if (hs.flag == flag) return hs;
         }
index f4f9a65c27ef18ba3eb4a4b0e6fdca8d6e8f0b3f..2e09606bddad57cacba5d87a4ca5f45f5f6dd237 100644 (file)
@@ -105,7 +105,7 @@ public enum HwmfMapMode {
         this.scale = scale;
     }
 
-    static HwmfMapMode valueOf(int flag) {
+    public static HwmfMapMode valueOf(int flag) {
         for (HwmfMapMode mm : values()) {
             if (mm.flag == flag) return mm;
         }
index f5ab077d222230a0b6cb57b22f3591956130a548..631383d6e400c07fe2e50b24b0f47790491f0280 100644 (file)
@@ -17,6 +17,7 @@
 
 package org.apache.poi.hwmf.record;
 
+import java.awt.geom.Dimension2D;
 import java.awt.image.BufferedImage;
 import java.io.IOException;
 
@@ -24,6 +25,7 @@ import org.apache.poi.hwmf.draw.HwmfDrawProperties;
 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.util.Dimension2DDouble;
 import org.apache.poi.util.LittleEndianConsts;
 import org.apache.poi.util.LittleEndianInputStream;
 
@@ -34,7 +36,7 @@ public class HwmfMisc {
      */
     public static class WmfSaveDc implements HwmfRecord {
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.saveDc;
         }
 
@@ -53,7 +55,7 @@ public class HwmfMisc {
      * The META_SETRELABS record is reserved and not supported.
      */
     public static class WmfSetRelabs implements HwmfRecord {
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.setRelabs;
         }
 
@@ -81,7 +83,7 @@ public class HwmfMisc {
         private int nSavedDC;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.restoreDc;
         }
 
@@ -106,7 +108,7 @@ public class HwmfMisc {
         private HwmfColorRef colorRef;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.setBkColor;
         }
 
@@ -140,7 +142,7 @@ public class HwmfMisc {
                 this.flag = flag;
             }
 
-            static HwmfBkMode valueOf(int flag) {
+            public static HwmfBkMode valueOf(int flag) {
                 for (HwmfBkMode bs : values()) {
                     if (bs.flag == flag) return bs;
                 }
@@ -148,9 +150,9 @@ public class HwmfMisc {
             }
         }
 
-        private HwmfBkMode bkMode;
+        protected HwmfBkMode bkMode;
 
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.setBkMode;
         }
 
@@ -180,7 +182,7 @@ public class HwmfMisc {
         private int layout;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.setLayout;
         }
 
@@ -205,10 +207,10 @@ public class HwmfMisc {
      */
     public static class WmfSetMapMode implements HwmfRecord {
 
-        private HwmfMapMode mapMode;
+        protected HwmfMapMode mapMode;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.setMapMode;
         }
 
@@ -234,12 +236,13 @@ public class HwmfMisc {
         /**
          * A 32-bit unsigned integer that defines whether the font mapper should attempt to
          * match a font's aspect ratio to the current device's aspect ratio. If bit 0 is
-         * set, the mapper selects only matching fonts.
+         * set, the font mapper SHOULD select only fonts that match the aspect ratio of the
+         * output device, as it is currently defined in the playback device context.
          */
         private long mapperValues;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.setMapperFlags;
         }
 
@@ -262,14 +265,11 @@ public class HwmfMisc {
      */
     public static class WmfSetRop2 implements HwmfRecord {
 
-        /**
-         * A 16-bit unsigned integer that defines the foreground binary raster
-         * operation mixing mode
-         */
-        private HwmfBinaryRasterOp drawMode;
+        /** An unsigned integer that defines the foreground binary raster operation mixing mode */
+        protected HwmfBinaryRasterOp drawMode;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.setRop2;
         }
 
@@ -291,24 +291,63 @@ public class HwmfMisc {
      */
     public static class WmfSetStretchBltMode implements HwmfRecord {
 
-        /**
-         * A 16-bit unsigned integer that defines bitmap stretching mode.
-         * This MUST be one of the values:
-         * BLACKONWHITE = 0x0001,
-         * WHITEONBLACK = 0x0002,
-         * COLORONCOLOR = 0x0003,
-         * HALFTONE = 0x0004
-         */
-        private int setStretchBltMode;
+        public enum StretchBltMode {
+            /**
+             * Performs a Boolean AND operation by using the color values for the eliminated and existing pixels.
+             * If the bitmap is a monochrome bitmap, this mode preserves black pixels at the expense of white pixels.
+             *
+             * EMF name: STRETCH_ANDSCANS
+             */
+            BLACKONWHITE(0x0001),
+            /**
+             * Performs a Boolean OR operation by using the color values for the eliminated and existing pixels.
+             * If the bitmap is a monochrome bitmap, this mode preserves white pixels at the expense of black pixels.
+             *
+             * EMF name: STRETCH_ORSCANS
+             */
+            WHITEONBLACK(0x0002),
+            /**
+             * Deletes the pixels. This mode deletes all eliminated lines of pixels without trying
+             * to preserve their information.
+             *
+             * EMF name: STRETCH_DELETESCANS
+             */
+            COLORONCOLOR(0x0003),
+            /**
+             * Maps pixels from the source rectangle into blocks of pixels in the destination rectangle.
+             * The average color over the destination block of pixels approximates the color of the source
+             * pixels.
+             *
+             * After setting the HALFTONE stretching mode, the brush origin MUST be set to avoid misalignment
+             * artifacts - in EMF this is done via EmfSetBrushOrgEx
+             *
+             * EMF name: STRETCH_HALFTONE
+             */
+            HALFTONE(0x0004);
+
+            public final int flag;
+            StretchBltMode(int flag) {
+                this.flag = flag;
+            }
+
+            public static StretchBltMode valueOf(int flag) {
+                for (StretchBltMode bs : values()) {
+                    if (bs.flag == flag) return bs;
+                }
+                return null;
+            }
+        }
+
+        protected StretchBltMode stretchBltMode;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.setStretchBltMode;
         }
 
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
-            setStretchBltMode = leis.readUShort();
+            stretchBltMode = StretchBltMode.valueOf(leis.readUShort());
             return LittleEndianConsts.SHORT_SIZE;
         }
 
@@ -341,7 +380,7 @@ public class HwmfMisc {
         private HwmfBitmap16 pattern16;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.dibCreatePatternBrush;
         }
 
@@ -403,10 +442,10 @@ public class HwmfMisc {
          * A 16-bit unsigned integer used to index into the WMF Object Table to
          * get the object to be deleted.
          */
-        private int objectIndex;
+        protected int objectIndex;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.deleteObject;
         }
 
@@ -418,6 +457,12 @@ public class HwmfMisc {
 
         @Override
         public void draw(HwmfGraphics ctx) {
+            /* TODO:
+             * The object specified by this record MUST be deleted from the EMF Object Table.
+             * If the deleted object is currently selected in the playback device context,
+             * the default object for that graphics property MUST be restored.
+             */
+
             ctx.unsetObjectTableEntry(objectIndex);
         }
     }
@@ -427,7 +472,7 @@ public class HwmfMisc {
         private HwmfBitmap16 pattern;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.createPatternBrush;
         }
 
@@ -452,30 +497,28 @@ public class HwmfMisc {
 
     public static class WmfCreatePenIndirect implements HwmfRecord, HwmfObjectTableEntry {
 
-        private HwmfPenStyle penStyle;
-        /**
-         * A 32-bit PointS Object that specifies a point for the object dimensions.
-         * The x-coordinate is the pen width. The y-coordinate is ignored.
-         */
-        private int xWidth;
-        @SuppressWarnings("unused")
-        private int yWidth;
+        protected HwmfPenStyle penStyle;
+
+        protected final Dimension2D dimension = new Dimension2DDouble();
         /**
          * A 32-bit ColorRef Object that specifies the pen color value.
          */
-        private HwmfColorRef colorRef;
+        protected final HwmfColorRef colorRef = new HwmfColorRef();
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.createPenIndirect;
         }
 
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
             penStyle = HwmfPenStyle.valueOf(leis.readUShort());
-            xWidth = leis.readShort();
-            yWidth = leis.readShort();
-            colorRef = new HwmfColorRef();
+            // A 32-bit PointS Object that specifies a point for the object dimensions.
+            // The x-coordinate is the pen width. The y-coordinate is ignored.
+            int xWidth = leis.readShort();
+            int yWidth = leis.readShort();
+            dimension.setSize(xWidth, yWidth);
+
             int size = colorRef.init(leis);
             return size+3*LittleEndianConsts.SHORT_SIZE;
         }
@@ -490,7 +533,7 @@ public class HwmfMisc {
             HwmfDrawProperties p = ctx.getProperties();
             p.setPenStyle(penStyle);
             p.setPenColor(colorRef);
-            p.setPenWidth(xWidth);
+            p.setPenWidth(dimension.getWidth());
         }
     }
 
@@ -540,19 +583,14 @@ public class HwmfMisc {
      * </table>
      */
     public static class WmfCreateBrushIndirect implements HwmfRecord, HwmfObjectTableEntry {
-        private HwmfBrushStyle brushStyle;
+        protected HwmfBrushStyle brushStyle;
 
-        private HwmfColorRef colorRef;
+        protected HwmfColorRef colorRef;
 
-        /**
-         * A 16-bit field that specifies the brush hatch type.
-         * Its interpretation depends on the value of BrushStyle.
-         *
-         */
-        private HwmfHatchStyle brushHatch;
+        protected HwmfHatchStyle brushHatch;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.createBrushIndirect;
         }
 
index cb80c454eefa9db1dcd9281e844fc13192601962..cef75f698d610a92f1bc59c5ac2247d203fae9f7 100644 (file)
@@ -39,12 +39,12 @@ public class HwmfPalette {
         private int values;
         private Color colorRef;
 
-        private PaletteEntry() {
+        public PaletteEntry() {
             this.values = PC_RESERVED.set(0);
             this.colorRef = Color.BLACK;
         }
 
-        private PaletteEntry(PaletteEntry other) {
+        public PaletteEntry(PaletteEntry other) {
             this.values = other.values;
             this.colorRef = other.colorRef;
         }
@@ -100,19 +100,24 @@ public class HwmfPalette {
          * used with the META_SETPALENTRIES and META_ANIMATEPALETTE record types.
          * When used with META_CREATEPALETTE, it MUST be 0x0300
          */
-        private int start;
+        protected int start;
 
-        private List<PaletteEntry> palette = new ArrayList<>();
+        protected final List<PaletteEntry> palette = new ArrayList<>();
 
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
             start = leis.readUShort();
+            int size = readPaletteEntries(leis, -1);
+            return size + LittleEndianConsts.SHORT_SIZE;
+        }
+
+        protected int readPaletteEntries(LittleEndianInputStream leis, int nbrOfEntries) throws IOException {
             /**
              * NumberOfEntries (2 bytes):  A 16-bit unsigned integer that defines the number of objects in
              * aPaletteEntries.
              */
-            int numberOfEntries = leis.readUShort();
-            int size = 2*LittleEndianConsts.SHORT_SIZE;
+            final int numberOfEntries = (nbrOfEntries > -1) ? nbrOfEntries : leis.readUShort();
+            int size = (nbrOfEntries > -1) ? 0 : LittleEndianConsts.SHORT_SIZE;
             for (int i=0; i<numberOfEntries; i++) {
                 PaletteEntry pe = new PaletteEntry();
                 size += pe.init(leis);
@@ -144,7 +149,7 @@ public class HwmfPalette {
      */
     public static class WmfCreatePalette extends WmfPaletteParent implements HwmfObjectTableEntry {
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.createPalette;
         }
 
@@ -160,7 +165,7 @@ public class HwmfPalette {
      */
     public static class WmfSetPaletteEntries extends WmfPaletteParent {
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.setPalEntries;
         }
 
@@ -197,10 +202,10 @@ public class HwmfPalette {
          * A 16-bit unsigned integer that defines the number of entries in
          * the logical palette.
          */
-        int numberOfEntries;
+        protected int numberOfEntries;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.resizePalette;
         }
 
@@ -238,10 +243,10 @@ public class HwmfPalette {
          * A 16-bit unsigned integer used to index into the WMF Object Table to get
          * the Palette Object to be selected.
          */
-        private int paletteIndex;
+        protected int paletteIndex;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.selectPalette;
         }
 
@@ -263,7 +268,7 @@ public class HwmfPalette {
      */
     public static class WmfRealizePalette implements HwmfRecord {
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.realizePalette;
         }
 
@@ -292,7 +297,7 @@ public class HwmfPalette {
      */
     public static class WmfAnimatePalette extends WmfPaletteParent {
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.animatePalette;
         }
 
index d432b63216e1fd2feebaf35d6ef34b135445209b..0f79a03d9a54a731eafd6ee832b15e262f9f5dd8 100644 (file)
@@ -136,11 +136,12 @@ public class HwmfPenStyle implements Cloneable {
         }    
     }
     
-    private static final BitField SUBSECTION_DASH      = BitFieldFactory.getInstance(0x0007);
-    private static final BitField SUBSECTION_ALTERNATE = BitFieldFactory.getInstance(0x0008);
-    private static final BitField SUBSECTION_ENDCAP    = BitFieldFactory.getInstance(0x0300);
-    private static final BitField SUBSECTION_JOIN      = BitFieldFactory.getInstance(0x3000);
-    
+    private static final BitField SUBSECTION_DASH      = BitFieldFactory.getInstance(0x00007);
+    private static final BitField SUBSECTION_ALTERNATE = BitFieldFactory.getInstance(0x00008);
+    private static final BitField SUBSECTION_ENDCAP    = BitFieldFactory.getInstance(0x00300);
+    private static final BitField SUBSECTION_JOIN      = BitFieldFactory.getInstance(0x03000);
+    private static final BitField SUBSECTION_GEOMETRIC = BitFieldFactory.getInstance(0x10000);
+
     private int flag;
     
     public static HwmfPenStyle valueOf(int flag) {
@@ -169,6 +170,14 @@ public class HwmfPenStyle implements Cloneable {
         return SUBSECTION_ALTERNATE.isSet(flag);
     }
 
+    /**
+     * A pen type that specifies a line with a width that is measured in logical units
+     * and a style that can contain any of the attributes of a brush.
+     */
+    public boolean isGeometric()  {
+        return SUBSECTION_GEOMETRIC.isSet(flag);
+    }
+
 
     /**
      * Creates a new object of the same class and with the
index 6c7680f07753c6e419975bd083eb6c8491de0857..2673e8078335d3acb769f5f56526dce901381f54 100644 (file)
@@ -23,7 +23,7 @@ import org.apache.poi.hwmf.draw.HwmfGraphics;
 import org.apache.poi.util.LittleEndianInputStream;
 
 public interface HwmfRecord {
-    HwmfRecordType getRecordType();
+    HwmfRecordType getWmfRecordType();
 
     /**
      * Init record from stream
index 5d903e45a6e45675fe19cb378d2e331295db98b2..297808debde144e8355c1c1729f184dab95155a3 100644 (file)
@@ -17,6 +17,8 @@
 
 package org.apache.poi.hwmf.record;
 
+import java.util.function.Supplier;
+
 /**
  * Available record types for WMF
  * 
@@ -24,83 +26,83 @@ package org.apache.poi.hwmf.record;
  */
 public enum HwmfRecordType {
      eof(0x0000, null)
-    ,animatePalette(0x0436, HwmfPalette.WmfAnimatePalette.class)
-    ,arc(0x0817, HwmfDraw.WmfArc.class)
-    ,bitBlt(0x0922, HwmfFill.WmfBitBlt.class)
-    ,chord(0x0830, HwmfDraw.WmfChord.class)
-    ,createBrushIndirect(0x02fc, HwmfMisc.WmfCreateBrushIndirect.class)
-    ,createFontIndirect(0x02fb, HwmfText.WmfCreateFontIndirect.class)
-    ,createPalette(0x00f7, HwmfPalette.WmfCreatePalette.class)
-    ,createPatternBrush(0x01f9, HwmfMisc.WmfCreatePatternBrush.class)
-    ,createPenIndirect(0x02fa, HwmfMisc.WmfCreatePenIndirect.class)
-    ,createRegion(0x06ff, HwmfWindowing.WmfCreateRegion.class)
-    ,deleteObject(0x01f0, HwmfMisc.WmfDeleteObject.class)
-    ,dibBitBlt(0x0940, HwmfFill.WmfDibBitBlt.class)
-    ,dibCreatePatternBrush(0x0142, HwmfMisc.WmfDibCreatePatternBrush.class)
-    ,dibStretchBlt(0x0b41, HwmfFill.WmfDibStretchBlt.class)
-    ,ellipse(0x0418, HwmfDraw.WmfEllipse.class)
-    ,escape(0x0626, HwmfEscape.class)
-    ,excludeClipRect(0x0415, HwmfWindowing.WmfExcludeClipRect.class)
-    ,extFloodFill(0x0548, HwmfFill.WmfExtFloodFill.class)
-    ,extTextOut(0x0a32, HwmfText.WmfExtTextOut.class)
-    ,fillRegion(0x0228, HwmfFill.WmfFillRegion.class)
-    ,floodFill(0x0419, HwmfFill.WmfFloodFill.class)
-    ,frameRegion(0x0429, HwmfDraw.WmfFrameRegion.class)
-    ,intersectClipRect(0x0416, HwmfWindowing.WmfIntersectClipRect.class)
-    ,invertRegion(0x012a, HwmfFill.WmfInvertRegion.class)
-    ,lineTo(0x0213, HwmfDraw.WmfLineTo.class)
-    ,moveTo(0x0214, HwmfDraw.WmfMoveTo.class)
-    ,offsetClipRgn(0x0220, HwmfWindowing.WmfOffsetClipRgn.class)
-    ,offsetViewportOrg(0x0211, HwmfWindowing.WmfOffsetViewportOrg.class)
-    ,offsetWindowOrg(0x020f, HwmfWindowing.WmfOffsetWindowOrg.class)
-    ,paintRegion(0x012b, HwmfFill.WmfPaintRegion.class)
-    ,patBlt(0x061d, HwmfFill.WmfPatBlt.class)
-    ,pie(0x081a, HwmfDraw.WmfPie.class)
-    ,polygon(0x0324, HwmfDraw.WmfPolygon.class)
-    ,polyline(0x0325, HwmfDraw.WmfPolyline.class)
-    ,polyPolygon(0x0538, HwmfDraw.WmfPolyPolygon.class)
-    ,realizePalette(0x0035, HwmfPalette.WmfRealizePalette.class)
-    ,rectangle(0x041b, HwmfDraw.WmfRectangle.class)
-    ,resizePalette(0x0139, HwmfPalette.WmfResizePalette.class)
-    ,restoreDc(0x0127, HwmfMisc.WmfRestoreDc.class)
-    ,roundRect(0x061c, HwmfDraw.WmfRoundRect.class)
-    ,saveDc(0x001e, HwmfMisc.WmfSaveDc.class)
-    ,scaleViewportExt(0x0412, HwmfWindowing.WmfScaleViewportExt.class) 
-    ,scaleWindowExt(0x0410, HwmfWindowing.WmfScaleWindowExt.class)
-    ,selectClipRegion(0x012c, HwmfWindowing.WmfSelectClipRegion.class)
-    ,selectObject(0x012d, HwmfDraw.WmfSelectObject.class)
-    ,selectPalette(0x0234, HwmfPalette.WmfSelectPalette.class)
-    ,setBkColor(0x0201, HwmfMisc.WmfSetBkColor.class)
-    ,setBkMode(0x0102, HwmfMisc.WmfSetBkMode.class)
-    ,setDibToDev(0x0d33, HwmfFill.WmfSetDibToDev.class)
-    ,setLayout(0x0149, HwmfMisc.WmfSetLayout.class)
-    ,setMapMode(0x0103, HwmfMisc.WmfSetMapMode.class)
-    ,setMapperFlags(0x0231, HwmfMisc.WmfSetMapperFlags.class)
-    ,setPalEntries(0x0037, HwmfPalette.WmfSetPaletteEntries.class)
-    ,setPixel(0x041f, HwmfDraw.WmfSetPixel.class)
-    ,setPolyFillMode(0x0106, HwmfFill.WmfSetPolyfillMode.class)
-    ,setRelabs(0x0105, HwmfMisc.WmfSetRelabs.class)
-    ,setRop2(0x0104, HwmfMisc.WmfSetRop2.class)
-    ,setStretchBltMode(0x0107, HwmfMisc.WmfSetStretchBltMode.class)
-    ,setTextAlign(0x012e, HwmfText.WmfSetTextAlign.class)
-    ,setTextCharExtra(0x0108, HwmfText.WmfSetTextCharExtra.class)
-    ,setTextColor(0x0209, HwmfText.WmfSetTextColor.class)
-    ,setTextJustification(0x020a, HwmfText.WmfSetTextJustification.class)
-    ,setViewportExt(0x020e, HwmfWindowing.WmfSetViewportExt.class)
-    ,setViewportOrg(0x020d, HwmfWindowing.WmfSetViewportOrg.class)
-    ,setWindowExt(0x020c, HwmfWindowing.WmfSetWindowExt.class)
-    ,setWindowOrg(0x020b, HwmfWindowing.WmfSetWindowOrg.class)
-    ,stretchBlt(0x0b23, HwmfFill.WmfStretchBlt.class)
-    ,stretchDib(0x0f43, HwmfFill.WmfStretchDib.class)
-    ,textOut(0x0521, HwmfText.WmfTextOut.class)
+    ,animatePalette(0x0436, HwmfPalette.WmfAnimatePalette::new)
+    ,arc(0x0817, HwmfDraw.WmfArc::new)
+    ,bitBlt(0x0922, HwmfFill.WmfBitBlt::new)
+    ,chord(0x0830, HwmfDraw.WmfChord::new)
+    ,createBrushIndirect(0x02fc, HwmfMisc.WmfCreateBrushIndirect::new)
+    ,createFontIndirect(0x02fb, HwmfText.WmfCreateFontIndirect::new)
+    ,createPalette(0x00f7, HwmfPalette.WmfCreatePalette::new)
+    ,createPatternBrush(0x01f9, HwmfMisc.WmfCreatePatternBrush::new)
+    ,createPenIndirect(0x02fa, HwmfMisc.WmfCreatePenIndirect::new)
+    ,createRegion(0x06ff, HwmfWindowing.WmfCreateRegion::new)
+    ,deleteObject(0x01f0, HwmfMisc.WmfDeleteObject::new)
+    ,dibBitBlt(0x0940, HwmfFill.WmfDibBitBlt::new)
+    ,dibCreatePatternBrush(0x0142, HwmfMisc.WmfDibCreatePatternBrush::new)
+    ,dibStretchBlt(0x0b41, HwmfFill.WmfDibStretchBlt::new)
+    ,ellipse(0x0418, HwmfDraw.WmfEllipse::new)
+    ,escape(0x0626, HwmfEscape::new)
+    ,excludeClipRect(0x0415, HwmfWindowing.WmfExcludeClipRect::new)
+    ,extFloodFill(0x0548, HwmfFill.WmfExtFloodFill::new)
+    ,extTextOut(0x0a32, HwmfText.WmfExtTextOut::new)
+    ,fillRegion(0x0228, HwmfFill.WmfFillRegion::new)
+    ,floodFill(0x0419, HwmfFill.WmfFloodFill::new)
+    ,frameRegion(0x0429, HwmfDraw.WmfFrameRegion::new)
+    ,intersectClipRect(0x0416, HwmfWindowing.WmfIntersectClipRect::new)
+    ,invertRegion(0x012a, HwmfFill.WmfInvertRegion::new)
+    ,lineTo(0x0213, HwmfDraw.WmfLineTo::new)
+    ,moveTo(0x0214, HwmfDraw.WmfMoveTo::new)
+    ,offsetClipRgn(0x0220, HwmfWindowing.WmfOffsetClipRgn::new)
+    ,offsetViewportOrg(0x0211, HwmfWindowing.WmfOffsetViewportOrg::new)
+    ,offsetWindowOrg(0x020f, HwmfWindowing.WmfOffsetWindowOrg::new)
+    ,paintRegion(0x012b, HwmfFill.WmfPaintRegion::new)
+    ,patBlt(0x061d, HwmfFill.WmfPatBlt::new)
+    ,pie(0x081a, HwmfDraw.WmfPie::new)
+    ,polygon(0x0324, HwmfDraw.WmfPolygon::new)
+    ,polyline(0x0325, HwmfDraw.WmfPolyline::new)
+    ,polyPolygon(0x0538, HwmfDraw.WmfPolyPolygon::new)
+    ,realizePalette(0x0035, HwmfPalette.WmfRealizePalette::new)
+    ,rectangle(0x041b, HwmfDraw.WmfRectangle::new)
+    ,resizePalette(0x0139, HwmfPalette.WmfResizePalette::new)
+    ,restoreDc(0x0127, HwmfMisc.WmfRestoreDc::new)
+    ,roundRect(0x061c, HwmfDraw.WmfRoundRect::new)
+    ,saveDc(0x001e, HwmfMisc.WmfSaveDc::new)
+    ,scaleViewportExt(0x0412, HwmfWindowing.WmfScaleViewportExt::new)
+    ,scaleWindowExt(0x0410, HwmfWindowing.WmfScaleWindowExt::new)
+    ,selectClipRegion(0x012c, HwmfWindowing.WmfSelectClipRegion::new)
+    ,selectObject(0x012d, HwmfDraw.WmfSelectObject::new)
+    ,selectPalette(0x0234, HwmfPalette.WmfSelectPalette::new)
+    ,setBkColor(0x0201, HwmfMisc.WmfSetBkColor::new)
+    ,setBkMode(0x0102, HwmfMisc.WmfSetBkMode::new)
+    ,setDibToDev(0x0d33, HwmfFill.WmfSetDibToDev::new)
+    ,setLayout(0x0149, HwmfMisc.WmfSetLayout::new)
+    ,setMapMode(0x0103, HwmfMisc.WmfSetMapMode::new)
+    ,setMapperFlags(0x0231, HwmfMisc.WmfSetMapperFlags::new)
+    ,setPalEntries(0x0037, HwmfPalette.WmfSetPaletteEntries::new)
+    ,setPixel(0x041f, HwmfDraw.WmfSetPixel::new)
+    ,setPolyFillMode(0x0106, HwmfFill.WmfSetPolyfillMode::new)
+    ,setRelabs(0x0105, HwmfMisc.WmfSetRelabs::new)
+    ,setRop2(0x0104, HwmfMisc.WmfSetRop2::new)
+    ,setStretchBltMode(0x0107, HwmfMisc.WmfSetStretchBltMode::new)
+    ,setTextAlign(0x012e, HwmfText.WmfSetTextAlign::new)
+    ,setTextCharExtra(0x0108, HwmfText.WmfSetTextCharExtra::new)
+    ,setTextColor(0x0209, HwmfText.WmfSetTextColor::new)
+    ,setTextJustification(0x020a, HwmfText.WmfSetTextJustification::new)
+    ,setViewportExt(0x020e, HwmfWindowing.WmfSetViewportExt::new)
+    ,setViewportOrg(0x020d, HwmfWindowing.WmfSetViewportOrg::new)
+    ,setWindowExt(0x020c, HwmfWindowing.WmfSetWindowExt::new)
+    ,setWindowOrg(0x020b, HwmfWindowing.WmfSetWindowOrg::new)
+    ,stretchBlt(0x0b23, HwmfFill.WmfStretchBlt::new)
+    ,stretchDib(0x0f43, HwmfFill.WmfStretchDib::new)
+    ,textOut(0x0521, HwmfText.WmfTextOut::new)
     ;
     
     public final int id;
-    public final Class<? extends HwmfRecord> clazz;
+    public final Supplier<? extends HwmfRecord> constructor;
     
-    HwmfRecordType(int id, Class<? extends HwmfRecord> clazz) {
+    HwmfRecordType(int id, Supplier<? extends HwmfRecord> constructor) {
         this.id = id;
-        this.clazz = clazz;
+        this.constructor = constructor;
     }
     
     public static HwmfRecordType getById(int id) {
index 9c6ed1e7f4af581557475488a549e7abaf4f043d..787f444beb7dc954471a2f5946887832f9e11293 100644 (file)
 
 package org.apache.poi.hwmf.record;
 
+import static org.apache.poi.hwmf.record.HwmfDraw.readPointS;
+import static org.apache.poi.hwmf.record.HwmfDraw.readRectS;
+
+import java.awt.geom.Point2D;
 import java.awt.geom.Rectangle2D;
+import java.io.ByteArrayInputStream;
+import java.io.EOFException;
 import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
 import java.nio.charset.Charset;
 
 import org.apache.poi.hwmf.draw.HwmfDrawProperties;
@@ -31,6 +39,7 @@ import org.apache.poi.util.LittleEndianConsts;
 import org.apache.poi.util.LittleEndianInputStream;
 import org.apache.poi.util.POILogFactory;
 import org.apache.poi.util.POILogger;
+import org.apache.poi.util.RecordFormatException;
 
 public class HwmfText {
     private static final POILogger logger = POILogFactory.getLogger(HwmfText.class);
@@ -52,7 +61,7 @@ public class HwmfText {
         private int charExtra;
         
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.setTextCharExtra;
         }
         
@@ -76,7 +85,7 @@ public class HwmfText {
         private HwmfColorRef colorRef;
         
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.setTextColor;
         }
         
@@ -112,7 +121,7 @@ public class HwmfText {
         private int breakExtra;
         
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.setBkColor;
         }
         
@@ -159,7 +168,7 @@ public class HwmfText {
         private int xStart;  
         
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.textOut;
         }
         
@@ -195,40 +204,47 @@ public class HwmfText {
             return ret;
         }
     }
-    
-    /**
-     * The META_EXTTEXTOUT record outputs text by using the font, background color, and text color that
-     * are defined in the playback device context. Optionally, dimensions can be provided for clipping,
-     * opaquing, or both.
-     */
-    public static class WmfExtTextOut implements HwmfRecord {
 
+    public static class WmfExtTextOutOptions {
         /**
-         * Indicates that the background color that is defined in the playback device context 
+         * Indicates that the background color that is defined in the playback device context
          * SHOULD be used to fill the rectangle.
-         */ 
+         */
         private static final BitField ETO_OPAQUE = BitFieldFactory.getInstance(0x0002);
-        
+
         /**
          * Indicates that the text SHOULD be clipped to the rectangle.
          */
         private static final BitField ETO_CLIPPED = BitFieldFactory.getInstance(0x0004);
 
         /**
-         * Indicates that the string to be output SHOULD NOT require further processing 
-         * with respect to the placement of the characters, and an array of character 
-         * placement values SHOULD be provided. This character placement process is 
+         * Indicates that the string to be output SHOULD NOT require further processing
+         * with respect to the placement of the characters, and an array of character
+         * placement values SHOULD be provided. This character placement process is
          * useful for fonts in which diacritical characters affect character spacing.
          */
         private static final BitField ETO_GLYPH_INDEX = BitFieldFactory.getInstance(0x0010);
 
         /**
-         * Indicates that the text MUST be laid out in right-to-left reading order, instead of 
-         * the default left-to-right order. This SHOULD be applied only when the font that is 
+         * Indicates that the text MUST be laid out in right-to-left reading order, instead of
+         * the default left-to-right order. This SHOULD be applied only when the font that is
          * defined in the playback device context is either Hebrew or Arabic.
          */
         private static final BitField ETO_RTLREADING = BitFieldFactory.getInstance(0x0080);
 
+        /**
+         * This bit indicates that the record does not specify a bounding rectangle for the
+         * text output.
+         */
+        private static final BitField ETO_NO_RECT = BitFieldFactory.getInstance(0x0100);
+
+        /**
+         * This bit indicates that the codes for characters in an output text string are 8 bits,
+         * derived from the low bytes of 16-bit Unicode UTF16-LE character codes, in which
+         * the high byte is assumed to be 0.
+         */
+        private static final BitField ETO_SMALL_CHARS = BitFieldFactory.getInstance(0x0200);
+
         /**
          * Indicates that to display numbers, digits appropriate to the locale SHOULD be used.
          */
@@ -240,32 +256,58 @@ public class HwmfText {
         private static final BitField ETO_NUMERICSLATIN = BitFieldFactory.getInstance(0x0800);
 
         /**
-         * Indicates that both horizontal and vertical character displacement values 
+         * This bit indicates that no special operating system processing for glyph placement
+         * should be performed on right-to-left strings; that is, all glyph positioning
+         * SHOULD be taken care of by drawing and state records in the metafile
+         */
+        private static final BitField ETO_IGNORELANGUAGE = BitFieldFactory.getInstance(0x1000);
+
+        /**
+         * Indicates that both horizontal and vertical character displacement values
          * SHOULD be provided.
          */
         private static final BitField ETO_PDY = BitFieldFactory.getInstance(0x2000);
 
+        /** This bit is reserved and SHOULD NOT be used. */
+        private static final BitField ETO_REVERSE_INDEX_MAP = BitFieldFactory.getInstance(0x10000);
+
+        protected int flag;
+
+        public int init(LittleEndianInputStream leis) {
+            flag = leis.readUShort();
+            return LittleEndianConsts.SHORT_SIZE;
+        }
+
+        public boolean isOpaque() {
+            return ETO_OPAQUE.isSet(flag);
+        }
+
+        public boolean isClipped() {
+            return ETO_CLIPPED.isSet(flag);
+        }
+    }
+
+    /**
+     * The META_EXTTEXTOUT record outputs text by using the font, background color, and text color that
+     * are defined in the playback device context. Optionally, dimensions can be provided for clipping,
+     * opaquing, or both.
+     */
+    public static class WmfExtTextOut implements HwmfRecord {
         /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, where the 
-        text string is to be located.
+         * The location, in logical units, where the text string is to be placed.
          */
-        private int y;  
+        protected final Point2D reference = new Point2D.Double();
+
         /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, where the 
-        text string is to be located.
+         * A 16-bit signed integer that defines the length of the string.
          */
-        private int x;  
+        protected int stringLength;
         /**
-         * A 16-bit signed integer that defines the length of the string.
+         * A 16-bit unsigned integer that defines the use of the application-defined
+         * rectangle. This member can be a combination of one or more values in the
+         * ExtTextOutOptions Flags (ETO_*)
          */
-        private int stringLength;
-
-         /**
-          * A 16-bit unsigned integer that defines the use of the application-defined 
-          * rectangle. This member can be a combination of one or more values in the 
-          * ExtTextOutOptions Flags (ETO_*)
-          */
-        private int fwOpts;
+        protected final WmfExtTextOutOptions options;
         /**
          * An optional 8-byte Rect Object (section 2.2.2.18) that defines the 
          * dimensions, in logical coordinates, of a rectangle that is used for clipping, opaquing, or both.
@@ -274,14 +316,14 @@ public class HwmfText {
          * Each value is a 16-bit signed integer that defines the coordinate, in logical coordinates, of 
          * the upper-left corner of the rectangle
          */
-        private int left,top,right,bottom;
+        protected final Rectangle2D bounds = new Rectangle2D.Double();
         /**
          * A variable-length string that specifies the text to be drawn. The string does 
          * not need to be null-terminated, because StringLength specifies the length of the string. If 
          * the length is odd, an extra byte is placed after it so that the following member (optional Dx) is 
          * aligned on a 16-bit boundary.
          */
-        private byte[] rawTextBytes;
+        protected byte[] rawTextBytes;
         /**
          * An optional array of 16-bit signed integers that indicate the distance between 
          * origins of adjacent character cells. For example, Dx[i] logical units separate the origins of 
@@ -289,9 +331,17 @@ public class HwmfText {
          * number of values as there are characters in the string.
          */
         private int dx[];
-        
+
+        public WmfExtTextOut() {
+            this(new WmfExtTextOutOptions());
+        }
+
+        protected WmfExtTextOut(WmfExtTextOutOptions options) {
+            this.options = options;
+        }
+
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.extTextOut;
         }
         
@@ -299,22 +349,17 @@ public class HwmfText {
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
             // -6 bytes of record function and length header
             final int remainingRecordSize = (int)(recordSize-6);
-            
-            y = leis.readShort();
-            x = leis.readShort();
+
+            int size = readPointS(leis, reference);
+
             stringLength = leis.readShort();
-            fwOpts = leis.readUShort();
-            
-            int size = 4*LittleEndianConsts.SHORT_SIZE;
-            
+            size += LittleEndianConsts.SHORT_SIZE;
+            size += options.init(leis);
+
             // Check if we have a rectangle
-            if ((ETO_OPAQUE.isSet(fwOpts) || ETO_CLIPPED.isSet(fwOpts)) && size+8<=remainingRecordSize) {
-                // the bounding rectangle is optional and only read when fwOpts are given
-                left = leis.readShort();
-                top = leis.readShort();
-                right = leis.readShort();
-                bottom = leis.readShort();
-                size += 4*LittleEndianConsts.SHORT_SIZE;
+            if ((options.isOpaque() || options.isClipped()) && size+8<=remainingRecordSize) {
+                // the bounding rectangle is optional and only read when options are given
+                size += readRectS(leis, bounds);
             }
             
             rawTextBytes = IOUtils.safelyAllocate(stringLength+(stringLength&1), MAX_RECORD_LENGTH);
@@ -342,12 +387,46 @@ public class HwmfText {
 
         @Override
         public void draw(HwmfGraphics ctx) {
-            Rectangle2D bounds = new Rectangle2D.Double(x, y, 0, 0);
+            Rectangle2D bounds = new Rectangle2D.Double(reference.getX(), reference.getY(), 0, 0);
             ctx.drawString(getTextBytes(), bounds, dx);
         }
 
-        public String getText(Charset charset) {
-            return new String(getTextBytes(), charset);
+
+        public String getText(Charset charset) throws IOException {
+            StringBuilder sb = new StringBuilder();
+            try (Reader r = new InputStreamReader(new ByteArrayInputStream(rawTextBytes), charset)) {
+                for (int i = 0; i < stringLength; i++) {
+                    sb.appendCodePoint(readCodePoint(r));
+                }
+            }
+            return sb.toString();
+        }
+
+        //TODO: move this to IOUtils?
+        private int readCodePoint(Reader r) throws IOException {
+            int c1 = r.read();
+            if (c1 == -1) {
+                throw new EOFException("Tried to read beyond byte array");
+            }
+            if (!Character.isHighSurrogate((char)c1)) {
+                return c1;
+            }
+            int c2 = r.read();
+            if (c2 == -1) {
+                throw new EOFException("Tried to read beyond byte array");
+            }
+            if (!Character.isLowSurrogate((char)c2)) {
+                throw new RecordFormatException("Expected low surrogate after high surrogate");
+            }
+            return Character.toCodePoint((char)c1, (char)c2);
+        }
+
+        public Point2D getReference() {
+            return reference;
+        }
+
+        public Rectangle2D getBounds() {
+            return bounds;
         }
 
         /**
@@ -482,10 +561,10 @@ public class HwmfText {
          * for text with a horizontal baseline, and VerticalTextAlignmentMode Flags
          * for text with a vertical baseline.
          */
-        private int textAlignmentMode;
+        protected int textAlignmentMode;
         
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.setTextAlign;
         }
         
@@ -533,17 +612,24 @@ public class HwmfText {
     }
     
     public static class WmfCreateFontIndirect implements HwmfRecord, HwmfObjectTableEntry {
-        private HwmfFont font;
-        
+        protected final HwmfFont font;
+
+        public WmfCreateFontIndirect() {
+            this(new HwmfFont());
+        }
+
+        protected WmfCreateFontIndirect(HwmfFont font) {
+            this.font = font;
+        }
+
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.createFontIndirect;
         }
         
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
-            font = new HwmfFont();
-            return font.init(leis);
+            return font.init(leis, recordSize);
         }
 
         @Override
index 42913aad643df493d22ee4bce8c3e4eb80427857..389cfbc7dd9d9065bdaa86fa22d2d3bb76e08e82 100644 (file)
@@ -33,18 +33,14 @@ public class HwmfWindowing {
      */
     public static class WmfSetViewportOrg implements HwmfRecord {
 
-        /**
-         * A 16-bit signed integer that defines the vertical offset, in device units.
-         */
-        private int y;
+        /** A signed integer that defines the vertical offset, in device units. */
+        protected int y;
 
-        /**
-         * A 16-bit signed integer that defines the horizontal offset, in device units.
-         */
-        private int x;
+        /** A signed integer that defines the horizontal offset, in device units. */
+        protected int x;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.setViewportOrg;
         }
 
@@ -67,20 +63,14 @@ public class HwmfWindowing {
      */
     public static class WmfSetViewportExt implements HwmfRecord {
 
-        /**
-         * A 16-bit signed integer that defines the vertical extent
-         * of the viewport in device units.
-         */
-        private int height;
+        /** A signed integer that defines the vertical extent of the viewport in device units. */
+        protected int height;
 
-        /**
-         * A 16-bit signed integer that defines the horizontal extent
-         * of the viewport in device units.
-         */
-        private int width;
+        /** A signed integer that defines the horizontal extent of the viewport in device units. */
+        protected int width;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.setViewportExt;
         }
 
@@ -114,7 +104,7 @@ public class HwmfWindowing {
         private int xOffset;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.offsetViewportOrg;
         }
 
@@ -139,18 +129,14 @@ public class HwmfWindowing {
      */
     public static class WmfSetWindowOrg implements HwmfRecord {
 
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units.
-         */
-        private int y;
+        /** A signed integer that defines the y-coordinate, in logical units. */
+        protected int y;
 
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units.
-         */
-        private int x;
+        /** A signed integer that defines the x-coordinate, in logical units. */
+        protected int x;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.setWindowOrg;
         }
 
@@ -182,20 +168,14 @@ public class HwmfWindowing {
      */
     public static class WmfSetWindowExt implements HwmfRecord {
 
-        /**
-         * A 16-bit signed integer that defines the vertical extent of
-         * the window in logical units.
-         */
-        private int height;
+        /** A signed integer that defines the vertical extent of the window in logical units. */
+        protected int height;
 
-        /**
-         * A 16-bit signed integer that defines the horizontal extent of
-         * the window in logical units.
-         */
-        private int width;
+        /** A signed integer that defines the horizontal extent of the window in logical units. */
+        protected int width;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.setWindowExt;
         }
 
@@ -238,7 +218,7 @@ public class HwmfWindowing {
         private int xOffset;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.offsetWindowOrg;
         }
 
@@ -264,31 +244,31 @@ public class HwmfWindowing {
     public static class WmfScaleWindowExt implements HwmfRecord {
 
         /**
-         * A 16-bit signed integer that defines the amount by which to divide the
+         * A signed integer that defines the amount by which to divide the
          * result of multiplying the current y-extent by the value of the yNum member.
          */
-        private int yDenom;
+        protected int yDenom;
 
         /**
-         * A 16-bit signed integer that defines the amount by which to multiply the
+         * A signed integer that defines the amount by which to multiply the
          * current y-extent.
          */
-        private int yNum;
+        protected int yNum;
 
         /**
-         * A 16-bit signed integer that defines the amount by which to divide the
+         * A signed integer that defines the amount by which to divide the
          * result of multiplying the current x-extent by the value of the xNum member.
          */
-        private int xDenom;
+        protected int xDenom;
 
         /**
-         * A 16-bit signed integer that defines the amount by which to multiply the
+         * A signed integer that defines the amount by which to multiply the
          * current x-extent.
          */
-        private int xNum;
+        protected int xNum;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.scaleWindowExt;
         }
 
@@ -320,31 +300,31 @@ public class HwmfWindowing {
     public static class WmfScaleViewportExt implements HwmfRecord {
 
         /**
-         * A 16-bit signed integer that defines the amount by which to divide the
+         * A signed integer that defines the amount by which to divide the
          * result of multiplying the current y-extent by the value of the yNum member.
          */
-        private int yDenom;
+        protected int yDenom;
 
         /**
-         * A 16-bit signed integer that defines the amount by which to multiply the
+         * A signed integer that defines the amount by which to multiply the
          * current y-extent.
          */
-        private int yNum;
+        protected int yNum;
 
         /**
-         * A 16-bit signed integer that defines the amount by which to divide the
+         * A signed integer that defines the amount by which to divide the
          * result of multiplying the current x-extent by the value of the xNum member.
          */
-        private int xDenom;
+        protected int xDenom;
 
         /**
-         * A 16-bit signed integer that defines the amount by which to multiply the
+         * A signed integer that defines the amount by which to multiply the
          * current x-extent.
          */
-        private int xNum;
+        protected int xNum;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.scaleViewportExt;
         }
 
@@ -376,17 +356,17 @@ public class HwmfWindowing {
     public static class WmfOffsetClipRgn implements HwmfRecord, HwmfObjectTableEntry {
 
         /**
-         * A 16-bit signed integer that defines the number of logical units to move up or down.
+         * A signed integer that defines the number of logical units to move up or down.
          */
-        private int yOffset;
+        protected int yOffset;
 
         /**
-         * A 16-bit signed integer that defines the number of logical units to move left or right.
+         * A signed integer that defines the number of logical units to move left or right.
          */
-        private int xOffset;
+        protected int xOffset;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.offsetClipRgn;
         }
 
@@ -413,41 +393,29 @@ public class HwmfWindowing {
      */
     public static class WmfExcludeClipRect implements HwmfRecord, HwmfObjectTableEntry {
 
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of the
-         * lower-right corner of the rectangle.
-         */
-        private int bottom;
-
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of the
-         * lower-right corner of the rectangle.
-         */
-        private int right;
-
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of the
-         * upper-left corner of the rectangle.
-         */
-        private int top;
-
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of the
-         * upper-left corner of the rectangle.
-         */
-        private int left;
+        /** a rectangle in logical units */
+        protected final Rectangle2D bounds = new Rectangle2D.Double();
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.excludeClipRect;
         }
 
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
-            bottom = leis.readShort();
-            right = leis.readShort();
-            top = leis.readShort();
-            left = leis.readShort();
+            // A 16-bit signed integer that defines the y-coordinate, in logical units, of the
+            // lower-right corner of the rectangle.
+            final int bottom = leis.readShort();
+            // A 16-bit signed integer that defines the x-coordinate, in logical units, of the
+            // lower-right corner of the rectangle.
+            final int right = leis.readShort();
+            // A 16-bit signed integer that defines the y-coordinate, in logical units, of the
+            // upper-left corner of the rectangle.
+            final int top = leis.readShort();
+            // A 16-bit signed integer that defines the x-coordinate, in logical units, of the
+            // upper-left corner of the rectangle.
+            final int left = leis.readShort();
+            bounds.setRect(left, top, right-left, bottom-top);
             return 4*LittleEndianConsts.SHORT_SIZE;
         }
 
@@ -468,41 +436,29 @@ public class HwmfWindowing {
      */
     public static class WmfIntersectClipRect implements HwmfRecord, HwmfObjectTableEntry {
 
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of the
-         * lower-right corner of the rectangle.
-         */
-        private int bottom;
-
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of the
-         * lower-right corner of the rectangle.
-         */
-        private int right;
-
-        /**
-         * A 16-bit signed integer that defines the y-coordinate, in logical units, of the
-         * upper-left corner of the rectangle.
-         */
-        private int top;
-
-        /**
-         * A 16-bit signed integer that defines the x-coordinate, in logical units, of the
-         * upper-left corner of the rectangle.
-         */
-        private int left;
+        /** a rectangle in logical units */
+        protected final Rectangle2D bounds = new Rectangle2D.Double();
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.intersectClipRect;
         }
 
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
-            bottom = leis.readShort();
-            right = leis.readShort();
-            top = leis.readShort();
-            left = leis.readShort();
+            // A 16-bit signed integer that defines the y-coordinate, in logical units, of the
+            // lower-right corner of the rectangle.
+            final int bottom = leis.readShort();
+            // A 16-bit signed integer that defines the x-coordinate, in logical units, of the
+            // lower-right corner of the rectangle.
+            final int right = leis.readShort();
+            // A 16-bit signed integer that defines the y-coordinate, in logical units, of the
+            // upper-left corner of the rectangle.
+            final int top = leis.readShort();
+            // A 16-bit signed integer that defines the x-coordinate, in logical units, of the
+            // upper-left corner of the rectangle.
+            final int left = leis.readShort();
+            bounds.setRect(left, top, right-left, bottom-top);
             return 4*LittleEndianConsts.SHORT_SIZE;
         }
 
@@ -528,7 +484,7 @@ public class HwmfWindowing {
         private int region;
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.selectClipRegion;
         }
 
@@ -650,7 +606,7 @@ public class HwmfWindowing {
         private WmfScanObject scanObjects[];
 
         @Override
-        public HwmfRecordType getRecordType() {
+        public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.createRegion;
         }
 
index fa59f4bc311ce3099cd453f11624d5f9f8cc40c4..b380d46143012d7f3962bf3185849335f8baf9ab 100644 (file)
@@ -51,8 +51,7 @@ public class HwmfPicture {
     
     public HwmfPicture(InputStream inputStream) throws IOException {
 
-        try (BufferedInputStream bis = new BufferedInputStream(inputStream, 10000);
-             LittleEndianInputStream leis = new LittleEndianInputStream(bis)) {
+        try (LittleEndianInputStream leis = new LittleEndianInputStream(inputStream)) {
             placeableHeader = HwmfPlaceableHeader.readHeader(leis);
             header = new HwmfHeader(leis);
 
@@ -82,17 +81,12 @@ public class HwmfPicture {
                 if (wrt == HwmfRecordType.eof) {
                     break;
                 }
-                if (wrt.clazz == null) {
+                if (wrt.constructor == null) {
                     throw new IOException("unsupported record type: "+recordFunction);
                 }
 
-                HwmfRecord wr;
-                try {
-                    wr = wrt.clazz.newInstance();
-                    records.add(wr);
-                } catch (Exception e) {
-                    throw (IOException)new IOException("can't create wmf record").initCause(e);
-                }
+                final HwmfRecord wr = wrt.constructor.get();
+                records.add(wr);
 
                 consumedSize += wr.init(leis, recordSize, recordFunction);
                 int remainingSize = (int)(recordSize - consumedSize);
diff --git a/src/scratchpad/testcases/org/apache/poi/hemf/extractor/HemfExtractorTest.java b/src/scratchpad/testcases/org/apache/poi/hemf/extractor/HemfExtractorTest.java
deleted file mode 100644 (file)
index 8d32b21..0000000
+++ /dev/null
@@ -1,198 +0,0 @@
-/* ====================================================================
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================== */
-
-
-package org.apache.poi.hemf.extractor;
-
-import static org.apache.poi.POITestCase.assertContains;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.apache.poi.POIDataSamples;
-import org.apache.poi.hemf.record.AbstractHemfComment;
-import org.apache.poi.hemf.record.HemfCommentPublic;
-import org.apache.poi.hemf.record.HemfCommentRecord;
-import org.apache.poi.hemf.record.HemfHeader;
-import org.apache.poi.hemf.record.HemfRecord;
-import org.apache.poi.hemf.record.HemfRecordType;
-import org.apache.poi.hemf.record.HemfText;
-import org.apache.poi.util.IOUtils;
-import org.apache.poi.util.RecordFormatException;
-import org.junit.Test;
-
-public class HemfExtractorTest {
-
-    @Test
-    public void testBasicWindows() throws Exception {
-        InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("SimpleEMF_windows.emf");
-        HemfExtractor ex = new HemfExtractor(is);
-        HemfHeader header = ex.getHeader();
-        assertEquals(27864, header.getBytes());
-        assertEquals(31, header.getRecords());
-        assertEquals(3, header.getHandles());
-        assertEquals(346000, header.getMicrometersX());
-        assertEquals(194000, header.getMicrometersY());
-
-        int records = 0;
-        for (HemfRecord record : ex) {
-            records++;
-        }
-
-        assertEquals(header.getRecords() - 1, records);
-    }
-
-    @Test
-    public void testBasicMac() throws Exception {
-        InputStream is =
-                POIDataSamples.getSpreadSheetInstance().openResourceAsStream("SimpleEMF_mac.emf");
-        HemfExtractor ex = new HemfExtractor(is);
-        HemfHeader header = ex.getHeader();
-
-        int records = 0;
-        boolean extractedData = false;
-        for (HemfRecord record : ex) {
-            if (record.getRecordType() == HemfRecordType.comment) {
-                AbstractHemfComment comment = ((HemfCommentRecord) record).getComment();
-                if (comment instanceof HemfCommentPublic.MultiFormats) {
-                    for (HemfCommentPublic.HemfMultiFormatsData d : ((HemfCommentPublic.MultiFormats) comment).getData()) {
-                        byte[] data = d.getData();
-                        //make sure header starts at 0
-                        assertEquals('%', data[0]);
-                        assertEquals('P', data[1]);
-                        assertEquals('D', data[2]);
-                        assertEquals('F', data[3]);
-
-                        //make sure byte array ends at EOF\n
-                        assertEquals('E', data[data.length - 4]);
-                        assertEquals('O', data[data.length - 3]);
-                        assertEquals('F', data[data.length - 2]);
-                        assertEquals('\n', data[data.length - 1]);
-                        extractedData = true;
-                    }
-                }
-            }
-            records++;
-        }
-        assertTrue(extractedData);
-        assertEquals(header.getRecords() - 1, records);
-    }
-
-    @Test
-    public void testMacText() throws Exception {
-        InputStream is =
-                POIDataSamples.getSpreadSheetInstance().openResourceAsStream("SimpleEMF_mac.emf");
-        HemfExtractor ex = new HemfExtractor(is);
-
-        long lastY = -1;
-        long lastX = -1;
-        long fudgeFactorX = 1000;//derive this from the font information!
-        StringBuilder sb = new StringBuilder();
-        for (HemfRecord record : ex) {
-            if (record.getRecordType().equals(HemfRecordType.exttextoutw)) {
-                HemfText.ExtTextOutW extTextOutW = (HemfText.ExtTextOutW) record;
-                if (lastY > -1 && lastY != extTextOutW.getY()) {
-                    sb.append("\n");
-                    lastX = -1;
-                }
-                if (lastX > -1 && extTextOutW.getX() - lastX > fudgeFactorX) {
-                    sb.append(" ");
-                }
-                sb.append(extTextOutW.getText());
-                lastY = extTextOutW.getY();
-                lastX = extTextOutW.getX();
-            }
-        }
-        String txt = sb.toString();
-        assertContains(txt, "Tika http://incubator.apache.org");
-        assertContains(txt, "Latest News\n");
-    }
-
-    @Test
-    public void testWindowsText() throws Exception {
-        InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("SimpleEMF_windows.emf");
-        HemfExtractor ex = new HemfExtractor(is);
-        long lastY = -1;
-        long lastX = -1;
-        long fudgeFactorX = 1000;//derive this from the font or frame/bounds information
-        StringBuilder sb = new StringBuilder();
-        Set<String> expectedParts = new HashSet<>();
-        expectedParts.add("C:\\Users\\tallison\\");
-        expectedParts.add("testPDF.pdf");
-        int foundExpected = 0;
-        for (HemfRecord record : ex) {
-            if (record.getRecordType().equals(HemfRecordType.exttextoutw)) {
-                HemfText.ExtTextOutW extTextOutW = (HemfText.ExtTextOutW) record;
-                if (lastY > -1 && lastY != extTextOutW.getY()) {
-                    sb.append("\n");
-                    lastX = -1;
-                }
-                if (lastX > -1 && extTextOutW.getX() - lastX > fudgeFactorX) {
-                    sb.append(" ");
-                }
-                String txt = extTextOutW.getText();
-                if (expectedParts.contains(txt)) {
-                    foundExpected++;
-                }
-                sb.append(txt);
-                lastY = extTextOutW.getY();
-                lastX = extTextOutW.getX();
-            }
-        }
-        String txt = sb.toString();
-        assertContains(txt, "C:\\Users\\tallison\\\n");
-        assertContains(txt, "asf2-git-1.x\\tika-\n");
-        assertEquals(expectedParts.size(), foundExpected);
-    }
-
-    @Test(expected = RecordFormatException.class)
-    public void testInfiniteLoopOnFile() throws Exception {
-        InputStream is = null;
-        try {
-            is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("61294.emf");
-
-            HemfExtractor ex = new HemfExtractor(is);
-            for (HemfRecord record : ex) {
-
-            }
-        } finally {
-            IOUtils.closeQuietly(is);
-        }
-    }
-
-    @Test(expected = RecordFormatException.class)
-    public void testInfiniteLoopOnByteArray() throws Exception {
-        InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("61294.emf");
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        IOUtils.copy(is, bos);
-        is.close();
-
-        HemfExtractor ex = new HemfExtractor(new ByteArrayInputStream(bos.toByteArray()));
-        for (HemfRecord record : ex) {
-
-        }
-    }
-
-     /*
-        govdocs1 064213.doc-0.emf contains an example of extextouta
-     */
-}
\ No newline at end of file
index f3bdbf7b4f56242d346a81da65f2130eefeabd97..5811accac50f55278fb90c979c538886866993ff 100644 (file)
@@ -25,13 +25,13 @@ import java.util.ArrayList;
 import java.util.List;
 
 import org.apache.poi.POIDataSamples;
-import org.apache.poi.hemf.extractor.HemfExtractor;
-import org.apache.poi.hemf.hemfplus.record.HemfPlusHeader;
-import org.apache.poi.hemf.hemfplus.record.HemfPlusRecord;
-import org.apache.poi.hemf.hemfplus.record.HemfPlusRecordType;
-import org.apache.poi.hemf.record.HemfCommentEMFPlus;
-import org.apache.poi.hemf.record.HemfCommentRecord;
-import org.apache.poi.hemf.record.HemfRecord;
+import org.apache.poi.hemf.record.emf.HemfComment.EmfComment;
+import org.apache.poi.hemf.record.emf.HemfComment.EmfCommentDataPlus;
+import org.apache.poi.hemf.record.emf.HemfRecord;
+import org.apache.poi.hemf.record.emfplus.HemfPlusHeader;
+import org.apache.poi.hemf.record.emfplus.HemfPlusRecord;
+import org.apache.poi.hemf.record.emfplus.HemfPlusRecordType;
+import org.apache.poi.hemf.usermodel.HemfPicture;
 import org.junit.Test;
 
 public class HemfPlusExtractorTest {
@@ -39,7 +39,7 @@ public class HemfPlusExtractorTest {
     @Test
     public void testBasic() throws Exception {
         //test header
-        HemfCommentEMFPlus emfPlus = getCommentRecord("SimpleEMF_windows.emf", 0);
+        EmfCommentDataPlus emfPlus = getCommentRecord("SimpleEMF_windows.emf", 0);
         List<HemfPlusRecord> records = emfPlus.getRecords();
         assertEquals(1, records.size());
         assertEquals(HemfPlusRecordType.header, records.get(0).getRecordType());
@@ -72,24 +72,20 @@ public class HemfPlusExtractorTest {
     }
 
 
-    private HemfCommentEMFPlus getCommentRecord(String testFileName, int recordIndex) throws Exception {
-        InputStream is = null;
-        HemfCommentEMFPlus returnRecord = null;
+    private EmfCommentDataPlus getCommentRecord(String testFileName, int recordIndex) throws Exception {
+        EmfCommentDataPlus returnRecord = null;
 
-        try {
-            is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream(testFileName);
-            HemfExtractor ex = new HemfExtractor(is);
+        try (InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream(testFileName)) {
+            HemfPicture ex = new HemfPicture(is);
             int i = 0;
             for (HemfRecord record : ex) {
                 if (i == recordIndex) {
-                    HemfCommentRecord commentRecord = ((HemfCommentRecord) record);
-                    returnRecord = (HemfCommentEMFPlus) commentRecord.getComment();
+                    EmfComment commentRecord = ((EmfComment) record);
+                    returnRecord = (EmfCommentDataPlus) commentRecord.getCommentData();
                     break;
                 }
                 i++;
             }
-        } finally {
-            is.close();
         }
         return returnRecord;
     }
diff --git a/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java
new file mode 100644 (file)
index 0000000..f0e259d
--- /dev/null
@@ -0,0 +1,201 @@
+/* ====================================================================
+   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 static org.apache.poi.POITestCase.assertContains;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.awt.geom.Point2D;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.poi.POIDataSamples;
+import org.apache.poi.hemf.record.emf.HemfComment;
+import org.apache.poi.hemf.record.emf.HemfComment.EmfComment;
+import org.apache.poi.hemf.record.emf.HemfComment.EmfCommentDataFormat;
+import org.apache.poi.hemf.record.emf.HemfComment.EmfCommentDataMultiformats;
+import org.apache.poi.hemf.record.emf.HemfHeader;
+import org.apache.poi.hemf.record.emf.HemfRecord;
+import org.apache.poi.hemf.record.emf.HemfRecordType;
+import org.apache.poi.hemf.record.emf.HemfText;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.RecordFormatException;
+import org.junit.Test;
+
+public class HemfPictureTest {
+
+    private POIDataSamples samples = POIDataSamples.getSpreadSheetInstance();
+
+    @Test
+    public void testBasicWindows() throws Exception {
+        try (InputStream is = samples.openResourceAsStream("SimpleEMF_windows.emf")) {
+            HemfPicture pic = new HemfPicture(is);
+            HemfHeader header = pic.getHeader();
+            assertEquals(27864, header.getBytes());
+            assertEquals(31, header.getRecords());
+            assertEquals(3, header.getHandles());
+            assertEquals(346000, header.getMicrometersX());
+            assertEquals(194000, header.getMicrometersY());
+
+            List<HemfRecord> records = pic.getRecords();
+
+            assertEquals(31, records.size());
+        }
+    }
+
+    @Test
+    public void testBasicMac() throws Exception {
+        try (InputStream is = samples.openResourceAsStream("SimpleEMF_mac.emf")) {
+            HemfPicture pic = new HemfPicture(is);
+            HemfHeader header = pic.getHeader();
+
+            int records = 0;
+            boolean extractedData = false;
+            for (HemfRecord record : pic) {
+                if (record.getEmfRecordType() == HemfRecordType.comment) {
+                    HemfComment.EmfCommentData comment = ((EmfComment) record).getCommentData();
+                    if (comment instanceof EmfCommentDataMultiformats) {
+                        for (EmfCommentDataFormat d : ((EmfCommentDataMultiformats) comment).getFormats()) {
+                            byte[] data = d.getRawData();
+                            //make sure header starts at 0
+                            assertEquals('%', data[0]);
+                            assertEquals('P', data[1]);
+                            assertEquals('D', data[2]);
+                            assertEquals('F', data[3]);
+
+                            //make sure byte array ends at EOF\n
+                            assertEquals('E', data[data.length - 4]);
+                            assertEquals('O', data[data.length - 3]);
+                            assertEquals('F', data[data.length - 2]);
+                            assertEquals('\n', data[data.length - 1]);
+                            extractedData = true;
+                        }
+                    }
+                }
+                records++;
+            }
+            assertTrue(extractedData);
+            assertEquals(header.getRecords(), records);
+        }
+    }
+
+    @Test
+    public void testMacText() throws Exception {
+        try (InputStream is = samples.openResourceAsStream("SimpleEMF_mac.emf")) {
+            HemfPicture pic = new HemfPicture(is);
+
+            double lastY = -1;
+            double lastX = -1;
+            //derive this from the font information!
+            long fudgeFactorX = 1000;
+            StringBuilder sb = new StringBuilder();
+            for (HemfRecord record : pic) {
+                if (record.getEmfRecordType().equals(HemfRecordType.exttextoutw)) {
+                    HemfText.ExtTextOutW extTextOutW = (HemfText.ExtTextOutW) record;
+                    Point2D reference = extTextOutW.getTextObject().getReference();
+                    if (lastY > -1 && lastY != reference.getY()) {
+                        sb.append("\n");
+                        lastX = -1;
+                    }
+                    if (lastX > -1 && reference.getX() - lastX > fudgeFactorX) {
+                        sb.append(" ");
+                    }
+                    sb.append(extTextOutW.getText());
+                    lastY = reference.getY();
+                    lastX = reference.getX();
+                }
+            }
+            String txt = sb.toString();
+            assertContains(txt, "Tika http://incubator.apache.org");
+            assertContains(txt, "Latest News\n");
+        }
+    }
+
+    @Test
+    public void testWindowsText() throws Exception {
+        try (InputStream is = samples.openResourceAsStream("SimpleEMF_windows.emf")) {
+            HemfPicture pic = new HemfPicture(is);
+            double lastY = -1;
+            double lastX = -1;
+            long fudgeFactorX = 1000;//derive this from the font or frame/bounds information
+            StringBuilder sb = new StringBuilder();
+            Set<String> expectedParts = new HashSet<>();
+            expectedParts.add("C:\\Users\\tallison\\");
+            expectedParts.add("testPDF.pdf");
+            int foundExpected = 0;
+            for (HemfRecord record : pic) {
+                if (record.getEmfRecordType().equals(HemfRecordType.exttextoutw)) {
+                    HemfText.ExtTextOutW extTextOutW = (HemfText.ExtTextOutW) record;
+                    Point2D reference = extTextOutW.getTextObject().getReference();
+                    if (lastY > -1 && lastY != reference.getY()) {
+                        sb.append("\n");
+                        lastX = -1;
+                    }
+                    if (lastX > -1 && reference.getX() - lastX > fudgeFactorX) {
+                        sb.append(" ");
+                    }
+                    String txt = extTextOutW.getText();
+                    if (expectedParts.contains(txt)) {
+                        foundExpected++;
+                    }
+                    sb.append(txt);
+                    lastY = reference.getY();
+                    lastX = reference.getX();
+                }
+            }
+            String txt = sb.toString();
+            assertContains(txt, "C:\\Users\\tallison\\\n");
+            assertContains(txt, "asf2-git-1.x\\tika-\n");
+            assertEquals(expectedParts.size(), foundExpected);
+        }
+    }
+
+    @Test(expected = RecordFormatException.class)
+    public void testInfiniteLoopOnFile() throws Exception {
+        try (InputStream is = samples.openResourceAsStream("61294.emf")) {
+            HemfPicture pic = new HemfPicture(is);
+            for (HemfRecord record : pic) {
+
+            }
+        }
+    }
+
+    @Test(expected = RecordFormatException.class)
+    public void testInfiniteLoopOnByteArray() throws Exception {
+        try (InputStream is = samples.openResourceAsStream("61294.emf")) {
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            IOUtils.copy(is, bos);
+            is.close();
+
+            HemfPicture pic = new HemfPicture(new ByteArrayInputStream(bos.toByteArray()));
+            for (HemfRecord record : pic) {
+
+            }
+        }
+    }
+
+     /*
+        govdocs1 064213.doc-0.emf contains an example of extextouta
+     */
+}
\ No newline at end of file
index 1667b67b49c2d68fae091e885fde4605c3e373f1..df084cdad3e22bffa82eb324d343ed32e2bad7a2 100644 (file)
@@ -222,11 +222,11 @@ public class TestHwmfParsing {
         //this happens to work on this test file, but you need to
         //do what Graphics does by maintaining the stack, etc.!
         for (HwmfRecord r : wmf.getRecords()) {
-            if (r.getRecordType().equals(HwmfRecordType.createFontIndirect)) {
+            if (r.getWmfRecordType().equals(HwmfRecordType.createFontIndirect)) {
                 HwmfFont font = ((HwmfText.WmfCreateFontIndirect)r).getFont();
                 charset = (font.getCharset().getCharset() == null) ? LocaleUtil.CHARSET_1252 : font.getCharset().getCharset();
             }
-            if (r.getRecordType().equals(HwmfRecordType.extTextOut)) {
+            if (r.getWmfRecordType().equals(HwmfRecordType.extTextOut)) {
                 HwmfText.WmfExtTextOut textOut = (HwmfText.WmfExtTextOut)r;
                 sb.append(textOut.getText(charset)).append("\n");
             }
@@ -250,11 +250,11 @@ public class TestHwmfParsing {
         //this happens to work on this test file, but you need to
         //do what Graphics does by maintaining the stack, etc.!
         for (HwmfRecord r : wmf.getRecords()) {
-            if (r.getRecordType().equals(HwmfRecordType.createFontIndirect)) {
+            if (r.getWmfRecordType().equals(HwmfRecordType.createFontIndirect)) {
                 HwmfFont font = ((HwmfText.WmfCreateFontIndirect)r).getFont();
                 charset = (font.getCharset().getCharset() == null) ? LocaleUtil.CHARSET_1252 : font.getCharset().getCharset();
             }
-            if (r.getRecordType().equals(HwmfRecordType.extTextOut)) {
+            if (r.getWmfRecordType().equals(HwmfRecordType.extTextOut)) {
                 HwmfText.WmfExtTextOut textOut = (HwmfText.WmfExtTextOut)r;
                 sb.append(textOut.getText(charset)).append("\n");
             }