--- /dev/null
+/* ====================================================================
+ 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;
+ }
+}
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;
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. */
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;
}
}
+ 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());
+ }
+ }
+
/**
}
}
- 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();
// 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;
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()) {
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();
// 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;
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() {
}
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
// 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;
}
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);
+ }
+
}
--- /dev/null
+/* ====================================================================
+ 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;
+ }
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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;
+ }
+ }
+
+}
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;
/**
* 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.
*/
/**
* 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.
}
}
- 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 {
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;
- }
- }
}
--- /dev/null
+/* ====================================================================
+ 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;
+ }
+ }
+
+
+}
--- /dev/null
+/* ====================================================================
+ 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;
+ }
+ }
+
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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);
+ }
+}
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;
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;
}
}
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();
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;
/**
* 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) {
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();
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;
}
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