#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:
Andreas Beeker 2018-12-09 01:06:15 +00:00
parent 740a756a3f
commit dc3c437d14
20 changed files with 529 additions and 365 deletions

View File

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

View File

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

View File

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

View File

@ -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){

View File

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

View File

@ -127,7 +127,7 @@ public class Units {
points /= MASTER_DPI;
return points;
}
public static int pointsToMaster(double points) {
points *= MASTER_DPI;
points /= POINT_DPI;

View File

@ -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() {

View File

@ -716,7 +716,6 @@ public abstract class XSLFSimpleShape extends XSLFShape
}
/**
*
* @return definition of the shape geometry
*/
@Override

View File

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

View File

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

View File

@ -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(){

View File

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

View File

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

View File

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

View File

@ -25,7 +25,6 @@ import org.junit.runners.Suite;
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({
TestBackground.class,
TestFreeform.class,
TestHeadersFooters.class,
TestHyperlink.class,

View File

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

View File

@ -26,6 +26,7 @@ import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({
TestAddingSlides.class,
TestBackground.class,
TestBugs.class,
TestCounts.class,
TestMostRecentRecords.class,

View File

@ -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.