]> source.dussan.org Git - poi.git/commitdiff
Bug 60656 - EMF image support in slideshows
authorAndreas Beeker <kiwiwings@apache.org>
Sun, 12 May 2019 19:50:04 +0000 (19:50 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Sun, 12 May 2019 19:50:04 +0000 (19:50 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1859159 13f79535-47bb-0310-9956-ffa450edef68

src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusBrush.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusDraw.java
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusFont.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusImage.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusObject.java
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusPath.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusPen.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRegion.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfEmbeddedIterator.java
src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java

diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusBrush.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusBrush.java
new file mode 100644 (file)
index 0000000..65c4a3d
--- /dev/null
@@ -0,0 +1,480 @@
+/* ====================================================================
+   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 static org.apache.poi.hemf.record.emf.HemfFill.readXForm;
+import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readARGB;
+import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readPointF;
+import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readRectF;
+
+import java.awt.Color;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
+import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusImage;
+import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusWrapMode;
+import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
+import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType;
+import org.apache.poi.hemf.record.emfplus.HemfPlusPath.EmfPlusPath;
+import org.apache.poi.util.BitField;
+import org.apache.poi.util.BitFieldFactory;
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianInputStream;
+
+public class HemfPlusBrush {
+    /** The BrushType enumeration defines types of graphics brushes, which are used to fill graphics regions. */
+    public enum EmfPlusBrushType {
+        SOLID_COLOR(0X00000000, EmfPlusSolidBrushData::new),
+        HATCH_FILL(0X00000001, EmfPlusHatchBrushData::new),
+        TEXTURE_FILL(0X00000002, EmfPlusTextureBrushData::new),
+        PATH_GRADIENT(0X00000003, EmfPlusPathGradientBrushData::new),
+        LINEAR_GRADIENT(0X00000004, EmfPlusLinearGradientBrushData::new)
+        ;
+
+        public final int id;
+        public final Supplier<? extends EmfPlusBrushData> constructor;
+
+        EmfPlusBrushType(int id, Supplier<? extends EmfPlusBrushData> constructor) {
+            this.id = id;
+            this.constructor = constructor;
+        }
+
+        public static EmfPlusBrushType valueOf(int id) {
+            for (EmfPlusBrushType wrt : values()) {
+                if (wrt.id == id) return wrt;
+            }
+            return null;
+        }
+    }
+
+    public enum EmfPlusHatchStyle {
+        HORIZONTAL(0X00000000),
+        VERTICAL(0X00000001),
+        FORWARD_DIAGONAL(0X00000002),
+        BACKWARD_DIAGONAL(0X00000003),
+        LARGE_GRID(0X00000004),
+        DIAGONAL_CROSS(0X00000005),
+        PERCENT_05(0X00000006),
+        PERCENT_10(0X00000007),
+        PERCENT_20(0X00000008),
+        PERCENT_25(0X00000009),
+        PERCENT_30(0X0000000A),
+        PERCENT_40(0X0000000B),
+        PERCENT_50(0X0000000C),
+        PERCENT_60(0X0000000D),
+        PERCENT_70(0X0000000E),
+        PERCENT_75(0X0000000F),
+        PERCENT_80(0X00000010),
+        PERCENT_90(0X00000011),
+        LIGHT_DOWNWARD_DIAGONAL(0X00000012),
+        LIGHT_UPWARD_DIAGONAL(0X00000013),
+        DARK_DOWNWARD_DIAGONAL(0X00000014),
+        DARK_UPWARD_DIAGONAL(0X00000015),
+        WIDE_DOWNWARD_DIAGONAL(0X00000016),
+        WIDE_UPWARD_DIAGONAL(0X00000017),
+        LIGHT_VERTICAL(0X00000018),
+        LIGHT_HORIZONTAL(0X00000019),
+        NARROW_VERTICAL(0X0000001A),
+        NARROW_HORIZONTAL(0X0000001B),
+        DARK_VERTICAL(0X0000001C),
+        DARK_HORIZONTAL(0X0000001D),
+        DASHED_DOWNWARD_DIAGONAL(0X0000001E),
+        DASHED_UPWARD_DIAGONAL(0X0000001F),
+        DASHED_HORIZONTAL(0X00000020),
+        DASHED_VERTICAL(0X00000021),
+        SMALL_CONFETTI(0X00000022),
+        LARGE_CONFETTI(0X00000023),
+        ZIGZAG(0X00000024),
+        WAVE(0X00000025),
+        DIAGONAL_BRICK(0X00000026),
+        HORIZONTAL_BRICK(0X00000027),
+        WEAVE(0X00000028),
+        PLAID(0X00000029),
+        DIVOT(0X0000002A),
+        DOTTED_GRID(0X0000002B),
+        DOTTED_DIAMOND(0X0000002C),
+        SHINGLE(0X0000002D),
+        TRELLIS(0X0000002E),
+        SPHERE(0X0000002F),
+        SMALL_GRID(0X00000030),
+        SMALL_CHECKER_BOARD(0X00000031),
+        LARGE_CHECKER_BOARD(0X00000032),
+        OUTLINED_DIAMOND(0X00000033),
+        SOLID_DIAMOND(0X00000034)
+        ;
+
+
+        public final int id;
+
+        EmfPlusHatchStyle(int id) {
+            this.id = id;
+        }
+
+        public static EmfPlusHatchStyle valueOf(int id) {
+            for (EmfPlusHatchStyle wrt : values()) {
+                if (wrt.id == id) return wrt;
+            }
+            return null;
+        }
+
+    }
+
+    public interface EmfPlusBrushData {
+        /**
+         * This flag is meaningful in EmfPlusPathGradientBrushData objects.
+         *
+         * If set, an EmfPlusBoundaryPathData object MUST be specified in the BoundaryData field of the brush data object.
+         * If clear, an EmfPlusBoundaryPointData object MUST be specified in the BoundaryData field of the brush data object.
+         */
+        BitField PATH = BitFieldFactory.getInstance(0x00000001);
+
+        /**
+         * This flag is meaningful in EmfPlusLinearGradientBrushData objects , EmfPlusPathGradientBrushData objects,
+         * and EmfPlusTextureBrushData objects.
+         *
+         * If set, a 2x3 world space to device space transform matrix MUST be specified in the OptionalData field of
+         * the brush data object.
+         */
+        BitField TRANSFORM = BitFieldFactory.getInstance(0x00000002);
+
+        /**
+         * This flag is meaningful in EmfPlusLinearGradientBrushData and EmfPlusPathGradientBrushData objects.
+         *
+         * If set, an EmfPlusBlendColors object MUST be specified in the OptionalData field of the brush data object.
+         */
+        BitField PRESET_COLORS = BitFieldFactory.getInstance(0x00000004);
+
+        /**
+         * This flag is meaningful in EmfPlusLinearGradientBrushData and EmfPlusPathGradientBrushData objects.
+         *
+         * If set, an EmfPlusBlendFactors object that specifies a blend pattern along a horizontal gradient MUST be
+         * specified in the OptionalData field of the brush data object.
+         */
+        BitField BLEND_FACTORS_H = BitFieldFactory.getInstance(0x00000008);
+
+        /**
+         * This flag is meaningful in EmfPlusLinearGradientBrushData objects.
+         *
+         * If set, an EmfPlusBlendFactors object that specifies a blend pattern along a vertical gradient MUST be
+         * specified in the OptionalData field of the brush data object.
+         */
+        BitField BLEND_FACTORS_V = BitFieldFactory.getInstance(0x00000010);
+
+        /**
+         * This flag is meaningful in EmfPlusPathGradientBrushData objects.
+         *
+         * If set, an EmfPlusFocusScaleData object MUST be specified in the OptionalData field of the brush data object.
+         */
+        BitField FOCUS_SCALES = BitFieldFactory.getInstance(0x00000040);
+
+        /**
+         * This flag is meaningful in EmfPlusLinearGradientBrushData, EmfPlusPathGradientBrushData, and
+         * EmfPlusTextureBrushData objects.
+         *
+         * If set, the brush MUST already be gamma corrected; that is, output brightness and intensity have been
+         * corrected to match the input image.
+         */
+        BitField IS_GAMMA_CORRECTED = BitFieldFactory.getInstance(0x00000080);
+
+        /**
+         * This flag is meaningful in EmfPlusTextureBrushData objects.
+         *
+         * If set, a world space to device space transform SHOULD NOT be applied to the texture brush.
+         */
+        BitField DO_NOT_TRANSFORM = BitFieldFactory.getInstance(0x00000100);
+
+        long init(LittleEndianInputStream leis, long dataSize) throws IOException;
+    }
+
+    /** The EmfPlusBrush object specifies a graphics brush for filling regions. */
+    public static class EmfPlusBrush implements EmfPlusObjectData {
+        private final EmfPlusGraphicsVersion version = new EmfPlusGraphicsVersion();
+        private EmfPlusBrushType brushType;
+        private EmfPlusBrushData brushData;
+
+        @Override
+        public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
+            long size = version.init(leis);
+
+            brushType = EmfPlusBrushType.valueOf(leis.readInt());
+            size += LittleEndianConsts.INT_SIZE;
+            assert(brushType != null);
+
+            size += (brushData = brushType.constructor.get()).init(leis, dataSize-size);
+
+            return size;
+        }
+    }
+
+    /** The EmfPlusSolidBrushData object specifies a solid color for a graphics brush. */
+    public static class EmfPlusSolidBrushData implements EmfPlusBrushData {
+        private Color solidColor;
+        @Override
+        public long init(LittleEndianInputStream leis, long dataSize) throws IOException {
+            solidColor = readARGB(leis.readInt());
+            return LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+
+    /** The EmfPlusHatchBrushData object specifies a hatch pattern for a graphics brush. */
+    public static class EmfPlusHatchBrushData implements EmfPlusBrushData {
+        private EmfPlusHatchStyle style;
+        private Color foreColor, backColor;
+        public long init(LittleEndianInputStream leis, long dataSize) {
+            style = EmfPlusHatchStyle.valueOf(leis.readInt());
+            foreColor = readARGB(leis.readInt());
+            backColor = readARGB(leis.readInt());
+            return 3*LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+    /** The EmfPlusLinearGradientBrushData object specifies a linear gradient for a graphics brush. */
+    public static class EmfPlusLinearGradientBrushData implements EmfPlusBrushData {
+        private int dataFlags;
+        private EmfPlusWrapMode wrapMode;
+        private Rectangle2D rect = new Rectangle2D.Double();
+        private Color startColor, endColor;
+        private AffineTransform transform;
+        private double[] positions;
+        private Color[] blendColors;
+        private double[] positionsV;
+        private double[] blendFactorsV;
+        private double[] positionsH;
+        private double[] blendFactorsH;
+
+        @Override
+        public long init(LittleEndianInputStream leis, long dataSize) throws IOException {
+            // A 32-bit unsigned integer that specifies the data in the OptionalData field.
+            // This value MUST be composed of BrushData flags
+            dataFlags = leis.readInt();
+
+            // A 32-bit signed integer from the WrapMode enumeration that specifies whether to paint the area outside
+            // the boundary of the brush. When painting outside the boundary, the wrap mode specifies how the color
+            // gradient is repeated.
+            wrapMode = EmfPlusWrapMode.valueOf(leis.readInt());
+
+            int size = 2*LittleEndianConsts.INT_SIZE;
+            size += readRectF(leis, rect);
+
+            // An EmfPlusARGB object that specifies the color at the starting/ending boundary point of the linear gradient brush.
+            startColor = readARGB(leis.readInt());
+            endColor = readARGB(leis.readInt());
+
+            // skip reserved1/2 fields
+            leis.skipFully(2*LittleEndianConsts.INT_SIZE);
+
+            size += 4*LittleEndianConsts.INT_SIZE;
+
+            if (TRANSFORM.isSet(dataFlags)) {
+                size += readXForm(leis, (transform = new AffineTransform()));
+            }
+
+            final boolean isPreset = PRESET_COLORS.isSet(dataFlags);
+            final boolean blendH = BLEND_FACTORS_H.isSet(dataFlags);
+            final boolean blendV = BLEND_FACTORS_V.isSet(dataFlags);
+            if (isPreset && (blendH || blendV)) {
+                throw new RuntimeException("invalid combination of preset colors and blend factors v/h");
+            }
+
+            size += (isPreset) ? readColors(leis, d -> positions = d, c -> blendColors = c) : 0;
+            size += (blendV) ? readFactors(leis, d -> positionsV = d, f -> blendFactorsV = f) : 0;
+            size += (blendH) ? readFactors(leis, d -> positionsH = d, f -> blendFactorsH = f) : 0;
+
+            return size;
+        }
+    }
+
+    /** The EmfPlusPathGradientBrushData object specifies a path gradient for a graphics brush. */
+    public static class EmfPlusPathGradientBrushData implements EmfPlusBrushData {
+        private int dataFlags;
+        private EmfPlusWrapMode wrapMode;
+        private Color centerColor;
+        private final Point2D centerPoint = new Point2D.Double();
+        private Color[] surroundingColor;
+        private EmfPlusPath boundaryPath;
+        private Point2D[] boundaryPoints;
+        private AffineTransform transform;
+        private double[] positions;
+        private Color[] blendColors;
+        private double[] blendFactorsH;
+        private Double focusScaleX, focusScaleY;
+
+        @Override
+        public long init(LittleEndianInputStream leis, long dataSize) throws IOException {
+            // A 32-bit unsigned integer that specifies the data in the OptionalData field.
+            // This value MUST be composed of BrushData flags
+            dataFlags = leis.readInt();
+
+            // A 32-bit signed integer from the WrapMode enumeration that specifies whether to paint the area outside
+            // the boundary of the brush. When painting outside the boundary, the wrap mode specifies how the color
+            // gradient is repeated.
+            wrapMode = EmfPlusWrapMode.valueOf(leis.readInt());
+
+            // An EmfPlusARGB object that specifies the center color of the path gradient brush, which is the color
+            // that appears at the center point of the brush. The color of the brush changes gradually from the
+            // boundary color to the center color as it moves from the boundary to the center point.
+            centerColor = readARGB(leis.readInt());
+
+            int size = 3*LittleEndianConsts.INT_SIZE;
+            size += readPointF(leis, centerPoint);
+
+            // An unsigned 32-bit integer that specifies the number of colors specified in the SurroundingColor field.
+            // The surrounding colors are colors specified for discrete points on the boundary of the brush.
+            final int colorCount = leis.readInt();
+
+            // An array of SurroundingColorCount EmfPlusARGB objects that specify the colors for discrete points on the
+            // boundary of the brush.
+            surroundingColor = new Color[colorCount];
+            for (int i=0; i<colorCount; i++) {
+                surroundingColor[i] = readARGB(leis.readInt());
+            }
+            size += (colorCount+1) * LittleEndianConsts.INT_SIZE;
+
+            // The boundary of the path gradient brush, which is specified by either a path or a closed cardinal spline.
+            // If the BrushDataPath flag is set in the BrushDataFlags field, this field MUST contain an
+            // EmfPlusBoundaryPathData object; otherwise, this field MUST contain an EmfPlusBoundaryPointData object.
+            if (PATH.isSet(dataFlags)) {
+                // A 32-bit signed integer that specifies the size in bytes of the BoundaryPathData field.
+                int pathDataSize = leis.readInt();
+                size += LittleEndianConsts.INT_SIZE;
+
+                // An EmfPlusPath object that specifies the boundary of the brush.
+                size += (boundaryPath = new EmfPlusPath()).init(leis, pathDataSize, EmfPlusObjectType.PATH, 0);
+            } else {
+                // A 32-bit signed integer that specifies the number of points in the BoundaryPointData field.
+                int pointCount = leis.readInt();
+                size += LittleEndianConsts.INT_SIZE;
+
+                // An array of BoundaryPointCount EmfPlusPointF objects that specify the boundary of the brush.
+                boundaryPoints = new Point2D[pointCount];
+                for (int i=0; i<pointCount; i++) {
+                    size += readPointF(leis, boundaryPoints[i] = new Point2D.Double());
+                }
+            }
+
+            // An optional EmfPlusTransformMatrix object that specifies a world space to device space transform for
+            // the path gradient brush. This field MUST be present if the BrushDataTransform flag is set in the
+            // BrushDataFlags field of the EmfPlusPathGradientBrushData object.
+            if (TRANSFORM.isSet(dataFlags)) {
+                size += readXForm(leis, (transform = new AffineTransform()));
+            }
+
+            // An optional blend pattern for the path gradient brush. If this field is present, it MUST contain either
+            // an EmfPlusBlendColors object, or an EmfPlusBlendFactors object, but it MUST NOT contain both.
+            final boolean isPreset = PRESET_COLORS.isSet(dataFlags);
+            final boolean blendH = BLEND_FACTORS_H.isSet(dataFlags);
+            if (isPreset && blendH) {
+                throw new RuntimeException("invalid combination of preset colors and blend factors h");
+            }
+
+            size += (isPreset) ? readColors(leis, d -> positions = d, c -> blendColors = c) : 0;
+            size += (blendH) ? readFactors(leis, d -> positions = d, f -> blendFactorsH = f) : 0;
+
+            // An optional EmfPlusFocusScaleData object that specifies focus scales for the path gradient brush.
+            // This field MUST be present if the BrushDataFocusScales flag is set in the BrushDataFlags field of the
+            // EmfPlusPathGradientBrushData object.
+            if (FOCUS_SCALES.isSet(dataFlags)) {
+                // A 32-bit unsigned integer that specifies the number of focus scales. This value MUST be 2.
+                int focusScaleCount = leis.readInt();
+                if (focusScaleCount != 2) {
+                    throw new RuntimeException("invalid focus scale count");
+                }
+                // A floating-point value that defines the horizontal/vertical focus scale.
+                // The focus scale MUST be a value between 0.0 and 1.0, exclusive.
+                focusScaleX = (double)leis.readFloat();
+                focusScaleY = (double)leis.readFloat();
+                size += 3*LittleEndianConsts.INT_SIZE;
+            }
+
+            return size;
+        }
+    }
+
+    /** The EmfPlusTextureBrushData object specifies a texture image for a graphics brush. */
+    public static class EmfPlusTextureBrushData implements EmfPlusBrushData {
+        private int dataFlags;
+        private EmfPlusWrapMode wrapMode;
+        private AffineTransform transform;
+        private EmfPlusImage image;
+
+        @Override
+        public long init(LittleEndianInputStream leis, long dataSize) throws IOException {
+            // A 32-bit unsigned integer that specifies the data in the OptionalData field.
+            // This value MUST be composed of BrushData flags.
+            dataFlags = leis.readInt();
+
+            // A 32-bit signed integer from the WrapMode enumeration that specifies how to repeat the texture image
+            // across a shape, when the image is smaller than the area being filled.
+            wrapMode = EmfPlusWrapMode.valueOf(leis.readInt());
+
+            int size = 2*LittleEndianConsts.INT_SIZE;
+
+            if (TRANSFORM.isSet(dataFlags)) {
+                size += readXForm(leis, (transform = new AffineTransform()));
+            }
+
+            if (dataSize > size) {
+                size += (image = new EmfPlusImage()).init(leis, dataSize-size, EmfPlusObjectType.IMAGE, 0);
+            }
+
+            return size;
+        }
+    }
+
+    private static int readPositions(LittleEndianInputStream leis, Consumer<double[]> pos) {
+        final int count = leis.readInt();
+        int size = LittleEndianConsts.INT_SIZE;
+
+        double[] positions = new double[count];
+        for (int i=0; i<count; i++) {
+            positions[i] = leis.readFloat();
+            size += LittleEndianConsts.INT_SIZE;
+        }
+
+        pos.accept(positions);
+        return size;
+    }
+
+    private static int readColors(LittleEndianInputStream leis, Consumer<double[]> pos, Consumer<Color[]>  cols) {
+        int[] count = { 0 };
+        int size = readPositions(leis, p -> { count[0] = p.length; pos.accept(p); });
+        Color[] colors = new Color[count[0]];
+        for (int i=0; i<colors.length; i++) {
+            colors[i] = readARGB(leis.readInt());
+        }
+        cols.accept(colors);
+        return size + colors.length * LittleEndianConsts.INT_SIZE;
+    }
+
+    private static int readFactors(LittleEndianInputStream leis, Consumer<double[]> pos, Consumer<double[]> facs) {
+        int[] count = { 0 };
+        int size = readPositions(leis, p -> { count[0] = p.length; pos.accept(p); });
+        double[] factors = new double[count[0]];
+        for (int i=0; i<factors.length; i++) {
+            factors[i] = leis.readFloat();
+        }
+        facs.accept(factors);
+        return size + factors.length * LittleEndianConsts.INT_SIZE;
+    }
+}
index 8f9a13ba0214c8d77d36e5cc4e1769d3f007756c..f35a1ea3ab5649860ea21085467246cfb9aa08d9 100644 (file)
@@ -35,7 +35,6 @@ import org.apache.poi.hemf.record.emfplus.HemfPlusMisc.EmfPlusObjectId;
 import org.apache.poi.util.BitField;
 import org.apache.poi.util.BitFieldFactory;
 import org.apache.poi.util.IOUtils;
-import org.apache.poi.util.LittleEndian;
 import org.apache.poi.util.LittleEndianConsts;
 import org.apache.poi.util.LittleEndianInputStream;
 import org.apache.poi.util.StringUtil;
@@ -43,7 +42,7 @@ import org.apache.poi.util.StringUtil;
 public class HemfPlusDraw {
     private static final int MAX_OBJECT_SIZE = 1_000_000;
 
-    public enum UnitType {
+    public enum EmfPlusUnitType {
         /** Specifies a unit of logical distance within the world space. */
         World(0x00),
         /** Specifies a unit of distance based on the characteristics of the physical display. */
@@ -62,12 +61,12 @@ public class HemfPlusDraw {
 
         public final int id;
 
-        UnitType(int id) {
+        EmfPlusUnitType(int id) {
             this.id = id;
         }
 
-        public static UnitType valueOf(int id) {
-            for (UnitType wrt : values()) {
+        public static EmfPlusUnitType valueOf(int id) {
+            for (EmfPlusUnitType wrt : values()) {
                 if (wrt.id == id) return wrt;
             }
             return null;
@@ -98,6 +97,25 @@ public class HemfPlusDraw {
         }
     }
 
+    public interface EmfPlusRelativePosition {
+        /**
+         * This bit indicates whether the PointData field specifies relative or absolute locations.
+         * If set, each element in PointData specifies a location in the coordinate space that is relative to the
+         * location specified by the previous element in the array. In the case of the first element in PointData,
+         * a previous location at coordinates (0,0) is assumed.
+         * If clear, PointData specifies absolute locations according to the {@link #isCompressed()} flag.
+         *
+         * Note If this flag is set, the {@link #isCompressed()} flag (above) is undefined and MUST be ignored.
+         */
+        BitField POSITION = BitFieldFactory.getInstance(0x0800);
+
+        int getFlags();
+
+        default boolean isRelativePosition() {
+            return POSITION.isSet(getFlags());
+        }
+    }
+
 
 
     /**
@@ -182,27 +200,16 @@ public class HemfPlusDraw {
         }
     }
 
-    public static class EmfPlusDrawImagePoints implements HemfPlusRecord, EmfPlusObjectId, EmfPlusCompressed {
+    public static class EmfPlusDrawImagePoints implements HemfPlusRecord, EmfPlusObjectId, EmfPlusCompressed, EmfPlusRelativePosition {
         /**
          * This bit indicates that the rendering of the image includes applying an effect.
          * If set, an object of the Effect class MUST have been specified in an earlier EmfPlusSerializableObject record.
          */
         private static final BitField EFFECT = BitFieldFactory.getInstance(0x2000);
 
-        /**
-         * This bit indicates whether the PointData field specifies relative or absolute locations.
-         * If set, each element in PointData specifies a location in the coordinate space that is relative to the
-         * location specified by the previous element in the array. In the case of the first element in PointData,
-         * a previous location at coordinates (0,0) is assumed.
-         * If clear, PointData specifies absolute locations according to the {@link #isCompressed()} flag.
-         *
-         * Note If this flag is set, the {@link #isCompressed()} flag (above) is undefined and MUST be ignored.
-         */
-        private static final BitField POSITION = BitFieldFactory.getInstance(0x0800);
-
         private int flags;
         private int imageAttributesID;
-        private UnitType srcUnit;
+        private EmfPlusUnitType srcUnit;
         private final Rectangle2D srcRect = new Rectangle2D.Double();
         private final Point2D upperLeft = new Point2D.Double();
         private final Point2D lowerRight = new Point2D.Double();
@@ -229,8 +236,8 @@ public class HemfPlusDraw {
 
             // A 32-bit signed integer that defines the units of the SrcRect field.
             // It MUST be the UnitPixel value of the UnitType enumeration
-            srcUnit = UnitType.valueOf(leis.readInt());
-            assert(srcUnit == UnitType.Pixel);
+            srcUnit = EmfPlusUnitType.valueOf(leis.readInt());
+            assert(srcUnit == EmfPlusUnitType.Pixel);
 
             int size = 2 * LittleEndianConsts.INT_SIZE;
 
@@ -245,7 +252,7 @@ public class HemfPlusDraw {
 
             BiFunction<LittleEndianInputStream, Point2D, Integer> readPoint;
 
-            if (POSITION.isSet(flags)) {
+            if (isRelativePosition()) {
                 // If the POSITION flag is set in the Flags, the points specify relative locations.
                 readPoint = HemfPlusDraw::readPointR;
             } else if (isCompressed()) {
@@ -301,7 +308,7 @@ public class HemfPlusDraw {
     public static class EmfPlusDrawImage implements HemfPlusRecord, EmfPlusObjectId, EmfPlusCompressed {
         private int flags;
         private int imageAttributesID;
-        private UnitType srcUnit;
+        private EmfPlusUnitType srcUnit;
         private final Rectangle2D srcRect = new Rectangle2D.Double();
         private final Rectangle2D rectData = new Rectangle2D.Double();
 
@@ -325,8 +332,8 @@ public class HemfPlusDraw {
 
             // A 32-bit signed integer that defines the units of the SrcRect field.
             // It MUST be the UnitPixel value of the UnitType enumeration
-            srcUnit = UnitType.valueOf(leis.readInt());
-            assert(srcUnit == UnitType.Pixel);
+            srcUnit = EmfPlusUnitType.valueOf(leis.readInt());
+            assert(srcUnit == EmfPlusUnitType.Pixel);
 
             int size = 2 * LittleEndianConsts.INT_SIZE;
 
@@ -347,7 +354,7 @@ public class HemfPlusDraw {
         private static final BitField SOLID_COLOR = BitFieldFactory.getInstance(0x8000);
 
         private int flags;
-        private final byte[] brushId = new byte[LittleEndianConsts.INT_SIZE];
+        private int brushId;
 
         @Override
         public HemfPlusRecordType getEmfPlusRecordType() {
@@ -364,11 +371,11 @@ public class HemfPlusDraw {
         }
 
         public int getBrushId() {
-            return (isSolidColor()) ? -1 : LittleEndian.getInt(brushId);
+            return (isSolidColor()) ? -1 : brushId;
         }
 
         public Color getSolidColor() {
-            return (isSolidColor()) ? new Color(brushId[2], brushId[1], brushId[0], brushId[3]) : null;
+            return (isSolidColor()) ? readARGB(brushId) : null;
         }
 
         @Override
@@ -379,7 +386,7 @@ public class HemfPlusDraw {
             // the SOLID_COLOR bit in the Flags field.
             // If SOLID_COLOR is set, BrushId specifies a color as an EmfPlusARGB object.
             // If clear, BrushId contains the index of an EmfPlusBrush object in the EMF+ Object Table.
-            leis.readFully(brushId);
+            brushId = leis.readInt();
 
             return LittleEndianConsts.INT_SIZE;
         }
@@ -606,4 +613,9 @@ public class HemfPlusDraw {
         value[0] = ((value[0] << 8) | leis.readByte()) & 0x7FFF;
         return LittleEndianConsts.SHORT_SIZE;
     }
+
+    static Color readARGB(int argb) {
+        return new Color(  (argb >>> 8) & 0xFF, (argb >>> 16) & 0xFF, (argb >>> 24) & 0xFF, argb & 0xFF);
+    }
+
 }
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusFont.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusFont.java
new file mode 100644 (file)
index 0000000..fc3550c
--- /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.io.IOException;
+
+import org.apache.poi.hemf.record.emfplus.HemfPlusDraw.EmfPlusUnitType;
+import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
+import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
+import org.apache.poi.util.BitField;
+import org.apache.poi.util.BitFieldFactory;
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianInputStream;
+import org.apache.poi.util.StringUtil;
+
+public class HemfPlusFont {
+
+
+    public static class EmfPlusFont implements EmfPlusObjectData {
+        /**
+         * If set, the font typeface MUST be rendered with a heavier weight or thickness.
+         * If clear, the font typeface MUST be rendered with a normal thickness.
+         */
+        private static final BitField BOLD = BitFieldFactory.getInstance(0x00000001);
+
+        /**
+         * If set, the font typeface MUST be rendered with the vertical stems of the characters at an increased angle
+         * or slant relative to the baseline.
+         *
+         * If clear, the font typeface MUST be rendered with the vertical stems of the characters at a normal angle.
+         */
+        private static final BitField ITALIC = BitFieldFactory.getInstance(0x00000002);
+
+        /**
+         * If set, the font typeface MUST be rendered with a line underneath the baseline of the characters.
+         * If clear, the font typeface MUST be rendered without a line underneath the baseline.
+         */
+        private static final BitField UNDERLINE = BitFieldFactory.getInstance(0x00000004);
+
+        /**
+         * If set, the font typeface MUST be rendered with a line parallel to the baseline drawn through the middle of
+         * the characters.
+         * If clear, the font typeface MUST be rendered without a line through the characters.
+         */
+        private static final BitField STRIKEOUT = BitFieldFactory.getInstance(0x00000008);
+
+
+        private final EmfPlusGraphicsVersion version = new EmfPlusGraphicsVersion();
+        private double emSize;
+        private EmfPlusUnitType sizeUnit;
+        private int styleFlags;
+        private String family;
+
+        @Override
+        public long init(LittleEndianInputStream leis, long dataSize, HemfPlusObject.EmfPlusObjectType objectType, int flags) throws IOException {
+            // An EmfPlusGraphicsVersion object that specifies the version of operating system graphics that was used
+            // to create this object.
+            long size = version.init(leis);
+
+            // A 32-bit floating-point value that specifies the em size of the font in units specified by the SizeUnit field.
+            emSize = leis.readFloat();
+
+            // A 32-bit unsigned integer that specifies the units used for the EmSize field. These are typically the
+            // units that were employed when designing the font. The value MUST be in the UnitType enumeration
+            sizeUnit = EmfPlusUnitType.valueOf(leis.readInt());
+
+            // A 32-bit signed integer that specifies attributes of the character glyphs that affect the appearance of
+            // the font, such as bold and italic. This value MUST be composed of FontStyle flags
+            styleFlags = leis.readInt();
+
+            // A 32-bit unsigned integer that is reserved and MUST be ignored.
+            leis.skipFully(LittleEndianConsts.INT_SIZE);
+
+            // A 32-bit unsigned integer that specifies the number of characters in the FamilyName field.
+            int len = leis.readInt();
+            size += 5*LittleEndianConsts.INT_SIZE;
+
+            // A string of Length Unicode characters that contains the name of the font family.
+            family = StringUtil.readUnicodeLE(leis, len);
+            size += len*LittleEndianConsts.SHORT_SIZE;
+
+            return size;
+        }
+    }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusImage.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusImage.java
new file mode 100644 (file)
index 0000000..4374b17
--- /dev/null
@@ -0,0 +1,439 @@
+/* ====================================================================
+   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 static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readARGB;
+
+import java.awt.Color;
+import java.io.IOException;
+
+import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
+import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
+import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType;
+import org.apache.poi.util.BitField;
+import org.apache.poi.util.BitFieldFactory;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianInputStream;
+
+public class HemfPlusImage {
+    /** The ImageDataType enumeration defines types of image data formats. */
+    public enum EmfPlusImageDataType {
+        /** The type of image is not known. */
+        UNKNOWN(0x00000000),
+        /** Specifies a bitmap image. */
+        BITMAP(0x00000001),
+        /** Specifies a metafile image. */
+        METAFILE(0x00000002),
+        /** POI-specific - marks an unfinished/continuable image part */
+        CONTINUED(-1);
+
+        public final int id;
+
+        EmfPlusImageDataType(int id) {
+            this.id = id;
+        }
+
+        public static EmfPlusImageDataType valueOf(int id) {
+            for (EmfPlusImageDataType wrt : values()) {
+                if (wrt.id == id) return wrt;
+            }
+            return null;
+        }
+    }
+
+    public enum EmfPlusPixelFormat {
+        UNDEFINED(0X00000000),
+        INDEXED_1BPP(0X00030101),
+        INDEXED_4BPP(0X00030402),
+        INDEXED_8BPP(0X00030803),
+        GRAYSCALE_16BPP(0X00101004),
+        RGB555_16BPP(0X00021005),
+        RGB565_16BPP(0X00021006),
+        ARGB1555_16BPP(0X00061007),
+        RGB_24BPP(0X00021808),
+        RGB_32BPP(0X00022009),
+        ARGB_32BPP(0X0026200A),
+        PARGB_32BPP(0X000E200B),
+        RGB_48BPP(0X0010300C),
+        ARGB_64BPP(0X0034400D),
+        PARGB_64BPP(0X001A400E),
+        ;
+
+        private static final BitField CANONICAL = BitFieldFactory.getInstance(0x00200000);
+        private static final BitField EXTCOLORS = BitFieldFactory.getInstance(0x00100000);
+        private static final BitField PREMULTI = BitFieldFactory.getInstance(0x00080000);
+        private static final BitField ALPHA = BitFieldFactory.getInstance(0x00040000);
+        private static final BitField GDI = BitFieldFactory.getInstance(0x00020000);
+        private static final BitField PALETTE = BitFieldFactory.getInstance(0x00010000);
+        private static final BitField BPP = BitFieldFactory.getInstance(0x0000FF00);
+        private static final BitField INDEX = BitFieldFactory.getInstance(0x000000FF);
+
+        public final int id;
+
+        EmfPlusPixelFormat(int id) {
+            this.id = id;
+        }
+
+        public static EmfPlusPixelFormat valueOf(int id) {
+            for (EmfPlusPixelFormat wrt : values()) {
+                if (wrt.id == id) return wrt;
+            }
+            return null;
+        }
+
+        /**
+         * The pixel format enumeration index.
+         */
+        public int getGDIEnumIndex() {
+            return id == -1 ? -1 : INDEX.getValue(id);
+        }
+
+        /**
+         * The total number of bits per pixel.
+         */
+        public int getBitsPerPixel() {
+            return id == -1 ? -1 : BPP.getValue(id);
+        }
+
+        /**
+         * If set, the pixel values are indexes into a palette.
+         * If clear, the pixel values are actual colors.
+         */
+        public boolean isPaletteIndexed() {
+            return id != -1 && PALETTE.isSet(id);
+        }
+
+        /**
+         * If set, the pixel format is supported in Windows GDI.
+         * If clear, the pixel format is not supported in Windows GDI.
+         */
+        public boolean isGDISupported() {
+            return id != -1 && GDI.isSet(id);
+        }
+
+        /**
+         * If set, the pixel format includes an alpha transparency component.
+         * If clear, the pixel format does not include a component that specifies transparency.
+         */
+        public boolean isAlpha() {
+            return id != -1 && ALPHA.isSet(id);
+        }
+
+        /**
+         * If set, each color component in the pixel has been premultiplied by the pixel's alpha transparency value.
+         * If clear, each color component is multiplied by the pixel's alpha transparency value when the source pixel
+         * is blended with the destination pixel.
+         */
+        public boolean isPreMultiplied() {
+            return id != -1 && PREMULTI.isSet(id);
+        }
+
+        /**
+         * If set, the pixel format supports extended colors in 16-bits per channel.
+         * If clear, extended colors are not supported.
+         */
+        public boolean isExtendedColors() {
+            return id != -1 && EXTCOLORS.isSet(id);
+        }
+
+        /**
+         * If set, the pixel format is "canonical", which means that 32 bits per pixel are
+         * supported, with 24-bits for color components and an 8-bit alpha channel.
+         * If clear, the pixel format is not canonical.
+         */
+        public boolean isCanonical() {
+            return id != -1 && CANONICAL.isSet(id);
+        }
+    }
+
+    public enum EmfPlusBitmapDataType {
+        PIXEL(0x00000000),
+        COMPRESSED(0x00000001);
+
+        public final int id;
+
+        EmfPlusBitmapDataType(int id) {
+            this.id = id;
+        }
+
+        public static EmfPlusBitmapDataType valueOf(int id) {
+            for (EmfPlusBitmapDataType wrt : values()) {
+                if (wrt.id == id) return wrt;
+            }
+            return null;
+        }
+    }
+
+    public enum EmfPlusMetafileDataType {
+        Wmf(0x00000001),
+        WmfPlaceable(0x00000002),
+        Emf(0x00000003),
+        EmfPlusOnly(0x00000004),
+        EmfPlusDual(0x00000005);
+
+        public final int id;
+
+        EmfPlusMetafileDataType(int id) {
+            this.id = id;
+        }
+
+        public static EmfPlusMetafileDataType valueOf(int id) {
+            for (EmfPlusMetafileDataType wrt : values()) {
+                if (wrt.id == id) return wrt;
+            }
+            return null;
+        }
+    }
+
+
+    /**
+     * The WrapMode enumeration defines how the pattern from a texture or gradient brush is tiled
+     * across a shape or at shape boundaries, when it is smaller than the area being filled.
+     */
+    public enum EmfPlusWrapMode {
+        WRAP_MODE_TILE(0x00000000),
+        WRAP_MODE_TILE_FLIP_X(0x00000001),
+        WRAP_MODE_TILE_FLIP_Y(0x00000002),
+        WRAP_MODE_TILE_FLIP_XY(0x00000003),
+        WRAP_MODE_CLAMP(0x00000004)
+        ;
+
+        public final int id;
+
+        EmfPlusWrapMode(int id) {
+            this.id = id;
+        }
+
+        public static EmfPlusWrapMode valueOf(int id) {
+            for (EmfPlusWrapMode wrt : values()) {
+                if (wrt.id == id) return wrt;
+            }
+            return null;
+        }
+    }
+
+    public enum EmfPlusObjectClamp {
+        /** The object is clamped to a rectangle. */
+        RectClamp(0x00000000),
+        /** The object is clamped to a bitmap. */
+        BitmapClamp(0x00000001)
+        ;
+
+        public final int id;
+
+        EmfPlusObjectClamp(int id) {
+            this.id = id;
+        }
+
+        public static EmfPlusObjectClamp valueOf(int id) {
+            for (EmfPlusObjectClamp wrt : values()) {
+                if (wrt.id == id) return wrt;
+            }
+            return null;
+        }
+    }
+
+
+    public static class EmfPlusImage implements EmfPlusObjectData {
+        private static final int MAX_OBJECT_SIZE = 50_000_000;
+
+
+        private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
+        private EmfPlusImageDataType imageDataType;
+        private int bitmapWidth;
+        private int bitmapHeight;
+        private int bitmapStride;
+        private EmfPlusPixelFormat pixelFormat;
+        private EmfPlusBitmapDataType bitmapType;
+        private byte[] imageData;
+        private EmfPlusMetafileDataType metafileType;
+        private int metafileDataSize;
+
+        public EmfPlusImageDataType getImageDataType() {
+            return imageDataType;
+        }
+
+        public byte[] getImageData() {
+            return imageData;
+        }
+
+        public EmfPlusPixelFormat getPixelFormat() {
+            return pixelFormat;
+        }
+
+        public EmfPlusBitmapDataType getBitmapType() {
+            return bitmapType;
+        }
+
+        public int getBitmapWidth() {
+            return bitmapWidth;
+        }
+
+        public int getBitmapHeight() {
+            return bitmapHeight;
+        }
+
+        public int getBitmapStride() {
+            return bitmapStride;
+        }
+
+        public EmfPlusMetafileDataType getMetafileType() {
+            return metafileType;
+        }
+
+        @Override
+        public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
+            leis.mark(LittleEndianConsts.INT_SIZE);
+            long size = graphicsVersion.init(leis);
+
+            if (graphicsVersion.getGraphicsVersion() == null || graphicsVersion.getMetafileSignature() != 0xDBC01) {
+                // CONTINUABLE is not always correctly set, so we check the version field if this record is continued
+                imageDataType = EmfPlusImageDataType.CONTINUED;
+                leis.reset();
+                size = 0;
+            } else {
+                imageDataType = EmfPlusImageDataType.valueOf(leis.readInt());
+                size += LittleEndianConsts.INT_SIZE;
+            }
+
+            if (imageDataType == null) {
+                imageDataType = EmfPlusImageDataType.UNKNOWN;
+            }
+
+            int fileSize;
+            switch (imageDataType) {
+                default:
+                case UNKNOWN:
+                case CONTINUED:
+                    bitmapWidth = -1;
+                    bitmapHeight = -1;
+                    bitmapStride = -1;
+                    bitmapType = null;
+                    pixelFormat = null;
+
+                    fileSize = (int) (dataSize);
+                    break;
+
+                case BITMAP:
+                    // A 32-bit signed integer that specifies the width in pixels of the area occupied by the bitmap.
+                    // If the image is compressed, according to the Type field, this value is undefined and MUST be ignored.
+                    bitmapWidth = leis.readInt();
+                    // A 32-bit signed integer that specifies the height in pixels of the area occupied by the bitmap.
+                    // If the image is compressed, according to the Type field, this value is undefined and MUST be ignored.
+                    bitmapHeight = leis.readInt();
+                    // A 32-bit signed integer that specifies the byte offset between the beginning of one scan-line
+                    // and the next. This value is the number of bytes per pixel, which is specified in the PixelFormat
+                    // field, multiplied by the width in pixels, which is specified in the Width field.
+                    // The value of this field MUST be a multiple of four. If the image is compressed, according to the
+                    // Type field, this value is undefined and MUST be ignored.
+                    bitmapStride = leis.readInt();
+                    // A 32-bit unsigned integer that specifies the format of the pixels that make up the bitmap image.
+                    //  The supported pixel formats are specified in the PixelFormat enumeration
+                    int pixelFormatInt = leis.readInt();
+                    // A 32-bit unsigned integer that specifies the metafileType of data in the BitmapData field.
+                    // This value MUST be defined in the BitmapDataType enumeration
+                    bitmapType = EmfPlusBitmapDataType.valueOf(leis.readInt());
+                    size += 5 * LittleEndianConsts.INT_SIZE;
+
+                    pixelFormat = (bitmapType == EmfPlusBitmapDataType.PIXEL)
+                            ? EmfPlusPixelFormat.valueOf(pixelFormatInt)
+                            : EmfPlusPixelFormat.UNDEFINED;
+                    assert (pixelFormat != null);
+
+                    fileSize = (int) (dataSize - size);
+
+                    break;
+
+                case METAFILE:
+                    // A 32-bit unsigned integer that specifies the type of metafile that is embedded in the
+                    // MetafileData field. This value MUST be defined in the MetafileDataType enumeration
+                    metafileType = EmfPlusMetafileDataType.valueOf(leis.readInt());
+
+                    // A 32-bit unsigned integer that specifies the size in bytes of the
+                    // metafile data in the MetafileData field.
+                    metafileDataSize = leis.readInt();
+
+                    size += 2 * LittleEndianConsts.INT_SIZE;
+
+                    // ignore metafileDataSize, which might ignore a (placeable) header in front
+                    // and also use the remaining bytes, which might contain padding bytes ...
+                    fileSize = (int) (dataSize - size);
+                    break;
+            }
+
+            assert (fileSize <= dataSize - size);
+
+            imageData = IOUtils.toByteArray(leis, fileSize, MAX_OBJECT_SIZE);
+
+            // TODO: remove padding bytes between placeable WMF header and body?
+
+            return size + fileSize;
+        }
+    }
+
+    public static class EmfPlusImageAttributes implements EmfPlusObjectData {
+        private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
+        private EmfPlusWrapMode wrapMode;
+        private Color clampColor;
+        private EmfPlusObjectClamp objectClamp;
+
+        @Override
+        public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
+            // An EmfPlusGraphicsVersion object that specifies the version of operating system graphics that
+            // was used to create this object.
+            long size = graphicsVersion.init(leis);
+
+            // A 32-bit field that is not used and MUST be ignored.
+            leis.skipFully(LittleEndianConsts.INT_SIZE);
+
+            // A 32-bit unsigned integer that specifies how to handle edge conditions with a value from the WrapMode enumeration
+            wrapMode = EmfPlusWrapMode.valueOf(leis.readInt());
+
+            // An EmfPlusARGB object that specifies the edge color to use when the WrapMode value is WrapModeClamp.
+            // This color is visible when the source rectangle processed by an EmfPlusDrawImage record is larger than the image itself.
+            clampColor = readARGB(leis.readInt());
+
+            // A 32-bit signed integer that specifies the object clamping behavior. It is not used until this object
+            // is applied to an image being drawn. This value MUST be one of the values defined in the following table.
+            objectClamp = EmfPlusObjectClamp.valueOf(leis.readInt());
+
+            // A value that SHOULD be set to zero and MUST be ignored upon receipt.
+            leis.skipFully(LittleEndianConsts.INT_SIZE);
+
+            return size + 5*LittleEndianConsts.INT_SIZE;
+        }
+
+        public EmfPlusGraphicsVersion getGraphicsVersion() {
+            return graphicsVersion;
+        }
+
+        public EmfPlusWrapMode getWrapMode() {
+            return wrapMode;
+        }
+
+        public Color getClampColor() {
+            return clampColor;
+        }
+
+        public EmfPlusObjectClamp getObjectClamp() {
+            return objectClamp;
+        }
+    }
+
+}
index 5e30ba536b11a09363821dc74d61a318ae49a5f7..35845044da5b9c1d48d855ea88dd700bc9d420dd 100644 (file)
 
 package org.apache.poi.hemf.record.emfplus;
 
-import java.awt.Color;
 import java.io.IOException;
 import java.util.function.Supplier;
 
+import org.apache.poi.hemf.record.emfplus.HemfPlusBrush.EmfPlusBrush;
+import org.apache.poi.hemf.record.emfplus.HemfPlusFont.EmfPlusFont;
 import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
+import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusImage;
+import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusImageAttributes;
 import org.apache.poi.hemf.record.emfplus.HemfPlusMisc.EmfPlusObjectId;
+import org.apache.poi.hemf.record.emfplus.HemfPlusPath.EmfPlusPath;
+import org.apache.poi.hemf.record.emfplus.HemfPlusPen.EmfPlusPen;
+import org.apache.poi.hemf.record.emfplus.HemfPlusRegion.EmfPlusRegion;
 import org.apache.poi.util.BitField;
 import org.apache.poi.util.BitFieldFactory;
 import org.apache.poi.util.IOUtils;
@@ -43,19 +49,19 @@ public class HemfPlusObject {
         /**
          * Brush objects fill graphics regions.
          */
-        BRUSH(0x00000001, EmfPlusUnknownData::new),
+        BRUSH(0x00000001, EmfPlusBrush::new),
         /**
          * Pen objects draw graphics lines.
          */
-        PEN(0x00000002, EmfPlusUnknownData::new),
+        PEN(0x00000002, EmfPlusPen::new),
         /**
          * Path objects specify sequences of lines, curves, and shapes.
          */
-        PATH(0x00000003, EmfPlusUnknownData::new),
+        PATH(0x00000003, EmfPlusPath::new),
         /**
          * Region objects specify areas of the output surface.
          */
-        REGION(0x00000004, EmfPlusUnknownData::new),
+        REGION(0x00000004, EmfPlusRegion::new),
         /**
          * Image objects encapsulate bitmaps and metafiles.
          */
@@ -63,7 +69,7 @@ public class HemfPlusObject {
         /**
          * Font objects specify font properties, including typeface style, em size, and font family.
          */
-        FONT(0x00000006, EmfPlusUnknownData::new),
+        FONT(0x00000006, EmfPlusFont::new),
         /**
          * String format objects specify text layout, including alignment, orientation, tab stops, clipping,
          * and digit substitution for languages that do not use Western European digits.
@@ -96,220 +102,10 @@ public class HemfPlusObject {
         }
     }
 
-    public enum EmfPlusImageDataType {
-        UNKNOWN(0x00000000),
-        BITMAP(0x00000001),
-        METAFILE(0x00000002),
-        CONTINUED(-1);
-
-        public final int id;
-
-        EmfPlusImageDataType(int id) {
-            this.id = id;
-        }
-
-        public static EmfPlusImageDataType valueOf(int id) {
-            for (EmfPlusImageDataType wrt : values()) {
-                if (wrt.id == id) return wrt;
-            }
-            return null;
-        }
-    }
-
-    public enum EmfPlusBitmapDataType {
-        PIXEL(0x00000000),
-        COMPRESSED(0x00000001);
-
-        public final int id;
-
-        EmfPlusBitmapDataType(int id) {
-            this.id = id;
-        }
-
-        public static EmfPlusBitmapDataType valueOf(int id) {
-            for (EmfPlusBitmapDataType wrt : values()) {
-                if (wrt.id == id) return wrt;
-            }
-            return null;
-        }
-    }
-
-    public enum EmfPlusPixelFormat {
-        UNDEFINED(0X00000000),
-        INDEXED_1BPP(0X00030101),
-        INDEXED_4BPP(0X00030402),
-        INDEXED_8BPP(0X00030803),
-        GRAYSCALE_16BPP(0X00101004),
-        RGB555_16BPP(0X00021005),
-        RGB565_16BPP(0X00021006),
-        ARGB1555_16BPP(0X00061007),
-        RGB_24BPP(0X00021808),
-        RGB_32BPP(0X00022009),
-        ARGB_32BPP(0X0026200A),
-        PARGB_32BPP(0X000E200B),
-        RGB_48BPP(0X0010300C),
-        ARGB_64BPP(0X0034400D),
-        PARGB_64BPP(0X001A400E),
-        ;
-
-        private static final BitField CANONICAL = BitFieldFactory.getInstance(0x00200000);
-        private static final BitField EXTCOLORS = BitFieldFactory.getInstance(0x00100000);
-        private static final BitField PREMULTI = BitFieldFactory.getInstance(0x00080000);
-        private static final BitField ALPHA = BitFieldFactory.getInstance(0x00040000);
-        private static final BitField GDI = BitFieldFactory.getInstance(0x00020000);
-        private static final BitField PALETTE = BitFieldFactory.getInstance(0x00010000);
-        private static final BitField BPP = BitFieldFactory.getInstance(0x0000FF00);
-        private static final BitField INDEX = BitFieldFactory.getInstance(0x000000FF);
-
-        public final int id;
-
-        EmfPlusPixelFormat(int id) {
-            this.id = id;
-        }
-
-        public static EmfPlusPixelFormat valueOf(int id) {
-            for (EmfPlusPixelFormat wrt : values()) {
-                if (wrt.id == id) return wrt;
-            }
-            return null;
-        }
-
-        /**
-         * The pixel format enumeration index.
-         */
-        public int getGDIEnumIndex() {
-            return id == -1 ? -1 : INDEX.getValue(id);
-        }
-
-        /**
-         * The total number of bits per pixel.
-         */
-        public int getBitsPerPixel() {
-            return id == -1 ? -1 : BPP.getValue(id);
-        }
-
-        /**
-         * If set, the pixel values are indexes into a palette.
-         * If clear, the pixel values are actual colors.
-         */
-        public boolean isPaletteIndexed() {
-            return id != -1 && PALETTE.isSet(id);
-        }
-
-        /**
-         * If set, the pixel format is supported in Windows GDI.
-         * If clear, the pixel format is not supported in Windows GDI.
-         */
-        public boolean isGDISupported() {
-            return id != -1 && GDI.isSet(id);
-        }
-
-        /**
-         * If set, the pixel format includes an alpha transparency component.
-         * If clear, the pixel format does not include a component that specifies transparency.
-         */
-        public boolean isAlpha() {
-            return id != -1 && ALPHA.isSet(id);
-        }
-
-        /**
-         * If set, each color component in the pixel has been premultiplied by the pixel's alpha transparency value.
-         * If clear, each color component is multiplied by the pixel's alpha transparency value when the source pixel
-         * is blended with the destination pixel.
-         */
-        public boolean isPreMultiplied() {
-            return id != -1 && PREMULTI.isSet(id);
-        }
-
-        /**
-         * If set, the pixel format supports extended colors in 16-bits per channel.
-         * If clear, extended colors are not supported.
-         */
-        public boolean isExtendedColors() {
-            return id != -1 && EXTCOLORS.isSet(id);
-        }
-
-        /**
-         * If set, the pixel format is "canonical", which means that 32 bits per pixel are
-         * supported, with 24-bits for color components and an 8-bit alpha channel.
-         * If clear, the pixel format is not canonical.
-         */
-        public boolean isCanonical() {
-            return id != -1 && CANONICAL.isSet(id);
-        }
-    }
-
-    public enum EmfPlusMetafileDataType {
-        Wmf(0x00000001),
-        WmfPlaceable(0x00000002),
-        Emf(0x00000003),
-        EmfPlusOnly(0x00000004),
-        EmfPlusDual(0x00000005);
-
-        public final int id;
-
-        EmfPlusMetafileDataType(int id) {
-            this.id = id;
-        }
-
-        public static EmfPlusMetafileDataType valueOf(int id) {
-            for (EmfPlusMetafileDataType wrt : values()) {
-                if (wrt.id == id) return wrt;
-            }
-            return null;
-        }
-    }
-
-    /**
-     * The WrapMode enumeration defines how the pattern from a texture or gradient brush is tiled
-     * across a shape or at shape boundaries, when it is smaller than the area being filled.
-     */
-    public enum EmfPlusWrapMode {
-        WRAP_MODE_TILE(0x00000000),
-        WRAP_MODE_TILE_FLIP_X(0x00000001),
-        WRAP_MODE_TILE_FLIP_Y(0x00000002),
-        WRAP_MODE_TILE_FLIP_XY(0x00000003),
-        WRAP_MODE_CLAMP(0x00000004)
-        ;
-
-        public final int id;
-
-        EmfPlusWrapMode(int id) {
-            this.id = id;
-        }
-
-        public static EmfPlusWrapMode valueOf(int id) {
-            for (EmfPlusWrapMode wrt : values()) {
-                if (wrt.id == id) return wrt;
-            }
-            return null;
-        }
-    }
-
-    public enum EmfPlusObjectClamp {
-        /** The object is clamped to a rectangle. */
-        RectClamp(0x00000000),
-        /** The object is clamped to a bitmap. */
-        BitmapClamp(0x00000001)
-        ;
-
-        public final int id;
-
-        EmfPlusObjectClamp(int id) {
-            this.id = id;
-        }
-
-        public static EmfPlusObjectClamp valueOf(int id) {
-            for (EmfPlusObjectClamp wrt : values()) {
-                if (wrt.id == id) return wrt;
-            }
-            return null;
-        }
-    }
 
     /**
      * The EmfPlusObject record specifies an object for use in graphics operations. The object definition
-     * can span multiple records, which is indicated by the value of the Flags field.
+     * can span multiple records), which is indicated by the value of the Flags field.
      */
     public static class EmfPlusObject implements HemfPlusRecord, EmfPlusObjectId {
 
@@ -400,189 +196,4 @@ public class HemfPlusObject {
             return dataSize;
         }
     }
-
-    public static class EmfPlusImage implements EmfPlusObjectData {
-        private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
-        private EmfPlusImageDataType imageDataType;
-        private int bitmapWidth;
-        private int bitmapHeight;
-        private int bitmapStride;
-        private EmfPlusPixelFormat pixelFormat;
-        private EmfPlusBitmapDataType bitmapType;
-        private byte[] imageData;
-        private EmfPlusMetafileDataType metafileType;
-        private int metafileDataSize;
-
-        public EmfPlusImageDataType getImageDataType() {
-            return imageDataType;
-        }
-
-        public byte[] getImageData() {
-            return imageData;
-        }
-
-        public EmfPlusPixelFormat getPixelFormat() {
-            return pixelFormat;
-        }
-
-        public EmfPlusBitmapDataType getBitmapType() {
-            return bitmapType;
-        }
-
-        public int getBitmapWidth() {
-            return bitmapWidth;
-        }
-
-        public int getBitmapHeight() {
-            return bitmapHeight;
-        }
-
-        public int getBitmapStride() {
-            return bitmapStride;
-        }
-
-        public EmfPlusMetafileDataType getMetafileType() {
-            return metafileType;
-        }
-
-        @Override
-        public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
-            leis.mark(LittleEndianConsts.INT_SIZE);
-            long size = graphicsVersion.init(leis);
-
-            if (graphicsVersion.getGraphicsVersion() == null || graphicsVersion.getMetafileSignature() != 0xDBC01) {
-                // CONTINUABLE is not always correctly set, so we check the version field if this record is continued
-                imageDataType = EmfPlusImageDataType.CONTINUED;
-                leis.reset();
-                size = 0;
-            } else {
-                imageDataType = EmfPlusImageDataType.valueOf(leis.readInt());
-                size += LittleEndianConsts.INT_SIZE;
-            }
-
-            if (imageDataType == null) {
-                imageDataType = EmfPlusImageDataType.UNKNOWN;
-            }
-
-            int fileSize;
-            switch (imageDataType) {
-                default:
-                case UNKNOWN:
-                case CONTINUED:
-                    bitmapWidth = -1;
-                    bitmapHeight = -1;
-                    bitmapStride = -1;
-                    bitmapType = null;
-                    pixelFormat = null;
-
-                    fileSize = (int) (dataSize);
-                    break;
-
-                case BITMAP:
-                    // A 32-bit signed integer that specifies the width in pixels of the area occupied by the bitmap.
-                    // If the image is compressed, according to the Type field, this value is undefined and MUST be ignored.
-                    bitmapWidth = leis.readInt();
-                    // A 32-bit signed integer that specifies the height in pixels of the area occupied by the bitmap.
-                    // If the image is compressed, according to the Type field, this value is undefined and MUST be ignored.
-                    bitmapHeight = leis.readInt();
-                    // A 32-bit signed integer that specifies the byte offset between the beginning of one scan-line
-                    // and the next. This value is the number of bytes per pixel, which is specified in the PixelFormat
-                    // field, multiplied by the width in pixels, which is specified in the Width field.
-                    // The value of this field MUST be a multiple of four. If the image is compressed, according to the
-                    // Type field, this value is undefined and MUST be ignored.
-                    bitmapStride = leis.readInt();
-                    // A 32-bit unsigned integer that specifies the format of the pixels that make up the bitmap image.
-                    //  The supported pixel formats are specified in the PixelFormat enumeration
-                    int pixelFormatInt = leis.readInt();
-                    // A 32-bit unsigned integer that specifies the metafileType of data in the BitmapData field.
-                    // This value MUST be defined in the BitmapDataType enumeration
-                    bitmapType = EmfPlusBitmapDataType.valueOf(leis.readInt());
-                    size += 5 * LittleEndianConsts.INT_SIZE;
-
-                    pixelFormat = (bitmapType == EmfPlusBitmapDataType.PIXEL)
-                            ? EmfPlusPixelFormat.valueOf(pixelFormatInt)
-                            : EmfPlusPixelFormat.UNDEFINED;
-                    assert (pixelFormat != null);
-
-                    fileSize = (int) (dataSize - size);
-
-                    break;
-
-                case METAFILE:
-                    // A 32-bit unsigned integer that specifies the type of metafile that is embedded in the
-                    // MetafileData field. This value MUST be defined in the MetafileDataType enumeration
-                    metafileType = EmfPlusMetafileDataType.valueOf(leis.readInt());
-
-                    // A 32-bit unsigned integer that specifies the size in bytes of the
-                    // metafile data in the MetafileData field.
-                    metafileDataSize = leis.readInt();
-
-                    size += 2 * LittleEndianConsts.INT_SIZE;
-
-                    // ignore metafileDataSize, which might ignore a (placeable) header in front
-                    // and also use the remaining bytes, which might contain padding bytes ...
-                    fileSize = (int) (dataSize - size);
-                    break;
-            }
-
-            assert (fileSize <= dataSize - size);
-
-            imageData = IOUtils.toByteArray(leis, fileSize, MAX_OBJECT_SIZE);
-
-            // TODO: remove padding bytes between placeable WMF header and body?
-
-            return size + fileSize;
-        }
-    }
-
-    public static class EmfPlusImageAttributes implements EmfPlusObjectData {
-        private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
-        private EmfPlusWrapMode wrapMode;
-        private Color clampColor;
-        private EmfPlusObjectClamp objectClamp;
-
-        @Override
-        public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
-            // An EmfPlusGraphicsVersion object that specifies the version of operating system graphics that
-            // was used to create this object.
-            long size = graphicsVersion.init(leis);
-
-            // A 32-bit field that is not used and MUST be ignored.
-            leis.skip(LittleEndianConsts.INT_SIZE);
-
-            // A 32-bit unsigned integer that specifies how to handle edge conditions with a value from the WrapMode enumeration
-            wrapMode = EmfPlusWrapMode.valueOf(leis.readInt());
-
-            // An EmfPlusARGB object that specifies the edge color to use when the WrapMode value is WrapModeClamp.
-            // This color is visible when the source rectangle processed by an EmfPlusDrawImage record is larger than the image itself.
-            byte[] buf = new byte[LittleEndianConsts.INT_SIZE];
-            leis.readFully(buf);
-            clampColor = new Color(buf[2], buf[1], buf[0], buf[3]);
-
-            // A 32-bit signed integer that specifies the object clamping behavior. It is not used until this object
-            // is applied to an image being drawn. This value MUST be one of the values defined in the following table.
-            objectClamp = EmfPlusObjectClamp.valueOf(leis.readInt());
-
-            // A value that SHOULD be set to zero and MUST be ignored upon receipt.
-            leis.skip(LittleEndianConsts.INT_SIZE);
-
-            return size + 5*LittleEndianConsts.INT_SIZE;
-        }
-
-        public EmfPlusGraphicsVersion getGraphicsVersion() {
-            return graphicsVersion;
-        }
-
-        public EmfPlusWrapMode getWrapMode() {
-            return wrapMode;
-        }
-
-        public Color getClampColor() {
-            return clampColor;
-        }
-
-        public EmfPlusObjectClamp getObjectClamp() {
-            return objectClamp;
-        }
-    }
 }
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusPath.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusPath.java
new file mode 100644 (file)
index 0000000..86d2e48
--- /dev/null
@@ -0,0 +1,150 @@
+/* ====================================================================
+   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.awt.geom.Point2D;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.function.BiFunction;
+
+import org.apache.poi.hemf.record.emfplus.HemfPlusDraw.EmfPlusCompressed;
+import org.apache.poi.hemf.record.emfplus.HemfPlusDraw.EmfPlusRelativePosition;
+import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
+import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType;
+import org.apache.poi.util.BitField;
+import org.apache.poi.util.BitFieldFactory;
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianInputStream;
+
+public class HemfPlusPath {
+
+    /** The PathPointType enumeration defines types of points on a graphics path. */
+    public enum EmfPlusPathPointType {
+        /** Specifies that the point is the starting point of a path. */
+        START,
+        /** Specifies that the point is one of the two endpoints of a line. */
+        LINE,
+        // not defined
+        UNUSED,
+        /** Specifies that the point is an endpoint or control point of a cubic Bezier curve */
+        BEZIER;
+    }
+
+    public static class EmfPlusPath implements EmfPlusObjectData, EmfPlusCompressed, EmfPlusRelativePosition {
+        /**
+         * If set, the point types in the PathPointTypes array are specified by EmfPlusPathPointTypeRLE objects,
+         * which use run-length encoding (RLE) compression, and/or EmfPlusPathPointType objects.
+         * If clear, the point types in the PathPointTypes array are specified by EmfPlusPathPointType objects.
+         */
+        private static final BitField RLE_COMPRESSED = BitFieldFactory.getInstance(0x00001000);
+
+        /** Specifies that a line segment that passes through the point is dashed. */
+        private static final BitField POINT_TYPE_DASHED = BitFieldFactory.getInstance(0x10);
+
+        /** Specifies that the point is a position marker. */
+        private static final BitField POINT_TYPE_MARKER = BitFieldFactory.getInstance(0x20);
+
+        /** Specifies that the point is the endpoint of a subpath. */
+        private static final BitField POINT_TYPE_CLOSE = BitFieldFactory.getInstance(0x80);
+
+        private static final BitField POINT_TYPE_ENUM = BitFieldFactory.getInstance(0x0F);
+
+
+        private static final BitField POINT_RLE_BEZIER = BitFieldFactory.getInstance(0x80);
+
+        private static final BitField POINT_RLE_COUNT = BitFieldFactory.getInstance(0x3F);
+
+        private final HemfPlusHeader.EmfPlusGraphicsVersion version = new HemfPlusHeader.EmfPlusGraphicsVersion();
+        private int pointFlags;
+        private Point2D[] pathPoints;
+        private byte[] pointTypes;
+
+        @Override
+        public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
+            long size = version.init(leis);
+
+            // A 32-bit unsigned integer that specifies the number of points and associated point types that
+            // are defined by this object.
+            int pointCount = leis.readInt();
+
+            // A 16-bit unsigned integer that specifies how to interpret the points
+            // and associated point types that are defined by this object.
+            pointFlags = leis.readShort();
+
+            leis.skipFully(LittleEndianConsts.SHORT_SIZE);
+            size += 2* LittleEndianConsts.INT_SIZE;
+
+            BiFunction<LittleEndianInputStream,Point2D,Integer> readPoint;
+
+            if (isRelativePosition()) {
+                readPoint = HemfPlusDraw::readPointR;
+            } else if (isCompressed()) {
+                readPoint = HemfPlusDraw::readPointS;
+            } else {
+                readPoint = HemfPlusDraw::readPointF;
+            }
+
+            pathPoints = new Point2D[pointCount];
+            for (int i=0; i<pointCount; i++) {
+                pathPoints[i] = new Point2D.Double();
+                size += readPoint.apply(leis,pathPoints[i]);
+            }
+
+            pointTypes = new byte[pointCount];
+            final boolean isRLE = RLE_COMPRESSED.isSet(pointFlags);
+            if (isRLE) {
+                for (int i=0, rleCount; i<pointCount; i+=rleCount, size+=2) {
+                    rleCount = POINT_RLE_COUNT.getValue(leis.readByte());
+                    Arrays.fill(pointTypes, pointCount, pointCount+rleCount, leis.readByte());
+                }
+            } else {
+                leis.readFully(pointTypes);
+                size += pointCount;
+            }
+
+            int padding = (int)((4 - (size % 4)) % 4);
+            leis.skipFully(padding);
+            size += padding;
+
+            return size;
+        }
+
+        public boolean isPointDashed(int index) {
+            return POINT_TYPE_DASHED.isSet(pointTypes[index]);
+        }
+
+        public boolean isPointMarker(int index) {
+            return POINT_TYPE_MARKER.isSet(pointTypes[index]);
+        }
+
+        public boolean isPointClosed(int index) {
+            return POINT_TYPE_CLOSE.isSet(pointTypes[index]);
+        }
+
+        public EmfPlusPathPointType getPointType(int index) {
+            return EmfPlusPathPointType.values()[POINT_TYPE_ENUM.getValue(pointTypes[index])];
+        }
+
+        @Override
+        public int getFlags() {
+            return pointFlags;
+        }
+    }
+
+
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusPen.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusPen.java
new file mode 100644 (file)
index 0000000..6dff00b
--- /dev/null
@@ -0,0 +1,602 @@
+/* ====================================================================
+   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 static org.apache.poi.hemf.record.emf.HemfFill.readXForm;
+import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readPointF;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.io.IOException;
+import java.util.function.Consumer;
+
+import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
+import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
+import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType;
+import org.apache.poi.hemf.record.emfplus.HemfPlusPath.EmfPlusPath;
+import org.apache.poi.util.BitField;
+import org.apache.poi.util.BitFieldFactory;
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianInputStream;
+
+public class HemfPlusPen {
+    /**
+     * The LineCapType enumeration defines types of line caps to use at the ends of lines that are drawn
+     * with graphics pens.
+     */
+    public enum EmfPlusLineCapType {
+        /** Specifies a squared-off line cap. The end of the line MUST be the last point in the line. */
+        FLAT(0X00000000),
+        /**
+         * Specifies a square line cap. The center of the square MUST be located at
+         * the last point in the line. The width of the square is the line width.
+         */
+        SQUARE(0X00000001),
+        /**
+         * Specifies a circular line cap. The center of the circle MUST be located at
+         * the last point in the line. The diameter of the circle is the line width.
+         */
+        ROUND(0X00000002),
+        /**
+         * Specifies a triangular line cap. The base of the triangle MUST be located
+         * at the last point in the line. The base of the triangle is the line width.
+         */
+        TRIANGLE(0X00000003),
+        /** Specifies that the line end is not anchored. */
+        NO_ANCHOR(0X00000010),
+        /**
+         * Specifies that the line end is anchored with a square line cap. The center of the square MUST be located
+         * at the last point in the line. The height and width of the square are the line width.
+         */
+        SQUARE_ANCHOR(0X00000011),
+        /**
+         * Specifies that the line end is anchored with a circular line cap. The center of the circle MUST be located
+         * at the last point in the line. The circle SHOULD be wider than the line.
+         */
+        ROUND_ANCHOR(0X00000012),
+        /**
+         * Specifies that the line end is anchored with a diamond-shaped line cap, which is a square turned at
+         * 45 degrees. The center of the diamond MUST be located at the last point in the line.
+         * The diamond SHOULD be wider than the line.
+         */
+        DIAMOND_ANCHOR(0X00000013),
+        /**
+         * Specifies that the line end is anchored with an arrowhead shape. The arrowhead point MUST be located at
+         * the last point in the line. The arrowhead SHOULD be wider than the line.
+         */
+        ARROW_ANCHOR(0X00000014),
+        /** Mask used to check whether a line cap is an anchor cap. */
+        ANCHOR_MASK(0X000000F0),
+        /** Specifies a custom line cap. */
+        CUSTOM(0X000000FF)
+        ;
+
+        public final int id;
+
+        EmfPlusLineCapType(int id) {
+            this.id = id;
+        }
+
+        public static EmfPlusLineCapType valueOf(int id) {
+            for (EmfPlusLineCapType wrt : values()) {
+                if (wrt.id == id) return wrt;
+            }
+            return null;
+        }
+    }
+
+    /**
+     * The LineJoinType enumeration defines ways to join two lines that are drawn by the same graphics
+     * pen and whose ends meet.
+     */
+    public enum EmfPlusLineJoin {
+        MITER(0X00000000),
+        BEVEL(0X00000001),
+        ROUND(0X00000002),
+        MITER_CLIPPED(0X00000003)
+        ;
+
+        public final int id;
+
+        EmfPlusLineJoin(int id) {
+            this.id = id;
+        }
+
+        public static EmfPlusLineJoin valueOf(int id) {
+            for (EmfPlusLineJoin wrt : values()) {
+                if (wrt.id == id) return wrt;
+            }
+            return null;
+        }
+
+    }
+
+    /** The LineStyle enumeration defines styles of lines that are drawn with graphics pens. */
+    public enum EmfPlusLineStyle {
+        /** Specifies a solid line. */
+        SOLID(0X00000000),
+        /** Specifies a dashed line. */
+        DASH(0X00000001),
+        /** Specifies a dotted line. */
+        DOT(0X00000002),
+        /** Specifies an alternating dash-dot line. */
+        DASH_DOT(0X00000003),
+        /** Specifies an alternating dash-dot-dot line. */
+        DASH_DOT_DOT(0X00000004),
+        /** Specifies a user-defined, custom dashed line. */
+        CUSTOM(0X00000005)
+        ;
+
+        public final int id;
+
+        EmfPlusLineStyle(int id) {
+            this.id = id;
+        }
+
+        public static EmfPlusLineStyle valueOf(int id) {
+            for (EmfPlusLineStyle wrt : values()) {
+                if (wrt.id == id) return wrt;
+            }
+            return null;
+        }
+    }
+
+    /**
+     * The DashedLineCapType enumeration defines types of line caps to use at the ends of dashed lines
+     * that are drawn with graphics pens.
+     */
+    public enum EmfPlusDashedLineCapType {
+        /** Specifies a flat dashed line cap. */
+        FLAT(0X00000000),
+        /** Specifies a round dashed line cap. */
+        ROUND(0X00000002),
+        /** Specifies a triangular dashed line cap. */
+        TRIANGLE(0X00000003)
+        ;
+
+        public final int id;
+
+        EmfPlusDashedLineCapType(int id) {
+            this.id = id;
+        }
+
+        public static EmfPlusDashedLineCapType valueOf(int id) {
+            for (EmfPlusDashedLineCapType wrt : values()) {
+                if (wrt.id == id) return wrt;
+            }
+            return null;
+        }
+    }
+
+    /**
+     * The PenAlignment enumeration defines the distribution of the width of the pen with respect to the
+     * line being drawn.
+     */
+    public enum EmfPlusPenAlignment {
+        /** Specifies that the EmfPlusPen object is centered over the theoretical line. */
+        CENTER(0X00000000),
+        /** Specifies that the pen is positioned on the inside of the theoretical line. */
+        INSET(0X00000001),
+        /** Specifies that the pen is positioned to the left of the theoretical line. */
+        LEFT(0X00000002),
+        /** Specifies that the pen is positioned on the outside of the theoretical line. */
+        OUTSET(0X00000003),
+        /** Specifies that the pen is positioned to the right of the theoretical line. */
+        RIGHT(0X00000004)
+        ;
+
+        public final int id;
+
+        EmfPlusPenAlignment(int id) {
+            this.id = id;
+        }
+
+        public static EmfPlusPenAlignment valueOf(int id) {
+            for (EmfPlusPenAlignment wrt : values()) {
+                if (wrt.id == id) return wrt;
+            }
+            return null;
+        }
+    }
+
+    public static class EmfPlusPen implements EmfPlusObjectData {
+
+
+        /**
+         * If set, a 2x3 transform matrix MUST be specified in the OptionalData field of an EmfPlusPenData object.
+         */
+        private final static BitField TRANSFORM = BitFieldFactory.getInstance(0x00000001);
+        /**
+         * If set, the style of a starting line cap MUST be specified in the OptionalData field of an
+         * EmfPlusPenData object.
+         */
+        private final static BitField START_CAP = BitFieldFactory.getInstance(0x00000002);
+        /**
+         * Indicates whether the style of an ending line cap MUST be specified in the OptionalData field
+         * of an EmfPlusPenData object.
+         */
+        private final static BitField END_CAP = BitFieldFactory.getInstance(0x00000004);
+        /**
+         * Indicates whether a line join type MUST be specified in the OptionalData
+         * field of an EmfPlusPenData object.
+         */
+        private final static BitField JOIN = BitFieldFactory.getInstance(0x00000008);
+        /**
+         * Indicates whether a miter limit MUST be specified in the OptionalData field of an EmfPlusPenData object.
+         */
+        private final static BitField MITER_LIMIT = BitFieldFactory.getInstance(0x00000010);
+        /**
+         * Indicates whether a line style MUST be specified in the OptionalData field of an EmfPlusPenData object.
+         */
+        private final static BitField LINE_STYLE = BitFieldFactory.getInstance(0x00000020);
+        /**
+         * Indicates whether a dashed line cap MUST be specified in the OptionalData field of an EmfPlusPenData object.
+         */
+        private final static BitField DASHED_LINE_CAP = BitFieldFactory.getInstance(0x00000040);
+        /**
+         * Indicates whether a dashed line offset MUST be specified in the OptionalData field of an EmfPlusPenData object.
+         */
+        private final static BitField DASHED_LINE_OFFSET = BitFieldFactory.getInstance(0x00000080);
+        /**
+         * Indicates whether an EmfPlusDashedLineData object MUST be specified in the
+         * OptionalData field of an EmfPlusPenData object.
+         */
+        private final static BitField DASHED_LINE = BitFieldFactory.getInstance(0x00000100);
+        /**
+         * Indicates whether a pen alignment MUST be specified in the OptionalData field of an EmfPlusPenData object.
+         */
+        private final static BitField NON_CENTER = BitFieldFactory.getInstance(0x00000200);
+        /**
+         * Indicates whether the length and content of a EmfPlusCompoundLineData object are present in the
+         * OptionalData field of an EmfPlusPenData object.
+         */
+        private final static BitField COMPOUND_LINE = BitFieldFactory.getInstance(0x00000400);
+        /**
+         * Indicates whether an EmfPlusCustomStartCapData object MUST be specified
+         * in the OptionalData field of an EmfPlusPenData object.y
+         */
+        private final static BitField CUSTOM_START_CAP = BitFieldFactory.getInstance(0x00000800);
+        /**
+         * Indicates whether an EmfPlusCustomEndCapData object MUST be specified in
+         * the OptionalData field of an EmfPlusPenData object.
+         */
+        private final static BitField CUSTOM_END_CAP = BitFieldFactory.getInstance(0x00001000);
+
+        private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
+
+
+        private int type;
+        private int penDataFlags;
+        private HemfPlusDraw.EmfPlusUnitType unitType;
+        private double penWidth;
+        private AffineTransform trans;
+        private EmfPlusLineCapType startCap, endCap;
+        private EmfPlusLineJoin join;
+        private Double mitterLimit;
+        private EmfPlusLineStyle style;
+        EmfPlusDashedLineCapType dashedLineCapType;
+        private Double dashOffset;
+        private double[] dashedLineData;
+        private EmfPlusPenAlignment penAlignment;
+        private double[] compoundLineData;
+        private EmfPlusCustomLineCap customStartCap;
+        private EmfPlusCustomLineCap customEndCap;
+
+        @Override
+        public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
+            // An EmfPlusGraphicsVersion object that specifies the version of operating system graphics that
+            // was used to create this object.
+            long size = graphicsVersion.init(leis);
+            // This field MUST be set to zero.
+            type = leis.readInt();
+            // A 32-bit unsigned integer that specifies the data in the OptionalData field.
+            // This value MUST be composed of PenData flags
+            penDataFlags = leis.readInt();
+            // A 32-bit unsigned integer that specifies the measuring units for the pen.
+            // The value MUST be from the UnitType enumeration
+            unitType = HemfPlusDraw.EmfPlusUnitType.valueOf(leis.readInt());
+            // A 32-bit floating-point value that specifies the width of the line drawn by the pen in the units specified
+            // by the PenUnit field. If a zero width is specified, a minimum value is used, which is determined by the units.
+            penWidth = leis.readFloat();
+            size += 4* LittleEndianConsts.INT_SIZE;
+
+            if (TRANSFORM.isSet(penDataFlags)) {
+                // An optional EmfPlusTransformMatrix object that specifies a world space to device space transform for
+                // the pen. This field MUST be present if the PenDataTransform flag is set in the PenDataFlags field of
+                // the EmfPlusPenData object.
+                trans = new AffineTransform();
+                size += readXForm(leis, trans);
+            }
+
+            if (START_CAP.isSet(penDataFlags)) {
+                // An optional 32-bit signed integer that specifies the shape for the start of a line in the
+                // CustomStartCapData field. This field MUST be present if the PenDataStartCap flag is set in the
+                // PenDataFlags field of the EmfPlusPenData object, and the value MUST be defined in the LineCapType enumeration
+                startCap = EmfPlusLineCapType.valueOf(leis.readInt());
+                size += LittleEndianConsts.INT_SIZE;
+            }
+
+            if (END_CAP.isSet(penDataFlags)) {
+                // An optional 32-bit signed integer that specifies the shape for the end of a line in the
+                // CustomEndCapData field. This field MUST be present if the PenDataEndCap flag is set in the
+                // PenDataFlags field of the EmfPlusPenData object, and the value MUST be defined in the LineCapType enumeration.
+                endCap = EmfPlusLineCapType.valueOf(leis.readInt());
+                size += LittleEndianConsts.INT_SIZE;
+            }
+
+            if (JOIN.isSet(penDataFlags)) {
+                // An optional 32-bit signed integer that specifies how to join two lines that are drawn by the same pen
+                // and whose ends meet. This field MUST be present if the PenDataJoin flag is set in the PenDataFlags
+                // field of the EmfPlusPenData object, and the value MUST be defined in the LineJoinType enumeration
+                join = EmfPlusLineJoin.valueOf(leis.readInt());
+                size += LittleEndianConsts.INT_SIZE;
+            }
+
+            if (MITER_LIMIT.isSet(penDataFlags)) {
+                // An optional 32-bit floating-point value that specifies the miter limit, which is the maximum allowed
+                // ratio of miter length to line width. The miter length is the distance from the intersection of the
+                // line walls on the inside the join to the intersection of the line walls outside the join. The miter
+                // length can be large when the angle between two lines is small. This field MUST be present if the
+                // PenDataMiterLimit flag is set in the PenDataFlags field of the EmfPlusPenData object.
+                mitterLimit = (double)leis.readFloat();
+                size += LittleEndianConsts.INT_SIZE;
+            }
+
+            if (LINE_STYLE.isSet(penDataFlags)) {
+                // An optional 32-bit signed integer that specifies the style used for lines drawn with this pen object.
+                // This field MUST be present if the PenDataLineStyle flag is set in the PenDataFlags field of the
+                // EmfPlusPenData object, and the value MUST be defined in the LineStyle enumeration
+                style = EmfPlusLineStyle.valueOf(leis.readInt());
+                size += LittleEndianConsts.INT_SIZE;
+            }
+
+            if (DASHED_LINE_CAP.isSet(penDataFlags)) {
+                // An optional 32-bit signed integer that specifies the shape for both ends of each dash in a dashed line.
+                // This field MUST be present if the PenDataDashedLineCap flag is set in the PenDataFlags field of the
+                // EmfPlusPenData object, and the value MUST be defined in the DashedLineCapType enumeration
+                dashedLineCapType = EmfPlusDashedLineCapType.valueOf(leis.readInt());
+                size += LittleEndianConsts.INT_SIZE;
+            }
+
+            if (DASHED_LINE_OFFSET.isSet(penDataFlags)) {
+                // An optional 32-bit floating-point value that specifies the distance from the start of a line to the
+                // start of the first space in a dashed line pattern. This field MUST be present if the
+                // PenDataDashedLineOffset flag is set in the PenDataFlags field of the EmfPlusPenData object.
+                dashOffset = (double)leis.readFloat();
+                size += LittleEndianConsts.INT_SIZE;
+            }
+
+            if (DASHED_LINE.isSet(penDataFlags)) {
+                // A 32-bit unsigned integer that specifies the number of elements in the DashedLineData field.
+                int dashesSize = leis.readInt();
+                if (dashesSize < 0 || dashesSize > 1000) {
+                    throw new RuntimeException("Invalid dash data size");
+                }
+
+                // An array of DashedLineDataSize floating-point values that specify the lengths of the dashes and spaces in a dashed line.
+                dashedLineData = new double[dashesSize];
+                for (int i=0; i<dashesSize; i++) {
+                    dashedLineData[i] = leis.readFloat();
+                }
+
+                size += LittleEndianConsts.INT_SIZE * (dashesSize+1);
+            }
+
+            if (NON_CENTER.isSet(penDataFlags)) {
+                // An optional 32-bit signed integer that specifies the distribution of the pen width with respect to
+                // the coordinates of the line being drawn. This field MUST be present if the PenDataNonCenter flag is
+                // set in the PenDataFlags field of the EmfPlusPenData object, and the value MUST be defined in the
+                // PenAlignment enumeration
+                penAlignment = EmfPlusPenAlignment.valueOf(leis.readInt());
+                size += LittleEndianConsts.INT_SIZE;
+            }
+
+            if (COMPOUND_LINE.isSet(penDataFlags)) {
+                // A 32-bit unsigned integer that specifies the number of elements in the CompoundLineData field.
+                int compoundSize = leis.readInt();
+                if (compoundSize < 0 || compoundSize > 1000) {
+                    throw new RuntimeException("Invalid compound line data size");
+                }
+
+                // An array of CompoundLineDataSize floating-point values that specify the compound line of a pen.
+                // The elements MUST be in increasing order, and their values MUST be between 0.0 and 1.0, inclusive.
+                compoundLineData = new double[compoundSize];
+
+                for (int i=0; i<compoundSize; i++) {
+                    compoundLineData[i] = leis.readFloat();
+                }
+                size += LittleEndianConsts.INT_SIZE * (compoundSize+1);
+            }
+
+            if (CUSTOM_START_CAP.isSet(penDataFlags)) {
+                size += initCustomCap(c -> customStartCap = c, leis);
+            }
+
+            if (CUSTOM_END_CAP.isSet(penDataFlags)) {
+                size += initCustomCap(c -> customEndCap = c, leis);
+            }
+
+            return size;
+        }
+
+        private long initCustomCap(Consumer<EmfPlusCustomLineCap> setter, LittleEndianInputStream leis) throws IOException {
+            EmfPlusGraphicsVersion version = new EmfPlusGraphicsVersion();
+            long size = version.init(leis);
+
+            boolean adjustableArrow = (leis.readInt() != 0);
+            size += LittleEndianConsts.INT_SIZE;
+
+            EmfPlusCustomLineCap cap = (adjustableArrow) ? new EmfPlusAdjustableArrowCap() : new EmfPlusPathArrowCap();
+            size += cap.init(leis);
+
+            setter.accept(cap);
+
+            return size;
+        }
+
+        @Internal
+        public interface EmfPlusCustomLineCap {
+            long init(LittleEndianInputStream leis) throws IOException;
+        }
+
+        public static class EmfPlusPathArrowCap implements EmfPlusCustomLineCap {
+            /**
+             * If set, an EmfPlusFillPath object MUST be specified in the OptionalData field of the
+             * EmfPlusCustomLineCapData object for filling the custom line cap.
+             */
+            private static final BitField FILL_PATH = BitFieldFactory.getInstance(0x00000001);
+            /**
+             * If set, an EmfPlusLinePath object MUST be specified in the OptionalData field of the
+             * EmfPlusCustomLineCapData object for outlining the custom line cap.
+             */
+            private static final BitField LINE_PATH = BitFieldFactory.getInstance(0x00000002);
+
+
+            private int dataFlags;
+            private EmfPlusLineCapType baseCap;
+            private double baseInset;
+            private EmfPlusLineCapType startCap;
+            private EmfPlusLineCapType endCap;
+            private EmfPlusLineJoin join;
+            private double mitterLimit;
+            private double widthScale;
+            private final Point2D fillHotSpot = new Point2D.Double();
+            private final Point2D lineHotSpot = new Point2D.Double();
+            private EmfPlusPath fillPath;
+            private EmfPlusPath outlinePath;
+
+            @Override
+            public long init(LittleEndianInputStream leis) throws IOException {
+                // A 32-bit unsigned integer that specifies the data in the OptionalData field.
+                // This value MUST be composed of CustomLineCapData flags
+                dataFlags = leis.readInt();
+
+                // A 32-bit unsigned integer that specifies the value from the LineCap enumeration on which
+                // the custom line cap is based.
+                baseCap = EmfPlusLineCapType.valueOf(leis.readInt());
+
+                // A 32-bit floating-point value that specifies the distance between the
+                // beginning of the line cap and the end of the line.
+                baseInset = leis.readFloat();
+
+                // A 32-bit unsigned integer that specifies the value in the LineCap enumeration that indicates the line
+                // cap used at the start/end of the line to be drawn.
+                startCap = EmfPlusLineCapType.valueOf(leis.readInt());
+                endCap = EmfPlusLineCapType.valueOf(leis.readInt());
+
+                // A 32-bit unsigned integer that specifies the value in the LineJoin enumeration, which specifies how
+                // to join two lines that are drawn by the same pen and whose ends meet. At the intersection of the two
+                // line ends, a line join makes the connection look more continuous.
+                join = EmfPlusLineJoin.valueOf(leis.readInt());
+
+                // A 32-bit floating-point value that contains the limit of the thickness of the join on a mitered corner
+                // by setting the maximum allowed ratio of miter length to line width.
+                mitterLimit = leis.readFloat();
+
+                // A 32-bit floating-point value that specifies the amount by which to scale the custom line cap with
+                // respect to the width of the EmfPlusPen object that is used to draw the lines.
+                widthScale = leis.readFloat();
+
+                int size = 8* LittleEndianConsts.INT_SIZE;
+
+                // An EmfPlusPointF object that is not currently used. It MUST be set to {0.0, 0.0}.
+                size += readPointF(leis, fillHotSpot);
+                size += readPointF(leis, lineHotSpot);
+
+                if (FILL_PATH.isSet(dataFlags)) {
+                    fillPath = new EmfPlusPath();
+                    size += fillPath.init(leis, -1, null, -1);
+                }
+
+                if (LINE_PATH.isSet(dataFlags)) {
+                    outlinePath = new EmfPlusPath();
+                    size += outlinePath.init(leis, -1, null, -1);
+                }
+
+                return size;
+            }
+        }
+
+        public static class EmfPlusAdjustableArrowCap implements EmfPlusCustomLineCap {
+            private double width;
+            private double height;
+            private double middleInset;
+            private boolean isFilled;
+            private EmfPlusLineCapType startCap;
+            private EmfPlusLineCapType endCap;
+            private EmfPlusLineJoin join;
+            private double mitterLimit;
+            private double widthScale;
+            private final Point2D fillHotSpot = new Point2D.Double();
+            private final Point2D lineHotSpot = new Point2D.Double();
+
+            @Override
+            public long init(LittleEndianInputStream leis) throws IOException {
+                // A 32-bit floating-point value that specifies the width of the arrow cap.
+                // The width of the arrow cap is scaled by the width of the EmfPlusPen object that is used to draw the
+                // line being capped. For example, when drawing a capped line with a pen that has a width of 5 pixels,
+                // and the adjustable arrow cap object has a width of 3, the actual arrow cap is drawn 15 pixels wide.
+                width = leis.readFloat();
+
+                // A 32-bit floating-point value that specifies the height of the arrow cap.
+                // The height of the arrow cap is scaled by the width of the EmfPlusPen object that is used to draw the
+                // line being capped. For example, when drawing a capped line with a pen that has a width of 5 pixels,
+                // and the adjustable arrow cap object has a height of 3, the actual arrow cap is drawn 15 pixels high.
+                height = leis.readFloat();
+
+                // A 32-bit floating-point value that specifies the number of pixels between the outline of the arrow
+                // cap and the fill of the arrow cap.
+                middleInset = leis.readFloat();
+
+                // A 32-bit Boolean value that specifies whether the arrow cap is filled.
+                // If the arrow cap is not filled, only the outline is drawn.
+                isFilled = (leis.readInt() != 0);
+
+                // A 32-bit unsigned integer that specifies the value in the LineCap enumeration that indicates
+                // the line cap to be used at the start/end of the line to be drawn.
+                startCap = EmfPlusLineCapType.valueOf(leis.readInt());
+                endCap = EmfPlusLineCapType.valueOf(leis.readInt());
+
+                // 32-bit unsigned integer that specifies the value in the LineJoin enumeration that specifies how to
+                // join two lines that are drawn by the same pen and whose ends meet. At the intersection of the two
+                // line ends, a line join makes the connection look more continuous.
+                join = EmfPlusLineJoin.valueOf(leis.readInt());
+
+                // A 32-bit floating-point value that specifies the limit of the thickness of the join on a mitered
+                // corner by setting the maximum allowed ratio of miter length to line width.
+                mitterLimit = leis.readFloat();
+
+                // A 32-bit floating-point value that specifies the amount by which to scale an EmfPlusCustomLineCap
+                // object with respect to the width of the graphics pen that is used to draw the lines.
+                widthScale = leis.readFloat();
+
+                int size = 9 * LittleEndianConsts.INT_SIZE;
+
+                // An EmfPlusPointF object that is not currently used. It MUST be set to {0.0, 0.0}.
+                size += readPointF(leis, fillHotSpot);
+
+                // An EmfPlusPointF object that is not currently used. It MUST be set to {0.0, 0.0}.
+                size += readPointF(leis, lineHotSpot);
+
+                return size;
+            }
+        }
+
+    }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRegion.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRegion.java
new file mode 100644 (file)
index 0000000..d3f3002
--- /dev/null
@@ -0,0 +1,173 @@
+/* ====================================================================
+   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 static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readRectF;
+
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
+import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
+import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType;
+import org.apache.poi.hemf.record.emfplus.HemfPlusPath.EmfPlusPath;
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianInputStream;
+
+public class HemfPlusRegion {
+    public enum EmfPlusRegionNodeDataType {
+        /**
+         * Specifies a region node with child nodes. A Boolean AND operation SHOULD be applied to the left and right
+         * child nodes specified by an EmfPlusRegionNodeChildNodes object
+         */
+        AND(0X00000001, EmfPlusRegionNode::new),
+        /**
+         * Specifies a region node with child nodes. A Boolean OR operation SHOULD be applied to the left and right
+         * child nodes specified by an EmfPlusRegionNodeChildNodes object.
+         */
+        OR(0X00000002, EmfPlusRegionNode::new),
+        /**
+         * Specifies a region node with child nodes. A Boolean XOR operation SHOULD be applied to the left and right
+         * child nodes specified by an EmfPlusRegionNodeChildNodes object.
+         */
+        XOR(0X00000003, EmfPlusRegionNode::new),
+        /**
+         * Specifies a region node with child nodes. A Boolean operation, defined as "the part of region 1 that is excluded
+         * from region 2", SHOULD be applied to the left and right child nodes specified by an EmfPlusRegionNodeChildNodes object.
+         */
+        EXCLUDE(0X00000004, EmfPlusRegionNode::new),
+        /**
+         * Specifies a region node with child nodes. A Boolean operation, defined as "the part of region 2 that is excluded
+         * from region 1", SHOULD be applied to the left and right child nodes specified by an EmfPlusRegionNodeChildNodes object.
+         */
+        COMPLEMENT(0X00000005, EmfPlusRegionNode::new),
+        /**
+         * Specifies a region node with no child nodes.
+         * The RegionNodeData field SHOULD specify a boundary with an EmfPlusRectF object.
+         */
+        RECT(0X10000000, EmfPlusRegionRect::new),
+        /**
+         * Specifies a region node with no child nodes.
+         * The RegionNodeData field SHOULD specify a boundary with an EmfPlusRegionNodePath object
+         */
+        PATH(0X10000001, EmfPlusRegionPath::new),
+        /** Specifies a region node with no child nodes. The RegionNodeData field SHOULD NOT be present. */
+        EMPTY(0X10000002, EmfPlusRegionEmpty::new),
+        /** Specifies a region node with no child nodes, and its bounds are not defined. */
+        INFINITE(0X10000003, EmfPlusRegionInfinite::new)
+        ;
+
+        public final int id;
+        public final Supplier<EmfPlusRegionNodeData> constructor;
+
+        EmfPlusRegionNodeDataType(int id, Supplier<EmfPlusRegionNodeData> constructor) {
+            this.id = id;
+            this.constructor = constructor;
+        }
+
+        public static EmfPlusRegionNodeDataType valueOf(int id) {
+            for (EmfPlusRegionNodeDataType wrt : values()) {
+                if (wrt.id == id) return wrt;
+            }
+            return null;
+        }
+    }
+
+    public static class EmfPlusRegion implements EmfPlusObjectData {
+
+        private final EmfPlusGraphicsVersion version = new EmfPlusGraphicsVersion();
+        private EmfPlusRegionNodeData regionNode;
+
+        @Override
+        public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
+            long size = version.init(leis);
+
+            // A 32-bit unsigned integer that specifies the number of child nodes in the RegionNode field.
+            int nodeCount = leis.readInt();
+            size += LittleEndianConsts.INT_SIZE;
+
+            // An array of RegionNodeCount+1 EmfPlusRegionNode objects. Regions are specified as a binary tree of
+            // region nodes, and each node MUST either be a terminal node or specify one or two child nodes.
+            // RegionNode MUST contain at least one element.
+            size += readNode(leis, d -> regionNode = d);
+
+            return size;
+        }
+
+
+    }
+
+
+    public interface EmfPlusRegionNodeData {
+        long init(LittleEndianInputStream leis) throws IOException;
+    }
+
+    public static class EmfPlusRegionPath extends EmfPlusPath implements EmfPlusRegionNodeData {
+        public long init(LittleEndianInputStream leis) throws IOException {
+            int dataSize = leis.readInt();
+            return super.init(leis, dataSize, EmfPlusObjectType.PATH, 0) + LittleEndianConsts.INT_SIZE;
+        }
+    }
+
+    public static class EmfPlusRegionInfinite implements EmfPlusRegionNodeData {
+        @Override
+        public long init(LittleEndianInputStream leis) throws IOException {
+            return 0;
+        }
+    }
+
+    public static class EmfPlusRegionEmpty implements EmfPlusRegionNodeData {
+        @Override
+        public long init(LittleEndianInputStream leis) throws IOException {
+            return 0;
+        }
+    }
+
+    public static class EmfPlusRegionRect implements EmfPlusRegionNodeData {
+        private final Rectangle2D rect = new Rectangle2D.Double();
+
+        @Override
+        public long init(LittleEndianInputStream leis) {
+            return readRectF(leis, rect);
+        }
+    }
+
+    public static class EmfPlusRegionNode implements EmfPlusRegionNodeData {
+        private EmfPlusRegionNodeData left, right;
+
+        @Override
+        public long init(LittleEndianInputStream leis) throws IOException {
+            long size = readNode(leis, n -> left = n);
+            size += readNode(leis, n -> right = n);
+            return size;
+        }
+    }
+
+
+    private static long readNode(LittleEndianInputStream leis, Consumer<EmfPlusRegionNodeData> con) throws IOException {
+        // A 32-bit unsigned integer that specifies the type of data in the RegionNodeData field.
+        // This value MUST be defined in the RegionNodeDataType enumeration
+        EmfPlusRegionNodeDataType type = EmfPlusRegionNodeDataType.valueOf(leis.readInt());
+        assert(type != null);
+        EmfPlusRegionNodeData nd = type.constructor.get();
+        con.accept(nd);
+        return LittleEndianConsts.INT_SIZE + nd.init(leis);
+    }
+}
index 2d5699deba5a280787ec1a3261ca27b890a4b71b..751f102b9e5298db6a8673dced9eb9bb659ac1fd 100644 (file)
@@ -36,7 +36,11 @@ import javax.imageio.ImageIO;
 
 import org.apache.poi.hemf.record.emf.HemfComment;
 import org.apache.poi.hemf.record.emf.HemfRecord;
+import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusBitmapDataType;
+import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusImage;
+import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusPixelFormat;
 import org.apache.poi.hemf.record.emfplus.HemfPlusObject;
+import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObject;
 import org.apache.poi.hwmf.record.HwmfBitmapDib;
 import org.apache.poi.hwmf.record.HwmfFill;
 import org.apache.poi.hwmf.usermodel.HwmfEmbedded;
@@ -105,7 +109,7 @@ public class HemfEmbeddedIterator implements Iterator<HwmfEmbedded> {
                     return true;
                 }
 
-                if (obj instanceof HemfPlusObject.EmfPlusObject && ((HemfPlusObject.EmfPlusObject)obj).getObjectType() == HemfPlusObject.EmfPlusObjectType.IMAGE) {
+                if (obj instanceof EmfPlusObject && ((EmfPlusObject)obj).getObjectType() == HemfPlusObject.EmfPlusObjectType.IMAGE) {
                     current = obj;
                     return true;
                 }
@@ -196,13 +200,13 @@ public class HemfEmbeddedIterator implements Iterator<HwmfEmbedded> {
     }
 
     private HwmfEmbedded checkEmfPlusObject() {
-        if (!(current instanceof HemfPlusObject.EmfPlusObject)) {
+        if (!(current instanceof EmfPlusObject)) {
             return null;
         }
 
-        HemfPlusObject.EmfPlusObject epo = (HemfPlusObject.EmfPlusObject)current;
+        EmfPlusObject epo = (EmfPlusObject)current;
         assert(epo.getObjectType() == HemfPlusObject.EmfPlusObjectType.IMAGE);
-        HemfPlusObject.EmfPlusImage img = epo.getObjectData();
+        EmfPlusImage img = epo.getObjectData();
         assert(img.getImageDataType() != null);
 
         HwmfEmbedded emb = getEmfPlusImageData();
@@ -210,7 +214,7 @@ public class HemfEmbeddedIterator implements Iterator<HwmfEmbedded> {
         HwmfEmbeddedType et;
         switch (img.getImageDataType()) {
             case BITMAP:
-                if (img.getBitmapType() == HemfPlusObject.EmfPlusBitmapDataType.COMPRESSED) {
+                if (img.getBitmapType() == EmfPlusBitmapDataType.COMPRESSED) {
                     switch (FileMagic.valueOf(emb.getRawData())) {
                         case JPEG:
                             et = HwmfEmbeddedType.JPEG;
@@ -262,11 +266,11 @@ public class HemfEmbeddedIterator implements Iterator<HwmfEmbedded> {
     /**
      * Compress GDIs internal format to something useful
      */
-    private void compressGDIBitmap(HemfPlusObject.EmfPlusImage img, HwmfEmbedded emb, HwmfEmbeddedType et) {
+    private void compressGDIBitmap(EmfPlusImage img, HwmfEmbedded emb, HwmfEmbeddedType et) {
         final int width = img.getBitmapWidth();
         final int height = img.getBitmapHeight();
         final int stride = img.getBitmapStride();
-        final HemfPlusObject.EmfPlusPixelFormat pf = img.getPixelFormat();
+        final EmfPlusPixelFormat pf = img.getPixelFormat();
 
         int[] nBits, bOffs;
         switch (pf) {
@@ -306,14 +310,14 @@ public class HemfEmbeddedIterator implements Iterator<HwmfEmbedded> {
 
 
     private HwmfEmbedded getEmfPlusImageData() {
-        HemfPlusObject.EmfPlusObject epo = (HemfPlusObject.EmfPlusObject)current;
+        EmfPlusObject epo = (EmfPlusObject)current;
         assert(epo.getObjectType() == HemfPlusObject.EmfPlusObjectType.IMAGE);
 
         final int objectId = epo.getObjectId();
 
         HwmfEmbedded emb = new HwmfEmbedded();
 
-        HemfPlusObject.EmfPlusImage img = (HemfPlusObject.EmfPlusImage)epo.getObjectData();
+        EmfPlusImage img = (EmfPlusImage)epo.getObjectData();
         assert(img.getImageDataType() != null);
 
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
@@ -324,10 +328,10 @@ public class HemfEmbeddedIterator implements Iterator<HwmfEmbedded> {
                 current = null;
                 //noinspection ConstantConditions
                 if (hasNext() &&
-                    (current instanceof HemfPlusObject.EmfPlusObject) &&
-                    ((epo = (HemfPlusObject.EmfPlusObject) current).getObjectId() == objectId)
+                    (current instanceof EmfPlusObject) &&
+                    ((epo = (EmfPlusObject) current).getObjectId() == objectId)
                 ) {
-                    img = (HemfPlusObject.EmfPlusImage)epo.getObjectData();
+                    img = (EmfPlusImage)epo.getObjectData();
                 } else {
                     return emb;
                 }
index d7862c451fd55c82139b5015e6c883b8351b870e..5caa4afe129d5ead1d08b582e8b680b866dddade 100644 (file)
@@ -94,7 +94,7 @@ public class HemfPictureTest {
 
                 if (entry.isDirectory() || !etName.endsWith(".emf") || passed.contains(etName)) continue;
 
-                if (!etName.equals("emfs/commoncrawl2/2S/2SYMYPLNJURGCXJKLNZCJQGIBHVMQTRS_0.emf")) continue;
+                // if (!etName.equals("emfs/commoncrawl2/2S/2SYMYPLNJURGCXJKLNZCJQGIBHVMQTRS_0.emf")) continue;
 
                 // emfs/commoncrawl2/ZJ/ZJT2BZPLQR7DKSKYLYL6GRDEUM2KIO5F_4.emf
                 // emfs/govdocs1/005/005203.ppt_3.emf