Bug 60656 - EMF image support in slideshows

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1859159 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2019-05-12 19:50:04 +00:00
parent 049baa86d7
commit d6ee139b39
10 changed files with 2014 additions and 443 deletions

View File

@ -0,0 +1,480 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hemf.record.emfplus;
import static org.apache.poi.hemf.record.emf.HemfFill.readXForm;
import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readARGB;
import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readPointF;
import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readRectF;
import java.awt.Color;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusImage;
import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusWrapMode;
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType;
import org.apache.poi.hemf.record.emfplus.HemfPlusPath.EmfPlusPath;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;
public class HemfPlusBrush {
/** The BrushType enumeration defines types of graphics brushes, which are used to fill graphics regions. */
public enum EmfPlusBrushType {
SOLID_COLOR(0X00000000, EmfPlusSolidBrushData::new),
HATCH_FILL(0X00000001, EmfPlusHatchBrushData::new),
TEXTURE_FILL(0X00000002, EmfPlusTextureBrushData::new),
PATH_GRADIENT(0X00000003, EmfPlusPathGradientBrushData::new),
LINEAR_GRADIENT(0X00000004, EmfPlusLinearGradientBrushData::new)
;
public final int id;
public final Supplier<? extends EmfPlusBrushData> constructor;
EmfPlusBrushType(int id, Supplier<? extends EmfPlusBrushData> constructor) {
this.id = id;
this.constructor = constructor;
}
public static EmfPlusBrushType valueOf(int id) {
for (EmfPlusBrushType wrt : values()) {
if (wrt.id == id) return wrt;
}
return null;
}
}
public enum EmfPlusHatchStyle {
HORIZONTAL(0X00000000),
VERTICAL(0X00000001),
FORWARD_DIAGONAL(0X00000002),
BACKWARD_DIAGONAL(0X00000003),
LARGE_GRID(0X00000004),
DIAGONAL_CROSS(0X00000005),
PERCENT_05(0X00000006),
PERCENT_10(0X00000007),
PERCENT_20(0X00000008),
PERCENT_25(0X00000009),
PERCENT_30(0X0000000A),
PERCENT_40(0X0000000B),
PERCENT_50(0X0000000C),
PERCENT_60(0X0000000D),
PERCENT_70(0X0000000E),
PERCENT_75(0X0000000F),
PERCENT_80(0X00000010),
PERCENT_90(0X00000011),
LIGHT_DOWNWARD_DIAGONAL(0X00000012),
LIGHT_UPWARD_DIAGONAL(0X00000013),
DARK_DOWNWARD_DIAGONAL(0X00000014),
DARK_UPWARD_DIAGONAL(0X00000015),
WIDE_DOWNWARD_DIAGONAL(0X00000016),
WIDE_UPWARD_DIAGONAL(0X00000017),
LIGHT_VERTICAL(0X00000018),
LIGHT_HORIZONTAL(0X00000019),
NARROW_VERTICAL(0X0000001A),
NARROW_HORIZONTAL(0X0000001B),
DARK_VERTICAL(0X0000001C),
DARK_HORIZONTAL(0X0000001D),
DASHED_DOWNWARD_DIAGONAL(0X0000001E),
DASHED_UPWARD_DIAGONAL(0X0000001F),
DASHED_HORIZONTAL(0X00000020),
DASHED_VERTICAL(0X00000021),
SMALL_CONFETTI(0X00000022),
LARGE_CONFETTI(0X00000023),
ZIGZAG(0X00000024),
WAVE(0X00000025),
DIAGONAL_BRICK(0X00000026),
HORIZONTAL_BRICK(0X00000027),
WEAVE(0X00000028),
PLAID(0X00000029),
DIVOT(0X0000002A),
DOTTED_GRID(0X0000002B),
DOTTED_DIAMOND(0X0000002C),
SHINGLE(0X0000002D),
TRELLIS(0X0000002E),
SPHERE(0X0000002F),
SMALL_GRID(0X00000030),
SMALL_CHECKER_BOARD(0X00000031),
LARGE_CHECKER_BOARD(0X00000032),
OUTLINED_DIAMOND(0X00000033),
SOLID_DIAMOND(0X00000034)
;
public final int id;
EmfPlusHatchStyle(int id) {
this.id = id;
}
public static EmfPlusHatchStyle valueOf(int id) {
for (EmfPlusHatchStyle wrt : values()) {
if (wrt.id == id) return wrt;
}
return null;
}
}
public interface EmfPlusBrushData {
/**
* This flag is meaningful in EmfPlusPathGradientBrushData objects.
*
* If set, an EmfPlusBoundaryPathData object MUST be specified in the BoundaryData field of the brush data object.
* If clear, an EmfPlusBoundaryPointData object MUST be specified in the BoundaryData field of the brush data object.
*/
BitField PATH = BitFieldFactory.getInstance(0x00000001);
/**
* This flag is meaningful in EmfPlusLinearGradientBrushData objects , EmfPlusPathGradientBrushData objects,
* and EmfPlusTextureBrushData objects.
*
* If set, a 2x3 world space to device space transform matrix MUST be specified in the OptionalData field of
* the brush data object.
*/
BitField TRANSFORM = BitFieldFactory.getInstance(0x00000002);
/**
* This flag is meaningful in EmfPlusLinearGradientBrushData and EmfPlusPathGradientBrushData objects.
*
* If set, an EmfPlusBlendColors object MUST be specified in the OptionalData field of the brush data object.
*/
BitField PRESET_COLORS = BitFieldFactory.getInstance(0x00000004);
/**
* This flag is meaningful in EmfPlusLinearGradientBrushData and EmfPlusPathGradientBrushData objects.
*
* If set, an EmfPlusBlendFactors object that specifies a blend pattern along a horizontal gradient MUST be
* specified in the OptionalData field of the brush data object.
*/
BitField BLEND_FACTORS_H = BitFieldFactory.getInstance(0x00000008);
/**
* This flag is meaningful in EmfPlusLinearGradientBrushData objects.
*
* If set, an EmfPlusBlendFactors object that specifies a blend pattern along a vertical gradient MUST be
* specified in the OptionalData field of the brush data object.
*/
BitField BLEND_FACTORS_V = BitFieldFactory.getInstance(0x00000010);
/**
* This flag is meaningful in EmfPlusPathGradientBrushData objects.
*
* If set, an EmfPlusFocusScaleData object MUST be specified in the OptionalData field of the brush data object.
*/
BitField FOCUS_SCALES = BitFieldFactory.getInstance(0x00000040);
/**
* This flag is meaningful in EmfPlusLinearGradientBrushData, EmfPlusPathGradientBrushData, and
* EmfPlusTextureBrushData objects.
*
* If set, the brush MUST already be gamma corrected; that is, output brightness and intensity have been
* corrected to match the input image.
*/
BitField IS_GAMMA_CORRECTED = BitFieldFactory.getInstance(0x00000080);
/**
* This flag is meaningful in EmfPlusTextureBrushData objects.
*
* If set, a world space to device space transform SHOULD NOT be applied to the texture brush.
*/
BitField DO_NOT_TRANSFORM = BitFieldFactory.getInstance(0x00000100);
long init(LittleEndianInputStream leis, long dataSize) throws IOException;
}
/** The EmfPlusBrush object specifies a graphics brush for filling regions. */
public static class EmfPlusBrush implements EmfPlusObjectData {
private final EmfPlusGraphicsVersion version = new EmfPlusGraphicsVersion();
private EmfPlusBrushType brushType;
private EmfPlusBrushData brushData;
@Override
public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
long size = version.init(leis);
brushType = EmfPlusBrushType.valueOf(leis.readInt());
size += LittleEndianConsts.INT_SIZE;
assert(brushType != null);
size += (brushData = brushType.constructor.get()).init(leis, dataSize-size);
return size;
}
}
/** The EmfPlusSolidBrushData object specifies a solid color for a graphics brush. */
public static class EmfPlusSolidBrushData implements EmfPlusBrushData {
private Color solidColor;
@Override
public long init(LittleEndianInputStream leis, long dataSize) throws IOException {
solidColor = readARGB(leis.readInt());
return LittleEndianConsts.INT_SIZE;
}
}
/** The EmfPlusHatchBrushData object specifies a hatch pattern for a graphics brush. */
public static class EmfPlusHatchBrushData implements EmfPlusBrushData {
private EmfPlusHatchStyle style;
private Color foreColor, backColor;
public long init(LittleEndianInputStream leis, long dataSize) {
style = EmfPlusHatchStyle.valueOf(leis.readInt());
foreColor = readARGB(leis.readInt());
backColor = readARGB(leis.readInt());
return 3*LittleEndianConsts.INT_SIZE;
}
}
/** The EmfPlusLinearGradientBrushData object specifies a linear gradient for a graphics brush. */
public static class EmfPlusLinearGradientBrushData implements EmfPlusBrushData {
private int dataFlags;
private EmfPlusWrapMode wrapMode;
private Rectangle2D rect = new Rectangle2D.Double();
private Color startColor, endColor;
private AffineTransform transform;
private double[] positions;
private Color[] blendColors;
private double[] positionsV;
private double[] blendFactorsV;
private double[] positionsH;
private double[] blendFactorsH;
@Override
public long init(LittleEndianInputStream leis, long dataSize) throws IOException {
// A 32-bit unsigned integer that specifies the data in the OptionalData field.
// This value MUST be composed of BrushData flags
dataFlags = leis.readInt();
// A 32-bit signed integer from the WrapMode enumeration that specifies whether to paint the area outside
// the boundary of the brush. When painting outside the boundary, the wrap mode specifies how the color
// gradient is repeated.
wrapMode = EmfPlusWrapMode.valueOf(leis.readInt());
int size = 2*LittleEndianConsts.INT_SIZE;
size += readRectF(leis, rect);
// An EmfPlusARGB object that specifies the color at the starting/ending boundary point of the linear gradient brush.
startColor = readARGB(leis.readInt());
endColor = readARGB(leis.readInt());
// skip reserved1/2 fields
leis.skipFully(2*LittleEndianConsts.INT_SIZE);
size += 4*LittleEndianConsts.INT_SIZE;
if (TRANSFORM.isSet(dataFlags)) {
size += readXForm(leis, (transform = new AffineTransform()));
}
final boolean isPreset = PRESET_COLORS.isSet(dataFlags);
final boolean blendH = BLEND_FACTORS_H.isSet(dataFlags);
final boolean blendV = BLEND_FACTORS_V.isSet(dataFlags);
if (isPreset && (blendH || blendV)) {
throw new RuntimeException("invalid combination of preset colors and blend factors v/h");
}
size += (isPreset) ? readColors(leis, d -> positions = d, c -> blendColors = c) : 0;
size += (blendV) ? readFactors(leis, d -> positionsV = d, f -> blendFactorsV = f) : 0;
size += (blendH) ? readFactors(leis, d -> positionsH = d, f -> blendFactorsH = f) : 0;
return size;
}
}
/** The EmfPlusPathGradientBrushData object specifies a path gradient for a graphics brush. */
public static class EmfPlusPathGradientBrushData implements EmfPlusBrushData {
private int dataFlags;
private EmfPlusWrapMode wrapMode;
private Color centerColor;
private final Point2D centerPoint = new Point2D.Double();
private Color[] surroundingColor;
private EmfPlusPath boundaryPath;
private Point2D[] boundaryPoints;
private AffineTransform transform;
private double[] positions;
private Color[] blendColors;
private double[] blendFactorsH;
private Double focusScaleX, focusScaleY;
@Override
public long init(LittleEndianInputStream leis, long dataSize) throws IOException {
// A 32-bit unsigned integer that specifies the data in the OptionalData field.
// This value MUST be composed of BrushData flags
dataFlags = leis.readInt();
// A 32-bit signed integer from the WrapMode enumeration that specifies whether to paint the area outside
// the boundary of the brush. When painting outside the boundary, the wrap mode specifies how the color
// gradient is repeated.
wrapMode = EmfPlusWrapMode.valueOf(leis.readInt());
// An EmfPlusARGB object that specifies the center color of the path gradient brush, which is the color
// that appears at the center point of the brush. The color of the brush changes gradually from the
// boundary color to the center color as it moves from the boundary to the center point.
centerColor = readARGB(leis.readInt());
int size = 3*LittleEndianConsts.INT_SIZE;
size += readPointF(leis, centerPoint);
// An unsigned 32-bit integer that specifies the number of colors specified in the SurroundingColor field.
// The surrounding colors are colors specified for discrete points on the boundary of the brush.
final int colorCount = leis.readInt();
// An array of SurroundingColorCount EmfPlusARGB objects that specify the colors for discrete points on the
// boundary of the brush.
surroundingColor = new Color[colorCount];
for (int i=0; i<colorCount; i++) {
surroundingColor[i] = readARGB(leis.readInt());
}
size += (colorCount+1) * LittleEndianConsts.INT_SIZE;
// The boundary of the path gradient brush, which is specified by either a path or a closed cardinal spline.
// If the BrushDataPath flag is set in the BrushDataFlags field, this field MUST contain an
// EmfPlusBoundaryPathData object; otherwise, this field MUST contain an EmfPlusBoundaryPointData object.
if (PATH.isSet(dataFlags)) {
// A 32-bit signed integer that specifies the size in bytes of the BoundaryPathData field.
int pathDataSize = leis.readInt();
size += LittleEndianConsts.INT_SIZE;
// An EmfPlusPath object that specifies the boundary of the brush.
size += (boundaryPath = new EmfPlusPath()).init(leis, pathDataSize, EmfPlusObjectType.PATH, 0);
} else {
// A 32-bit signed integer that specifies the number of points in the BoundaryPointData field.
int pointCount = leis.readInt();
size += LittleEndianConsts.INT_SIZE;
// An array of BoundaryPointCount EmfPlusPointF objects that specify the boundary of the brush.
boundaryPoints = new Point2D[pointCount];
for (int i=0; i<pointCount; i++) {
size += readPointF(leis, boundaryPoints[i] = new Point2D.Double());
}
}
// An optional EmfPlusTransformMatrix object that specifies a world space to device space transform for
// the path gradient brush. This field MUST be present if the BrushDataTransform flag is set in the
// BrushDataFlags field of the EmfPlusPathGradientBrushData object.
if (TRANSFORM.isSet(dataFlags)) {
size += readXForm(leis, (transform = new AffineTransform()));
}
// An optional blend pattern for the path gradient brush. If this field is present, it MUST contain either
// an EmfPlusBlendColors object, or an EmfPlusBlendFactors object, but it MUST NOT contain both.
final boolean isPreset = PRESET_COLORS.isSet(dataFlags);
final boolean blendH = BLEND_FACTORS_H.isSet(dataFlags);
if (isPreset && blendH) {
throw new RuntimeException("invalid combination of preset colors and blend factors h");
}
size += (isPreset) ? readColors(leis, d -> positions = d, c -> blendColors = c) : 0;
size += (blendH) ? readFactors(leis, d -> positions = d, f -> blendFactorsH = f) : 0;
// An optional EmfPlusFocusScaleData object that specifies focus scales for the path gradient brush.
// This field MUST be present if the BrushDataFocusScales flag is set in the BrushDataFlags field of the
// EmfPlusPathGradientBrushData object.
if (FOCUS_SCALES.isSet(dataFlags)) {
// A 32-bit unsigned integer that specifies the number of focus scales. This value MUST be 2.
int focusScaleCount = leis.readInt();
if (focusScaleCount != 2) {
throw new RuntimeException("invalid focus scale count");
}
// A floating-point value that defines the horizontal/vertical focus scale.
// The focus scale MUST be a value between 0.0 and 1.0, exclusive.
focusScaleX = (double)leis.readFloat();
focusScaleY = (double)leis.readFloat();
size += 3*LittleEndianConsts.INT_SIZE;
}
return size;
}
}
/** The EmfPlusTextureBrushData object specifies a texture image for a graphics brush. */
public static class EmfPlusTextureBrushData implements EmfPlusBrushData {
private int dataFlags;
private EmfPlusWrapMode wrapMode;
private AffineTransform transform;
private EmfPlusImage image;
@Override
public long init(LittleEndianInputStream leis, long dataSize) throws IOException {
// A 32-bit unsigned integer that specifies the data in the OptionalData field.
// This value MUST be composed of BrushData flags.
dataFlags = leis.readInt();
// A 32-bit signed integer from the WrapMode enumeration that specifies how to repeat the texture image
// across a shape, when the image is smaller than the area being filled.
wrapMode = EmfPlusWrapMode.valueOf(leis.readInt());
int size = 2*LittleEndianConsts.INT_SIZE;
if (TRANSFORM.isSet(dataFlags)) {
size += readXForm(leis, (transform = new AffineTransform()));
}
if (dataSize > size) {
size += (image = new EmfPlusImage()).init(leis, dataSize-size, EmfPlusObjectType.IMAGE, 0);
}
return size;
}
}
private static int readPositions(LittleEndianInputStream leis, Consumer<double[]> pos) {
final int count = leis.readInt();
int size = LittleEndianConsts.INT_SIZE;
double[] positions = new double[count];
for (int i=0; i<count; i++) {
positions[i] = leis.readFloat();
size += LittleEndianConsts.INT_SIZE;
}
pos.accept(positions);
return size;
}
private static int readColors(LittleEndianInputStream leis, Consumer<double[]> pos, Consumer<Color[]> cols) {
int[] count = { 0 };
int size = readPositions(leis, p -> { count[0] = p.length; pos.accept(p); });
Color[] colors = new Color[count[0]];
for (int i=0; i<colors.length; i++) {
colors[i] = readARGB(leis.readInt());
}
cols.accept(colors);
return size + colors.length * LittleEndianConsts.INT_SIZE;
}
private static int readFactors(LittleEndianInputStream leis, Consumer<double[]> pos, Consumer<double[]> facs) {
int[] count = { 0 };
int size = readPositions(leis, p -> { count[0] = p.length; pos.accept(p); });
double[] factors = new double[count[0]];
for (int i=0; i<factors.length; i++) {
factors[i] = leis.readFloat();
}
facs.accept(factors);
return size + factors.length * LittleEndianConsts.INT_SIZE;
}
}

View File

@ -35,7 +35,6 @@ import org.apache.poi.hemf.record.emfplus.HemfPlusMisc.EmfPlusObjectId;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;
import org.apache.poi.util.StringUtil;
@ -43,7 +42,7 @@ import org.apache.poi.util.StringUtil;
public class HemfPlusDraw {
private static final int MAX_OBJECT_SIZE = 1_000_000;
public enum UnitType {
public enum EmfPlusUnitType {
/** Specifies a unit of logical distance within the world space. */
World(0x00),
/** Specifies a unit of distance based on the characteristics of the physical display. */
@ -62,12 +61,12 @@ public class HemfPlusDraw {
public final int id;
UnitType(int id) {
EmfPlusUnitType(int id) {
this.id = id;
}
public static UnitType valueOf(int id) {
for (UnitType wrt : values()) {
public static EmfPlusUnitType valueOf(int id) {
for (EmfPlusUnitType wrt : values()) {
if (wrt.id == id) return wrt;
}
return null;
@ -98,6 +97,25 @@ public class HemfPlusDraw {
}
}
public interface EmfPlusRelativePosition {
/**
* This bit indicates whether the PointData field specifies relative or absolute locations.
* If set, each element in PointData specifies a location in the coordinate space that is relative to the
* location specified by the previous element in the array. In the case of the first element in PointData,
* a previous location at coordinates (0,0) is assumed.
* If clear, PointData specifies absolute locations according to the {@link #isCompressed()} flag.
*
* Note If this flag is set, the {@link #isCompressed()} flag (above) is undefined and MUST be ignored.
*/
BitField POSITION = BitFieldFactory.getInstance(0x0800);
int getFlags();
default boolean isRelativePosition() {
return POSITION.isSet(getFlags());
}
}
/**
@ -182,27 +200,16 @@ public class HemfPlusDraw {
}
}
public static class EmfPlusDrawImagePoints implements HemfPlusRecord, EmfPlusObjectId, EmfPlusCompressed {
public static class EmfPlusDrawImagePoints implements HemfPlusRecord, EmfPlusObjectId, EmfPlusCompressed, EmfPlusRelativePosition {
/**
* This bit indicates that the rendering of the image includes applying an effect.
* If set, an object of the Effect class MUST have been specified in an earlier EmfPlusSerializableObject record.
*/
private static final BitField EFFECT = BitFieldFactory.getInstance(0x2000);
/**
* This bit indicates whether the PointData field specifies relative or absolute locations.
* If set, each element in PointData specifies a location in the coordinate space that is relative to the
* location specified by the previous element in the array. In the case of the first element in PointData,
* a previous location at coordinates (0,0) is assumed.
* If clear, PointData specifies absolute locations according to the {@link #isCompressed()} flag.
*
* Note If this flag is set, the {@link #isCompressed()} flag (above) is undefined and MUST be ignored.
*/
private static final BitField POSITION = BitFieldFactory.getInstance(0x0800);
private int flags;
private int imageAttributesID;
private UnitType srcUnit;
private EmfPlusUnitType srcUnit;
private final Rectangle2D srcRect = new Rectangle2D.Double();
private final Point2D upperLeft = new Point2D.Double();
private final Point2D lowerRight = new Point2D.Double();
@ -229,8 +236,8 @@ public class HemfPlusDraw {
// A 32-bit signed integer that defines the units of the SrcRect field.
// It MUST be the UnitPixel value of the UnitType enumeration
srcUnit = UnitType.valueOf(leis.readInt());
assert(srcUnit == UnitType.Pixel);
srcUnit = EmfPlusUnitType.valueOf(leis.readInt());
assert(srcUnit == EmfPlusUnitType.Pixel);
int size = 2 * LittleEndianConsts.INT_SIZE;
@ -245,7 +252,7 @@ public class HemfPlusDraw {
BiFunction<LittleEndianInputStream, Point2D, Integer> readPoint;
if (POSITION.isSet(flags)) {
if (isRelativePosition()) {
// If the POSITION flag is set in the Flags, the points specify relative locations.
readPoint = HemfPlusDraw::readPointR;
} else if (isCompressed()) {
@ -301,7 +308,7 @@ public class HemfPlusDraw {
public static class EmfPlusDrawImage implements HemfPlusRecord, EmfPlusObjectId, EmfPlusCompressed {
private int flags;
private int imageAttributesID;
private UnitType srcUnit;
private EmfPlusUnitType srcUnit;
private final Rectangle2D srcRect = new Rectangle2D.Double();
private final Rectangle2D rectData = new Rectangle2D.Double();
@ -325,8 +332,8 @@ public class HemfPlusDraw {
// A 32-bit signed integer that defines the units of the SrcRect field.
// It MUST be the UnitPixel value of the UnitType enumeration
srcUnit = UnitType.valueOf(leis.readInt());
assert(srcUnit == UnitType.Pixel);
srcUnit = EmfPlusUnitType.valueOf(leis.readInt());
assert(srcUnit == EmfPlusUnitType.Pixel);
int size = 2 * LittleEndianConsts.INT_SIZE;
@ -347,7 +354,7 @@ public class HemfPlusDraw {
private static final BitField SOLID_COLOR = BitFieldFactory.getInstance(0x8000);
private int flags;
private final byte[] brushId = new byte[LittleEndianConsts.INT_SIZE];
private int brushId;
@Override
public HemfPlusRecordType getEmfPlusRecordType() {
@ -364,11 +371,11 @@ public class HemfPlusDraw {
}
public int getBrushId() {
return (isSolidColor()) ? -1 : LittleEndian.getInt(brushId);
return (isSolidColor()) ? -1 : brushId;
}
public Color getSolidColor() {
return (isSolidColor()) ? new Color(brushId[2], brushId[1], brushId[0], brushId[3]) : null;
return (isSolidColor()) ? readARGB(brushId) : null;
}
@Override
@ -379,7 +386,7 @@ public class HemfPlusDraw {
// the SOLID_COLOR bit in the Flags field.
// If SOLID_COLOR is set, BrushId specifies a color as an EmfPlusARGB object.
// If clear, BrushId contains the index of an EmfPlusBrush object in the EMF+ Object Table.
leis.readFully(brushId);
brushId = leis.readInt();
return LittleEndianConsts.INT_SIZE;
}
@ -606,4 +613,9 @@ public class HemfPlusDraw {
value[0] = ((value[0] << 8) | leis.readByte()) & 0x7FFF;
return LittleEndianConsts.SHORT_SIZE;
}
static Color readARGB(int argb) {
return new Color( (argb >>> 8) & 0xFF, (argb >>> 16) & 0xFF, (argb >>> 24) & 0xFF, argb & 0xFF);
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,150 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hemf.record.emfplus;
import java.awt.geom.Point2D;
import java.io.IOException;
import java.util.Arrays;
import java.util.function.BiFunction;
import org.apache.poi.hemf.record.emfplus.HemfPlusDraw.EmfPlusCompressed;
import org.apache.poi.hemf.record.emfplus.HemfPlusDraw.EmfPlusRelativePosition;
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;
public class HemfPlusPath {
/** The PathPointType enumeration defines types of points on a graphics path. */
public enum EmfPlusPathPointType {
/** Specifies that the point is the starting point of a path. */
START,
/** Specifies that the point is one of the two endpoints of a line. */
LINE,
// not defined
UNUSED,
/** Specifies that the point is an endpoint or control point of a cubic Bezier curve */
BEZIER;
}
public static class EmfPlusPath implements EmfPlusObjectData, EmfPlusCompressed, EmfPlusRelativePosition {
/**
* If set, the point types in the PathPointTypes array are specified by EmfPlusPathPointTypeRLE objects,
* which use run-length encoding (RLE) compression, and/or EmfPlusPathPointType objects.
* If clear, the point types in the PathPointTypes array are specified by EmfPlusPathPointType objects.
*/
private static final BitField RLE_COMPRESSED = BitFieldFactory.getInstance(0x00001000);
/** Specifies that a line segment that passes through the point is dashed. */
private static final BitField POINT_TYPE_DASHED = BitFieldFactory.getInstance(0x10);
/** Specifies that the point is a position marker. */
private static final BitField POINT_TYPE_MARKER = BitFieldFactory.getInstance(0x20);
/** Specifies that the point is the endpoint of a subpath. */
private static final BitField POINT_TYPE_CLOSE = BitFieldFactory.getInstance(0x80);
private static final BitField POINT_TYPE_ENUM = BitFieldFactory.getInstance(0x0F);
private static final BitField POINT_RLE_BEZIER = BitFieldFactory.getInstance(0x80);
private static final BitField POINT_RLE_COUNT = BitFieldFactory.getInstance(0x3F);
private final HemfPlusHeader.EmfPlusGraphicsVersion version = new HemfPlusHeader.EmfPlusGraphicsVersion();
private int pointFlags;
private Point2D[] pathPoints;
private byte[] pointTypes;
@Override
public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
long size = version.init(leis);
// A 32-bit unsigned integer that specifies the number of points and associated point types that
// are defined by this object.
int pointCount = leis.readInt();
// A 16-bit unsigned integer that specifies how to interpret the points
// and associated point types that are defined by this object.
pointFlags = leis.readShort();
leis.skipFully(LittleEndianConsts.SHORT_SIZE);
size += 2* LittleEndianConsts.INT_SIZE;
BiFunction<LittleEndianInputStream,Point2D,Integer> readPoint;
if (isRelativePosition()) {
readPoint = HemfPlusDraw::readPointR;
} else if (isCompressed()) {
readPoint = HemfPlusDraw::readPointS;
} else {
readPoint = HemfPlusDraw::readPointF;
}
pathPoints = new Point2D[pointCount];
for (int i=0; i<pointCount; i++) {
pathPoints[i] = new Point2D.Double();
size += readPoint.apply(leis,pathPoints[i]);
}
pointTypes = new byte[pointCount];
final boolean isRLE = RLE_COMPRESSED.isSet(pointFlags);
if (isRLE) {
for (int i=0, rleCount; i<pointCount; i+=rleCount, size+=2) {
rleCount = POINT_RLE_COUNT.getValue(leis.readByte());
Arrays.fill(pointTypes, pointCount, pointCount+rleCount, leis.readByte());
}
} else {
leis.readFully(pointTypes);
size += pointCount;
}
int padding = (int)((4 - (size % 4)) % 4);
leis.skipFully(padding);
size += padding;
return size;
}
public boolean isPointDashed(int index) {
return POINT_TYPE_DASHED.isSet(pointTypes[index]);
}
public boolean isPointMarker(int index) {
return POINT_TYPE_MARKER.isSet(pointTypes[index]);
}
public boolean isPointClosed(int index) {
return POINT_TYPE_CLOSE.isSet(pointTypes[index]);
}
public EmfPlusPathPointType getPointType(int index) {
return EmfPlusPathPointType.values()[POINT_TYPE_ENUM.getValue(pointTypes[index])];
}
@Override
public int getFlags() {
return pointFlags;
}
}
}

View File

@ -0,0 +1,602 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hemf.record.emfplus;
import static org.apache.poi.hemf.record.emf.HemfFill.readXForm;
import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readPointF;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.io.IOException;
import java.util.function.Consumer;
import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType;
import org.apache.poi.hemf.record.emfplus.HemfPlusPath.EmfPlusPath;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.Internal;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;
public class HemfPlusPen {
/**
* The LineCapType enumeration defines types of line caps to use at the ends of lines that are drawn
* with graphics pens.
*/
public enum EmfPlusLineCapType {
/** Specifies a squared-off line cap. The end of the line MUST be the last point in the line. */
FLAT(0X00000000),
/**
* Specifies a square line cap. The center of the square MUST be located at
* the last point in the line. The width of the square is the line width.
*/
SQUARE(0X00000001),
/**
* Specifies a circular line cap. The center of the circle MUST be located at
* the last point in the line. The diameter of the circle is the line width.
*/
ROUND(0X00000002),
/**
* Specifies a triangular line cap. The base of the triangle MUST be located
* at the last point in the line. The base of the triangle is the line width.
*/
TRIANGLE(0X00000003),
/** Specifies that the line end is not anchored. */
NO_ANCHOR(0X00000010),
/**
* Specifies that the line end is anchored with a square line cap. The center of the square MUST be located
* at the last point in the line. The height and width of the square are the line width.
*/
SQUARE_ANCHOR(0X00000011),
/**
* Specifies that the line end is anchored with a circular line cap. The center of the circle MUST be located
* at the last point in the line. The circle SHOULD be wider than the line.
*/
ROUND_ANCHOR(0X00000012),
/**
* Specifies that the line end is anchored with a diamond-shaped line cap, which is a square turned at
* 45 degrees. The center of the diamond MUST be located at the last point in the line.
* The diamond SHOULD be wider than the line.
*/
DIAMOND_ANCHOR(0X00000013),
/**
* Specifies that the line end is anchored with an arrowhead shape. The arrowhead point MUST be located at
* the last point in the line. The arrowhead SHOULD be wider than the line.
*/
ARROW_ANCHOR(0X00000014),
/** Mask used to check whether a line cap is an anchor cap. */
ANCHOR_MASK(0X000000F0),
/** Specifies a custom line cap. */
CUSTOM(0X000000FF)
;
public final int id;
EmfPlusLineCapType(int id) {
this.id = id;
}
public static EmfPlusLineCapType valueOf(int id) {
for (EmfPlusLineCapType wrt : values()) {
if (wrt.id == id) return wrt;
}
return null;
}
}
/**
* The LineJoinType enumeration defines ways to join two lines that are drawn by the same graphics
* pen and whose ends meet.
*/
public enum EmfPlusLineJoin {
MITER(0X00000000),
BEVEL(0X00000001),
ROUND(0X00000002),
MITER_CLIPPED(0X00000003)
;
public final int id;
EmfPlusLineJoin(int id) {
this.id = id;
}
public static EmfPlusLineJoin valueOf(int id) {
for (EmfPlusLineJoin wrt : values()) {
if (wrt.id == id) return wrt;
}
return null;
}
}
/** The LineStyle enumeration defines styles of lines that are drawn with graphics pens. */
public enum EmfPlusLineStyle {
/** Specifies a solid line. */
SOLID(0X00000000),
/** Specifies a dashed line. */
DASH(0X00000001),
/** Specifies a dotted line. */
DOT(0X00000002),
/** Specifies an alternating dash-dot line. */
DASH_DOT(0X00000003),
/** Specifies an alternating dash-dot-dot line. */
DASH_DOT_DOT(0X00000004),
/** Specifies a user-defined, custom dashed line. */
CUSTOM(0X00000005)
;
public final int id;
EmfPlusLineStyle(int id) {
this.id = id;
}
public static EmfPlusLineStyle valueOf(int id) {
for (EmfPlusLineStyle wrt : values()) {
if (wrt.id == id) return wrt;
}
return null;
}
}
/**
* The DashedLineCapType enumeration defines types of line caps to use at the ends of dashed lines
* that are drawn with graphics pens.
*/
public enum EmfPlusDashedLineCapType {
/** Specifies a flat dashed line cap. */
FLAT(0X00000000),
/** Specifies a round dashed line cap. */
ROUND(0X00000002),
/** Specifies a triangular dashed line cap. */
TRIANGLE(0X00000003)
;
public final int id;
EmfPlusDashedLineCapType(int id) {
this.id = id;
}
public static EmfPlusDashedLineCapType valueOf(int id) {
for (EmfPlusDashedLineCapType wrt : values()) {
if (wrt.id == id) return wrt;
}
return null;
}
}
/**
* The PenAlignment enumeration defines the distribution of the width of the pen with respect to the
* line being drawn.
*/
public enum EmfPlusPenAlignment {
/** Specifies that the EmfPlusPen object is centered over the theoretical line. */
CENTER(0X00000000),
/** Specifies that the pen is positioned on the inside of the theoretical line. */
INSET(0X00000001),
/** Specifies that the pen is positioned to the left of the theoretical line. */
LEFT(0X00000002),
/** Specifies that the pen is positioned on the outside of the theoretical line. */
OUTSET(0X00000003),
/** Specifies that the pen is positioned to the right of the theoretical line. */
RIGHT(0X00000004)
;
public final int id;
EmfPlusPenAlignment(int id) {
this.id = id;
}
public static EmfPlusPenAlignment valueOf(int id) {
for (EmfPlusPenAlignment wrt : values()) {
if (wrt.id == id) return wrt;
}
return null;
}
}
public static class EmfPlusPen implements EmfPlusObjectData {
/**
* If set, a 2x3 transform matrix MUST be specified in the OptionalData field of an EmfPlusPenData object.
*/
private final static BitField TRANSFORM = BitFieldFactory.getInstance(0x00000001);
/**
* If set, the style of a starting line cap MUST be specified in the OptionalData field of an
* EmfPlusPenData object.
*/
private final static BitField START_CAP = BitFieldFactory.getInstance(0x00000002);
/**
* Indicates whether the style of an ending line cap MUST be specified in the OptionalData field
* of an EmfPlusPenData object.
*/
private final static BitField END_CAP = BitFieldFactory.getInstance(0x00000004);
/**
* Indicates whether a line join type MUST be specified in the OptionalData
* field of an EmfPlusPenData object.
*/
private final static BitField JOIN = BitFieldFactory.getInstance(0x00000008);
/**
* Indicates whether a miter limit MUST be specified in the OptionalData field of an EmfPlusPenData object.
*/
private final static BitField MITER_LIMIT = BitFieldFactory.getInstance(0x00000010);
/**
* Indicates whether a line style MUST be specified in the OptionalData field of an EmfPlusPenData object.
*/
private final static BitField LINE_STYLE = BitFieldFactory.getInstance(0x00000020);
/**
* Indicates whether a dashed line cap MUST be specified in the OptionalData field of an EmfPlusPenData object.
*/
private final static BitField DASHED_LINE_CAP = BitFieldFactory.getInstance(0x00000040);
/**
* Indicates whether a dashed line offset MUST be specified in the OptionalData field of an EmfPlusPenData object.
*/
private final static BitField DASHED_LINE_OFFSET = BitFieldFactory.getInstance(0x00000080);
/**
* Indicates whether an EmfPlusDashedLineData object MUST be specified in the
* OptionalData field of an EmfPlusPenData object.
*/
private final static BitField DASHED_LINE = BitFieldFactory.getInstance(0x00000100);
/**
* Indicates whether a pen alignment MUST be specified in the OptionalData field of an EmfPlusPenData object.
*/
private final static BitField NON_CENTER = BitFieldFactory.getInstance(0x00000200);
/**
* Indicates whether the length and content of a EmfPlusCompoundLineData object are present in the
* OptionalData field of an EmfPlusPenData object.
*/
private final static BitField COMPOUND_LINE = BitFieldFactory.getInstance(0x00000400);
/**
* Indicates whether an EmfPlusCustomStartCapData object MUST be specified
* in the OptionalData field of an EmfPlusPenData object.y
*/
private final static BitField CUSTOM_START_CAP = BitFieldFactory.getInstance(0x00000800);
/**
* Indicates whether an EmfPlusCustomEndCapData object MUST be specified in
* the OptionalData field of an EmfPlusPenData object.
*/
private final static BitField CUSTOM_END_CAP = BitFieldFactory.getInstance(0x00001000);
private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
private int type;
private int penDataFlags;
private HemfPlusDraw.EmfPlusUnitType unitType;
private double penWidth;
private AffineTransform trans;
private EmfPlusLineCapType startCap, endCap;
private EmfPlusLineJoin join;
private Double mitterLimit;
private EmfPlusLineStyle style;
EmfPlusDashedLineCapType dashedLineCapType;
private Double dashOffset;
private double[] dashedLineData;
private EmfPlusPenAlignment penAlignment;
private double[] compoundLineData;
private EmfPlusCustomLineCap customStartCap;
private EmfPlusCustomLineCap customEndCap;
@Override
public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
// An EmfPlusGraphicsVersion object that specifies the version of operating system graphics that
// was used to create this object.
long size = graphicsVersion.init(leis);
// This field MUST be set to zero.
type = leis.readInt();
// A 32-bit unsigned integer that specifies the data in the OptionalData field.
// This value MUST be composed of PenData flags
penDataFlags = leis.readInt();
// A 32-bit unsigned integer that specifies the measuring units for the pen.
// The value MUST be from the UnitType enumeration
unitType = HemfPlusDraw.EmfPlusUnitType.valueOf(leis.readInt());
// A 32-bit floating-point value that specifies the width of the line drawn by the pen in the units specified
// by the PenUnit field. If a zero width is specified, a minimum value is used, which is determined by the units.
penWidth = leis.readFloat();
size += 4* LittleEndianConsts.INT_SIZE;
if (TRANSFORM.isSet(penDataFlags)) {
// An optional EmfPlusTransformMatrix object that specifies a world space to device space transform for
// the pen. This field MUST be present if the PenDataTransform flag is set in the PenDataFlags field of
// the EmfPlusPenData object.
trans = new AffineTransform();
size += readXForm(leis, trans);
}
if (START_CAP.isSet(penDataFlags)) {
// An optional 32-bit signed integer that specifies the shape for the start of a line in the
// CustomStartCapData field. This field MUST be present if the PenDataStartCap flag is set in the
// PenDataFlags field of the EmfPlusPenData object, and the value MUST be defined in the LineCapType enumeration
startCap = EmfPlusLineCapType.valueOf(leis.readInt());
size += LittleEndianConsts.INT_SIZE;
}
if (END_CAP.isSet(penDataFlags)) {
// An optional 32-bit signed integer that specifies the shape for the end of a line in the
// CustomEndCapData field. This field MUST be present if the PenDataEndCap flag is set in the
// PenDataFlags field of the EmfPlusPenData object, and the value MUST be defined in the LineCapType enumeration.
endCap = EmfPlusLineCapType.valueOf(leis.readInt());
size += LittleEndianConsts.INT_SIZE;
}
if (JOIN.isSet(penDataFlags)) {
// An optional 32-bit signed integer that specifies how to join two lines that are drawn by the same pen
// and whose ends meet. This field MUST be present if the PenDataJoin flag is set in the PenDataFlags
// field of the EmfPlusPenData object, and the value MUST be defined in the LineJoinType enumeration
join = EmfPlusLineJoin.valueOf(leis.readInt());
size += LittleEndianConsts.INT_SIZE;
}
if (MITER_LIMIT.isSet(penDataFlags)) {
// An optional 32-bit floating-point value that specifies the miter limit, which is the maximum allowed
// ratio of miter length to line width. The miter length is the distance from the intersection of the
// line walls on the inside the join to the intersection of the line walls outside the join. The miter
// length can be large when the angle between two lines is small. This field MUST be present if the
// PenDataMiterLimit flag is set in the PenDataFlags field of the EmfPlusPenData object.
mitterLimit = (double)leis.readFloat();
size += LittleEndianConsts.INT_SIZE;
}
if (LINE_STYLE.isSet(penDataFlags)) {
// An optional 32-bit signed integer that specifies the style used for lines drawn with this pen object.
// This field MUST be present if the PenDataLineStyle flag is set in the PenDataFlags field of the
// EmfPlusPenData object, and the value MUST be defined in the LineStyle enumeration
style = EmfPlusLineStyle.valueOf(leis.readInt());
size += LittleEndianConsts.INT_SIZE;
}
if (DASHED_LINE_CAP.isSet(penDataFlags)) {
// An optional 32-bit signed integer that specifies the shape for both ends of each dash in a dashed line.
// This field MUST be present if the PenDataDashedLineCap flag is set in the PenDataFlags field of the
// EmfPlusPenData object, and the value MUST be defined in the DashedLineCapType enumeration
dashedLineCapType = EmfPlusDashedLineCapType.valueOf(leis.readInt());
size += LittleEndianConsts.INT_SIZE;
}
if (DASHED_LINE_OFFSET.isSet(penDataFlags)) {
// An optional 32-bit floating-point value that specifies the distance from the start of a line to the
// start of the first space in a dashed line pattern. This field MUST be present if the
// PenDataDashedLineOffset flag is set in the PenDataFlags field of the EmfPlusPenData object.
dashOffset = (double)leis.readFloat();
size += LittleEndianConsts.INT_SIZE;
}
if (DASHED_LINE.isSet(penDataFlags)) {
// A 32-bit unsigned integer that specifies the number of elements in the DashedLineData field.
int dashesSize = leis.readInt();
if (dashesSize < 0 || dashesSize > 1000) {
throw new RuntimeException("Invalid dash data size");
}
// An array of DashedLineDataSize floating-point values that specify the lengths of the dashes and spaces in a dashed line.
dashedLineData = new double[dashesSize];
for (int i=0; i<dashesSize; i++) {
dashedLineData[i] = leis.readFloat();
}
size += LittleEndianConsts.INT_SIZE * (dashesSize+1);
}
if (NON_CENTER.isSet(penDataFlags)) {
// An optional 32-bit signed integer that specifies the distribution of the pen width with respect to
// the coordinates of the line being drawn. This field MUST be present if the PenDataNonCenter flag is
// set in the PenDataFlags field of the EmfPlusPenData object, and the value MUST be defined in the
// PenAlignment enumeration
penAlignment = EmfPlusPenAlignment.valueOf(leis.readInt());
size += LittleEndianConsts.INT_SIZE;
}
if (COMPOUND_LINE.isSet(penDataFlags)) {
// A 32-bit unsigned integer that specifies the number of elements in the CompoundLineData field.
int compoundSize = leis.readInt();
if (compoundSize < 0 || compoundSize > 1000) {
throw new RuntimeException("Invalid compound line data size");
}
// An array of CompoundLineDataSize floating-point values that specify the compound line of a pen.
// The elements MUST be in increasing order, and their values MUST be between 0.0 and 1.0, inclusive.
compoundLineData = new double[compoundSize];
for (int i=0; i<compoundSize; i++) {
compoundLineData[i] = leis.readFloat();
}
size += LittleEndianConsts.INT_SIZE * (compoundSize+1);
}
if (CUSTOM_START_CAP.isSet(penDataFlags)) {
size += initCustomCap(c -> customStartCap = c, leis);
}
if (CUSTOM_END_CAP.isSet(penDataFlags)) {
size += initCustomCap(c -> customEndCap = c, leis);
}
return size;
}
private long initCustomCap(Consumer<EmfPlusCustomLineCap> setter, LittleEndianInputStream leis) throws IOException {
EmfPlusGraphicsVersion version = new EmfPlusGraphicsVersion();
long size = version.init(leis);
boolean adjustableArrow = (leis.readInt() != 0);
size += LittleEndianConsts.INT_SIZE;
EmfPlusCustomLineCap cap = (adjustableArrow) ? new EmfPlusAdjustableArrowCap() : new EmfPlusPathArrowCap();
size += cap.init(leis);
setter.accept(cap);
return size;
}
@Internal
public interface EmfPlusCustomLineCap {
long init(LittleEndianInputStream leis) throws IOException;
}
public static class EmfPlusPathArrowCap implements EmfPlusCustomLineCap {
/**
* If set, an EmfPlusFillPath object MUST be specified in the OptionalData field of the
* EmfPlusCustomLineCapData object for filling the custom line cap.
*/
private static final BitField FILL_PATH = BitFieldFactory.getInstance(0x00000001);
/**
* If set, an EmfPlusLinePath object MUST be specified in the OptionalData field of the
* EmfPlusCustomLineCapData object for outlining the custom line cap.
*/
private static final BitField LINE_PATH = BitFieldFactory.getInstance(0x00000002);
private int dataFlags;
private EmfPlusLineCapType baseCap;
private double baseInset;
private EmfPlusLineCapType startCap;
private EmfPlusLineCapType endCap;
private EmfPlusLineJoin join;
private double mitterLimit;
private double widthScale;
private final Point2D fillHotSpot = new Point2D.Double();
private final Point2D lineHotSpot = new Point2D.Double();
private EmfPlusPath fillPath;
private EmfPlusPath outlinePath;
@Override
public long init(LittleEndianInputStream leis) throws IOException {
// A 32-bit unsigned integer that specifies the data in the OptionalData field.
// This value MUST be composed of CustomLineCapData flags
dataFlags = leis.readInt();
// A 32-bit unsigned integer that specifies the value from the LineCap enumeration on which
// the custom line cap is based.
baseCap = EmfPlusLineCapType.valueOf(leis.readInt());
// A 32-bit floating-point value that specifies the distance between the
// beginning of the line cap and the end of the line.
baseInset = leis.readFloat();
// A 32-bit unsigned integer that specifies the value in the LineCap enumeration that indicates the line
// cap used at the start/end of the line to be drawn.
startCap = EmfPlusLineCapType.valueOf(leis.readInt());
endCap = EmfPlusLineCapType.valueOf(leis.readInt());
// A 32-bit unsigned integer that specifies the value in the LineJoin enumeration, which specifies how
// to join two lines that are drawn by the same pen and whose ends meet. At the intersection of the two
// line ends, a line join makes the connection look more continuous.
join = EmfPlusLineJoin.valueOf(leis.readInt());
// A 32-bit floating-point value that contains the limit of the thickness of the join on a mitered corner
// by setting the maximum allowed ratio of miter length to line width.
mitterLimit = leis.readFloat();
// A 32-bit floating-point value that specifies the amount by which to scale the custom line cap with
// respect to the width of the EmfPlusPen object that is used to draw the lines.
widthScale = leis.readFloat();
int size = 8* LittleEndianConsts.INT_SIZE;
// An EmfPlusPointF object that is not currently used. It MUST be set to {0.0, 0.0}.
size += readPointF(leis, fillHotSpot);
size += readPointF(leis, lineHotSpot);
if (FILL_PATH.isSet(dataFlags)) {
fillPath = new EmfPlusPath();
size += fillPath.init(leis, -1, null, -1);
}
if (LINE_PATH.isSet(dataFlags)) {
outlinePath = new EmfPlusPath();
size += outlinePath.init(leis, -1, null, -1);
}
return size;
}
}
public static class EmfPlusAdjustableArrowCap implements EmfPlusCustomLineCap {
private double width;
private double height;
private double middleInset;
private boolean isFilled;
private EmfPlusLineCapType startCap;
private EmfPlusLineCapType endCap;
private EmfPlusLineJoin join;
private double mitterLimit;
private double widthScale;
private final Point2D fillHotSpot = new Point2D.Double();
private final Point2D lineHotSpot = new Point2D.Double();
@Override
public long init(LittleEndianInputStream leis) throws IOException {
// A 32-bit floating-point value that specifies the width of the arrow cap.
// The width of the arrow cap is scaled by the width of the EmfPlusPen object that is used to draw the
// line being capped. For example, when drawing a capped line with a pen that has a width of 5 pixels,
// and the adjustable arrow cap object has a width of 3, the actual arrow cap is drawn 15 pixels wide.
width = leis.readFloat();
// A 32-bit floating-point value that specifies the height of the arrow cap.
// The height of the arrow cap is scaled by the width of the EmfPlusPen object that is used to draw the
// line being capped. For example, when drawing a capped line with a pen that has a width of 5 pixels,
// and the adjustable arrow cap object has a height of 3, the actual arrow cap is drawn 15 pixels high.
height = leis.readFloat();
// A 32-bit floating-point value that specifies the number of pixels between the outline of the arrow
// cap and the fill of the arrow cap.
middleInset = leis.readFloat();
// A 32-bit Boolean value that specifies whether the arrow cap is filled.
// If the arrow cap is not filled, only the outline is drawn.
isFilled = (leis.readInt() != 0);
// A 32-bit unsigned integer that specifies the value in the LineCap enumeration that indicates
// the line cap to be used at the start/end of the line to be drawn.
startCap = EmfPlusLineCapType.valueOf(leis.readInt());
endCap = EmfPlusLineCapType.valueOf(leis.readInt());
// 32-bit unsigned integer that specifies the value in the LineJoin enumeration that specifies how to
// join two lines that are drawn by the same pen and whose ends meet. At the intersection of the two
// line ends, a line join makes the connection look more continuous.
join = EmfPlusLineJoin.valueOf(leis.readInt());
// A 32-bit floating-point value that specifies the limit of the thickness of the join on a mitered
// corner by setting the maximum allowed ratio of miter length to line width.
mitterLimit = leis.readFloat();
// A 32-bit floating-point value that specifies the amount by which to scale an EmfPlusCustomLineCap
// object with respect to the width of the graphics pen that is used to draw the lines.
widthScale = leis.readFloat();
int size = 9 * LittleEndianConsts.INT_SIZE;
// An EmfPlusPointF object that is not currently used. It MUST be set to {0.0, 0.0}.
size += readPointF(leis, fillHotSpot);
// An EmfPlusPointF object that is not currently used. It MUST be set to {0.0, 0.0}.
size += readPointF(leis, lineHotSpot);
return size;
}
}
}
}

View File

@ -0,0 +1,173 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hemf.record.emfplus;
import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readRectF;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData;
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType;
import org.apache.poi.hemf.record.emfplus.HemfPlusPath.EmfPlusPath;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;
public class HemfPlusRegion {
public enum EmfPlusRegionNodeDataType {
/**
* Specifies a region node with child nodes. A Boolean AND operation SHOULD be applied to the left and right
* child nodes specified by an EmfPlusRegionNodeChildNodes object
*/
AND(0X00000001, EmfPlusRegionNode::new),
/**
* Specifies a region node with child nodes. A Boolean OR operation SHOULD be applied to the left and right
* child nodes specified by an EmfPlusRegionNodeChildNodes object.
*/
OR(0X00000002, EmfPlusRegionNode::new),
/**
* Specifies a region node with child nodes. A Boolean XOR operation SHOULD be applied to the left and right
* child nodes specified by an EmfPlusRegionNodeChildNodes object.
*/
XOR(0X00000003, EmfPlusRegionNode::new),
/**
* Specifies a region node with child nodes. A Boolean operation, defined as "the part of region 1 that is excluded
* from region 2", SHOULD be applied to the left and right child nodes specified by an EmfPlusRegionNodeChildNodes object.
*/
EXCLUDE(0X00000004, EmfPlusRegionNode::new),
/**
* Specifies a region node with child nodes. A Boolean operation, defined as "the part of region 2 that is excluded
* from region 1", SHOULD be applied to the left and right child nodes specified by an EmfPlusRegionNodeChildNodes object.
*/
COMPLEMENT(0X00000005, EmfPlusRegionNode::new),
/**
* Specifies a region node with no child nodes.
* The RegionNodeData field SHOULD specify a boundary with an EmfPlusRectF object.
*/
RECT(0X10000000, EmfPlusRegionRect::new),
/**
* Specifies a region node with no child nodes.
* The RegionNodeData field SHOULD specify a boundary with an EmfPlusRegionNodePath object
*/
PATH(0X10000001, EmfPlusRegionPath::new),
/** Specifies a region node with no child nodes. The RegionNodeData field SHOULD NOT be present. */
EMPTY(0X10000002, EmfPlusRegionEmpty::new),
/** Specifies a region node with no child nodes, and its bounds are not defined. */
INFINITE(0X10000003, EmfPlusRegionInfinite::new)
;
public final int id;
public final Supplier<EmfPlusRegionNodeData> constructor;
EmfPlusRegionNodeDataType(int id, Supplier<EmfPlusRegionNodeData> constructor) {
this.id = id;
this.constructor = constructor;
}
public static EmfPlusRegionNodeDataType valueOf(int id) {
for (EmfPlusRegionNodeDataType wrt : values()) {
if (wrt.id == id) return wrt;
}
return null;
}
}
public static class EmfPlusRegion implements EmfPlusObjectData {
private final EmfPlusGraphicsVersion version = new EmfPlusGraphicsVersion();
private EmfPlusRegionNodeData regionNode;
@Override
public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
long size = version.init(leis);
// A 32-bit unsigned integer that specifies the number of child nodes in the RegionNode field.
int nodeCount = leis.readInt();
size += LittleEndianConsts.INT_SIZE;
// An array of RegionNodeCount+1 EmfPlusRegionNode objects. Regions are specified as a binary tree of
// region nodes, and each node MUST either be a terminal node or specify one or two child nodes.
// RegionNode MUST contain at least one element.
size += readNode(leis, d -> regionNode = d);
return size;
}
}
public interface EmfPlusRegionNodeData {
long init(LittleEndianInputStream leis) throws IOException;
}
public static class EmfPlusRegionPath extends EmfPlusPath implements EmfPlusRegionNodeData {
public long init(LittleEndianInputStream leis) throws IOException {
int dataSize = leis.readInt();
return super.init(leis, dataSize, EmfPlusObjectType.PATH, 0) + LittleEndianConsts.INT_SIZE;
}
}
public static class EmfPlusRegionInfinite implements EmfPlusRegionNodeData {
@Override
public long init(LittleEndianInputStream leis) throws IOException {
return 0;
}
}
public static class EmfPlusRegionEmpty implements EmfPlusRegionNodeData {
@Override
public long init(LittleEndianInputStream leis) throws IOException {
return 0;
}
}
public static class EmfPlusRegionRect implements EmfPlusRegionNodeData {
private final Rectangle2D rect = new Rectangle2D.Double();
@Override
public long init(LittleEndianInputStream leis) {
return readRectF(leis, rect);
}
}
public static class EmfPlusRegionNode implements EmfPlusRegionNodeData {
private EmfPlusRegionNodeData left, right;
@Override
public long init(LittleEndianInputStream leis) throws IOException {
long size = readNode(leis, n -> left = n);
size += readNode(leis, n -> right = n);
return size;
}
}
private static long readNode(LittleEndianInputStream leis, Consumer<EmfPlusRegionNodeData> con) throws IOException {
// A 32-bit unsigned integer that specifies the type of data in the RegionNodeData field.
// This value MUST be defined in the RegionNodeDataType enumeration
EmfPlusRegionNodeDataType type = EmfPlusRegionNodeDataType.valueOf(leis.readInt());
assert(type != null);
EmfPlusRegionNodeData nd = type.constructor.get();
con.accept(nd);
return LittleEndianConsts.INT_SIZE + nd.init(leis);
}
}

View File

@ -36,7 +36,11 @@ import javax.imageio.ImageIO;
import org.apache.poi.hemf.record.emf.HemfComment;
import org.apache.poi.hemf.record.emf.HemfRecord;
import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusBitmapDataType;
import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusImage;
import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusPixelFormat;
import org.apache.poi.hemf.record.emfplus.HemfPlusObject;
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObject;
import org.apache.poi.hwmf.record.HwmfBitmapDib;
import org.apache.poi.hwmf.record.HwmfFill;
import org.apache.poi.hwmf.usermodel.HwmfEmbedded;
@ -105,7 +109,7 @@ public class HemfEmbeddedIterator implements Iterator<HwmfEmbedded> {
return true;
}
if (obj instanceof HemfPlusObject.EmfPlusObject && ((HemfPlusObject.EmfPlusObject)obj).getObjectType() == HemfPlusObject.EmfPlusObjectType.IMAGE) {
if (obj instanceof EmfPlusObject && ((EmfPlusObject)obj).getObjectType() == HemfPlusObject.EmfPlusObjectType.IMAGE) {
current = obj;
return true;
}
@ -196,13 +200,13 @@ public class HemfEmbeddedIterator implements Iterator<HwmfEmbedded> {
}
private HwmfEmbedded checkEmfPlusObject() {
if (!(current instanceof HemfPlusObject.EmfPlusObject)) {
if (!(current instanceof EmfPlusObject)) {
return null;
}
HemfPlusObject.EmfPlusObject epo = (HemfPlusObject.EmfPlusObject)current;
EmfPlusObject epo = (EmfPlusObject)current;
assert(epo.getObjectType() == HemfPlusObject.EmfPlusObjectType.IMAGE);
HemfPlusObject.EmfPlusImage img = epo.getObjectData();
EmfPlusImage img = epo.getObjectData();
assert(img.getImageDataType() != null);
HwmfEmbedded emb = getEmfPlusImageData();
@ -210,7 +214,7 @@ public class HemfEmbeddedIterator implements Iterator<HwmfEmbedded> {
HwmfEmbeddedType et;
switch (img.getImageDataType()) {
case BITMAP:
if (img.getBitmapType() == HemfPlusObject.EmfPlusBitmapDataType.COMPRESSED) {
if (img.getBitmapType() == EmfPlusBitmapDataType.COMPRESSED) {
switch (FileMagic.valueOf(emb.getRawData())) {
case JPEG:
et = HwmfEmbeddedType.JPEG;
@ -262,11 +266,11 @@ public class HemfEmbeddedIterator implements Iterator<HwmfEmbedded> {
/**
* Compress GDIs internal format to something useful
*/
private void compressGDIBitmap(HemfPlusObject.EmfPlusImage img, HwmfEmbedded emb, HwmfEmbeddedType et) {
private void compressGDIBitmap(EmfPlusImage img, HwmfEmbedded emb, HwmfEmbeddedType et) {
final int width = img.getBitmapWidth();
final int height = img.getBitmapHeight();
final int stride = img.getBitmapStride();
final HemfPlusObject.EmfPlusPixelFormat pf = img.getPixelFormat();
final EmfPlusPixelFormat pf = img.getPixelFormat();
int[] nBits, bOffs;
switch (pf) {
@ -306,14 +310,14 @@ public class HemfEmbeddedIterator implements Iterator<HwmfEmbedded> {
private HwmfEmbedded getEmfPlusImageData() {
HemfPlusObject.EmfPlusObject epo = (HemfPlusObject.EmfPlusObject)current;
EmfPlusObject epo = (EmfPlusObject)current;
assert(epo.getObjectType() == HemfPlusObject.EmfPlusObjectType.IMAGE);
final int objectId = epo.getObjectId();
HwmfEmbedded emb = new HwmfEmbedded();
HemfPlusObject.EmfPlusImage img = (HemfPlusObject.EmfPlusImage)epo.getObjectData();
EmfPlusImage img = (EmfPlusImage)epo.getObjectData();
assert(img.getImageDataType() != null);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
@ -324,10 +328,10 @@ public class HemfEmbeddedIterator implements Iterator<HwmfEmbedded> {
current = null;
//noinspection ConstantConditions
if (hasNext() &&
(current instanceof HemfPlusObject.EmfPlusObject) &&
((epo = (HemfPlusObject.EmfPlusObject) current).getObjectId() == objectId)
(current instanceof EmfPlusObject) &&
((epo = (EmfPlusObject) current).getObjectId() == objectId)
) {
img = (HemfPlusObject.EmfPlusImage)epo.getObjectData();
img = (EmfPlusImage)epo.getObjectData();
} else {
return emb;
}

View File

@ -94,7 +94,7 @@ public class HemfPictureTest {
if (entry.isDirectory() || !etName.endsWith(".emf") || passed.contains(etName)) continue;
if (!etName.equals("emfs/commoncrawl2/2S/2SYMYPLNJURGCXJKLNZCJQGIBHVMQTRS_0.emf")) continue;
// if (!etName.equals("emfs/commoncrawl2/2S/2SYMYPLNJURGCXJKLNZCJQGIBHVMQTRS_0.emf")) continue;
// emfs/commoncrawl2/ZJ/ZJT2BZPLQR7DKSKYLYL6GRDEUM2KIO5F_4.emf
// emfs/govdocs1/005/005203.ppt_3.emf