#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:
Andreas Beeker 2018-05-19 21:05:10 +00:00
parent 8d04815f1a
commit 92268ed278
2 changed files with 174 additions and 81 deletions

View File

@ -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
*/ */

View File

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