git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1848492 13f79535-47bb-0310-9956-ffa450edef68tags/REL_4_1_0
* | * | ||||
* @author Glen Stampoultzis (glens at apache.org) | * @author Glen Stampoultzis (glens at apache.org) | ||||
*/ | */ | ||||
@SuppressWarnings("WeakerAccess") | |||||
public final class EscherProperties { | public final class EscherProperties { | ||||
// Property constants | // Property constants | ||||
public static final short GEOMETRY__ADJUST8VALUE = 334; | public static final short GEOMETRY__ADJUST8VALUE = 334; | ||||
public static final short GEOMETRY__ADJUST9VALUE = 335; | public static final short GEOMETRY__ADJUST9VALUE = 335; | ||||
public static final short GEOMETRY__ADJUST10VALUE = 336; | public static final short GEOMETRY__ADJUST10VALUE = 336; | ||||
public static final short GEOMETRY__PCONNECTIONSITES = 337; | |||||
public static final short GEOMETRY__PCONNECTIONSITESDIR = 338; | |||||
public static final short GEOMETRY__XLIMO = 339; | |||||
public static final short GEOMETRY__YLIMO = 340; | |||||
public static final short GEOMETRY__PADJUSTHANDLES = 341; | |||||
public static final short GEOMETRY__PGUIDES = 342; | |||||
public static final short GEOMETRY__PINSCRIBE = 343; | |||||
public static final short GEOMETRY__CXK = 344; | |||||
public static final short GEOMETRY__PFRAGMENTS = 345; | |||||
public static final short GEOMETRY__SHADOWok = 378; | public static final short GEOMETRY__SHADOWok = 378; | ||||
public static final short GEOMETRY__3DOK = 379; | public static final short GEOMETRY__3DOK = 379; | ||||
public static final short GEOMETRY__LINEOK = 380; | public static final short GEOMETRY__LINEOK = 380; | ||||
private static final Map<Short, EscherPropertyMetaData> properties = initProps(); | private static final Map<Short, EscherPropertyMetaData> properties = initProps(); | ||||
private EscherProperties() { | |||||
} | |||||
private static Map<Short, EscherPropertyMetaData> initProps() { | private static Map<Short, EscherPropertyMetaData> initProps() { | ||||
Map<Short, EscherPropertyMetaData> m = new HashMap<>(); | Map<Short, EscherPropertyMetaData> m = new HashMap<>(); | ||||
addProp(m, TRANSFORM__ROTATION, "transform.rotation"); | addProp(m, TRANSFORM__ROTATION, "transform.rotation"); | ||||
addProp(m, GEOMETRY__ADJUST8VALUE, "geometry.adjust8value"); | addProp(m, GEOMETRY__ADJUST8VALUE, "geometry.adjust8value"); | ||||
addProp(m, GEOMETRY__ADJUST9VALUE, "geometry.adjust9value"); | addProp(m, GEOMETRY__ADJUST9VALUE, "geometry.adjust9value"); | ||||
addProp(m, GEOMETRY__ADJUST10VALUE, "geometry.adjust10value"); | addProp(m, GEOMETRY__ADJUST10VALUE, "geometry.adjust10value"); | ||||
addProp(m, GEOMETRY__PCONNECTIONSITES, "geometry.pConnectionSites"); | |||||
addProp(m, GEOMETRY__PCONNECTIONSITESDIR, "geometry.pConnectionSitesDir"); | |||||
addProp(m, GEOMETRY__XLIMO, "geometry.xLimo"); | |||||
addProp(m, GEOMETRY__YLIMO, "geometry.yLimo"); | |||||
addProp(m, GEOMETRY__PADJUSTHANDLES, "geometry.pAdjustHandles"); | |||||
addProp(m, GEOMETRY__PGUIDES, "geometry.pGuides"); | |||||
addProp(m, GEOMETRY__PINSCRIBE, "geometry.pInscribe"); | |||||
addProp(m, GEOMETRY__CXK, "geometry.cxk"); | |||||
addProp(m, GEOMETRY__PFRAGMENTS, "geometry.pFragments"); | |||||
addProp(m, GEOMETRY__SHADOWok, "geometry.shadowOK"); | addProp(m, GEOMETRY__SHADOWok, "geometry.shadowOK"); | ||||
addProp(m, GEOMETRY__3DOK, "geometry.3dok"); | addProp(m, GEOMETRY__3DOK, "geometry.3dok"); | ||||
addProp(m, GEOMETRY__LINEOK, "geometry.lineok"); | addProp(m, GEOMETRY__LINEOK, "geometry.lineok"); | ||||
} | } | ||||
private static void addProp(Map<Short, EscherPropertyMetaData> m, int s, String propName) { | private static void addProp(Map<Short, EscherPropertyMetaData> m, int s, String propName) { | ||||
m.put(Short.valueOf((short) s), new EscherPropertyMetaData(propName)); | |||||
m.put((short) s, new EscherPropertyMetaData(propName)); | |||||
} | } | ||||
private static void addProp(Map<Short, EscherPropertyMetaData> m, int s, String propName, byte type) { | private static void addProp(Map<Short, EscherPropertyMetaData> m, int s, String propName, byte type) { | ||||
m.put(Short.valueOf((short) s), new EscherPropertyMetaData(propName, type)); | |||||
m.put((short) s, new EscherPropertyMetaData(propName, type)); | |||||
} | } | ||||
public static String getPropertyName(short propertyId) { | public static String getPropertyName(short propertyId) { | ||||
EscherPropertyMetaData o = properties.get(Short.valueOf(propertyId)); | |||||
EscherPropertyMetaData o = properties.get(propertyId); | |||||
return o == null ? "unknown" : o.getDescription(); | return o == null ? "unknown" : o.getDescription(); | ||||
} | } | ||||
public static byte getPropertyType(short propertyId) { | public static byte getPropertyType(short propertyId) { | ||||
EscherPropertyMetaData escherPropertyMetaData = properties.get(Short.valueOf(propertyId)); | |||||
EscherPropertyMetaData escherPropertyMetaData = properties.get(propertyId); | |||||
return escherPropertyMetaData == null ? 0 : escherPropertyMetaData.getType(); | return escherPropertyMetaData == null ? 0 : escherPropertyMetaData.getType(); | ||||
} | } | ||||
} | } |
package org.apache.poi.sl.draw; | package org.apache.poi.sl.draw; | ||||
import java.awt.Graphics2D; | |||||
import java.awt.geom.AffineTransform; | |||||
import java.awt.geom.Path2D; | |||||
import java.util.ArrayList; | |||||
import java.util.Collection; | |||||
import java.util.List; | |||||
import org.apache.poi.sl.draw.geom.Outline; | |||||
import org.apache.poi.sl.draw.geom.Path; | |||||
import org.apache.poi.sl.usermodel.*; | |||||
import org.apache.poi.sl.usermodel.FreeformShape; | |||||
@SuppressWarnings("WeakerAccess") | |||||
public class DrawFreeformShape extends DrawAutoShape { | public class DrawFreeformShape extends DrawAutoShape { | ||||
public DrawFreeformShape(FreeformShape<?,?> shape) { | public DrawFreeformShape(FreeformShape<?,?> shape) { | ||||
super(shape); | super(shape); | ||||
} | } | ||||
protected Collection<Outline> computeOutlines(Graphics2D graphics) { | |||||
List<Outline> lst = new ArrayList<>(); | |||||
FreeformShape<?,?> fsh = (FreeformShape<?, ?>) getShape(); | |||||
Path2D sh = fsh.getPath(); | |||||
AffineTransform tx = (AffineTransform)graphics.getRenderingHint(Drawable.GROUP_TRANSFORM); | |||||
if (tx == null) { | |||||
tx = new AffineTransform(); | |||||
} | |||||
java.awt.Shape canvasShape = tx.createTransformedShape(sh); | |||||
FillStyle fs = fsh.getFillStyle(); | |||||
StrokeStyle ss = fsh.getStrokeStyle(); | |||||
Path path = new Path(fs != null, ss != null); | |||||
lst.add(new Outline(canvasShape, path)); | |||||
return lst; | |||||
} | |||||
@Override | |||||
protected TextShape<?,? extends TextParagraph<?,?,? extends TextRun>> getShape() { | |||||
return (TextShape<?,? extends TextParagraph<?,?,? extends TextRun>>)shape; | |||||
} | |||||
} | } |
} | } | ||||
for (Path p : geom) { | for (Path p : geom) { | ||||
double w = p.getW(), h = p.getH(), scaleX = Units.toPoints(1), scaleY = scaleX; | |||||
double w = p.getW(), h = p.getH(), scaleX, scaleY; | |||||
if (w == -1) { | if (w == -1) { | ||||
w = Units.toEMU(anchor.getWidth()); | w = Units.toEMU(anchor.getWidth()); | ||||
scaleX = Units.toPoints(1); | |||||
} else if (anchor.getWidth() == 0) { | |||||
scaleX = 1; | |||||
} else { | } else { | ||||
scaleX = anchor.getWidth() / w; | scaleX = anchor.getWidth() / w; | ||||
} | } | ||||
if (h == -1) { | if (h == -1) { | ||||
h = Units.toEMU(anchor.getHeight()); | h = Units.toEMU(anchor.getHeight()); | ||||
scaleY = Units.toPoints(1); | |||||
} else if (anchor.getHeight() == 0) { | |||||
scaleY = 1; | |||||
} else { | } else { | ||||
scaleY = anchor.getHeight() / h; | scaleY = anchor.getHeight() / h; | ||||
} | } |
import java.awt.geom.Rectangle2D; | import java.awt.geom.Rectangle2D; | ||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.regex.Pattern; | |||||
public class Context { | public class Context { | ||||
final Map<String, Double> _ctx = new HashMap<>(); | |||||
final IAdjustableShape _props; | |||||
final Rectangle2D _anchor; | |||||
private static final Pattern DOUBLE_PATTERN = Pattern.compile( | |||||
"[\\x00-\\x20]*[+-]?(NaN|Infinity|((((\\p{Digit}+)(\\.)?((\\p{Digit}+)?)" + | |||||
"([eE][+-]?(\\p{Digit}+))?)|(\\.(\\p{Digit}+)([eE][+-]?(\\p{Digit}+))?)|" + | |||||
"(((0[xX](\\p{XDigit}+)(\\.)?)|(0[xX](\\p{XDigit}+)?(\\.)(\\p{XDigit}+)))" + | |||||
"[pP][+-]?(\\p{Digit}+)))[fFdD]?))[\\x00-\\x20]*"); | |||||
private final Map<String, Double> _ctx = new HashMap<>(); | |||||
private final IAdjustableShape _props; | |||||
private final Rectangle2D _anchor; | |||||
public Context(CustomGeometry geom, Rectangle2D anchor, IAdjustableShape props){ | public Context(CustomGeometry geom, Rectangle2D anchor, IAdjustableShape props){ | ||||
_props = props; | _props = props; | ||||
} | } | ||||
} | } | ||||
public Rectangle2D getShapeAnchor(){ | |||||
Rectangle2D getShapeAnchor(){ | |||||
return _anchor; | return _anchor; | ||||
} | } | ||||
public Guide getAdjustValue(String name){ | |||||
Guide getAdjustValue(String name){ | |||||
// ignore HSLF props for now ... the results with default value are usually better - see #59004 | // ignore HSLF props for now ... the results with default value are usually better - see #59004 | ||||
return (_props.getClass().getName().contains("hslf")) ? null : _props.getAdjustValue(name); | return (_props.getClass().getName().contains("hslf")) ? null : _props.getAdjustValue(name); | ||||
} | } | ||||
public double getValue(String key){ | public double getValue(String key){ | ||||
if(key.matches("(\\+|-)?\\d+")){ | |||||
if(DOUBLE_PATTERN.matcher(key).matches()){ | |||||
return Double.parseDouble(key); | return Double.parseDouble(key); | ||||
} | } | ||||
Double val = _ctx.get(key); | |||||
// BuiltInGuide throws IllegalArgumentException if key is not defined | // BuiltInGuide throws IllegalArgumentException if key is not defined | ||||
return (val != null) ? val : evaluate(BuiltInGuide.valueOf("_"+key)); | |||||
return _ctx.containsKey(key) ? _ctx.get(key) : evaluate(BuiltInGuide.valueOf("_"+key)); | |||||
} | } | ||||
public double evaluate(Formula fmla){ | public double evaluate(Formula fmla){ |
P extends TextParagraph<S,P,? extends TextRun> | P extends TextParagraph<S,P,? extends TextRun> | ||||
> extends AutoShape<S,P> { | > extends AutoShape<S,P> { | ||||
/** | /** | ||||
* Gets the shape path. | |||||
* <p> | |||||
* The path is translated in the shape's coordinate system, i.e. | |||||
* freeform.getPath().getBounds2D() equals to freeform.getAnchor() | |||||
* (small discrepancies are possible due to rounding errors) | |||||
* </p> | |||||
* Gets the shape path.<p> | |||||
* | |||||
* The path is translated in the shape's coordinate system, i.e. | |||||
* freeform.getPath2D().getBounds2D() equals to freeform.getAnchor() | |||||
* (small discrepancies are possible due to rounding errors) | |||||
* | * | ||||
* @return the path | * @return the path | ||||
*/ | */ | ||||
Path2D.Double getPath(); | |||||
Path2D getPath(); | |||||
/** | /** | ||||
* Set the shape path | * Set the shape path | ||||
* @param path shape outline | * @param path shape outline | ||||
* @return the number of points written | * @return the number of points written | ||||
*/ | */ | ||||
int setPath(Path2D.Double path); | |||||
int setPath(Path2D path); | |||||
} | } |
points /= MASTER_DPI; | points /= MASTER_DPI; | ||||
return points; | return points; | ||||
} | } | ||||
public static int pointsToMaster(double points) { | public static int pointsToMaster(double points) { | ||||
points *= MASTER_DPI; | points *= MASTER_DPI; | ||||
points /= POINT_DPI; | points /= POINT_DPI; |
import java.awt.geom.PathIterator; | import java.awt.geom.PathIterator; | ||||
import java.awt.geom.Rectangle2D; | import java.awt.geom.Rectangle2D; | ||||
import javax.xml.stream.XMLStreamException; | |||||
import javax.xml.stream.XMLStreamReader; | |||||
import org.apache.poi.ooxml.POIXMLTypeLoader; | |||||
import org.apache.poi.sl.draw.geom.CustomGeometry; | |||||
import org.apache.poi.sl.draw.geom.PresetGeometries; | |||||
import org.apache.poi.sl.usermodel.FreeformShape; | import org.apache.poi.sl.usermodel.FreeformShape; | ||||
import org.apache.poi.util.Beta; | import org.apache.poi.util.Beta; | ||||
import org.apache.poi.util.POILogFactory; | import org.apache.poi.util.POILogFactory; | ||||
import org.apache.poi.util.Units; | import org.apache.poi.util.Units; | ||||
import org.apache.xmlbeans.XmlCursor; | import org.apache.xmlbeans.XmlCursor; | ||||
import org.apache.xmlbeans.XmlObject; | import org.apache.xmlbeans.XmlObject; | ||||
import org.apache.xmlbeans.XmlOptions; | |||||
import org.openxmlformats.schemas.drawingml.x2006.main.CTAdjPoint2D; | import org.openxmlformats.schemas.drawingml.x2006.main.CTAdjPoint2D; | ||||
import org.openxmlformats.schemas.drawingml.x2006.main.CTCustomGeometry2D; | import org.openxmlformats.schemas.drawingml.x2006.main.CTCustomGeometry2D; | ||||
import org.openxmlformats.schemas.drawingml.x2006.main.CTGeomRect; | import org.openxmlformats.schemas.drawingml.x2006.main.CTGeomRect; | ||||
} | } | ||||
@Override | @Override | ||||
public int setPath(final Path2D.Double path) { | |||||
public int setPath(final Path2D path) { | |||||
final CTPath2D ctPath = CTPath2D.Factory.newInstance(); | final CTPath2D ctPath = CTPath2D.Factory.newInstance(); | ||||
final Rectangle2D bounds = path.getBounds2D(); | final Rectangle2D bounds = path.getBounds2D(); | ||||
return numPoints; | return numPoints; | ||||
} | } | ||||
/** | |||||
* @return definition of the shape geometry | |||||
*/ | |||||
@Override | |||||
public CustomGeometry getGeometry() { | |||||
final XmlObject xo = getShapeProperties(); | |||||
if (!(xo instanceof CTShapeProperties)) { | |||||
return null; | |||||
} | |||||
XmlOptions xop = new XmlOptions(POIXMLTypeLoader.DEFAULT_XML_OPTIONS); | |||||
xop.setSaveOuter(); | |||||
XMLStreamReader staxReader = ((CTShapeProperties)xo).getCustGeom().newXMLStreamReader(xop); | |||||
CustomGeometry custGeo = PresetGeometries.convertCustomGeometry(staxReader); | |||||
try { | |||||
staxReader.close(); | |||||
} catch (XMLStreamException e) { | |||||
LOG.log(POILogger.WARN, | |||||
"An error occurred while closing a Custom Geometry XML Stream Reader: " + e.getMessage()); | |||||
} | |||||
return custGeo; | |||||
} | |||||
@Override | @Override | ||||
public Path2D.Double getPath() { | public Path2D.Double getPath() { |
} | } | ||||
/** | /** | ||||
* | |||||
* @return definition of the shape geometry | * @return definition of the shape geometry | ||||
*/ | */ | ||||
@Override | @Override |
private static final POIDataSamples samples = POIDataSamples.getSlideShowInstance(); | private static final POIDataSamples samples = POIDataSamples.getSlideShowInstance(); | ||||
private static final File basedir = null; | private static final File basedir = null; | ||||
private static final String files = | private static final String files = | ||||
"53446.ppt, alterman_security.ppt, alterman_security.pptx, KEY02.pptx, themes.pptx, backgrounds.pptx, layouts.pptx, sample.pptx, shapes.pptx, 54880_chinese.ppt, keyframes.pptx"; | |||||
"53446.ppt, alterman_security.ppt, alterman_security.pptx, KEY02.pptx, themes.pptx, " + | |||||
"backgrounds.pptx, layouts.pptx, sample.pptx, shapes.pptx, 54880_chinese.ppt, keyframes.pptx," + | |||||
"customGeo.pptx, customGeo.ppt"; | |||||
package org.apache.poi.hslf.usermodel; | package org.apache.poi.hslf.usermodel; | ||||
import java.awt.geom.Arc2D; | |||||
import java.awt.geom.Path2D; | |||||
import java.awt.geom.Point2D; | |||||
import java.awt.geom.Rectangle2D; | |||||
import java.util.Iterator; | |||||
import java.util.List; | |||||
import org.apache.poi.ddf.AbstractEscherOptRecord; | |||||
import org.apache.poi.ddf.EscherArrayProperty; | |||||
import org.apache.poi.ddf.EscherContainerRecord; | import org.apache.poi.ddf.EscherContainerRecord; | ||||
import org.apache.poi.ddf.EscherProperties; | import org.apache.poi.ddf.EscherProperties; | ||||
import org.apache.poi.sl.usermodel.*; | |||||
import org.apache.poi.ddf.EscherProperty; | |||||
import org.apache.poi.ddf.EscherSimpleProperty; | |||||
import org.apache.poi.sl.draw.binding.CTAdjPoint2D; | |||||
import org.apache.poi.sl.draw.binding.CTCustomGeometry2D; | |||||
import org.apache.poi.sl.draw.binding.CTPath2D; | |||||
import org.apache.poi.sl.draw.binding.CTPath2DArcTo; | |||||
import org.apache.poi.sl.draw.binding.CTPath2DCubicBezierTo; | |||||
import org.apache.poi.sl.draw.binding.CTPath2DLineTo; | |||||
import org.apache.poi.sl.draw.binding.CTPath2DList; | |||||
import org.apache.poi.sl.draw.binding.CTPath2DMoveTo; | |||||
import org.apache.poi.sl.draw.binding.ObjectFactory; | |||||
import org.apache.poi.sl.draw.geom.CustomGeometry; | |||||
import org.apache.poi.sl.usermodel.AutoShape; | |||||
import org.apache.poi.sl.usermodel.ShapeContainer; | |||||
import org.apache.poi.sl.usermodel.ShapeType; | |||||
import org.apache.poi.sl.usermodel.VerticalAlignment; | |||||
import org.apache.poi.ss.usermodel.ShapeTypes; | import org.apache.poi.ss.usermodel.ShapeTypes; | ||||
import org.apache.poi.util.BitField; | |||||
import org.apache.poi.util.BitFieldFactory; | |||||
import org.apache.poi.util.LittleEndian; | |||||
import org.apache.poi.util.POILogFactory; | |||||
import org.apache.poi.util.POILogger; | |||||
/** | /** | ||||
* Represents an AutoShape. | |||||
* <p> | |||||
* Represents an AutoShape.<p> | |||||
* | |||||
* AutoShapes are drawing objects with a particular shape that may be customized through smart resizing and adjustments. | * AutoShapes are drawing objects with a particular shape that may be customized through smart resizing and adjustments. | ||||
* See {@link ShapeTypes} | * See {@link ShapeTypes} | ||||
* </p> | |||||
* | |||||
* @author Yegor Kozlov | |||||
*/ | */ | ||||
public class HSLFAutoShape extends HSLFTextShape implements AutoShape<HSLFShape,HSLFTextParagraph> { | public class HSLFAutoShape extends HSLFTextShape implements AutoShape<HSLFShape,HSLFTextParagraph> { | ||||
private static final POILogger LOG = POILogFactory.getLogger(HSLFAutoShape.class); | |||||
static final byte[] SEGMENTINFO_MOVETO = new byte[]{0x00, 0x40}; | |||||
static final byte[] SEGMENTINFO_LINETO = new byte[]{0x00, (byte)0xAC}; | |||||
static final byte[] SEGMENTINFO_ESCAPE = new byte[]{0x01, 0x00}; | |||||
static final byte[] SEGMENTINFO_ESCAPE2 = new byte[]{0x01, 0x20}; | |||||
static final byte[] SEGMENTINFO_CUBICTO = new byte[]{0x00, (byte)0xAD}; | |||||
// OpenOffice inserts 0xB3 instead of 0xAD. | |||||
// protected static final byte[] SEGMENTINFO_CUBICTO2 = new byte[]{0x00, (byte)0xB3}; | |||||
static final byte[] SEGMENTINFO_CLOSE = new byte[]{0x01, (byte)0x60}; | |||||
static final byte[] SEGMENTINFO_END = new byte[]{0x00, (byte)0x80}; | |||||
private static final BitField PATH_INFO = BitFieldFactory.getInstance(0xE000); | |||||
private static final BitField ESCAPE_INFO = BitFieldFactory.getInstance(0x1F00); | |||||
enum PathInfo { | |||||
lineTo(0),curveTo(1),moveTo(2),close(3),end(4),escape(5),clientEscape(6); | |||||
private final int flag; | |||||
PathInfo(int flag) { | |||||
this.flag = flag; | |||||
} | |||||
public int getFlag() { | |||||
return flag; | |||||
} | |||||
static PathInfo valueOf(int flag) { | |||||
for (PathInfo v : values()) { | |||||
if (v.flag == flag) { | |||||
return v; | |||||
} | |||||
} | |||||
return null; | |||||
} | |||||
} | |||||
enum EscapeInfo { | |||||
EXTENSION(0x0000), | |||||
ANGLE_ELLIPSE_TO(0x0001), | |||||
ANGLE_ELLIPSE(0x0002), | |||||
ARC_TO(0x0003), | |||||
ARC(0x0004), | |||||
CLOCKWISE_ARC_TO(0x0005), | |||||
CLOCKWISE_ARC(0x0006), | |||||
ELLIPTICAL_QUADRANT_X(0x0007), | |||||
ELLIPTICAL_QUADRANT_Y(0x0008), | |||||
QUADRATIC_BEZIER(0x0009), | |||||
NO_FILL(0X000A), | |||||
NO_LINE(0X000B), | |||||
AUTO_LINE(0X000C), | |||||
AUTO_CURVE(0X000D), | |||||
CORNER_LINE(0X000E), | |||||
CORNER_CURVE(0X000F), | |||||
SMOOTH_LINE(0X0010), | |||||
SMOOTH_CURVE(0X0011), | |||||
SYMMETRIC_LINE(0X0012), | |||||
SYMMETRIC_CURVE(0X0013), | |||||
FREEFORM(0X0014), | |||||
FILL_COLOR(0X0015), | |||||
LINE_COLOR(0X0016); | |||||
private final int flag; | |||||
EscapeInfo(int flag) { | |||||
this.flag = flag; | |||||
} | |||||
public int getFlag() { | |||||
return flag; | |||||
} | |||||
static EscapeInfo valueOf(int flag) { | |||||
for (EscapeInfo v : values()) { | |||||
if (v.flag == flag) { | |||||
return v; | |||||
} | |||||
} | |||||
return null; | |||||
} | |||||
} | |||||
protected HSLFAutoShape(EscherContainerRecord escherRecord, ShapeContainer<HSLFShape,HSLFTextParagraph> parent){ | protected HSLFAutoShape(EscherContainerRecord escherRecord, ShapeContainer<HSLFShape,HSLFTextParagraph> parent){ | ||||
super(escherRecord, parent); | super(escherRecord, parent); | ||||
} | } | ||||
/** | /** | ||||
* Gets adjust value which controls smart resizing of the auto-shape. | |||||
* Gets adjust value which controls smart resizing of the auto-shape.<p> | |||||
* | * | ||||
* <p> | |||||
* The adjustment values are given in shape coordinates: | * The adjustment values are given in shape coordinates: | ||||
* the origin is at the top-left, positive-x is to the right, positive-y is down. | * the origin is at the top-left, positive-x is to the right, positive-y is down. | ||||
* The region from (0,0) to (S,S) maps to the geometry box of the shape (S=21600 is a constant). | * The region from (0,0) to (S,S) maps to the geometry box of the shape (S=21600 is a constant). | ||||
* </p> | |||||
* | * | ||||
* @param idx the adjust index in the [0, 9] range | * @param idx the adjust index in the [0, 9] range | ||||
* @return the adjustment value | * @return the adjustment value | ||||
} | } | ||||
/** | /** | ||||
* Sets adjust value which controls smart resizing of the auto-shape. | |||||
* Sets adjust value which controls smart resizing of the auto-shape.<p> | |||||
* | * | ||||
* <p> | |||||
* The adjustment values are given in shape coordinates: | * The adjustment values are given in shape coordinates: | ||||
* the origin is at the top-left, positive-x is to the right, positive-y is down. | * the origin is at the top-left, positive-x is to the right, positive-y is down. | ||||
* The region from (0,0) to (S,S) maps to the geometry box of the shape (S=21600 is a constant). | * The region from (0,0) to (S,S) maps to the geometry box of the shape (S=21600 is a constant). | ||||
* </p> | |||||
* | * | ||||
* @param idx the adjust index in the [0, 9] range | * @param idx the adjust index in the [0, 9] range | ||||
* @param val the adjustment value | * @param val the adjustment value | ||||
setEscherProperty((short)(EscherProperties.GEOMETRY__ADJUSTVALUE + idx), val); | setEscherProperty((short)(EscherProperties.GEOMETRY__ADJUSTVALUE + idx), val); | ||||
} | } | ||||
@Override | |||||
public CustomGeometry getGeometry() { | |||||
return getGeometry(new Path2D.Double()); | |||||
} | |||||
CustomGeometry getGeometry(Path2D path2D) { | |||||
final ObjectFactory of = new ObjectFactory(); | |||||
final CTCustomGeometry2D cusGeo = of.createCTCustomGeometry2D(); | |||||
cusGeo.setAvLst(of.createCTGeomGuideList()); | |||||
cusGeo.setGdLst(of.createCTGeomGuideList()); | |||||
cusGeo.setAhLst(of.createCTAdjustHandleList()); | |||||
cusGeo.setCxnLst(of.createCTConnectionSiteList()); | |||||
final AbstractEscherOptRecord opt = getEscherOptRecord(); | |||||
EscherArrayProperty verticesProp = getShapeProp(opt, EscherProperties.GEOMETRY__VERTICES); | |||||
EscherArrayProperty segmentsProp = getShapeProp(opt, EscherProperties.GEOMETRY__SEGMENTINFO); | |||||
// return empty path if either GEOMETRY__VERTICES or GEOMETRY__SEGMENTINFO is missing, see Bugzilla 54188 | |||||
//sanity check | |||||
if(verticesProp == null) { | |||||
LOG.log(POILogger.WARN, "Freeform is missing GEOMETRY__VERTICES "); | |||||
return super.getGeometry(); | |||||
} | |||||
if(segmentsProp == null) { | |||||
LOG.log(POILogger.WARN, "Freeform is missing GEOMETRY__SEGMENTINFO "); | |||||
return super.getGeometry(); | |||||
} | |||||
final Iterator<byte[]> vertIter = verticesProp.iterator(); | |||||
final Iterator<byte[]> segIter = segmentsProp.iterator(); | |||||
final int[] xyPoints = new int[2]; | |||||
boolean isClosed = false; | |||||
final CTPath2DList pathLst = of.createCTPath2DList(); | |||||
final CTPath2D pathCT = of.createCTPath2D(); | |||||
final List<Object> moveLst = pathCT.getCloseOrMoveToOrLnTo(); | |||||
pathLst.getPath().add(pathCT); | |||||
cusGeo.setPathLst(pathLst); | |||||
while (segIter.hasNext()) { | |||||
byte[] segElem = segIter.next(); | |||||
HSLFFreeformShape.PathInfo pi = getPathInfo(segElem); | |||||
if (pi == null) { | |||||
continue; | |||||
} | |||||
switch (pi) { | |||||
case escape: { | |||||
handleEscapeInfo(pathCT, path2D, segElem, vertIter); | |||||
break; | |||||
} | |||||
case moveTo: | |||||
if (vertIter.hasNext()) { | |||||
final CTPath2DMoveTo m = of.createCTPath2DMoveTo(); | |||||
m.setPt(fillPoint(vertIter.next(), xyPoints)); | |||||
moveLst.add(m); | |||||
path2D.moveTo(xyPoints[0], xyPoints[1]); | |||||
} | |||||
break; | |||||
case lineTo: | |||||
if (vertIter.hasNext()) { | |||||
final CTPath2DLineTo m = of.createCTPath2DLineTo(); | |||||
m.setPt(fillPoint(vertIter.next(), xyPoints)); | |||||
moveLst.add(m); | |||||
path2D.lineTo(xyPoints[0], xyPoints[1]); | |||||
} | |||||
break; | |||||
case curveTo: { | |||||
final CTPath2DCubicBezierTo m = of.createCTPath2DCubicBezierTo(); | |||||
List<CTAdjPoint2D> mLst = m.getPt(); | |||||
int[] pts = new int[6]; | |||||
for (int i=0; vertIter.hasNext() && i<3; i++) { | |||||
mLst.add(fillPoint(vertIter.next(), xyPoints)); | |||||
pts[i*2] = xyPoints[0]; | |||||
pts[i*2+1] = xyPoints[1]; | |||||
if (i == 2) { | |||||
moveLst.add(m); | |||||
path2D.curveTo(pts[0], pts[1], pts[2], pts[3], pts[4], pts[5]); | |||||
} | |||||
} | |||||
break; | |||||
} | |||||
case close: | |||||
moveLst.add(of.createCTPath2DClose()); | |||||
path2D.closePath(); | |||||
isClosed = true; | |||||
break; | |||||
default: | |||||
break; | |||||
} | |||||
} | |||||
EscherSimpleProperty shapePath = getShapeProp(opt, EscherProperties.GEOMETRY__SHAPEPATH); | |||||
HSLFFreeformShape.ShapePath sp = HSLFFreeformShape.ShapePath.valueOf(shapePath == null ? 1 : shapePath.getPropertyValue()); | |||||
if ((sp == HSLFFreeformShape.ShapePath.LINES_CLOSED || sp == HSLFFreeformShape.ShapePath.CURVES_CLOSED) && !isClosed) { | |||||
moveLst.add(of.createCTPath2DClose()); | |||||
path2D.closePath(); | |||||
} | |||||
EscherSimpleProperty geoLeft = getShapeProp(opt, EscherProperties.GEOMETRY__LEFT); | |||||
EscherSimpleProperty geoRight = getShapeProp(opt, EscherProperties.GEOMETRY__RIGHT); | |||||
EscherSimpleProperty geoTop = getShapeProp(opt, EscherProperties.GEOMETRY__TOP); | |||||
EscherSimpleProperty geoBottom = getShapeProp(opt, EscherProperties.GEOMETRY__BOTTOM); | |||||
final Rectangle2D bounds; | |||||
if (geoLeft != null && geoRight != null && geoTop != null && geoBottom != null) { | |||||
bounds = new Rectangle2D.Double(); | |||||
bounds.setFrameFromDiagonal( | |||||
new Point2D.Double(geoLeft.getPropertyValue(), geoTop.getPropertyValue()), | |||||
new Point2D.Double(geoRight.getPropertyValue(), geoBottom.getPropertyValue()) | |||||
); | |||||
} else { | |||||
bounds = path2D.getBounds2D(); | |||||
} | |||||
pathCT.setW((int)Math.rint(bounds.getWidth())); | |||||
pathCT.setH((int)Math.rint(bounds.getHeight())); | |||||
return new CustomGeometry(cusGeo); | |||||
} | |||||
private void handleEscapeInfo(CTPath2D pathCT, Path2D path2D, byte[] segElem, Iterator<byte[]> vertIter) { | |||||
final ObjectFactory of = new ObjectFactory(); | |||||
HSLFFreeformShape.EscapeInfo ei = getEscapeInfo(segElem); | |||||
switch (ei) { | |||||
case EXTENSION: | |||||
break; | |||||
case ANGLE_ELLIPSE_TO: | |||||
break; | |||||
case ANGLE_ELLIPSE: | |||||
break; | |||||
case ARC_TO: { | |||||
// The first two POINT values specify the bounding rectangle of the ellipse. | |||||
// The second two POINT values specify the radial vectors for the ellipse. | |||||
// The radial vectors are cast from the center of the bounding rectangle. | |||||
// The path starts at the POINT where the first radial vector intersects the | |||||
// bounding rectangle and goes to the POINT where the second radial vector | |||||
// intersects the bounding rectangle. The drawing direction is always counterclockwise. | |||||
// If the path has already been started, a line is drawn from the last POINT to | |||||
// the starting POINT of the arc; otherwise, a new path is started. | |||||
// The number of arc segments drawn equals the number of segments divided by four. | |||||
int[] r1 = new int[2], r2 = new int[2], start = new int[2], end = new int[2]; | |||||
fillPoint(vertIter.next(), r1); | |||||
fillPoint(vertIter.next(), r2); | |||||
fillPoint(vertIter.next(), start); | |||||
fillPoint(vertIter.next(), end); | |||||
Arc2D arc2D = new Arc2D.Double(); | |||||
Rectangle2D.Double bounds = new Rectangle2D.Double(); | |||||
bounds.setFrameFromDiagonal(xy2p(r1), xy2p(r2)); | |||||
arc2D.setFrame(bounds); | |||||
arc2D.setAngles(xy2p(start), xy2p(end)); | |||||
path2D.append(arc2D, true); | |||||
CTPath2DArcTo arcTo = of.createCTPath2DArcTo(); | |||||
arcTo.setHR(d2s(bounds.getHeight()/2.0)); | |||||
arcTo.setWR(d2s(bounds.getWidth()/2.0)); | |||||
arcTo.setStAng(d2s(-arc2D.getAngleStart()*60000.)); | |||||
arcTo.setSwAng(d2s(-arc2D.getAngleExtent()*60000.)); | |||||
pathCT.getCloseOrMoveToOrLnTo().add(arcTo); | |||||
break; | |||||
} | |||||
case ARC: | |||||
break; | |||||
case CLOCKWISE_ARC_TO: | |||||
break; | |||||
case CLOCKWISE_ARC: | |||||
break; | |||||
case ELLIPTICAL_QUADRANT_X: | |||||
break; | |||||
case ELLIPTICAL_QUADRANT_Y: | |||||
break; | |||||
case QUADRATIC_BEZIER: | |||||
break; | |||||
case NO_FILL: | |||||
break; | |||||
case NO_LINE: | |||||
break; | |||||
case AUTO_LINE: | |||||
break; | |||||
case AUTO_CURVE: | |||||
break; | |||||
case CORNER_LINE: | |||||
break; | |||||
case CORNER_CURVE: | |||||
break; | |||||
case SMOOTH_LINE: | |||||
break; | |||||
case SMOOTH_CURVE: | |||||
break; | |||||
case SYMMETRIC_LINE: | |||||
break; | |||||
case SYMMETRIC_CURVE: | |||||
break; | |||||
case FREEFORM: | |||||
break; | |||||
case FILL_COLOR: | |||||
break; | |||||
case LINE_COLOR: | |||||
break; | |||||
default: | |||||
break; | |||||
} | |||||
} | |||||
private static String d2s(double d) { | |||||
return Integer.toString((int)Math.rint(d)); | |||||
} | |||||
private static Point2D xy2p(int[] xyPoints) { | |||||
return new Point2D.Double(xyPoints[0],xyPoints[1]); | |||||
} | |||||
private static HSLFFreeformShape.PathInfo getPathInfo(byte[] elem) { | |||||
int elemUS = LittleEndian.getUShort(elem, 0); | |||||
int pathInfo = PATH_INFO.getValue(elemUS); | |||||
return HSLFFreeformShape.PathInfo.valueOf(pathInfo); | |||||
} | |||||
private static HSLFFreeformShape.EscapeInfo getEscapeInfo(byte[] elem) { | |||||
int elemUS = LittleEndian.getUShort(elem, 0); | |||||
int escInfo = ESCAPE_INFO.getValue(elemUS); | |||||
return HSLFFreeformShape.EscapeInfo.valueOf(escInfo); | |||||
} | |||||
private static <T extends EscherProperty> T getShapeProp(AbstractEscherOptRecord opt, int propId) { | |||||
T prop = getEscherProperty(opt, (short)(propId + 0x4000)); | |||||
if (prop == null) { | |||||
prop = getEscherProperty(opt, propId); | |||||
} | |||||
return prop; | |||||
} | |||||
private CTAdjPoint2D fillPoint(byte[] xyMaster, int[] xyPoints) { | |||||
if (xyMaster == null || xyPoints == null) { | |||||
LOG.log(POILogger.WARN, "Master bytes or points not set - ignore point"); | |||||
return null; | |||||
} | |||||
if ((xyMaster.length != 4 && xyMaster.length != 8) || xyPoints.length != 2) { | |||||
LOG.log(POILogger.WARN, "Invalid number of master bytes for a single point - ignore point"); | |||||
return null; | |||||
} | |||||
int x, y; | |||||
if (xyMaster.length == 4) { | |||||
x = LittleEndian.getShort(xyMaster, 0); | |||||
y = LittleEndian.getShort(xyMaster, 2); | |||||
} else { | |||||
x = LittleEndian.getInt(xyMaster, 0); | |||||
y = LittleEndian.getInt(xyMaster, 4); | |||||
} | |||||
xyPoints[0] = x; | |||||
xyPoints[1] = y; | |||||
return toPoint(xyPoints); | |||||
} | |||||
private static CTAdjPoint2D toPoint(int[] xyPoints) { | |||||
CTAdjPoint2D pt = new CTAdjPoint2D(); | |||||
pt.setX(Integer.toString(xyPoints[0])); | |||||
pt.setY(Integer.toString(xyPoints[1])); | |||||
return pt; | |||||
} | |||||
} | } |
/** | /** | ||||
* Represents functionality provided by the 'Fill Effects' dialog in PowerPoint. | * Represents functionality provided by the 'Fill Effects' dialog in PowerPoint. | ||||
*/ | */ | ||||
@SuppressWarnings("WeakerAccess") | |||||
public final class HSLFFill { | public final class HSLFFill { | ||||
private static final POILogger LOG = POILogFactory.getLogger(HSLFFill.class); | private static final POILogger LOG = POILogFactory.getLogger(HSLFFill.class); | ||||
/** | /** | ||||
* Fill with a solid color | * Fill with a solid color | ||||
*/ | */ | ||||
public static final int FILL_SOLID = 0; | |||||
static final int FILL_SOLID = 0; | |||||
/** | /** | ||||
* Fill with a pattern (bitmap) | * Fill with a pattern (bitmap) | ||||
*/ | */ | ||||
public static final int FILL_PATTERN = 1; | |||||
static final int FILL_PATTERN = 1; | |||||
/** | /** | ||||
* A texture (pattern with its own color map) | * A texture (pattern with its own color map) | ||||
*/ | */ | ||||
public static final int FILL_TEXTURE = 2; | |||||
static final int FILL_TEXTURE = 2; | |||||
/** | /** | ||||
* Center a picture in the shape | * Center a picture in the shape | ||||
*/ | */ | ||||
public static final int FILL_PICTURE = 3; | |||||
static final int FILL_PICTURE = 3; | |||||
/** | /** | ||||
* Shade from start to end points | * Shade from start to end points | ||||
*/ | */ | ||||
public static final int FILL_SHADE = 4; | |||||
static final int FILL_SHADE = 4; | |||||
/** | /** | ||||
* Shade from bounding rectangle to end point | * Shade from bounding rectangle to end point | ||||
*/ | */ | ||||
public static final int FILL_SHADE_CENTER = 5; | |||||
static final int FILL_SHADE_CENTER = 5; | |||||
/** | /** | ||||
* Shade from shape outline to end point | * Shade from shape outline to end point | ||||
*/ | */ | ||||
public static final int FILL_SHADE_SHAPE = 6; | |||||
static final int FILL_SHADE_SHAPE = 6; | |||||
/** | /** | ||||
* Similar to FILL_SHADE, but the fill angle | * Similar to FILL_SHADE, but the fill angle | ||||
* is additionally scaled by the aspect ratio of | * is additionally scaled by the aspect ratio of | ||||
* the shape. If shape is square, it is the same as FILL_SHADE | * the shape. If shape is square, it is the same as FILL_SHADE | ||||
*/ | */ | ||||
public static final int FILL_SHADE_SCALE = 7; | |||||
static final int FILL_SHADE_SCALE = 7; | |||||
/** | /** | ||||
* shade to title | * shade to title | ||||
*/ | */ | ||||
public static final int FILL_SHADE_TITLE = 8; | |||||
static final int FILL_SHADE_TITLE = 8; | |||||
/** | /** | ||||
* Use the background fill color/pattern | * Use the background fill color/pattern | ||||
*/ | */ | ||||
public static final int FILL_BACKGROUND = 9; | |||||
static final int FILL_BACKGROUND = 9; | |||||
/** | /** | ||||
* A bit that specifies whether the RecolorFillAsPicture bit is set. | * A bit that specifies whether the RecolorFillAsPicture bit is set. | ||||
private HSLFShape shape; | private HSLFShape shape; | ||||
/** | /** | ||||
* Construct a <code>Fill</code> object for a shape. | |||||
* Construct a {@code Fill} object for a shape. | |||||
* Fill information will be read from shape's escher properties. | * Fill information will be read from shape's escher properties. | ||||
* | * | ||||
* @param shape the shape this background applies to | * @param shape the shape this background applies to | ||||
@Override | @Override | ||||
public ColorStyle[] getGradientColors() { | public ColorStyle[] getGradientColors() { | ||||
ColorStyle cs[]; | |||||
ColorStyle[] cs; | |||||
if (colorCnt == 0) { | if (colorCnt == 0) { | ||||
cs = new ColorStyle[2]; | cs = new ColorStyle[2]; | ||||
cs[0] = wrapColor(getBackgroundColor()); | cs[0] = wrapColor(getBackgroundColor()); | ||||
cs = new ColorStyle[colorCnt]; | cs = new ColorStyle[colorCnt]; | ||||
int idx = 0; | int idx = 0; | ||||
// TODO: handle palette colors and alpha(?) value | // TODO: handle palette colors and alpha(?) value | ||||
for (byte data[] : ep) { | |||||
for (byte[] data : ep) { | |||||
EscherColorRef ecr = new EscherColorRef(data, 0, 4); | EscherColorRef ecr = new EscherColorRef(data, 0, 4); | ||||
cs[idx++] = wrapColor(shape.getColor(ecr)); | cs[idx++] = wrapColor(shape.getColor(ecr)); | ||||
} | } | ||||
@Override | @Override | ||||
public float[] getGradientFractions() { | public float[] getGradientFractions() { | ||||
float frc[]; | |||||
float[] frc; | |||||
if (colorCnt == 0) { | if (colorCnt == 0) { | ||||
frc = new float[]{0, 1}; | frc = new float[]{0, 1}; | ||||
} else { | } else { | ||||
frc = new float[colorCnt]; | frc = new float[colorCnt]; | ||||
int idx = 0; | int idx = 0; | ||||
for (byte data[] : ep) { | |||||
for (byte[] data : ep) { | |||||
double pos = Units.fixedPointToDouble(LittleEndian.getInt(data, 4)); | double pos = Units.fixedPointToDouble(LittleEndian.getInt(data, 4)); | ||||
frc[idx++] = (float)pos; | frc[idx++] = (float)pos; | ||||
} | } | ||||
/** | /** | ||||
* Returns fill type. | * Returns fill type. | ||||
* Must be one of the <code>FILL_*</code> constants defined in this class. | |||||
* Must be one of the {@code FILL_*} constants defined in this class. | |||||
* | * | ||||
* @return type of fill | * @return type of fill | ||||
*/ | */ | ||||
return prop == null ? FILL_SOLID : prop.getPropertyValue(); | return prop == null ? FILL_SOLID : prop.getPropertyValue(); | ||||
} | } | ||||
/** | |||||
*/ | |||||
protected void afterInsert(HSLFSheet sh){ | |||||
void afterInsert(HSLFSheet sh){ | |||||
AbstractEscherOptRecord opt = shape.getEscherOptRecord(); | AbstractEscherOptRecord opt = shape.getEscherOptRecord(); | ||||
EscherSimpleProperty p = HSLFShape.getEscherProperty(opt, EscherProperties.FILL__PATTERNTEXTURE); | EscherSimpleProperty p = HSLFShape.getEscherProperty(opt, EscherProperties.FILL__PATTERNTEXTURE); | ||||
if(p != null) { | if(p != null) { | ||||
} | } | ||||
@SuppressWarnings("resource") | @SuppressWarnings("resource") | ||||
protected EscherBSERecord getEscherBSERecord(int idx){ | |||||
EscherBSERecord getEscherBSERecord(int idx){ | |||||
HSLFSheet sheet = shape.getSheet(); | HSLFSheet sheet = shape.getSheet(); | ||||
if(sheet == null) { | if(sheet == null) { | ||||
LOG.log(POILogger.DEBUG, "Fill has not yet been assigned to a sheet"); | LOG.log(POILogger.DEBUG, "Fill has not yet been assigned to a sheet"); | ||||
/** | /** | ||||
* Sets fill type. | * Sets fill type. | ||||
* Must be one of the <code>FILL_*</code> constants defined in this class. | |||||
* Must be one of the {@code FILL_*} constants defined in this class. | |||||
* | * | ||||
* @param type type of the fill | * @param type type of the fill | ||||
*/ | */ | ||||
AbstractEscherOptRecord opt = shape.getEscherOptRecord(); | AbstractEscherOptRecord opt = shape.getEscherOptRecord(); | ||||
EscherSimpleProperty p = HSLFShape.getEscherProperty(opt, EscherProperties.FILL__NOFILLHITTEST); | EscherSimpleProperty p = HSLFShape.getEscherProperty(opt, EscherProperties.FILL__NOFILLHITTEST); | ||||
int propVal = (p == null) ? 0 : p.getPropertyValue(); | int propVal = (p == null) ? 0 : p.getPropertyValue(); | ||||
return (FILL_USE_FILLED.isSet(propVal) && !FILL_FILLED.isSet(propVal)) | return (FILL_USE_FILLED.isSet(propVal) && !FILL_FILLED.isSet(propVal)) | ||||
? null | ? null | ||||
: shape.getColor(EscherProperties.FILL__FILLCOLOR, EscherProperties.FILL__FILLOPACITY, -1); | |||||
: shape.getColor(EscherProperties.FILL__FILLCOLOR, EscherProperties.FILL__FILLOPACITY); | |||||
} | } | ||||
/** | /** | ||||
return (FILL_USE_FILLED.isSet(propVal) && !FILL_FILLED.isSet(propVal)) | return (FILL_USE_FILLED.isSet(propVal) && !FILL_FILLED.isSet(propVal)) | ||||
? null | ? null | ||||
: shape.getColor(EscherProperties.FILL__FILLBACKCOLOR, EscherProperties.FILL__FILLOPACITY, -1); | |||||
: shape.getColor(EscherProperties.FILL__FILLBACKCOLOR, EscherProperties.FILL__FILLOPACITY); | |||||
} | } | ||||
/** | /** | ||||
} | } | ||||
/** | /** | ||||
* <code>PictureData</code> object used in a texture, pattern of picture fill. | |||||
* {@code PictureData} object used in a texture, pattern of picture fill. | |||||
*/ | */ | ||||
@SuppressWarnings("resource") | @SuppressWarnings("resource") | ||||
public HSLFPictureData getPictureData(){ | public HSLFPictureData getPictureData(){ |
import java.awt.geom.Point2D; | import java.awt.geom.Point2D; | ||||
import java.awt.geom.Rectangle2D; | import java.awt.geom.Rectangle2D; | ||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Iterator; | |||||
import java.util.List; | import java.util.List; | ||||
import org.apache.poi.ddf.AbstractEscherOptRecord; | import org.apache.poi.ddf.AbstractEscherOptRecord; | ||||
import org.apache.poi.ddf.EscherArrayProperty; | import org.apache.poi.ddf.EscherArrayProperty; | ||||
import org.apache.poi.ddf.EscherContainerRecord; | import org.apache.poi.ddf.EscherContainerRecord; | ||||
import org.apache.poi.ddf.EscherProperties; | import org.apache.poi.ddf.EscherProperties; | ||||
import org.apache.poi.ddf.EscherProperty; | |||||
import org.apache.poi.ddf.EscherSimpleProperty; | import org.apache.poi.ddf.EscherSimpleProperty; | ||||
import org.apache.poi.sl.usermodel.FreeformShape; | import org.apache.poi.sl.usermodel.FreeformShape; | ||||
import org.apache.poi.sl.usermodel.ShapeContainer; | import org.apache.poi.sl.usermodel.ShapeContainer; | ||||
import org.apache.poi.sl.usermodel.ShapeType; | import org.apache.poi.sl.usermodel.ShapeType; | ||||
import org.apache.poi.util.BitField; | |||||
import org.apache.poi.util.BitFieldFactory; | |||||
import org.apache.poi.util.LittleEndian; | import org.apache.poi.util.LittleEndian; | ||||
import org.apache.poi.util.POILogFactory; | import org.apache.poi.util.POILogFactory; | ||||
import org.apache.poi.util.POILogger; | import org.apache.poi.util.POILogger; | ||||
public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformShape<HSLFShape,HSLFTextParagraph> { | public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformShape<HSLFShape,HSLFTextParagraph> { | ||||
private static final POILogger LOG = POILogFactory.getLogger(HSLFFreeformShape.class); | private static final POILogger LOG = POILogFactory.getLogger(HSLFFreeformShape.class); | ||||
private static final byte[] SEGMENTINFO_MOVETO = new byte[]{0x00, 0x40}; | |||||
private static final byte[] SEGMENTINFO_LINETO = new byte[]{0x00, (byte)0xAC}; | |||||
private static final byte[] SEGMENTINFO_ESCAPE = new byte[]{0x01, 0x00}; | |||||
private static final byte[] SEGMENTINFO_ESCAPE2 = new byte[]{0x01, 0x20}; | |||||
private static final byte[] SEGMENTINFO_CUBICTO = new byte[]{0x00, (byte)0xAD}; | |||||
// OpenOffice inserts 0xB3 instead of 0xAD. | |||||
// private static final byte[] SEGMENTINFO_CUBICTO2 = new byte[]{0x00, (byte)0xB3}; | |||||
private static final byte[] SEGMENTINFO_CLOSE = new byte[]{0x01, (byte)0x60}; | |||||
private static final byte[] SEGMENTINFO_END = new byte[]{0x00, (byte)0x80}; | |||||
private static final BitField PATH_INFO = BitFieldFactory.getInstance(0xE000); | |||||
// private static final BitField ESCAPE_INFO = BitFieldFactory.getInstance(0x1F00); | |||||
enum PathInfo { | |||||
lineTo(0),curveTo(1),moveTo(2),close(3),end(4),escape(5),clientEscape(6); | |||||
private final int flag; | |||||
PathInfo(int flag) { | |||||
this.flag = flag; | |||||
} | |||||
public int getFlag() { | |||||
return flag; | |||||
} | |||||
static PathInfo valueOf(int flag) { | |||||
for (PathInfo v : values()) { | |||||
if (v.flag == flag) { | |||||
return v; | |||||
} | |||||
} | |||||
return null; | |||||
} | |||||
} | |||||
enum EscapeInfo { | |||||
EXTENSION(0x0000), | |||||
ANGLE_ELLIPSE_TO(0x0001), | |||||
ANGLE_ELLIPSE(0x0002), | |||||
ARC_TO(0x0003), | |||||
ARC(0x0004), | |||||
CLOCKWISE_ARC_TO(0x0005), | |||||
CLOCKWISE_ARC(0x0006), | |||||
ELLIPTICAL_QUADRANT_X(0x0007), | |||||
ELLIPTICAL_QUADRANT_Y(0x0008), | |||||
QUADRATIC_BEZIER(0x0009), | |||||
NO_FILL(0X000A), | |||||
NO_LINE(0X000B), | |||||
AUTO_LINE(0X000C), | |||||
AUTO_CURVE(0X000D), | |||||
CORNER_LINE(0X000E), | |||||
CORNER_CURVE(0X000F), | |||||
SMOOTH_LINE(0X0010), | |||||
SMOOTH_CURVE(0X0011), | |||||
SYMMETRIC_LINE(0X0012), | |||||
SYMMETRIC_CURVE(0X0013), | |||||
FREEFORM(0X0014), | |||||
FILL_COLOR(0X0015), | |||||
LINE_COLOR(0X0016); | |||||
private final int flag; | |||||
EscapeInfo(int flag) { | |||||
this.flag = flag; | |||||
} | |||||
public int getFlag() { | |||||
return flag; | |||||
} | |||||
static EscapeInfo valueOf(int flag) { | |||||
for (EscapeInfo v : values()) { | |||||
if (v.flag == flag) { | |||||
return v; | |||||
} | |||||
} | |||||
return null; | |||||
} | |||||
} | |||||
enum ShapePath { | enum ShapePath { | ||||
LINES(0), | LINES(0), | ||||
} | } | ||||
@Override | @Override | ||||
public int setPath(Path2D.Double path) { | |||||
public int setPath(Path2D path) { | |||||
Rectangle2D bounds = path.getBounds2D(); | Rectangle2D bounds = path.getBounds2D(); | ||||
PathIterator it = path.getPathIterator(new AffineTransform()); | |||||
PathIterator it = path.getPathIterator(null); | |||||
List<byte[]> segInfo = new ArrayList<>(); | List<byte[]> segInfo = new ArrayList<>(); | ||||
List<Point2D.Double> pntInfo = new ArrayList<>(); | List<Point2D.Double> pntInfo = new ArrayList<>(); | ||||
} | } | ||||
@Override | @Override | ||||
public Path2D.Double getPath(){ | |||||
AbstractEscherOptRecord opt = getEscherOptRecord(); | |||||
EscherArrayProperty verticesProp = getShapeProp(opt, EscherProperties.GEOMETRY__VERTICES); | |||||
EscherArrayProperty segmentsProp = getShapeProp(opt, EscherProperties.GEOMETRY__SEGMENTINFO); | |||||
// return empty path if either GEOMETRY__VERTICES or GEOMETRY__SEGMENTINFO is missing, see Bugzilla 54188 | |||||
Path2D.Double path = new Path2D.Double(); | |||||
public Path2D getPath(){ | |||||
Path2D path2D = new Path2D.Double(); | |||||
getGeometry(path2D); | |||||
//sanity check | |||||
if(verticesProp == null) { | |||||
LOG.log(POILogger.WARN, "Freeform is missing GEOMETRY__VERTICES "); | |||||
return path; | |||||
} | |||||
if(segmentsProp == null) { | |||||
LOG.log(POILogger.WARN, "Freeform is missing GEOMETRY__SEGMENTINFO "); | |||||
return path; | |||||
} | |||||
Iterator<byte[]> vertIter = verticesProp.iterator(); | |||||
Iterator<byte[]> segIter = segmentsProp.iterator(); | |||||
double xyPoints[] = new double[2]; | |||||
while (vertIter.hasNext() && segIter.hasNext()) { | |||||
byte[] segElem = segIter.next(); | |||||
PathInfo pi = getPathInfo(segElem); | |||||
if (pi != null) { | |||||
switch (pi) { | |||||
case escape: { | |||||
// handleEscapeInfo(path, segElem, vertIter); | |||||
break; | |||||
} | |||||
case moveTo: { | |||||
fillPoint(vertIter.next(), xyPoints); | |||||
double x = xyPoints[0]; | |||||
double y = xyPoints[1]; | |||||
path.moveTo(x, y); | |||||
break; | |||||
} | |||||
case curveTo: { | |||||
fillPoint(vertIter.next(), xyPoints); | |||||
double x1 = xyPoints[0]; | |||||
double y1 = xyPoints[1]; | |||||
fillPoint(vertIter.next(), xyPoints); | |||||
double x2 = xyPoints[0]; | |||||
double y2 = xyPoints[1]; | |||||
fillPoint(vertIter.next(), xyPoints); | |||||
double x3 = xyPoints[0]; | |||||
double y3 = xyPoints[1]; | |||||
path.curveTo(x1, y1, x2, y2, x3, y3); | |||||
break; | |||||
} | |||||
case lineTo: | |||||
if (vertIter.hasNext()) { | |||||
fillPoint(vertIter.next(), xyPoints); | |||||
double x = xyPoints[0]; | |||||
double y = xyPoints[1]; | |||||
path.lineTo(x, y); | |||||
} | |||||
break; | |||||
case close: | |||||
path.closePath(); | |||||
break; | |||||
default: | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
EscherSimpleProperty shapePath = getShapeProp(opt, EscherProperties.GEOMETRY__SHAPEPATH); | |||||
ShapePath sp = ShapePath.valueOf(shapePath == null ? 1 : shapePath.getPropertyValue()); | |||||
if (sp == ShapePath.LINES_CLOSED || sp == ShapePath.CURVES_CLOSED) { | |||||
path.closePath(); | |||||
} | |||||
Rectangle2D bounds = path2D.getBounds2D(); | |||||
Rectangle2D anchor = getAnchor(); | Rectangle2D anchor = getAnchor(); | ||||
Rectangle2D bounds = path.getBounds2D(); | |||||
AffineTransform at = new AffineTransform(); | AffineTransform at = new AffineTransform(); | ||||
at.translate(anchor.getX(), anchor.getY()); | at.translate(anchor.getX(), anchor.getY()); | ||||
at.scale( | at.scale( | ||||
anchor.getWidth()/bounds.getWidth(), | anchor.getWidth()/bounds.getWidth(), | ||||
anchor.getHeight()/bounds.getHeight() | anchor.getHeight()/bounds.getHeight() | ||||
); | ); | ||||
return new Path2D.Double(at.createTransformedShape(path)); | |||||
} | |||||
private void fillPoint(byte xyMaster[], double xyPoints[]) { | |||||
if (xyMaster == null || xyPoints == null) { | |||||
LOG.log(POILogger.WARN, "Master bytes or points not set - ignore point"); | |||||
return; | |||||
} | |||||
if ((xyMaster.length != 4 && xyMaster.length != 8) || xyPoints.length != 2) { | |||||
LOG.log(POILogger.WARN, "Invalid number of master bytes for a single point - ignore point"); | |||||
return; | |||||
} | |||||
int x, y; | |||||
if (xyMaster.length == 4) { | |||||
x = LittleEndian.getShort(xyMaster, 0); | |||||
y = LittleEndian.getShort(xyMaster, 2); | |||||
} else { | |||||
x = LittleEndian.getInt(xyMaster, 0); | |||||
y = LittleEndian.getInt(xyMaster, 4); | |||||
} | |||||
xyPoints[0] = Units.masterToPoints(x); | |||||
xyPoints[1] = Units.masterToPoints(y); | |||||
} | |||||
private static <T extends EscherProperty> T getShapeProp(AbstractEscherOptRecord opt, int propId) { | |||||
T prop = getEscherProperty(opt, (short)(propId + 0x4000)); | |||||
if (prop == null) { | |||||
prop = getEscherProperty(opt, propId); | |||||
} | |||||
return prop; | |||||
} | |||||
// private void handleEscapeInfo(Path2D path, byte segElem[], Iterator<byte[]> vertIter) { | |||||
// EscapeInfo ei = getEscapeInfo(segElem); | |||||
// switch (ei) { | |||||
// case EXTENSION: | |||||
// break; | |||||
// case ANGLE_ELLIPSE_TO: | |||||
// break; | |||||
// case ANGLE_ELLIPSE: | |||||
// break; | |||||
// case ARC_TO: | |||||
// break; | |||||
// case ARC: | |||||
// break; | |||||
// case CLOCKWISE_ARC_TO: | |||||
// break; | |||||
// case CLOCKWISE_ARC: | |||||
// break; | |||||
// case ELLIPTICAL_QUADRANT_X: | |||||
// break; | |||||
// case ELLIPTICAL_QUADRANT_Y: | |||||
// break; | |||||
// case QUADRATIC_BEZIER: | |||||
// break; | |||||
// case NO_FILL: | |||||
// break; | |||||
// case NO_LINE: | |||||
// break; | |||||
// case AUTO_LINE: | |||||
// break; | |||||
// case AUTO_CURVE: | |||||
// break; | |||||
// case CORNER_LINE: | |||||
// break; | |||||
// case CORNER_CURVE: | |||||
// break; | |||||
// case SMOOTH_LINE: | |||||
// break; | |||||
// case SMOOTH_CURVE: | |||||
// break; | |||||
// case SYMMETRIC_LINE: | |||||
// break; | |||||
// case SYMMETRIC_CURVE: | |||||
// break; | |||||
// case FREEFORM: | |||||
// break; | |||||
// case FILL_COLOR: | |||||
// break; | |||||
// case LINE_COLOR: | |||||
// break; | |||||
// default: | |||||
// break; | |||||
// } | |||||
// } | |||||
private static PathInfo getPathInfo(byte elem[]) { | |||||
int elemUS = LittleEndian.getUShort(elem, 0); | |||||
int pathInfo = PATH_INFO.getValue(elemUS); | |||||
return PathInfo.valueOf(pathInfo); | |||||
path2D.transform(at); | |||||
return path2D; | |||||
} | } | ||||
// private static EscapeInfo getEscapeInfo(byte elem[]) { | |||||
// int elemUS = LittleEndian.getUShort(elem, 0); | |||||
// int escInfo = ESCAPE_INFO.getValue(elemUS); | |||||
// return EscapeInfo.valueOf(escInfo); | |||||
// } | |||||
} | } |
_sheet = sheet; | _sheet = sheet; | ||||
} | } | ||||
Color getColor(short colorProperty, short opacityProperty, int defaultColor){ | |||||
AbstractEscherOptRecord opt = getEscherOptRecord(); | |||||
EscherSimpleProperty p = getEscherProperty(opt, colorProperty); | |||||
if(p == null && defaultColor == -1) return null; | |||||
int val = (p == null) ? defaultColor : p.getPropertyValue(); | |||||
EscherColorRef ecr = new EscherColorRef(val); | |||||
Color col = getColor(ecr); | |||||
if (col == null) { | |||||
return null; | |||||
Color getColor(short colorProperty, short opacityProperty){ | |||||
final AbstractEscherOptRecord opt = getEscherOptRecord(); | |||||
final EscherSimpleProperty colProp = getEscherProperty(opt, colorProperty); | |||||
final Color col; | |||||
if (colProp == null) { | |||||
col = Color.WHITE; | |||||
} else { | |||||
EscherColorRef ecr = new EscherColorRef(colProp.getPropertyValue()); | |||||
col = getColor(ecr); | |||||
if (col == null) { | |||||
return null; | |||||
} | |||||
} | } | ||||
double alpha = getAlpha(opacityProperty); | double alpha = getAlpha(opacityProperty); |
return null; | return null; | ||||
} | } | ||||
Color clr = getColor(EscherProperties.LINESTYLE__COLOR, EscherProperties.LINESTYLE__OPACITY, -1); | |||||
Color clr = getColor(EscherProperties.LINESTYLE__COLOR, EscherProperties.LINESTYLE__OPACITY); | |||||
return clr == null ? null : clr; | return clr == null ? null : clr; | ||||
} | } | ||||
return null; | return null; | ||||
} | } | ||||
Color clr = getColor(EscherProperties.LINESTYLE__BACKCOLOR, EscherProperties.LINESTYLE__OPACITY, -1); | |||||
Color clr = getColor(EscherProperties.LINESTYLE__BACKCOLOR, EscherProperties.LINESTYLE__OPACITY); | |||||
return clr == null ? null : clr; | return clr == null ? null : clr; | ||||
} | } | ||||
* @return color of the line. If color is not set returns <code>java.awt.Color.black</code> | * @return color of the line. If color is not set returns <code>java.awt.Color.black</code> | ||||
*/ | */ | ||||
public Color getShadowColor(){ | public Color getShadowColor(){ | ||||
Color clr = getColor(EscherProperties.SHADOWSTYLE__COLOR, EscherProperties.SHADOWSTYLE__OPACITY, -1); | |||||
Color clr = getColor(EscherProperties.SHADOWSTYLE__COLOR, EscherProperties.SHADOWSTYLE__OPACITY); | |||||
return clr == null ? Color.black : clr; | return clr == null ? Color.black : clr; | ||||
} | } | ||||
*/ | */ | ||||
@RunWith(Suite.class) | @RunWith(Suite.class) | ||||
@Suite.SuiteClasses({ | @Suite.SuiteClasses({ | ||||
TestBackground.class, | |||||
TestFreeform.class, | TestFreeform.class, | ||||
TestHeadersFooters.class, | TestHeadersFooters.class, | ||||
TestHyperlink.class, | TestHyperlink.class, |
public void test54188() { | public void test54188() { | ||||
HSLFFreeformShape p = new HSLFFreeformShape(); | HSLFFreeformShape p = new HSLFFreeformShape(); | ||||
Path2D.Double path = p.getPath(); | |||||
Path2D path = p.getPath(); | |||||
Path2D.Double emptyPath = new Path2D.Double(); | Path2D.Double emptyPath = new Path2D.Double(); | ||||
assertEquals(emptyPath.getBounds2D(), path.getBounds2D()); | assertEquals(emptyPath.getBounds2D(), path.getBounds2D()); | ||||
} | } |
@RunWith(Suite.class) | @RunWith(Suite.class) | ||||
@Suite.SuiteClasses({ | @Suite.SuiteClasses({ | ||||
TestAddingSlides.class, | TestAddingSlides.class, | ||||
TestBackground.class, | |||||
TestBugs.class, | TestBugs.class, | ||||
TestCounts.class, | TestCounts.class, | ||||
TestMostRecentRecords.class, | TestMostRecentRecords.class, |
limitations under the License. | limitations under the License. | ||||
==================================================================== */ | ==================================================================== */ | ||||
package org.apache.poi.hslf.model; | |||||
package org.apache.poi.hslf.usermodel; | |||||
import static org.junit.Assert.assertEquals; | import static org.junit.Assert.assertEquals; | ||||
import static org.junit.Assert.assertTrue; | import static org.junit.Assert.assertTrue; |