mirror of https://github.com/apache/poi.git
#62953 - Rendering of FreeformShapes with formula fails
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1848492 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
740a756a3f
commit
dc3c437d14
|
@ -26,6 +26,7 @@ import java.util.Map;
|
|||
*
|
||||
* @author Glen Stampoultzis (glens at apache.org)
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public final class EscherProperties {
|
||||
|
||||
// Property constants
|
||||
|
@ -117,6 +118,15 @@ public final class EscherProperties {
|
|||
public static final short GEOMETRY__ADJUST8VALUE = 334;
|
||||
public static final short GEOMETRY__ADJUST9VALUE = 335;
|
||||
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__3DOK = 379;
|
||||
public static final short GEOMETRY__LINEOK = 380;
|
||||
|
@ -333,6 +343,9 @@ public final class EscherProperties {
|
|||
|
||||
private static final Map<Short, EscherPropertyMetaData> properties = initProps();
|
||||
|
||||
private EscherProperties() {
|
||||
}
|
||||
|
||||
private static Map<Short, EscherPropertyMetaData> initProps() {
|
||||
Map<Short, EscherPropertyMetaData> m = new HashMap<>();
|
||||
addProp(m, TRANSFORM__ROTATION, "transform.rotation");
|
||||
|
@ -423,6 +436,15 @@ public final class EscherProperties {
|
|||
addProp(m, GEOMETRY__ADJUST8VALUE, "geometry.adjust8value");
|
||||
addProp(m, GEOMETRY__ADJUST9VALUE, "geometry.adjust9value");
|
||||
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__3DOK, "geometry.3dok");
|
||||
addProp(m, GEOMETRY__LINEOK, "geometry.lineok");
|
||||
|
@ -641,20 +663,20 @@ public final class EscherProperties {
|
|||
}
|
||||
|
||||
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) {
|
||||
m.put(Short.valueOf((short) s), new EscherPropertyMetaData(propName, type));
|
||||
m.put((short) s, new EscherPropertyMetaData(propName, type));
|
||||
}
|
||||
|
||||
public static String getPropertyName(short propertyId) {
|
||||
EscherPropertyMetaData o = properties.get(Short.valueOf(propertyId));
|
||||
EscherPropertyMetaData o = properties.get(propertyId);
|
||||
return o == null ? "unknown" : o.getDescription();
|
||||
}
|
||||
|
||||
public static byte getPropertyType(short propertyId) {
|
||||
EscherPropertyMetaData escherPropertyMetaData = properties.get(Short.valueOf(propertyId));
|
||||
EscherPropertyMetaData escherPropertyMetaData = properties.get(propertyId);
|
||||
return escherPropertyMetaData == null ? 0 : escherPropertyMetaData.getType();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,43 +17,11 @@
|
|||
|
||||
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 DrawFreeformShape(FreeformShape<?,?> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -410,14 +410,20 @@ public class DrawSimpleShape extends DrawShape {
|
|||
}
|
||||
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) {
|
||||
w = Units.toEMU(anchor.getWidth());
|
||||
scaleX = Units.toPoints(1);
|
||||
} else if (anchor.getWidth() == 0) {
|
||||
scaleX = 1;
|
||||
} else {
|
||||
scaleX = anchor.getWidth() / w;
|
||||
}
|
||||
if (h == -1) {
|
||||
h = Units.toEMU(anchor.getHeight());
|
||||
scaleY = Units.toPoints(1);
|
||||
} else if (anchor.getHeight() == 0) {
|
||||
scaleY = 1;
|
||||
} else {
|
||||
scaleY = anchor.getHeight() / h;
|
||||
}
|
||||
|
|
|
@ -22,11 +22,18 @@ package org.apache.poi.sl.draw.geom;
|
|||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
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){
|
||||
_props = props;
|
||||
|
@ -39,23 +46,22 @@ public class Context {
|
|||
}
|
||||
}
|
||||
|
||||
public Rectangle2D getShapeAnchor(){
|
||||
Rectangle2D getShapeAnchor(){
|
||||
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
|
||||
return (_props.getClass().getName().contains("hslf")) ? null : _props.getAdjustValue(name);
|
||||
}
|
||||
|
||||
public double getValue(String key){
|
||||
if(key.matches("(\\+|-)?\\d+")){
|
||||
if(DOUBLE_PATTERN.matcher(key).matches()){
|
||||
return Double.parseDouble(key);
|
||||
}
|
||||
|
||||
Double val = _ctx.get(key);
|
||||
// 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){
|
||||
|
|
|
@ -24,16 +24,15 @@ public interface FreeformShape<
|
|||
P extends TextParagraph<S,P,? extends TextRun>
|
||||
> 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
|
||||
*/
|
||||
Path2D.Double getPath();
|
||||
Path2D getPath();
|
||||
|
||||
/**
|
||||
* Set the shape path
|
||||
|
@ -41,5 +40,5 @@ public interface FreeformShape<
|
|||
* @param path shape outline
|
||||
* @return the number of points written
|
||||
*/
|
||||
int setPath(Path2D.Double path);
|
||||
int setPath(Path2D path);
|
||||
}
|
||||
|
|
|
@ -127,7 +127,7 @@ public class Units {
|
|||
points /= MASTER_DPI;
|
||||
return points;
|
||||
}
|
||||
|
||||
|
||||
public static int pointsToMaster(double points) {
|
||||
points *= MASTER_DPI;
|
||||
points /= POINT_DPI;
|
||||
|
|
|
@ -24,6 +24,12 @@ import java.awt.geom.Path2D;
|
|||
import java.awt.geom.PathIterator;
|
||||
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.util.Beta;
|
||||
import org.apache.poi.util.POILogFactory;
|
||||
|
@ -31,6 +37,7 @@ import org.apache.poi.util.POILogger;
|
|||
import org.apache.poi.util.Units;
|
||||
import org.apache.xmlbeans.XmlCursor;
|
||||
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.CTCustomGeometry2D;
|
||||
import org.openxmlformats.schemas.drawingml.x2006.main.CTGeomRect;
|
||||
|
@ -61,7 +68,7 @@ public class XSLFFreeformShape extends XSLFAutoShape
|
|||
}
|
||||
|
||||
@Override
|
||||
public int setPath(final Path2D.Double path) {
|
||||
public int setPath(final Path2D path) {
|
||||
final CTPath2D ctPath = CTPath2D.Factory.newInstance();
|
||||
|
||||
final Rectangle2D bounds = path.getBounds2D();
|
||||
|
@ -117,6 +124,30 @@ public class XSLFFreeformShape extends XSLFAutoShape
|
|||
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
|
||||
public Path2D.Double getPath() {
|
||||
|
|
|
@ -716,7 +716,6 @@ public abstract class XSLFSimpleShape extends XSLFShape
|
|||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return definition of the shape geometry
|
||||
*/
|
||||
@Override
|
||||
|
|
|
@ -47,7 +47,9 @@ public class TestPPTX2PNG {
|
|||
private static final POIDataSamples samples = POIDataSamples.getSlideShowInstance();
|
||||
private static final File basedir = null;
|
||||
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";
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -17,21 +17,122 @@
|
|||
|
||||
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.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.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.
|
||||
* See {@link ShapeTypes}
|
||||
* </p>
|
||||
*
|
||||
* @author Yegor Kozlov
|
||||
*/
|
||||
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){
|
||||
super(escherRecord, parent);
|
||||
|
@ -72,13 +173,11 @@ public class HSLFAutoShape extends HSLFTextShape implements AutoShape<HSLFShape,
|
|||
}
|
||||
|
||||
/**
|
||||
* 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 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).
|
||||
* </p>
|
||||
*
|
||||
* @param idx the adjust index in the [0, 9] range
|
||||
* @return the adjustment value
|
||||
|
@ -90,13 +189,11 @@ public class HSLFAutoShape extends HSLFTextShape implements AutoShape<HSLFShape,
|
|||
}
|
||||
|
||||
/**
|
||||
* 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 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).
|
||||
* </p>
|
||||
*
|
||||
* @param idx the adjust index in the [0, 9] range
|
||||
* @param val the adjustment value
|
||||
|
@ -106,4 +203,278 @@ public class HSLFAutoShape extends HSLFTextShape implements AutoShape<HSLFShape,
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,60 +49,61 @@ import org.apache.poi.util.Units;
|
|||
/**
|
||||
* Represents functionality provided by the 'Fill Effects' dialog in PowerPoint.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public final class HSLFFill {
|
||||
private static final POILogger LOG = POILogFactory.getLogger(HSLFFill.class);
|
||||
|
||||
/**
|
||||
* Fill with a solid color
|
||||
*/
|
||||
public static final int FILL_SOLID = 0;
|
||||
static final int FILL_SOLID = 0;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
public static final int FILL_TEXTURE = 2;
|
||||
static final int FILL_TEXTURE = 2;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static final int FILL_SHADE = 4;
|
||||
static final int FILL_SHADE = 4;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static final int FILL_SHADE_SHAPE = 6;
|
||||
static final int FILL_SHADE_SHAPE = 6;
|
||||
|
||||
/**
|
||||
* Similar to FILL_SHADE, but the fill angle
|
||||
* is additionally scaled by the aspect ratio of
|
||||
* 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
|
||||
*/
|
||||
public static final int FILL_SHADE_TITLE = 8;
|
||||
static final int FILL_SHADE_TITLE = 8;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
@ -214,7 +215,7 @@ public final class HSLFFill {
|
|||
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.
|
||||
*
|
||||
* @param shape the shape this background applies to
|
||||
|
@ -279,7 +280,7 @@ public final class HSLFFill {
|
|||
|
||||
@Override
|
||||
public ColorStyle[] getGradientColors() {
|
||||
ColorStyle cs[];
|
||||
ColorStyle[] cs;
|
||||
if (colorCnt == 0) {
|
||||
cs = new ColorStyle[2];
|
||||
cs[0] = wrapColor(getBackgroundColor());
|
||||
|
@ -288,7 +289,7 @@ public final class HSLFFill {
|
|||
cs = new ColorStyle[colorCnt];
|
||||
int idx = 0;
|
||||
// TODO: handle palette colors and alpha(?) value
|
||||
for (byte data[] : ep) {
|
||||
for (byte[] data : ep) {
|
||||
EscherColorRef ecr = new EscherColorRef(data, 0, 4);
|
||||
cs[idx++] = wrapColor(shape.getColor(ecr));
|
||||
}
|
||||
|
@ -302,13 +303,13 @@ public final class HSLFFill {
|
|||
|
||||
@Override
|
||||
public float[] getGradientFractions() {
|
||||
float frc[];
|
||||
float[] frc;
|
||||
if (colorCnt == 0) {
|
||||
frc = new float[]{0, 1};
|
||||
} else {
|
||||
frc = new float[colorCnt];
|
||||
int idx = 0;
|
||||
for (byte data[] : ep) {
|
||||
for (byte[] data : ep) {
|
||||
double pos = Units.fixedPointToDouble(LittleEndian.getInt(data, 4));
|
||||
frc[idx++] = (float)pos;
|
||||
}
|
||||
|
@ -354,7 +355,7 @@ public final class HSLFFill {
|
|||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
@ -364,9 +365,7 @@ public final class HSLFFill {
|
|||
return prop == null ? FILL_SOLID : prop.getPropertyValue();
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
protected void afterInsert(HSLFSheet sh){
|
||||
void afterInsert(HSLFSheet sh){
|
||||
AbstractEscherOptRecord opt = shape.getEscherOptRecord();
|
||||
EscherSimpleProperty p = HSLFShape.getEscherProperty(opt, EscherProperties.FILL__PATTERNTEXTURE);
|
||||
if(p != null) {
|
||||
|
@ -379,7 +378,7 @@ public final class HSLFFill {
|
|||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
protected EscherBSERecord getEscherBSERecord(int idx){
|
||||
EscherBSERecord getEscherBSERecord(int idx){
|
||||
HSLFSheet sheet = shape.getSheet();
|
||||
if(sheet == null) {
|
||||
LOG.log(POILogger.DEBUG, "Fill has not yet been assigned to a sheet");
|
||||
|
@ -399,7 +398,7 @@ public final class HSLFFill {
|
|||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
@ -415,10 +414,10 @@ public final class HSLFFill {
|
|||
AbstractEscherOptRecord opt = shape.getEscherOptRecord();
|
||||
EscherSimpleProperty p = HSLFShape.getEscherProperty(opt, EscherProperties.FILL__NOFILLHITTEST);
|
||||
int propVal = (p == null) ? 0 : p.getPropertyValue();
|
||||
|
||||
|
||||
return (FILL_USE_FILLED.isSet(propVal) && !FILL_FILLED.isSet(propVal))
|
||||
? null
|
||||
: shape.getColor(EscherProperties.FILL__FILLCOLOR, EscherProperties.FILL__FILLOPACITY, -1);
|
||||
: shape.getColor(EscherProperties.FILL__FILLCOLOR, EscherProperties.FILL__FILLOPACITY);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -462,7 +461,7 @@ public final class HSLFFill {
|
|||
|
||||
return (FILL_USE_FILLED.isSet(propVal) && !FILL_FILLED.isSet(propVal))
|
||||
? null
|
||||
: shape.getColor(EscherProperties.FILL__FILLBACKCOLOR, EscherProperties.FILL__FILLOPACITY, -1);
|
||||
: shape.getColor(EscherProperties.FILL__FILLBACKCOLOR, EscherProperties.FILL__FILLOPACITY);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -480,7 +479,7 @@ public final class HSLFFill {
|
|||
}
|
||||
|
||||
/**
|
||||
* <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")
|
||||
public HSLFPictureData getPictureData(){
|
||||
|
|
|
@ -23,20 +23,16 @@ import java.awt.geom.PathIterator;
|
|||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.ArrayList;
|
||||
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.EscherProperties;
|
||||
import org.apache.poi.ddf.EscherProperty;
|
||||
import org.apache.poi.ddf.EscherSimpleProperty;
|
||||
import org.apache.poi.sl.usermodel.FreeformShape;
|
||||
import org.apache.poi.sl.usermodel.ShapeContainer;
|
||||
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.POILogFactory;
|
||||
import org.apache.poi.util.POILogger;
|
||||
|
@ -53,79 +49,6 @@ import org.apache.poi.util.Units;
|
|||
public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformShape<HSLFShape,HSLFTextParagraph> {
|
||||
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 {
|
||||
LINES(0),
|
||||
|
@ -182,9 +105,9 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh
|
|||
}
|
||||
|
||||
@Override
|
||||
public int setPath(Path2D.Double path) {
|
||||
public int setPath(Path2D path) {
|
||||
Rectangle2D bounds = path.getBounds2D();
|
||||
PathIterator it = path.getPathIterator(new AffineTransform());
|
||||
PathIterator it = path.getPathIterator(null);
|
||||
|
||||
List<byte[]> segInfo = new ArrayList<>();
|
||||
List<Point2D.Double> pntInfo = new ArrayList<>();
|
||||
|
@ -275,187 +198,24 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh
|
|||
}
|
||||
|
||||
@Override
|
||||
public Path2D.Double getPath(){
|
||||
AbstractEscherOptRecord opt = getEscherOptRecord();
|
||||
public Path2D getPath(){
|
||||
Path2D path2D = new Path2D.Double();
|
||||
getGeometry(path2D);
|
||||
|
||||
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();
|
||||
|
||||
//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 bounds = path.getBounds2D();
|
||||
AffineTransform at = new AffineTransform();
|
||||
at.translate(anchor.getX(), anchor.getY());
|
||||
at.scale(
|
||||
anchor.getWidth()/bounds.getWidth(),
|
||||
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);
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -358,17 +358,18 @@ public abstract class HSLFShape implements Shape<HSLFShape,HSLFTextParagraph> {
|
|||
_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);
|
||||
|
|
|
@ -164,7 +164,7 @@ public abstract class HSLFSimpleShape extends HSLFShape implements SimpleShape<H
|
|||
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;
|
||||
}
|
||||
|
||||
|
@ -179,7 +179,7 @@ public abstract class HSLFSimpleShape extends HSLFShape implements SimpleShape<H
|
|||
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;
|
||||
}
|
||||
|
||||
|
@ -385,7 +385,7 @@ public abstract class HSLFSimpleShape extends HSLFShape implements SimpleShape<H
|
|||
* @return color of the line. If color is not set returns <code>java.awt.Color.black</code>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ import org.junit.runners.Suite;
|
|||
*/
|
||||
@RunWith(Suite.class)
|
||||
@Suite.SuiteClasses({
|
||||
TestBackground.class,
|
||||
TestFreeform.class,
|
||||
TestHeadersFooters.class,
|
||||
TestHyperlink.class,
|
||||
|
|
|
@ -87,7 +87,7 @@ public final class TestFreeform {
|
|||
public void test54188() {
|
||||
|
||||
HSLFFreeformShape p = new HSLFFreeformShape();
|
||||
Path2D.Double path = p.getPath();
|
||||
Path2D path = p.getPath();
|
||||
Path2D.Double emptyPath = new Path2D.Double();
|
||||
assertEquals(emptyPath.getBounds2D(), path.getBounds2D());
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.junit.runners.Suite;
|
|||
@RunWith(Suite.class)
|
||||
@Suite.SuiteClasses({
|
||||
TestAddingSlides.class,
|
||||
TestBackground.class,
|
||||
TestBugs.class,
|
||||
TestCounts.class,
|
||||
TestMostRecentRecords.class,
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
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.assertTrue;
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue