mirror of https://github.com/apache/poi.git
#61633 - Zero width shapes aren't rendered
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1831923 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
8d04815f1a
commit
92268ed278
|
@ -26,7 +26,10 @@ import java.awt.geom.Rectangle2D;
|
||||||
|
|
||||||
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.POILogger;
|
||||||
import org.apache.poi.util.Units;
|
import org.apache.poi.util.Units;
|
||||||
|
import org.apache.xmlbeans.XmlCursor;
|
||||||
import org.apache.xmlbeans.XmlObject;
|
import org.apache.xmlbeans.XmlObject;
|
||||||
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;
|
||||||
|
@ -39,6 +42,7 @@ import org.openxmlformats.schemas.drawingml.x2006.main.CTPath2DLineTo;
|
||||||
import org.openxmlformats.schemas.drawingml.x2006.main.CTPath2DMoveTo;
|
import org.openxmlformats.schemas.drawingml.x2006.main.CTPath2DMoveTo;
|
||||||
import org.openxmlformats.schemas.drawingml.x2006.main.CTPath2DQuadBezierTo;
|
import org.openxmlformats.schemas.drawingml.x2006.main.CTPath2DQuadBezierTo;
|
||||||
import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeProperties;
|
import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeProperties;
|
||||||
|
import org.openxmlformats.schemas.drawingml.x2006.main.CTTransform2D;
|
||||||
import org.openxmlformats.schemas.presentationml.x2006.main.CTShape;
|
import org.openxmlformats.schemas.presentationml.x2006.main.CTShape;
|
||||||
import org.openxmlformats.schemas.presentationml.x2006.main.CTShapeNonVisual;
|
import org.openxmlformats.schemas.presentationml.x2006.main.CTShapeNonVisual;
|
||||||
|
|
||||||
|
@ -50,67 +54,56 @@ import org.openxmlformats.schemas.presentationml.x2006.main.CTShapeNonVisual;
|
||||||
public class XSLFFreeformShape extends XSLFAutoShape
|
public class XSLFFreeformShape extends XSLFAutoShape
|
||||||
implements FreeformShape<XSLFShape,XSLFTextParagraph> {
|
implements FreeformShape<XSLFShape,XSLFTextParagraph> {
|
||||||
|
|
||||||
|
private static final POILogger LOG = POILogFactory.getLogger(XSLFFreeformShape.class);
|
||||||
|
|
||||||
/*package*/ XSLFFreeformShape(CTShape shape, XSLFSheet sheet) {
|
/*package*/ XSLFFreeformShape(CTShape shape, XSLFSheet sheet) {
|
||||||
super(shape, sheet);
|
super(shape, sheet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int setPath(Path2D.Double path) {
|
public int setPath(final Path2D.Double path) {
|
||||||
CTPath2D ctPath = CTPath2D.Factory.newInstance();
|
final CTPath2D ctPath = CTPath2D.Factory.newInstance();
|
||||||
|
|
||||||
Rectangle2D bounds = path.getBounds2D();
|
final Rectangle2D bounds = path.getBounds2D();
|
||||||
int x0 = Units.toEMU(bounds.getX());
|
final int x0 = Units.toEMU(bounds.getX());
|
||||||
int y0 = Units.toEMU(bounds.getY());
|
final int y0 = Units.toEMU(bounds.getY());
|
||||||
PathIterator it = path.getPathIterator(new AffineTransform());
|
final PathIterator it = path.getPathIterator(new AffineTransform());
|
||||||
int numPoints = 0;
|
int numPoints = 0;
|
||||||
ctPath.setH(Units.toEMU(bounds.getHeight()));
|
ctPath.setH(Units.toEMU(bounds.getHeight()));
|
||||||
ctPath.setW(Units.toEMU(bounds.getWidth()));
|
ctPath.setW(Units.toEMU(bounds.getWidth()));
|
||||||
|
|
||||||
|
final double[] vals = new double[6];
|
||||||
while (!it.isDone()) {
|
while (!it.isDone()) {
|
||||||
double[] vals = new double[6];
|
final int type = it.currentSegment(vals);
|
||||||
int type = it.currentSegment(vals);
|
final CTAdjPoint2D[] points;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case PathIterator.SEG_MOVETO:
|
case PathIterator.SEG_MOVETO:
|
||||||
CTAdjPoint2D mv = ctPath.addNewMoveTo().addNewPt();
|
points = addMoveTo(ctPath);
|
||||||
mv.setX(Units.toEMU(vals[0]) - x0);
|
|
||||||
mv.setY(Units.toEMU(vals[1]) - y0);
|
|
||||||
numPoints++;
|
|
||||||
break;
|
break;
|
||||||
case PathIterator.SEG_LINETO:
|
case PathIterator.SEG_LINETO:
|
||||||
CTAdjPoint2D ln = ctPath.addNewLnTo().addNewPt();
|
points = addLineTo(ctPath);
|
||||||
ln.setX(Units.toEMU(vals[0]) - x0);
|
|
||||||
ln.setY(Units.toEMU(vals[1]) - y0);
|
|
||||||
numPoints++;
|
|
||||||
break;
|
break;
|
||||||
case PathIterator.SEG_QUADTO:
|
case PathIterator.SEG_QUADTO:
|
||||||
CTPath2DQuadBezierTo qbez = ctPath.addNewQuadBezTo();
|
points = addQuadBezierTo(ctPath);
|
||||||
CTAdjPoint2D qp1 = qbez.addNewPt();
|
|
||||||
qp1.setX(Units.toEMU(vals[0]) - x0);
|
|
||||||
qp1.setY(Units.toEMU(vals[1]) - y0);
|
|
||||||
CTAdjPoint2D qp2 = qbez.addNewPt();
|
|
||||||
qp2.setX(Units.toEMU(vals[2]) - x0);
|
|
||||||
qp2.setY(Units.toEMU(vals[3]) - y0);
|
|
||||||
numPoints += 2;
|
|
||||||
break;
|
break;
|
||||||
case PathIterator.SEG_CUBICTO:
|
case PathIterator.SEG_CUBICTO:
|
||||||
CTPath2DCubicBezierTo bez = ctPath.addNewCubicBezTo();
|
points = addCubicBezierTo(ctPath);
|
||||||
CTAdjPoint2D p1 = bez.addNewPt();
|
|
||||||
p1.setX(Units.toEMU(vals[0]) - x0);
|
|
||||||
p1.setY(Units.toEMU(vals[1]) - y0);
|
|
||||||
CTAdjPoint2D p2 = bez.addNewPt();
|
|
||||||
p2.setX(Units.toEMU(vals[2]) - x0);
|
|
||||||
p2.setY(Units.toEMU(vals[3]) - y0);
|
|
||||||
CTAdjPoint2D p3 = bez.addNewPt();
|
|
||||||
p3.setX(Units.toEMU(vals[4]) - x0);
|
|
||||||
p3.setY(Units.toEMU(vals[5]) - y0);
|
|
||||||
numPoints += 3;
|
|
||||||
break;
|
break;
|
||||||
case PathIterator.SEG_CLOSE:
|
case PathIterator.SEG_CLOSE:
|
||||||
numPoints++;
|
points = addClosePath(ctPath);
|
||||||
ctPath.addNewClose();
|
|
||||||
break;
|
break;
|
||||||
default:
|
default: {
|
||||||
throw new IllegalStateException("Unrecognized path segment type: " + type);
|
throw new IllegalStateException("Unrecognized path segment type: " + type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int i=0;
|
||||||
|
for (final CTAdjPoint2D point : points) {
|
||||||
|
point.setX(Units.toEMU(vals[i++])-x0);
|
||||||
|
point.setY(Units.toEMU(vals[i++])-y0);
|
||||||
|
}
|
||||||
|
|
||||||
|
numPoints += Math.max(points.length, 1);
|
||||||
it.next();
|
it.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,63 +117,113 @@ public class XSLFFreeformShape extends XSLFAutoShape
|
||||||
return numPoints;
|
return numPoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Path2D.Double getPath() {
|
public Path2D.Double getPath() {
|
||||||
Path2D.Double path = new Path2D.Double();
|
final Path2D.Double path = new Path2D.Double();
|
||||||
Rectangle2D bounds = getAnchor();
|
|
||||||
|
|
||||||
XmlObject xo = getShapeProperties();
|
final XmlObject xo = getShapeProperties();
|
||||||
if (!(xo instanceof CTShapeProperties)) {
|
if (!(xo instanceof CTShapeProperties)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
CTCustomGeometry2D geom = ((CTShapeProperties)xo).getCustGeom();
|
final CTCustomGeometry2D geom = ((CTShapeProperties)xo).getCustGeom();
|
||||||
|
//noinspection deprecation
|
||||||
for(CTPath2D spPath : geom.getPathLst().getPathArray()){
|
for(CTPath2D spPath : geom.getPathLst().getPathArray()){
|
||||||
double scaleW = bounds.getWidth() / Units.toPoints(spPath.getW());
|
XmlCursor cursor = spPath.newCursor();
|
||||||
double scaleH = bounds.getHeight() / Units.toPoints(spPath.getH());
|
try {
|
||||||
for(XmlObject ch : spPath.selectPath("*")){
|
if (cursor.toFirstChild()) {
|
||||||
if(ch instanceof CTPath2DMoveTo){
|
do {
|
||||||
CTAdjPoint2D pt = ((CTPath2DMoveTo)ch).getPt();
|
final XmlObject ch = cursor.getObject();
|
||||||
path.moveTo(
|
if (ch instanceof CTPath2DMoveTo) {
|
||||||
(float) (Units.toPoints((Long) pt.getX()) * scaleW),
|
addMoveTo(path, (CTPath2DMoveTo)ch);
|
||||||
(float) (Units.toPoints((Long) pt.getY()) * scaleH));
|
} else if (ch instanceof CTPath2DLineTo) {
|
||||||
} else if (ch instanceof CTPath2DLineTo){
|
addLineTo(path, (CTPath2DLineTo)ch);
|
||||||
CTAdjPoint2D pt = ((CTPath2DLineTo)ch).getPt();
|
} else if (ch instanceof CTPath2DQuadBezierTo) {
|
||||||
path.lineTo((float)Units.toPoints((Long)pt.getX()),
|
addQuadBezierTo(path, (CTPath2DQuadBezierTo)ch);
|
||||||
(float)Units.toPoints((Long)pt.getY()));
|
} else if (ch instanceof CTPath2DCubicBezierTo) {
|
||||||
} else if (ch instanceof CTPath2DQuadBezierTo){
|
addCubicBezierTo(path, (CTPath2DCubicBezierTo)ch);
|
||||||
CTPath2DQuadBezierTo bez = ((CTPath2DQuadBezierTo)ch);
|
} else if (ch instanceof CTPath2DClose) {
|
||||||
CTAdjPoint2D pt1 = bez.getPtArray(0);
|
addClosePath(path);
|
||||||
CTAdjPoint2D pt2 = bez.getPtArray(1);
|
} else {
|
||||||
path.quadTo(
|
LOG.log(POILogger.WARN, "can't handle path of type "+xo.getClass());
|
||||||
(float) (Units.toPoints((Long) pt1.getX()) * scaleW),
|
}
|
||||||
(float) (Units.toPoints((Long) pt1.getY()) * scaleH),
|
} while (cursor.toNextSibling());
|
||||||
(float) (Units.toPoints((Long) pt2.getX()) * scaleW),
|
|
||||||
(float) (Units.toPoints((Long) pt2.getY()) * scaleH));
|
|
||||||
} else if (ch instanceof CTPath2DCubicBezierTo){
|
|
||||||
CTPath2DCubicBezierTo bez = ((CTPath2DCubicBezierTo)ch);
|
|
||||||
CTAdjPoint2D pt1 = bez.getPtArray(0);
|
|
||||||
CTAdjPoint2D pt2 = bez.getPtArray(1);
|
|
||||||
CTAdjPoint2D pt3 = bez.getPtArray(2);
|
|
||||||
path.curveTo(
|
|
||||||
(float) (Units.toPoints((Long) pt1.getX()) * scaleW),
|
|
||||||
(float) (Units.toPoints((Long) pt1.getY()) * scaleH),
|
|
||||||
(float) (Units.toPoints((Long) pt2.getX()) * scaleW),
|
|
||||||
(float) (Units.toPoints((Long) pt2.getY()) * scaleH),
|
|
||||||
(float) (Units.toPoints((Long) pt3.getX()) * scaleW),
|
|
||||||
(float) (Units.toPoints((Long) pt3.getY()) * scaleH));
|
|
||||||
} else if (ch instanceof CTPath2DClose){
|
|
||||||
path.closePath();
|
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
cursor.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// the created path starts at (x=0, y=0).
|
// the created path starts at (x=0, y=0).
|
||||||
// The returned path should fit in the bounding rectangle
|
// this used to scale each path element to the path bounding box,
|
||||||
AffineTransform at = new AffineTransform();
|
// but now the dimensions/relations are kept as-is
|
||||||
at.translate(bounds.getX(), bounds.getY());
|
final AffineTransform at = new AffineTransform();
|
||||||
|
|
||||||
|
final CTTransform2D xfrm = getXfrm(false);
|
||||||
|
final Rectangle2D xfrm2d = new Rectangle2D.Double
|
||||||
|
(xfrm.getOff().getX(), xfrm.getOff().getY(), xfrm.getExt().getCx(), xfrm.getExt().getCy());
|
||||||
|
|
||||||
|
final Rectangle2D bounds = getAnchor();
|
||||||
|
at.translate(bounds.getX()+bounds.getCenterX(), bounds.getY()+bounds.getCenterY());
|
||||||
|
at.scale(1./Units.EMU_PER_POINT, 1./Units.EMU_PER_POINT);
|
||||||
|
at.translate(-xfrm2d.getCenterX(), -xfrm2d.getCenterY());
|
||||||
return new Path2D.Double(at.createTransformedShape(path));
|
return new Path2D.Double(at.createTransformedShape(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static CTAdjPoint2D[] addMoveTo(final CTPath2D path) {
|
||||||
|
return new CTAdjPoint2D[]{path.addNewMoveTo().addNewPt()};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addMoveTo(final Path2D path, final CTPath2DMoveTo xo) {
|
||||||
|
final CTAdjPoint2D pt = xo.getPt();
|
||||||
|
path.moveTo((Long)pt.getX(), (Long)pt.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CTAdjPoint2D[] addLineTo(final CTPath2D path) {
|
||||||
|
return new CTAdjPoint2D[]{path.addNewLnTo().addNewPt()};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addLineTo(final Path2D path, final CTPath2DLineTo xo) {
|
||||||
|
final CTAdjPoint2D pt = xo.getPt();
|
||||||
|
path.lineTo((Long)pt.getX(), (Long)pt.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CTAdjPoint2D[] addQuadBezierTo(final CTPath2D path) {
|
||||||
|
final CTPath2DQuadBezierTo bez = path.addNewQuadBezTo();
|
||||||
|
return new CTAdjPoint2D[]{ bez.addNewPt(), bez.addNewPt() };
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addQuadBezierTo(final Path2D path, final CTPath2DQuadBezierTo xo) {
|
||||||
|
final CTAdjPoint2D pt1 = xo.getPtArray(0);
|
||||||
|
final CTAdjPoint2D pt2 = xo.getPtArray(1);
|
||||||
|
path.quadTo((Long)pt1.getX(), (Long)pt1.getY(),
|
||||||
|
(Long)pt2.getX(), (Long)pt2.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CTAdjPoint2D[] addCubicBezierTo(final CTPath2D path) {
|
||||||
|
final CTPath2DCubicBezierTo bez = path.addNewCubicBezTo();
|
||||||
|
return new CTAdjPoint2D[]{ bez.addNewPt(), bez.addNewPt(), bez.addNewPt() };
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addCubicBezierTo(final Path2D path, final CTPath2DCubicBezierTo xo) {
|
||||||
|
final CTAdjPoint2D pt1 = xo.getPtArray(0);
|
||||||
|
final CTAdjPoint2D pt2 = xo.getPtArray(1);
|
||||||
|
final CTAdjPoint2D pt3 = xo.getPtArray(2);
|
||||||
|
path.curveTo((Long)pt1.getX(), (Long)pt1.getY(),
|
||||||
|
(Long)pt2.getX(), (Long)pt2.getY(),
|
||||||
|
(Long)pt3.getX(), (Long)pt3.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CTAdjPoint2D[] addClosePath(final CTPath2D path) {
|
||||||
|
path.addNewClose();
|
||||||
|
return new CTAdjPoint2D[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addClosePath(final Path2D path) {
|
||||||
|
path.closePath();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param shapeId 1-based shapeId
|
* @param shapeId 1-based shapeId
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -17,15 +17,26 @@
|
||||||
package org.apache.poi.xslf.usermodel;
|
package org.apache.poi.xslf.usermodel;
|
||||||
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.apache.poi.xslf.usermodel.TestXSLFSimpleShape.getSpPr;
|
import static org.apache.poi.xslf.usermodel.TestXSLFSimpleShape.getSpPr;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Ellipse2D;
|
import java.awt.geom.Ellipse2D;
|
||||||
|
import java.awt.geom.Line2D;
|
||||||
import java.awt.geom.Path2D;
|
import java.awt.geom.Path2D;
|
||||||
|
import java.awt.geom.PathIterator;
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.mockito.AdditionalAnswers;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
public class TestXSLFFreeformShape {
|
public class TestXSLFFreeformShape {
|
||||||
|
|
||||||
|
@ -52,4 +63,43 @@ public class TestXSLFFreeformShape {
|
||||||
|
|
||||||
ppt.close();
|
ppt.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testZeroWidth() throws IOException {
|
||||||
|
// see #61633
|
||||||
|
try (XMLSlideShow ppt = new XMLSlideShow()) {
|
||||||
|
XSLFSlide slide = ppt.createSlide();
|
||||||
|
XSLFFreeformShape shape1 = slide.createFreeform();
|
||||||
|
Path2D.Double path1 = new Path2D.Double(new Line2D.Double(100, 150, 100, 300));
|
||||||
|
shape1.setPath(path1);
|
||||||
|
shape1.setLineColor(Color.BLUE);
|
||||||
|
shape1.setLineWidth(1);
|
||||||
|
|
||||||
|
BufferedImage img = new BufferedImage(300, 300, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D graphics = img.createGraphics();
|
||||||
|
try {
|
||||||
|
Graphics2D graphicsMock = Mockito.mock(Graphics2D.class, AdditionalAnswers.delegatesTo(graphics));
|
||||||
|
slide.draw(graphicsMock);
|
||||||
|
|
||||||
|
ArgumentCaptor<Path2D.Double> captor = ArgumentCaptor.forClass(Path2D.Double.class);
|
||||||
|
verify(graphicsMock, times(1)).draw(captor.capture());
|
||||||
|
|
||||||
|
Path2D.Double actual = captor.getValue();
|
||||||
|
PathIterator pi = actual.getPathIterator(new AffineTransform());
|
||||||
|
comparePoint(pi, PathIterator.SEG_MOVETO, 100, 150);
|
||||||
|
pi.next();
|
||||||
|
comparePoint(pi, PathIterator.SEG_LINETO, 100, 300);
|
||||||
|
} finally {
|
||||||
|
graphics.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void comparePoint(PathIterator pi, int type, double x0, double y0) {
|
||||||
|
double points[] = new double[6];
|
||||||
|
int piType = pi.currentSegment(points);
|
||||||
|
assertEquals(type, piType);
|
||||||
|
assertEquals(x0, points[0], 0);
|
||||||
|
assertEquals(y0, points[1], 0);
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue