From d6ee139b3990f4864c33c5ece78912a1684ae29f Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Sun, 12 May 2019 19:50:04 +0000 Subject: [PATCH] Bug 60656 - EMF image support in slideshows git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1859159 13f79535-47bb-0310-9956-ffa450edef68 --- .../hemf/record/emfplus/HemfPlusBrush.java | 480 ++++++++++++++ .../poi/hemf/record/emfplus/HemfPlusDraw.java | 68 +- .../poi/hemf/record/emfplus/HemfPlusFont.java | 100 +++ .../hemf/record/emfplus/HemfPlusImage.java | 439 +++++++++++++ .../hemf/record/emfplus/HemfPlusObject.java | 415 +----------- .../poi/hemf/record/emfplus/HemfPlusPath.java | 150 +++++ .../poi/hemf/record/emfplus/HemfPlusPen.java | 602 ++++++++++++++++++ .../hemf/record/emfplus/HemfPlusRegion.java | 173 +++++ .../hemf/usermodel/HemfEmbeddedIterator.java | 28 +- .../poi/hemf/usermodel/HemfPictureTest.java | 2 +- 10 files changed, 2014 insertions(+), 443 deletions(-) create mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusBrush.java create mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusFont.java create mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusImage.java create mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusPath.java create mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusPen.java create mode 100644 src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRegion.java diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusBrush.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusBrush.java new file mode 100644 index 0000000000..65c4a3d4d5 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusBrush.java @@ -0,0 +1,480 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hemf.record.emfplus; + +import static org.apache.poi.hemf.record.emf.HemfFill.readXForm; +import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readARGB; +import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readPointF; +import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readRectF; + +import java.awt.Color; +import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion; +import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusImage; +import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusWrapMode; +import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData; +import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType; +import org.apache.poi.hemf.record.emfplus.HemfPlusPath.EmfPlusPath; +import org.apache.poi.util.BitField; +import org.apache.poi.util.BitFieldFactory; +import org.apache.poi.util.LittleEndianConsts; +import org.apache.poi.util.LittleEndianInputStream; + +public class HemfPlusBrush { + /** The BrushType enumeration defines types of graphics brushes, which are used to fill graphics regions. */ + public enum EmfPlusBrushType { + SOLID_COLOR(0X00000000, EmfPlusSolidBrushData::new), + HATCH_FILL(0X00000001, EmfPlusHatchBrushData::new), + TEXTURE_FILL(0X00000002, EmfPlusTextureBrushData::new), + PATH_GRADIENT(0X00000003, EmfPlusPathGradientBrushData::new), + LINEAR_GRADIENT(0X00000004, EmfPlusLinearGradientBrushData::new) + ; + + public final int id; + public final Supplier constructor; + + EmfPlusBrushType(int id, Supplier 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 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 pos) { + final int count = leis.readInt(); + int size = LittleEndianConsts.INT_SIZE; + + double[] positions = new double[count]; + for (int i=0; i pos, Consumer 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 pos, Consumer 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 readPoint; - if (POSITION.isSet(flags)) { + if (isRelativePosition()) { // If the POSITION flag is set in the Flags, the points specify relative locations. readPoint = HemfPlusDraw::readPointR; } else if (isCompressed()) { @@ -301,7 +308,7 @@ public class HemfPlusDraw { public static class EmfPlusDrawImage implements HemfPlusRecord, EmfPlusObjectId, EmfPlusCompressed { private int flags; private int imageAttributesID; - private UnitType srcUnit; + private EmfPlusUnitType srcUnit; private final Rectangle2D srcRect = new Rectangle2D.Double(); private final Rectangle2D rectData = new Rectangle2D.Double(); @@ -325,8 +332,8 @@ public class HemfPlusDraw { // A 32-bit signed integer that defines the units of the SrcRect field. // It MUST be the UnitPixel value of the UnitType enumeration - srcUnit = UnitType.valueOf(leis.readInt()); - assert(srcUnit == UnitType.Pixel); + srcUnit = EmfPlusUnitType.valueOf(leis.readInt()); + assert(srcUnit == EmfPlusUnitType.Pixel); int size = 2 * LittleEndianConsts.INT_SIZE; @@ -347,7 +354,7 @@ public class HemfPlusDraw { private static final BitField SOLID_COLOR = BitFieldFactory.getInstance(0x8000); private int flags; - private final byte[] brushId = new byte[LittleEndianConsts.INT_SIZE]; + private int brushId; @Override public HemfPlusRecordType getEmfPlusRecordType() { @@ -364,11 +371,11 @@ public class HemfPlusDraw { } public int getBrushId() { - return (isSolidColor()) ? -1 : LittleEndian.getInt(brushId); + return (isSolidColor()) ? -1 : brushId; } public Color getSolidColor() { - return (isSolidColor()) ? new Color(brushId[2], brushId[1], brushId[0], brushId[3]) : null; + return (isSolidColor()) ? readARGB(brushId) : null; } @Override @@ -379,7 +386,7 @@ public class HemfPlusDraw { // the SOLID_COLOR bit in the Flags field. // If SOLID_COLOR is set, BrushId specifies a color as an EmfPlusARGB object. // If clear, BrushId contains the index of an EmfPlusBrush object in the EMF+ Object Table. - leis.readFully(brushId); + brushId = leis.readInt(); return LittleEndianConsts.INT_SIZE; } @@ -606,4 +613,9 @@ public class HemfPlusDraw { value[0] = ((value[0] << 8) | leis.readByte()) & 0x7FFF; return LittleEndianConsts.SHORT_SIZE; } + + static Color readARGB(int argb) { + return new Color( (argb >>> 8) & 0xFF, (argb >>> 16) & 0xFF, (argb >>> 24) & 0xFF, argb & 0xFF); + } + } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusFont.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusFont.java new file mode 100644 index 0000000000..fc3550c433 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusFont.java @@ -0,0 +1,100 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hemf.record.emfplus; + +import java.io.IOException; + +import org.apache.poi.hemf.record.emfplus.HemfPlusDraw.EmfPlusUnitType; +import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion; +import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData; +import org.apache.poi.util.BitField; +import org.apache.poi.util.BitFieldFactory; +import org.apache.poi.util.LittleEndianConsts; +import org.apache.poi.util.LittleEndianInputStream; +import org.apache.poi.util.StringUtil; + +public class HemfPlusFont { + + + public static class EmfPlusFont implements EmfPlusObjectData { + /** + * If set, the font typeface MUST be rendered with a heavier weight or thickness. + * If clear, the font typeface MUST be rendered with a normal thickness. + */ + private static final BitField BOLD = BitFieldFactory.getInstance(0x00000001); + + /** + * If set, the font typeface MUST be rendered with the vertical stems of the characters at an increased angle + * or slant relative to the baseline. + * + * If clear, the font typeface MUST be rendered with the vertical stems of the characters at a normal angle. + */ + private static final BitField ITALIC = BitFieldFactory.getInstance(0x00000002); + + /** + * If set, the font typeface MUST be rendered with a line underneath the baseline of the characters. + * If clear, the font typeface MUST be rendered without a line underneath the baseline. + */ + private static final BitField UNDERLINE = BitFieldFactory.getInstance(0x00000004); + + /** + * If set, the font typeface MUST be rendered with a line parallel to the baseline drawn through the middle of + * the characters. + * If clear, the font typeface MUST be rendered without a line through the characters. + */ + private static final BitField STRIKEOUT = BitFieldFactory.getInstance(0x00000008); + + + private final EmfPlusGraphicsVersion version = new EmfPlusGraphicsVersion(); + private double emSize; + private EmfPlusUnitType sizeUnit; + private int styleFlags; + private String family; + + @Override + public long init(LittleEndianInputStream leis, long dataSize, HemfPlusObject.EmfPlusObjectType objectType, int flags) throws IOException { + // An EmfPlusGraphicsVersion object that specifies the version of operating system graphics that was used + // to create this object. + long size = version.init(leis); + + // A 32-bit floating-point value that specifies the em size of the font in units specified by the SizeUnit field. + emSize = leis.readFloat(); + + // A 32-bit unsigned integer that specifies the units used for the EmSize field. These are typically the + // units that were employed when designing the font. The value MUST be in the UnitType enumeration + sizeUnit = EmfPlusUnitType.valueOf(leis.readInt()); + + // A 32-bit signed integer that specifies attributes of the character glyphs that affect the appearance of + // the font, such as bold and italic. This value MUST be composed of FontStyle flags + styleFlags = leis.readInt(); + + // A 32-bit unsigned integer that is reserved and MUST be ignored. + leis.skipFully(LittleEndianConsts.INT_SIZE); + + // A 32-bit unsigned integer that specifies the number of characters in the FamilyName field. + int len = leis.readInt(); + size += 5*LittleEndianConsts.INT_SIZE; + + // A string of Length Unicode characters that contains the name of the font family. + family = StringUtil.readUnicodeLE(leis, len); + size += len*LittleEndianConsts.SHORT_SIZE; + + return size; + } + } +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusImage.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusImage.java new file mode 100644 index 0000000000..4374b1781f --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusImage.java @@ -0,0 +1,439 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hemf.record.emfplus; + +import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readARGB; + +import java.awt.Color; +import java.io.IOException; + +import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion; +import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData; +import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType; +import org.apache.poi.util.BitField; +import org.apache.poi.util.BitFieldFactory; +import org.apache.poi.util.IOUtils; +import org.apache.poi.util.LittleEndianConsts; +import org.apache.poi.util.LittleEndianInputStream; + +public class HemfPlusImage { + /** The ImageDataType enumeration defines types of image data formats. */ + public enum EmfPlusImageDataType { + /** The type of image is not known. */ + UNKNOWN(0x00000000), + /** Specifies a bitmap image. */ + BITMAP(0x00000001), + /** Specifies a metafile image. */ + METAFILE(0x00000002), + /** POI-specific - marks an unfinished/continuable image part */ + CONTINUED(-1); + + public final int id; + + EmfPlusImageDataType(int id) { + this.id = id; + } + + public static EmfPlusImageDataType valueOf(int id) { + for (EmfPlusImageDataType wrt : values()) { + if (wrt.id == id) return wrt; + } + return null; + } + } + + public enum EmfPlusPixelFormat { + UNDEFINED(0X00000000), + INDEXED_1BPP(0X00030101), + INDEXED_4BPP(0X00030402), + INDEXED_8BPP(0X00030803), + GRAYSCALE_16BPP(0X00101004), + RGB555_16BPP(0X00021005), + RGB565_16BPP(0X00021006), + ARGB1555_16BPP(0X00061007), + RGB_24BPP(0X00021808), + RGB_32BPP(0X00022009), + ARGB_32BPP(0X0026200A), + PARGB_32BPP(0X000E200B), + RGB_48BPP(0X0010300C), + ARGB_64BPP(0X0034400D), + PARGB_64BPP(0X001A400E), + ; + + private static final BitField CANONICAL = BitFieldFactory.getInstance(0x00200000); + private static final BitField EXTCOLORS = BitFieldFactory.getInstance(0x00100000); + private static final BitField PREMULTI = BitFieldFactory.getInstance(0x00080000); + private static final BitField ALPHA = BitFieldFactory.getInstance(0x00040000); + private static final BitField GDI = BitFieldFactory.getInstance(0x00020000); + private static final BitField PALETTE = BitFieldFactory.getInstance(0x00010000); + private static final BitField BPP = BitFieldFactory.getInstance(0x0000FF00); + private static final BitField INDEX = BitFieldFactory.getInstance(0x000000FF); + + public final int id; + + EmfPlusPixelFormat(int id) { + this.id = id; + } + + public static EmfPlusPixelFormat valueOf(int id) { + for (EmfPlusPixelFormat wrt : values()) { + if (wrt.id == id) return wrt; + } + return null; + } + + /** + * The pixel format enumeration index. + */ + public int getGDIEnumIndex() { + return id == -1 ? -1 : INDEX.getValue(id); + } + + /** + * The total number of bits per pixel. + */ + public int getBitsPerPixel() { + return id == -1 ? -1 : BPP.getValue(id); + } + + /** + * If set, the pixel values are indexes into a palette. + * If clear, the pixel values are actual colors. + */ + public boolean isPaletteIndexed() { + return id != -1 && PALETTE.isSet(id); + } + + /** + * If set, the pixel format is supported in Windows GDI. + * If clear, the pixel format is not supported in Windows GDI. + */ + public boolean isGDISupported() { + return id != -1 && GDI.isSet(id); + } + + /** + * If set, the pixel format includes an alpha transparency component. + * If clear, the pixel format does not include a component that specifies transparency. + */ + public boolean isAlpha() { + return id != -1 && ALPHA.isSet(id); + } + + /** + * If set, each color component in the pixel has been premultiplied by the pixel's alpha transparency value. + * If clear, each color component is multiplied by the pixel's alpha transparency value when the source pixel + * is blended with the destination pixel. + */ + public boolean isPreMultiplied() { + return id != -1 && PREMULTI.isSet(id); + } + + /** + * If set, the pixel format supports extended colors in 16-bits per channel. + * If clear, extended colors are not supported. + */ + public boolean isExtendedColors() { + return id != -1 && EXTCOLORS.isSet(id); + } + + /** + * If set, the pixel format is "canonical", which means that 32 bits per pixel are + * supported, with 24-bits for color components and an 8-bit alpha channel. + * If clear, the pixel format is not canonical. + */ + public boolean isCanonical() { + return id != -1 && CANONICAL.isSet(id); + } + } + + public enum EmfPlusBitmapDataType { + PIXEL(0x00000000), + COMPRESSED(0x00000001); + + public final int id; + + EmfPlusBitmapDataType(int id) { + this.id = id; + } + + public static EmfPlusBitmapDataType valueOf(int id) { + for (EmfPlusBitmapDataType wrt : values()) { + if (wrt.id == id) return wrt; + } + return null; + } + } + + public enum EmfPlusMetafileDataType { + Wmf(0x00000001), + WmfPlaceable(0x00000002), + Emf(0x00000003), + EmfPlusOnly(0x00000004), + EmfPlusDual(0x00000005); + + public final int id; + + EmfPlusMetafileDataType(int id) { + this.id = id; + } + + public static EmfPlusMetafileDataType valueOf(int id) { + for (EmfPlusMetafileDataType wrt : values()) { + if (wrt.id == id) return wrt; + } + return null; + } + } + + + /** + * The WrapMode enumeration defines how the pattern from a texture or gradient brush is tiled + * across a shape or at shape boundaries, when it is smaller than the area being filled. + */ + public enum EmfPlusWrapMode { + WRAP_MODE_TILE(0x00000000), + WRAP_MODE_TILE_FLIP_X(0x00000001), + WRAP_MODE_TILE_FLIP_Y(0x00000002), + WRAP_MODE_TILE_FLIP_XY(0x00000003), + WRAP_MODE_CLAMP(0x00000004) + ; + + public final int id; + + EmfPlusWrapMode(int id) { + this.id = id; + } + + public static EmfPlusWrapMode valueOf(int id) { + for (EmfPlusWrapMode wrt : values()) { + if (wrt.id == id) return wrt; + } + return null; + } + } + + public enum EmfPlusObjectClamp { + /** The object is clamped to a rectangle. */ + RectClamp(0x00000000), + /** The object is clamped to a bitmap. */ + BitmapClamp(0x00000001) + ; + + public final int id; + + EmfPlusObjectClamp(int id) { + this.id = id; + } + + public static EmfPlusObjectClamp valueOf(int id) { + for (EmfPlusObjectClamp wrt : values()) { + if (wrt.id == id) return wrt; + } + return null; + } + } + + + public static class EmfPlusImage implements EmfPlusObjectData { + private static final int MAX_OBJECT_SIZE = 50_000_000; + + + private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion(); + private EmfPlusImageDataType imageDataType; + private int bitmapWidth; + private int bitmapHeight; + private int bitmapStride; + private EmfPlusPixelFormat pixelFormat; + private EmfPlusBitmapDataType bitmapType; + private byte[] imageData; + private EmfPlusMetafileDataType metafileType; + private int metafileDataSize; + + public EmfPlusImageDataType getImageDataType() { + return imageDataType; + } + + public byte[] getImageData() { + return imageData; + } + + public EmfPlusPixelFormat getPixelFormat() { + return pixelFormat; + } + + public EmfPlusBitmapDataType getBitmapType() { + return bitmapType; + } + + public int getBitmapWidth() { + return bitmapWidth; + } + + public int getBitmapHeight() { + return bitmapHeight; + } + + public int getBitmapStride() { + return bitmapStride; + } + + public EmfPlusMetafileDataType getMetafileType() { + return metafileType; + } + + @Override + public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException { + leis.mark(LittleEndianConsts.INT_SIZE); + long size = graphicsVersion.init(leis); + + if (graphicsVersion.getGraphicsVersion() == null || graphicsVersion.getMetafileSignature() != 0xDBC01) { + // CONTINUABLE is not always correctly set, so we check the version field if this record is continued + imageDataType = EmfPlusImageDataType.CONTINUED; + leis.reset(); + size = 0; + } else { + imageDataType = EmfPlusImageDataType.valueOf(leis.readInt()); + size += LittleEndianConsts.INT_SIZE; + } + + if (imageDataType == null) { + imageDataType = EmfPlusImageDataType.UNKNOWN; + } + + int fileSize; + switch (imageDataType) { + default: + case UNKNOWN: + case CONTINUED: + bitmapWidth = -1; + bitmapHeight = -1; + bitmapStride = -1; + bitmapType = null; + pixelFormat = null; + + fileSize = (int) (dataSize); + break; + + case BITMAP: + // A 32-bit signed integer that specifies the width in pixels of the area occupied by the bitmap. + // If the image is compressed, according to the Type field, this value is undefined and MUST be ignored. + bitmapWidth = leis.readInt(); + // A 32-bit signed integer that specifies the height in pixels of the area occupied by the bitmap. + // If the image is compressed, according to the Type field, this value is undefined and MUST be ignored. + bitmapHeight = leis.readInt(); + // A 32-bit signed integer that specifies the byte offset between the beginning of one scan-line + // and the next. This value is the number of bytes per pixel, which is specified in the PixelFormat + // field, multiplied by the width in pixels, which is specified in the Width field. + // The value of this field MUST be a multiple of four. If the image is compressed, according to the + // Type field, this value is undefined and MUST be ignored. + bitmapStride = leis.readInt(); + // A 32-bit unsigned integer that specifies the format of the pixels that make up the bitmap image. + // The supported pixel formats are specified in the PixelFormat enumeration + int pixelFormatInt = leis.readInt(); + // A 32-bit unsigned integer that specifies the metafileType of data in the BitmapData field. + // This value MUST be defined in the BitmapDataType enumeration + bitmapType = EmfPlusBitmapDataType.valueOf(leis.readInt()); + size += 5 * LittleEndianConsts.INT_SIZE; + + pixelFormat = (bitmapType == EmfPlusBitmapDataType.PIXEL) + ? EmfPlusPixelFormat.valueOf(pixelFormatInt) + : EmfPlusPixelFormat.UNDEFINED; + assert (pixelFormat != null); + + fileSize = (int) (dataSize - size); + + break; + + case METAFILE: + // A 32-bit unsigned integer that specifies the type of metafile that is embedded in the + // MetafileData field. This value MUST be defined in the MetafileDataType enumeration + metafileType = EmfPlusMetafileDataType.valueOf(leis.readInt()); + + // A 32-bit unsigned integer that specifies the size in bytes of the + // metafile data in the MetafileData field. + metafileDataSize = leis.readInt(); + + size += 2 * LittleEndianConsts.INT_SIZE; + + // ignore metafileDataSize, which might ignore a (placeable) header in front + // and also use the remaining bytes, which might contain padding bytes ... + fileSize = (int) (dataSize - size); + break; + } + + assert (fileSize <= dataSize - size); + + imageData = IOUtils.toByteArray(leis, fileSize, MAX_OBJECT_SIZE); + + // TODO: remove padding bytes between placeable WMF header and body? + + return size + fileSize; + } + } + + public static class EmfPlusImageAttributes implements EmfPlusObjectData { + private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion(); + private EmfPlusWrapMode wrapMode; + private Color clampColor; + private EmfPlusObjectClamp objectClamp; + + @Override + public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException { + // An EmfPlusGraphicsVersion object that specifies the version of operating system graphics that + // was used to create this object. + long size = graphicsVersion.init(leis); + + // A 32-bit field that is not used and MUST be ignored. + leis.skipFully(LittleEndianConsts.INT_SIZE); + + // A 32-bit unsigned integer that specifies how to handle edge conditions with a value from the WrapMode enumeration + wrapMode = EmfPlusWrapMode.valueOf(leis.readInt()); + + // An EmfPlusARGB object that specifies the edge color to use when the WrapMode value is WrapModeClamp. + // This color is visible when the source rectangle processed by an EmfPlusDrawImage record is larger than the image itself. + clampColor = readARGB(leis.readInt()); + + // A 32-bit signed integer that specifies the object clamping behavior. It is not used until this object + // is applied to an image being drawn. This value MUST be one of the values defined in the following table. + objectClamp = EmfPlusObjectClamp.valueOf(leis.readInt()); + + // A value that SHOULD be set to zero and MUST be ignored upon receipt. + leis.skipFully(LittleEndianConsts.INT_SIZE); + + return size + 5*LittleEndianConsts.INT_SIZE; + } + + public EmfPlusGraphicsVersion getGraphicsVersion() { + return graphicsVersion; + } + + public EmfPlusWrapMode getWrapMode() { + return wrapMode; + } + + public Color getClampColor() { + return clampColor; + } + + public EmfPlusObjectClamp getObjectClamp() { + return objectClamp; + } + } + +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusObject.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusObject.java index 5e30ba536b..35845044da 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusObject.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusObject.java @@ -17,12 +17,18 @@ package org.apache.poi.hemf.record.emfplus; -import java.awt.Color; import java.io.IOException; import java.util.function.Supplier; +import org.apache.poi.hemf.record.emfplus.HemfPlusBrush.EmfPlusBrush; +import org.apache.poi.hemf.record.emfplus.HemfPlusFont.EmfPlusFont; import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion; +import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusImage; +import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusImageAttributes; import org.apache.poi.hemf.record.emfplus.HemfPlusMisc.EmfPlusObjectId; +import org.apache.poi.hemf.record.emfplus.HemfPlusPath.EmfPlusPath; +import org.apache.poi.hemf.record.emfplus.HemfPlusPen.EmfPlusPen; +import org.apache.poi.hemf.record.emfplus.HemfPlusRegion.EmfPlusRegion; import org.apache.poi.util.BitField; import org.apache.poi.util.BitFieldFactory; import org.apache.poi.util.IOUtils; @@ -43,19 +49,19 @@ public class HemfPlusObject { /** * Brush objects fill graphics regions. */ - BRUSH(0x00000001, EmfPlusUnknownData::new), + BRUSH(0x00000001, EmfPlusBrush::new), /** * Pen objects draw graphics lines. */ - PEN(0x00000002, EmfPlusUnknownData::new), + PEN(0x00000002, EmfPlusPen::new), /** * Path objects specify sequences of lines, curves, and shapes. */ - PATH(0x00000003, EmfPlusUnknownData::new), + PATH(0x00000003, EmfPlusPath::new), /** * Region objects specify areas of the output surface. */ - REGION(0x00000004, EmfPlusUnknownData::new), + REGION(0x00000004, EmfPlusRegion::new), /** * Image objects encapsulate bitmaps and metafiles. */ @@ -63,7 +69,7 @@ public class HemfPlusObject { /** * Font objects specify font properties, including typeface style, em size, and font family. */ - FONT(0x00000006, EmfPlusUnknownData::new), + FONT(0x00000006, EmfPlusFont::new), /** * String format objects specify text layout, including alignment, orientation, tab stops, clipping, * and digit substitution for languages that do not use Western European digits. @@ -96,220 +102,10 @@ public class HemfPlusObject { } } - public enum EmfPlusImageDataType { - UNKNOWN(0x00000000), - BITMAP(0x00000001), - METAFILE(0x00000002), - CONTINUED(-1); - - public final int id; - - EmfPlusImageDataType(int id) { - this.id = id; - } - - public static EmfPlusImageDataType valueOf(int id) { - for (EmfPlusImageDataType wrt : values()) { - if (wrt.id == id) return wrt; - } - return null; - } - } - - public enum EmfPlusBitmapDataType { - PIXEL(0x00000000), - COMPRESSED(0x00000001); - - public final int id; - - EmfPlusBitmapDataType(int id) { - this.id = id; - } - - public static EmfPlusBitmapDataType valueOf(int id) { - for (EmfPlusBitmapDataType wrt : values()) { - if (wrt.id == id) return wrt; - } - return null; - } - } - - public enum EmfPlusPixelFormat { - UNDEFINED(0X00000000), - INDEXED_1BPP(0X00030101), - INDEXED_4BPP(0X00030402), - INDEXED_8BPP(0X00030803), - GRAYSCALE_16BPP(0X00101004), - RGB555_16BPP(0X00021005), - RGB565_16BPP(0X00021006), - ARGB1555_16BPP(0X00061007), - RGB_24BPP(0X00021808), - RGB_32BPP(0X00022009), - ARGB_32BPP(0X0026200A), - PARGB_32BPP(0X000E200B), - RGB_48BPP(0X0010300C), - ARGB_64BPP(0X0034400D), - PARGB_64BPP(0X001A400E), - ; - - private static final BitField CANONICAL = BitFieldFactory.getInstance(0x00200000); - private static final BitField EXTCOLORS = BitFieldFactory.getInstance(0x00100000); - private static final BitField PREMULTI = BitFieldFactory.getInstance(0x00080000); - private static final BitField ALPHA = BitFieldFactory.getInstance(0x00040000); - private static final BitField GDI = BitFieldFactory.getInstance(0x00020000); - private static final BitField PALETTE = BitFieldFactory.getInstance(0x00010000); - private static final BitField BPP = BitFieldFactory.getInstance(0x0000FF00); - private static final BitField INDEX = BitFieldFactory.getInstance(0x000000FF); - - public final int id; - - EmfPlusPixelFormat(int id) { - this.id = id; - } - - public static EmfPlusPixelFormat valueOf(int id) { - for (EmfPlusPixelFormat wrt : values()) { - if (wrt.id == id) return wrt; - } - return null; - } - - /** - * The pixel format enumeration index. - */ - public int getGDIEnumIndex() { - return id == -1 ? -1 : INDEX.getValue(id); - } - - /** - * The total number of bits per pixel. - */ - public int getBitsPerPixel() { - return id == -1 ? -1 : BPP.getValue(id); - } - - /** - * If set, the pixel values are indexes into a palette. - * If clear, the pixel values are actual colors. - */ - public boolean isPaletteIndexed() { - return id != -1 && PALETTE.isSet(id); - } - - /** - * If set, the pixel format is supported in Windows GDI. - * If clear, the pixel format is not supported in Windows GDI. - */ - public boolean isGDISupported() { - return id != -1 && GDI.isSet(id); - } - - /** - * If set, the pixel format includes an alpha transparency component. - * If clear, the pixel format does not include a component that specifies transparency. - */ - public boolean isAlpha() { - return id != -1 && ALPHA.isSet(id); - } - - /** - * If set, each color component in the pixel has been premultiplied by the pixel's alpha transparency value. - * If clear, each color component is multiplied by the pixel's alpha transparency value when the source pixel - * is blended with the destination pixel. - */ - public boolean isPreMultiplied() { - return id != -1 && PREMULTI.isSet(id); - } - - /** - * If set, the pixel format supports extended colors in 16-bits per channel. - * If clear, extended colors are not supported. - */ - public boolean isExtendedColors() { - return id != -1 && EXTCOLORS.isSet(id); - } - - /** - * If set, the pixel format is "canonical", which means that 32 bits per pixel are - * supported, with 24-bits for color components and an 8-bit alpha channel. - * If clear, the pixel format is not canonical. - */ - public boolean isCanonical() { - return id != -1 && CANONICAL.isSet(id); - } - } - - public enum EmfPlusMetafileDataType { - Wmf(0x00000001), - WmfPlaceable(0x00000002), - Emf(0x00000003), - EmfPlusOnly(0x00000004), - EmfPlusDual(0x00000005); - - public final int id; - - EmfPlusMetafileDataType(int id) { - this.id = id; - } - - public static EmfPlusMetafileDataType valueOf(int id) { - for (EmfPlusMetafileDataType wrt : values()) { - if (wrt.id == id) return wrt; - } - return null; - } - } - - /** - * The WrapMode enumeration defines how the pattern from a texture or gradient brush is tiled - * across a shape or at shape boundaries, when it is smaller than the area being filled. - */ - public enum EmfPlusWrapMode { - WRAP_MODE_TILE(0x00000000), - WRAP_MODE_TILE_FLIP_X(0x00000001), - WRAP_MODE_TILE_FLIP_Y(0x00000002), - WRAP_MODE_TILE_FLIP_XY(0x00000003), - WRAP_MODE_CLAMP(0x00000004) - ; - - public final int id; - - EmfPlusWrapMode(int id) { - this.id = id; - } - - public static EmfPlusWrapMode valueOf(int id) { - for (EmfPlusWrapMode wrt : values()) { - if (wrt.id == id) return wrt; - } - return null; - } - } - - public enum EmfPlusObjectClamp { - /** The object is clamped to a rectangle. */ - RectClamp(0x00000000), - /** The object is clamped to a bitmap. */ - BitmapClamp(0x00000001) - ; - - public final int id; - - EmfPlusObjectClamp(int id) { - this.id = id; - } - - public static EmfPlusObjectClamp valueOf(int id) { - for (EmfPlusObjectClamp wrt : values()) { - if (wrt.id == id) return wrt; - } - return null; - } - } /** * The EmfPlusObject record specifies an object for use in graphics operations. The object definition - * can span multiple records, which is indicated by the value of the Flags field. + * can span multiple records), which is indicated by the value of the Flags field. */ public static class EmfPlusObject implements HemfPlusRecord, EmfPlusObjectId { @@ -400,189 +196,4 @@ public class HemfPlusObject { return dataSize; } } - - public static class EmfPlusImage implements EmfPlusObjectData { - private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion(); - private EmfPlusImageDataType imageDataType; - private int bitmapWidth; - private int bitmapHeight; - private int bitmapStride; - private EmfPlusPixelFormat pixelFormat; - private EmfPlusBitmapDataType bitmapType; - private byte[] imageData; - private EmfPlusMetafileDataType metafileType; - private int metafileDataSize; - - public EmfPlusImageDataType getImageDataType() { - return imageDataType; - } - - public byte[] getImageData() { - return imageData; - } - - public EmfPlusPixelFormat getPixelFormat() { - return pixelFormat; - } - - public EmfPlusBitmapDataType getBitmapType() { - return bitmapType; - } - - public int getBitmapWidth() { - return bitmapWidth; - } - - public int getBitmapHeight() { - return bitmapHeight; - } - - public int getBitmapStride() { - return bitmapStride; - } - - public EmfPlusMetafileDataType getMetafileType() { - return metafileType; - } - - @Override - public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException { - leis.mark(LittleEndianConsts.INT_SIZE); - long size = graphicsVersion.init(leis); - - if (graphicsVersion.getGraphicsVersion() == null || graphicsVersion.getMetafileSignature() != 0xDBC01) { - // CONTINUABLE is not always correctly set, so we check the version field if this record is continued - imageDataType = EmfPlusImageDataType.CONTINUED; - leis.reset(); - size = 0; - } else { - imageDataType = EmfPlusImageDataType.valueOf(leis.readInt()); - size += LittleEndianConsts.INT_SIZE; - } - - if (imageDataType == null) { - imageDataType = EmfPlusImageDataType.UNKNOWN; - } - - int fileSize; - switch (imageDataType) { - default: - case UNKNOWN: - case CONTINUED: - bitmapWidth = -1; - bitmapHeight = -1; - bitmapStride = -1; - bitmapType = null; - pixelFormat = null; - - fileSize = (int) (dataSize); - break; - - case BITMAP: - // A 32-bit signed integer that specifies the width in pixels of the area occupied by the bitmap. - // If the image is compressed, according to the Type field, this value is undefined and MUST be ignored. - bitmapWidth = leis.readInt(); - // A 32-bit signed integer that specifies the height in pixels of the area occupied by the bitmap. - // If the image is compressed, according to the Type field, this value is undefined and MUST be ignored. - bitmapHeight = leis.readInt(); - // A 32-bit signed integer that specifies the byte offset between the beginning of one scan-line - // and the next. This value is the number of bytes per pixel, which is specified in the PixelFormat - // field, multiplied by the width in pixels, which is specified in the Width field. - // The value of this field MUST be a multiple of four. If the image is compressed, according to the - // Type field, this value is undefined and MUST be ignored. - bitmapStride = leis.readInt(); - // A 32-bit unsigned integer that specifies the format of the pixels that make up the bitmap image. - // The supported pixel formats are specified in the PixelFormat enumeration - int pixelFormatInt = leis.readInt(); - // A 32-bit unsigned integer that specifies the metafileType of data in the BitmapData field. - // This value MUST be defined in the BitmapDataType enumeration - bitmapType = EmfPlusBitmapDataType.valueOf(leis.readInt()); - size += 5 * LittleEndianConsts.INT_SIZE; - - pixelFormat = (bitmapType == EmfPlusBitmapDataType.PIXEL) - ? EmfPlusPixelFormat.valueOf(pixelFormatInt) - : EmfPlusPixelFormat.UNDEFINED; - assert (pixelFormat != null); - - fileSize = (int) (dataSize - size); - - break; - - case METAFILE: - // A 32-bit unsigned integer that specifies the type of metafile that is embedded in the - // MetafileData field. This value MUST be defined in the MetafileDataType enumeration - metafileType = EmfPlusMetafileDataType.valueOf(leis.readInt()); - - // A 32-bit unsigned integer that specifies the size in bytes of the - // metafile data in the MetafileData field. - metafileDataSize = leis.readInt(); - - size += 2 * LittleEndianConsts.INT_SIZE; - - // ignore metafileDataSize, which might ignore a (placeable) header in front - // and also use the remaining bytes, which might contain padding bytes ... - fileSize = (int) (dataSize - size); - break; - } - - assert (fileSize <= dataSize - size); - - imageData = IOUtils.toByteArray(leis, fileSize, MAX_OBJECT_SIZE); - - // TODO: remove padding bytes between placeable WMF header and body? - - return size + fileSize; - } - } - - public static class EmfPlusImageAttributes implements EmfPlusObjectData { - private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion(); - private EmfPlusWrapMode wrapMode; - private Color clampColor; - private EmfPlusObjectClamp objectClamp; - - @Override - public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException { - // An EmfPlusGraphicsVersion object that specifies the version of operating system graphics that - // was used to create this object. - long size = graphicsVersion.init(leis); - - // A 32-bit field that is not used and MUST be ignored. - leis.skip(LittleEndianConsts.INT_SIZE); - - // A 32-bit unsigned integer that specifies how to handle edge conditions with a value from the WrapMode enumeration - wrapMode = EmfPlusWrapMode.valueOf(leis.readInt()); - - // An EmfPlusARGB object that specifies the edge color to use when the WrapMode value is WrapModeClamp. - // This color is visible when the source rectangle processed by an EmfPlusDrawImage record is larger than the image itself. - byte[] buf = new byte[LittleEndianConsts.INT_SIZE]; - leis.readFully(buf); - clampColor = new Color(buf[2], buf[1], buf[0], buf[3]); - - // A 32-bit signed integer that specifies the object clamping behavior. It is not used until this object - // is applied to an image being drawn. This value MUST be one of the values defined in the following table. - objectClamp = EmfPlusObjectClamp.valueOf(leis.readInt()); - - // A value that SHOULD be set to zero and MUST be ignored upon receipt. - leis.skip(LittleEndianConsts.INT_SIZE); - - return size + 5*LittleEndianConsts.INT_SIZE; - } - - public EmfPlusGraphicsVersion getGraphicsVersion() { - return graphicsVersion; - } - - public EmfPlusWrapMode getWrapMode() { - return wrapMode; - } - - public Color getClampColor() { - return clampColor; - } - - public EmfPlusObjectClamp getObjectClamp() { - return objectClamp; - } - } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusPath.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusPath.java new file mode 100644 index 0000000000..86d2e48a59 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusPath.java @@ -0,0 +1,150 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hemf.record.emfplus; + +import java.awt.geom.Point2D; +import java.io.IOException; +import java.util.Arrays; +import java.util.function.BiFunction; + +import org.apache.poi.hemf.record.emfplus.HemfPlusDraw.EmfPlusCompressed; +import org.apache.poi.hemf.record.emfplus.HemfPlusDraw.EmfPlusRelativePosition; +import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData; +import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType; +import org.apache.poi.util.BitField; +import org.apache.poi.util.BitFieldFactory; +import org.apache.poi.util.LittleEndianConsts; +import org.apache.poi.util.LittleEndianInputStream; + +public class HemfPlusPath { + + /** The PathPointType enumeration defines types of points on a graphics path. */ + public enum EmfPlusPathPointType { + /** Specifies that the point is the starting point of a path. */ + START, + /** Specifies that the point is one of the two endpoints of a line. */ + LINE, + // not defined + UNUSED, + /** Specifies that the point is an endpoint or control point of a cubic Bezier curve */ + BEZIER; + } + + public static class EmfPlusPath implements EmfPlusObjectData, EmfPlusCompressed, EmfPlusRelativePosition { + /** + * If set, the point types in the PathPointTypes array are specified by EmfPlusPathPointTypeRLE objects, + * which use run-length encoding (RLE) compression, and/or EmfPlusPathPointType objects. + * If clear, the point types in the PathPointTypes array are specified by EmfPlusPathPointType objects. + */ + private static final BitField RLE_COMPRESSED = BitFieldFactory.getInstance(0x00001000); + + /** Specifies that a line segment that passes through the point is dashed. */ + private static final BitField POINT_TYPE_DASHED = BitFieldFactory.getInstance(0x10); + + /** Specifies that the point is a position marker. */ + private static final BitField POINT_TYPE_MARKER = BitFieldFactory.getInstance(0x20); + + /** Specifies that the point is the endpoint of a subpath. */ + private static final BitField POINT_TYPE_CLOSE = BitFieldFactory.getInstance(0x80); + + private static final BitField POINT_TYPE_ENUM = BitFieldFactory.getInstance(0x0F); + + + private static final BitField POINT_RLE_BEZIER = BitFieldFactory.getInstance(0x80); + + private static final BitField POINT_RLE_COUNT = BitFieldFactory.getInstance(0x3F); + + private final HemfPlusHeader.EmfPlusGraphicsVersion version = new HemfPlusHeader.EmfPlusGraphicsVersion(); + private int pointFlags; + private Point2D[] pathPoints; + private byte[] pointTypes; + + @Override + public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException { + long size = version.init(leis); + + // A 32-bit unsigned integer that specifies the number of points and associated point types that + // are defined by this object. + int pointCount = leis.readInt(); + + // A 16-bit unsigned integer that specifies how to interpret the points + // and associated point types that are defined by this object. + pointFlags = leis.readShort(); + + leis.skipFully(LittleEndianConsts.SHORT_SIZE); + size += 2* LittleEndianConsts.INT_SIZE; + + BiFunction 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 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 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 customStartCap = c, leis); + } + + if (CUSTOM_END_CAP.isSet(penDataFlags)) { + size += initCustomCap(c -> customEndCap = c, leis); + } + + return size; + } + + private long initCustomCap(Consumer setter, LittleEndianInputStream leis) throws IOException { + EmfPlusGraphicsVersion version = new EmfPlusGraphicsVersion(); + long size = version.init(leis); + + boolean adjustableArrow = (leis.readInt() != 0); + size += LittleEndianConsts.INT_SIZE; + + EmfPlusCustomLineCap cap = (adjustableArrow) ? new EmfPlusAdjustableArrowCap() : new EmfPlusPathArrowCap(); + size += cap.init(leis); + + setter.accept(cap); + + return size; + } + + @Internal + public interface EmfPlusCustomLineCap { + long init(LittleEndianInputStream leis) throws IOException; + } + + public static class EmfPlusPathArrowCap implements EmfPlusCustomLineCap { + /** + * If set, an EmfPlusFillPath object MUST be specified in the OptionalData field of the + * EmfPlusCustomLineCapData object for filling the custom line cap. + */ + private static final BitField FILL_PATH = BitFieldFactory.getInstance(0x00000001); + /** + * If set, an EmfPlusLinePath object MUST be specified in the OptionalData field of the + * EmfPlusCustomLineCapData object for outlining the custom line cap. + */ + private static final BitField LINE_PATH = BitFieldFactory.getInstance(0x00000002); + + + private int dataFlags; + private EmfPlusLineCapType baseCap; + private double baseInset; + private EmfPlusLineCapType startCap; + private EmfPlusLineCapType endCap; + private EmfPlusLineJoin join; + private double mitterLimit; + private double widthScale; + private final Point2D fillHotSpot = new Point2D.Double(); + private final Point2D lineHotSpot = new Point2D.Double(); + private EmfPlusPath fillPath; + private EmfPlusPath outlinePath; + + @Override + public long init(LittleEndianInputStream leis) throws IOException { + // A 32-bit unsigned integer that specifies the data in the OptionalData field. + // This value MUST be composed of CustomLineCapData flags + dataFlags = leis.readInt(); + + // A 32-bit unsigned integer that specifies the value from the LineCap enumeration on which + // the custom line cap is based. + baseCap = EmfPlusLineCapType.valueOf(leis.readInt()); + + // A 32-bit floating-point value that specifies the distance between the + // beginning of the line cap and the end of the line. + baseInset = leis.readFloat(); + + // A 32-bit unsigned integer that specifies the value in the LineCap enumeration that indicates the line + // cap used at the start/end of the line to be drawn. + startCap = EmfPlusLineCapType.valueOf(leis.readInt()); + endCap = EmfPlusLineCapType.valueOf(leis.readInt()); + + // A 32-bit unsigned integer that specifies the value in the LineJoin enumeration, which specifies how + // to join two lines that are drawn by the same pen and whose ends meet. At the intersection of the two + // line ends, a line join makes the connection look more continuous. + join = EmfPlusLineJoin.valueOf(leis.readInt()); + + // A 32-bit floating-point value that contains the limit of the thickness of the join on a mitered corner + // by setting the maximum allowed ratio of miter length to line width. + mitterLimit = leis.readFloat(); + + // A 32-bit floating-point value that specifies the amount by which to scale the custom line cap with + // respect to the width of the EmfPlusPen object that is used to draw the lines. + widthScale = leis.readFloat(); + + int size = 8* LittleEndianConsts.INT_SIZE; + + // An EmfPlusPointF object that is not currently used. It MUST be set to {0.0, 0.0}. + size += readPointF(leis, fillHotSpot); + size += readPointF(leis, lineHotSpot); + + if (FILL_PATH.isSet(dataFlags)) { + fillPath = new EmfPlusPath(); + size += fillPath.init(leis, -1, null, -1); + } + + if (LINE_PATH.isSet(dataFlags)) { + outlinePath = new EmfPlusPath(); + size += outlinePath.init(leis, -1, null, -1); + } + + return size; + } + } + + public static class EmfPlusAdjustableArrowCap implements EmfPlusCustomLineCap { + private double width; + private double height; + private double middleInset; + private boolean isFilled; + private EmfPlusLineCapType startCap; + private EmfPlusLineCapType endCap; + private EmfPlusLineJoin join; + private double mitterLimit; + private double widthScale; + private final Point2D fillHotSpot = new Point2D.Double(); + private final Point2D lineHotSpot = new Point2D.Double(); + + @Override + public long init(LittleEndianInputStream leis) throws IOException { + // A 32-bit floating-point value that specifies the width of the arrow cap. + // The width of the arrow cap is scaled by the width of the EmfPlusPen object that is used to draw the + // line being capped. For example, when drawing a capped line with a pen that has a width of 5 pixels, + // and the adjustable arrow cap object has a width of 3, the actual arrow cap is drawn 15 pixels wide. + width = leis.readFloat(); + + // A 32-bit floating-point value that specifies the height of the arrow cap. + // The height of the arrow cap is scaled by the width of the EmfPlusPen object that is used to draw the + // line being capped. For example, when drawing a capped line with a pen that has a width of 5 pixels, + // and the adjustable arrow cap object has a height of 3, the actual arrow cap is drawn 15 pixels high. + height = leis.readFloat(); + + // A 32-bit floating-point value that specifies the number of pixels between the outline of the arrow + // cap and the fill of the arrow cap. + middleInset = leis.readFloat(); + + // A 32-bit Boolean value that specifies whether the arrow cap is filled. + // If the arrow cap is not filled, only the outline is drawn. + isFilled = (leis.readInt() != 0); + + // A 32-bit unsigned integer that specifies the value in the LineCap enumeration that indicates + // the line cap to be used at the start/end of the line to be drawn. + startCap = EmfPlusLineCapType.valueOf(leis.readInt()); + endCap = EmfPlusLineCapType.valueOf(leis.readInt()); + + // 32-bit unsigned integer that specifies the value in the LineJoin enumeration that specifies how to + // join two lines that are drawn by the same pen and whose ends meet. At the intersection of the two + // line ends, a line join makes the connection look more continuous. + join = EmfPlusLineJoin.valueOf(leis.readInt()); + + // A 32-bit floating-point value that specifies the limit of the thickness of the join on a mitered + // corner by setting the maximum allowed ratio of miter length to line width. + mitterLimit = leis.readFloat(); + + // A 32-bit floating-point value that specifies the amount by which to scale an EmfPlusCustomLineCap + // object with respect to the width of the graphics pen that is used to draw the lines. + widthScale = leis.readFloat(); + + int size = 9 * LittleEndianConsts.INT_SIZE; + + // An EmfPlusPointF object that is not currently used. It MUST be set to {0.0, 0.0}. + size += readPointF(leis, fillHotSpot); + + // An EmfPlusPointF object that is not currently used. It MUST be set to {0.0, 0.0}. + size += readPointF(leis, lineHotSpot); + + return size; + } + } + + } +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRegion.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRegion.java new file mode 100644 index 0000000000..d3f3002f99 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRegion.java @@ -0,0 +1,173 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hemf.record.emfplus; + +import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readRectF; + +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion; +import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData; +import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType; +import org.apache.poi.hemf.record.emfplus.HemfPlusPath.EmfPlusPath; +import org.apache.poi.util.LittleEndianConsts; +import org.apache.poi.util.LittleEndianInputStream; + +public class HemfPlusRegion { + public enum EmfPlusRegionNodeDataType { + /** + * Specifies a region node with child nodes. A Boolean AND operation SHOULD be applied to the left and right + * child nodes specified by an EmfPlusRegionNodeChildNodes object + */ + AND(0X00000001, EmfPlusRegionNode::new), + /** + * Specifies a region node with child nodes. A Boolean OR operation SHOULD be applied to the left and right + * child nodes specified by an EmfPlusRegionNodeChildNodes object. + */ + OR(0X00000002, EmfPlusRegionNode::new), + /** + * Specifies a region node with child nodes. A Boolean XOR operation SHOULD be applied to the left and right + * child nodes specified by an EmfPlusRegionNodeChildNodes object. + */ + XOR(0X00000003, EmfPlusRegionNode::new), + /** + * Specifies a region node with child nodes. A Boolean operation, defined as "the part of region 1 that is excluded + * from region 2", SHOULD be applied to the left and right child nodes specified by an EmfPlusRegionNodeChildNodes object. + */ + EXCLUDE(0X00000004, EmfPlusRegionNode::new), + /** + * Specifies a region node with child nodes. A Boolean operation, defined as "the part of region 2 that is excluded + * from region 1", SHOULD be applied to the left and right child nodes specified by an EmfPlusRegionNodeChildNodes object. + */ + COMPLEMENT(0X00000005, EmfPlusRegionNode::new), + /** + * Specifies a region node with no child nodes. + * The RegionNodeData field SHOULD specify a boundary with an EmfPlusRectF object. + */ + RECT(0X10000000, EmfPlusRegionRect::new), + /** + * Specifies a region node with no child nodes. + * The RegionNodeData field SHOULD specify a boundary with an EmfPlusRegionNodePath object + */ + PATH(0X10000001, EmfPlusRegionPath::new), + /** Specifies a region node with no child nodes. The RegionNodeData field SHOULD NOT be present. */ + EMPTY(0X10000002, EmfPlusRegionEmpty::new), + /** Specifies a region node with no child nodes, and its bounds are not defined. */ + INFINITE(0X10000003, EmfPlusRegionInfinite::new) + ; + + public final int id; + public final Supplier constructor; + + EmfPlusRegionNodeDataType(int id, Supplier 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 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); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfEmbeddedIterator.java b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfEmbeddedIterator.java index 2d5699deba..751f102b9e 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfEmbeddedIterator.java +++ b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfEmbeddedIterator.java @@ -36,7 +36,11 @@ import javax.imageio.ImageIO; import org.apache.poi.hemf.record.emf.HemfComment; import org.apache.poi.hemf.record.emf.HemfRecord; +import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusBitmapDataType; +import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusImage; +import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusPixelFormat; import org.apache.poi.hemf.record.emfplus.HemfPlusObject; +import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObject; import org.apache.poi.hwmf.record.HwmfBitmapDib; import org.apache.poi.hwmf.record.HwmfFill; import org.apache.poi.hwmf.usermodel.HwmfEmbedded; @@ -105,7 +109,7 @@ public class HemfEmbeddedIterator implements Iterator { return true; } - if (obj instanceof HemfPlusObject.EmfPlusObject && ((HemfPlusObject.EmfPlusObject)obj).getObjectType() == HemfPlusObject.EmfPlusObjectType.IMAGE) { + if (obj instanceof EmfPlusObject && ((EmfPlusObject)obj).getObjectType() == HemfPlusObject.EmfPlusObjectType.IMAGE) { current = obj; return true; } @@ -196,13 +200,13 @@ public class HemfEmbeddedIterator implements Iterator { } private HwmfEmbedded checkEmfPlusObject() { - if (!(current instanceof HemfPlusObject.EmfPlusObject)) { + if (!(current instanceof EmfPlusObject)) { return null; } - HemfPlusObject.EmfPlusObject epo = (HemfPlusObject.EmfPlusObject)current; + EmfPlusObject epo = (EmfPlusObject)current; assert(epo.getObjectType() == HemfPlusObject.EmfPlusObjectType.IMAGE); - HemfPlusObject.EmfPlusImage img = epo.getObjectData(); + EmfPlusImage img = epo.getObjectData(); assert(img.getImageDataType() != null); HwmfEmbedded emb = getEmfPlusImageData(); @@ -210,7 +214,7 @@ public class HemfEmbeddedIterator implements Iterator { HwmfEmbeddedType et; switch (img.getImageDataType()) { case BITMAP: - if (img.getBitmapType() == HemfPlusObject.EmfPlusBitmapDataType.COMPRESSED) { + if (img.getBitmapType() == EmfPlusBitmapDataType.COMPRESSED) { switch (FileMagic.valueOf(emb.getRawData())) { case JPEG: et = HwmfEmbeddedType.JPEG; @@ -262,11 +266,11 @@ public class HemfEmbeddedIterator implements Iterator { /** * Compress GDIs internal format to something useful */ - private void compressGDIBitmap(HemfPlusObject.EmfPlusImage img, HwmfEmbedded emb, HwmfEmbeddedType et) { + private void compressGDIBitmap(EmfPlusImage img, HwmfEmbedded emb, HwmfEmbeddedType et) { final int width = img.getBitmapWidth(); final int height = img.getBitmapHeight(); final int stride = img.getBitmapStride(); - final HemfPlusObject.EmfPlusPixelFormat pf = img.getPixelFormat(); + final EmfPlusPixelFormat pf = img.getPixelFormat(); int[] nBits, bOffs; switch (pf) { @@ -306,14 +310,14 @@ public class HemfEmbeddedIterator implements Iterator { private HwmfEmbedded getEmfPlusImageData() { - HemfPlusObject.EmfPlusObject epo = (HemfPlusObject.EmfPlusObject)current; + EmfPlusObject epo = (EmfPlusObject)current; assert(epo.getObjectType() == HemfPlusObject.EmfPlusObjectType.IMAGE); final int objectId = epo.getObjectId(); HwmfEmbedded emb = new HwmfEmbedded(); - HemfPlusObject.EmfPlusImage img = (HemfPlusObject.EmfPlusImage)epo.getObjectData(); + EmfPlusImage img = (EmfPlusImage)epo.getObjectData(); assert(img.getImageDataType() != null); ByteArrayOutputStream bos = new ByteArrayOutputStream(); @@ -324,10 +328,10 @@ public class HemfEmbeddedIterator implements Iterator { 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; } diff --git a/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java index d7862c451f..5caa4afe12 100644 --- a/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java +++ b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java @@ -94,7 +94,7 @@ public class HemfPictureTest { if (entry.isDirectory() || !etName.endsWith(".emf") || passed.contains(etName)) continue; - if (!etName.equals("emfs/commoncrawl2/2S/2SYMYPLNJURGCXJKLNZCJQGIBHVMQTRS_0.emf")) continue; + // if (!etName.equals("emfs/commoncrawl2/2S/2SYMYPLNJURGCXJKLNZCJQGIBHVMQTRS_0.emf")) continue; // emfs/commoncrawl2/ZJ/ZJT2BZPLQR7DKSKYLYL6GRDEUM2KIO5F_4.emf // emfs/govdocs1/005/005203.ppt_3.emf -- 2.39.5