| /* ==================================================================== |
| 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 static org.apache.poi.util.GenericRecordUtil.getBitsAsString; |
| |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.Point2D; |
| import java.io.IOException; |
| import java.util.Collections; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.function.Consumer; |
| import java.util.function.Supplier; |
| |
| import org.apache.poi.common.usermodel.GenericRecord; |
| import org.apache.poi.hemf.draw.HemfDrawProperties; |
| import org.apache.poi.hemf.draw.HemfGraphics; |
| import org.apache.poi.hemf.record.emf.HemfPenStyle; |
| import org.apache.poi.hemf.record.emfplus.HemfPlusBrush.EmfPlusBrush; |
| 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.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType; |
| import org.apache.poi.hemf.record.emfplus.HemfPlusPath.EmfPlusPath; |
| import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineCap; |
| import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineDash; |
| import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineJoin; |
| import org.apache.poi.util.BitField; |
| import org.apache.poi.util.BitFieldFactory; |
| import org.apache.poi.util.GenericRecordUtil; |
| import org.apache.poi.util.Internal; |
| import org.apache.poi.util.LittleEndianConsts; |
| import org.apache.poi.util.LittleEndianInputStream; |
| |
| @SuppressWarnings("WeakerAccess") |
| 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; |
| } |
| } |
| |
| |
| @Internal |
| public static abstract class EmfPlusCustomLineCap implements GenericRecord { |
| private EmfPlusLineCapType startCap; |
| private EmfPlusLineCapType endCap; |
| private EmfPlusLineJoin join; |
| private double miterLimit; |
| private double widthScale; |
| private final Point2D fillHotSpot = new Point2D.Double(); |
| private final Point2D lineHotSpot = new Point2D.Double(); |
| |
| protected long init(LittleEndianInputStream leis) throws IOException { |
| // 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. |
| miterLimit = 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(); |
| |
| long size = 5L*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); |
| |
| return size; |
| } |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| final Map<String,Supplier<?>> m = new LinkedHashMap<>(); |
| m.put("startCap", () -> startCap); |
| m.put("endCap", () -> endCap); |
| m.put("join", () -> join); |
| m.put("miterLimit", () -> miterLimit); |
| m.put("widthScale", () -> widthScale); |
| m.put("fillHotSpot", () -> fillHotSpot); |
| m.put("lineHotSpot", () -> lineHotSpot); |
| return m; |
| } |
| |
| @Override |
| public final EmfPlusObjectType getGenericRecordType() { |
| return EmfPlusObjectType.CUSTOM_LINE_CAP; |
| } |
| } |
| |
| |
| public static class EmfPlusPen implements EmfPlusObjectData { |
| /** |
| * If set, a 2x3 transform matrix MUST be specified in the OptionalData field of an EmfPlusPenData object. |
| */ |
| private static final 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 static final 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 static final BitField END_CAP = BitFieldFactory.getInstance(0x00000004); |
| /** |
| * Indicates whether a line join type MUST be specified in the OptionalData |
| * field of an EmfPlusPenData object. |
| */ |
| private static final BitField JOIN = BitFieldFactory.getInstance(0x00000008); |
| /** |
| * Indicates whether a miter limit MUST be specified in the OptionalData field of an EmfPlusPenData object. |
| */ |
| private static final BitField MITER_LIMIT = BitFieldFactory.getInstance(0x00000010); |
| /** |
| * Indicates whether a line style MUST be specified in the OptionalData field of an EmfPlusPenData object. |
| */ |
| private static final BitField LINE_STYLE = BitFieldFactory.getInstance(0x00000020); |
| /** |
| * Indicates whether a dashed line cap MUST be specified in the OptionalData field of an EmfPlusPenData object. |
| */ |
| private static final 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 static final BitField DASHED_LINE_OFFSET = BitFieldFactory.getInstance(0x00000080); |
| /** |
| * Indicates whether an EmfPlusDashedLineData object MUST be specified in the |
| * OptionalData field of an EmfPlusPenData object. |
| */ |
| private static final BitField DASHED_LINE = BitFieldFactory.getInstance(0x00000100); |
| /** |
| * Indicates whether a pen alignment MUST be specified in the OptionalData field of an EmfPlusPenData object. |
| */ |
| private static final 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 static final BitField COMPOUND_LINE = BitFieldFactory.getInstance(0x00000400); |
| /** |
| * Indicates whether an EmfPlusCustomStartCapData object MUST be specified |
| * in the OptionalData field of an EmfPlusPenData object.y |
| */ |
| private static final BitField CUSTOM_START_CAP = BitFieldFactory.getInstance(0x00000800); |
| /** |
| * Indicates whether an EmfPlusCustomEndCapData object MUST be specified in |
| * the OptionalData field of an EmfPlusPenData object. |
| */ |
| private static final BitField CUSTOM_END_CAP = BitFieldFactory.getInstance(0x00001000); |
| |
| private static final int[] FLAGS_MASKS = { |
| 0x00000001, 0x00000002, 0x00000004, 0x00000008, 0x00000010, |
| 0x00000020, 0x00000040, 0x00000080, 0x00000100, 0x00000200, |
| 0x00000400, 0x00000800, 0x00001000 |
| }; |
| |
| private static final String[] FLAGS_NAMES = { |
| "TRANSFORM", "START_CAP", "END_CAP", "JOIN", "MITER_LIMIT", |
| "LINE_STYLE", "DASHED_LINE_CAP", "DASHED_LINE_OFFSET", "DASHED_LINE", "NON_CENTER", |
| "COMPOUND_LINE", "CUSTOM_START_CAP", "CUSTOM_END_CAP" |
| }; |
| |
| private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion(); |
| |
| |
| private int type; |
| private int penDataFlags; |
| private EmfPlusUnitType unitType; |
| private double penWidth; |
| private final AffineTransform trans = new AffineTransform(); |
| private EmfPlusLineCapType startCap = EmfPlusLineCapType.FLAT; |
| private EmfPlusLineCapType endCap = startCap; |
| private EmfPlusLineJoin lineJoin = EmfPlusLineJoin.ROUND; |
| private Double miterLimit = 1.; |
| private EmfPlusLineStyle style = EmfPlusLineStyle.SOLID; |
| private EmfPlusDashedLineCapType dashedLineCapType; |
| private Double dashOffset; |
| private float[] dashedLineData; |
| private EmfPlusPenAlignment penAlignment; |
| private double[] compoundLineData; |
| private EmfPlusCustomLineCap customStartCap; |
| private EmfPlusCustomLineCap customEndCap; |
| |
| private final EmfPlusBrush brush = new EmfPlusBrush(); |
| |
| @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 = 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. |
| 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 |
| lineJoin = 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. |
| miterLimit = (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 float[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); |
| } |
| |
| size += brush.init(leis, dataSize-size, EmfPlusObjectType.BRUSH, 0); |
| |
| return size; |
| } |
| |
| @Override |
| public EmfPlusGraphicsVersion getGraphicsVersion() { |
| return graphicsVersion; |
| } |
| |
| @SuppressWarnings("unused") |
| private long initCustomCap(Consumer<EmfPlusCustomLineCap> setter, LittleEndianInputStream leis) throws IOException { |
| int CustomStartCapSize = leis.readInt(); |
| int size = LittleEndianConsts.INT_SIZE; |
| |
| EmfPlusGraphicsVersion version = new EmfPlusGraphicsVersion(); |
| size += version.init(leis); |
| assert(version.getGraphicsVersion() != null); |
| |
| 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; |
| } |
| |
| @Override |
| public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) { |
| final HemfDrawProperties prop = ctx.getProperties(); |
| // TOOD: |
| // - set width according unit type |
| // - provide logic for different start and end cap |
| // - provide standard caps like diamond |
| // - support custom caps |
| |
| brush.applyPen(ctx, continuedObjectData); |
| prop.setPenWidth(penWidth); |
| |
| HwmfLineCap cap; |
| // ignore endCap for now |
| switch(startCap) { |
| default: |
| case FLAT: |
| cap = HwmfLineCap.FLAT; |
| break; |
| case ROUND: |
| cap = HwmfLineCap.ROUND; |
| break; |
| case SQUARE: |
| cap = HwmfLineCap.SQUARE; |
| break; |
| } |
| |
| HwmfLineJoin lineJoin; |
| switch (this.lineJoin) { |
| default: |
| case BEVEL: |
| lineJoin = HwmfLineJoin.BEVEL; |
| break; |
| case ROUND: |
| lineJoin = HwmfLineJoin.ROUND; |
| break; |
| case MITER_CLIPPED: |
| case MITER: |
| lineJoin = HwmfLineJoin.MITER; |
| break; |
| } |
| |
| HwmfLineDash lineDash = (dashedLineData == null) ? HwmfLineDash.SOLID : HwmfLineDash.USERSTYLE; |
| |
| boolean isAlternate = (lineDash != HwmfLineDash.SOLID && dashOffset != null && dashOffset == 0); |
| boolean isGeometric = (unitType == EmfPlusUnitType.World || unitType == EmfPlusUnitType.Display); |
| HemfPenStyle penStyle = HemfPenStyle.valueOf(cap, lineJoin, lineDash, isAlternate, isGeometric); |
| penStyle.setLineDashes(dashedLineData); |
| |
| prop.setPenStyle(penStyle); |
| } |
| |
| @Override |
| public EmfPlusObjectType getGenericRecordType() { |
| return EmfPlusObjectType.PEN; |
| } |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| final Map<String,Supplier<?>> m = new LinkedHashMap<>(); |
| m.put("type", () -> type); |
| m.put("flags", getBitsAsString(() -> penDataFlags, FLAGS_MASKS, FLAGS_NAMES)); |
| m.put("unitType", () -> unitType); |
| m.put("penWidth", () -> penWidth); |
| m.put("trans", () -> trans); |
| m.put("startCap", () -> startCap); |
| m.put("endCap", () -> endCap); |
| m.put("join", () -> lineJoin); |
| m.put("miterLimit", () -> miterLimit); |
| m.put("style", () -> style); |
| m.put("dashedLineCapType", () -> dashedLineCapType); |
| m.put("dashOffset", () -> dashOffset); |
| m.put("dashedLineData", () -> dashedLineData); |
| m.put("penAlignment", () -> penAlignment); |
| m.put("compoundLineData", () -> compoundLineData); |
| m.put("customStartCap", () -> customStartCap); |
| m.put("customEndCap", () -> customEndCap); |
| m.put("brush", () -> brush); |
| return Collections.unmodifiableMap(m); |
| } |
| } |
| |
| public static class EmfPlusPathArrowCap extends 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 static final int[] FLAGS_MASKS = { 0x00000001, 0x00000002 }; |
| |
| private static final String[] FLAGS_NAMES = { "FILL_PATH", "LINE_PATH" }; |
| |
| private int dataFlags; |
| private EmfPlusLineCapType baseCap; |
| private double baseInset; |
| 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(); |
| |
| long size = 3L*LittleEndianConsts.INT_SIZE; |
| |
| size += super.init(leis); |
| |
| size += initPath(leis, FILL_PATH, p -> fillPath = p); |
| size += initPath(leis, LINE_PATH, p -> outlinePath = p); |
| |
| return size; |
| } |
| |
| private long initPath(LittleEndianInputStream leis, BitField bitField, Consumer<EmfPlusPath> setter) throws IOException { |
| if (!bitField.isSet(dataFlags)) { |
| return 0; |
| } |
| int pathSize = leis.readInt(); |
| EmfPlusPath path = new EmfPlusPath(); |
| setter.accept(path); |
| return LittleEndianConsts.INT_SIZE + path.init(leis, pathSize, EmfPlusObjectType.PATH, -1); |
| } |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| return GenericRecordUtil.getGenericProperties( |
| "flags", getBitsAsString(() -> dataFlags, FLAGS_MASKS, FLAGS_NAMES), |
| "baseCap", () -> baseCap, |
| "baseInset", () -> baseInset, |
| "base", super::getGenericProperties, |
| "fillPath", () -> fillPath, |
| "outlinePath", () -> outlinePath |
| ); |
| } |
| } |
| |
| public static class EmfPlusAdjustableArrowCap extends EmfPlusCustomLineCap { |
| private double width; |
| private double height; |
| private double middleInset; |
| private boolean isFilled; |
| |
| @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); |
| |
| return 4*LittleEndianConsts.INT_SIZE + super.init(leis); |
| } |
| |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| return GenericRecordUtil.getGenericProperties( |
| "width", () -> width, |
| "height", () -> height, |
| "middleInset", () -> middleInset, |
| "isFilled", () -> isFilled, |
| "base", super::getGenericProperties |
| ); |
| } |
| } |
| } |