git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1859159 13f79535-47bb-0310-9956-ffa450edef68tags/REL_4_1_1
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} |
@@ -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 |