]> source.dussan.org Git - poi.git/commitdiff
#61633 - Zero width shapes aren't rendered
authorAndreas Beeker <kiwiwings@apache.org>
Sat, 19 May 2018 21:05:10 +0000 (21:05 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Sat, 19 May 2018 21:05:10 +0000 (21:05 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1831923 13f79535-47bb-0310-9956-ffa450edef68

src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFFreeformShape.java
src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFFreeformShape.java

index e4c02f58da940167597655584fc58f0889a09b01..92ec316a697962aa498016742258154bf68e7607 100644 (file)
@@ -26,7 +26,10 @@ import java.awt.geom.Rectangle2D;
 
 import org.apache.poi.sl.usermodel.FreeformShape;
 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.xmlbeans.XmlCursor;
 import org.apache.xmlbeans.XmlObject;
 import org.openxmlformats.schemas.drawingml.x2006.main.CTAdjPoint2D;
 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.CTPath2DQuadBezierTo;
 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.CTShapeNonVisual;
 
@@ -50,67 +54,56 @@ import org.openxmlformats.schemas.presentationml.x2006.main.CTShapeNonVisual;
 public class XSLFFreeformShape extends XSLFAutoShape
     implements FreeformShape<XSLFShape,XSLFTextParagraph> {
 
+    private static final POILogger LOG = POILogFactory.getLogger(XSLFFreeformShape.class);
+
     /*package*/ XSLFFreeformShape(CTShape shape, XSLFSheet sheet) {
         super(shape, sheet);
     }
 
     @Override
-    public int setPath(Path2D.Double path) {
-        CTPath2D ctPath = CTPath2D.Factory.newInstance();
+    public int setPath(final Path2D.Double path) {
+        final CTPath2D ctPath = CTPath2D.Factory.newInstance();
 
-        Rectangle2D bounds = path.getBounds2D();
-        int x0 = Units.toEMU(bounds.getX());
-        int y0 = Units.toEMU(bounds.getY());
-        PathIterator it = path.getPathIterator(new AffineTransform());
+        final Rectangle2D bounds = path.getBounds2D();
+        final int x0 = Units.toEMU(bounds.getX());
+        final int y0 = Units.toEMU(bounds.getY());
+        final PathIterator it = path.getPathIterator(new AffineTransform());
         int numPoints = 0;
         ctPath.setH(Units.toEMU(bounds.getHeight()));
         ctPath.setW(Units.toEMU(bounds.getWidth()));
+
+        final double[] vals = new double[6];
         while (!it.isDone()) {
-            double[] vals = new double[6];
-            int type = it.currentSegment(vals);
+            final int type = it.currentSegment(vals);
+            final CTAdjPoint2D[] points;
             switch (type) {
                 case PathIterator.SEG_MOVETO:
-                    CTAdjPoint2D mv = ctPath.addNewMoveTo().addNewPt();
-                    mv.setX(Units.toEMU(vals[0]) - x0);
-                    mv.setY(Units.toEMU(vals[1]) - y0);
-                    numPoints++;
+                    points = addMoveTo(ctPath);
                     break;
                 case PathIterator.SEG_LINETO:
-                    CTAdjPoint2D ln = ctPath.addNewLnTo().addNewPt();
-                    ln.setX(Units.toEMU(vals[0]) - x0);
-                    ln.setY(Units.toEMU(vals[1]) - y0);
-                    numPoints++;
+                    points = addLineTo(ctPath);
                     break;
                 case PathIterator.SEG_QUADTO:
-                    CTPath2DQuadBezierTo qbez = ctPath.addNewQuadBezTo();
-                    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;
+                    points = addQuadBezierTo(ctPath);
                     break;
                 case PathIterator.SEG_CUBICTO:
-                    CTPath2DCubicBezierTo bez = ctPath.addNewCubicBezTo();
-                    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;
+                    points = addCubicBezierTo(ctPath);
                     break;
                 case PathIterator.SEG_CLOSE:
-                    numPoints++;
-                    ctPath.addNewClose();
+                    points = addClosePath(ctPath);
                     break;
-                default:
+                default: {
                     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();
         }
         
@@ -124,63 +117,113 @@ public class XSLFFreeformShape extends XSLFAutoShape
         return numPoints;
     }
 
+
     @Override
     public Path2D.Double getPath() {
-        Path2D.Double path = new Path2D.Double();
-        Rectangle2D bounds = getAnchor();
+        final Path2D.Double path = new Path2D.Double();
 
-        XmlObject xo = getShapeProperties();
+        final XmlObject xo = getShapeProperties();
         if (!(xo instanceof CTShapeProperties)) {
             return null;
         }
         
-        CTCustomGeometry2D geom = ((CTShapeProperties)xo).getCustGeom();
+        final CTCustomGeometry2D geom = ((CTShapeProperties)xo).getCustGeom();
+        //noinspection deprecation
         for(CTPath2D spPath : geom.getPathLst().getPathArray()){
-            double scaleW = bounds.getWidth() / Units.toPoints(spPath.getW());
-            double scaleH = bounds.getHeight() / Units.toPoints(spPath.getH());
-            for(XmlObject ch : spPath.selectPath("*")){
-                if(ch instanceof CTPath2DMoveTo){
-                    CTAdjPoint2D pt = ((CTPath2DMoveTo)ch).getPt();
-                    path.moveTo(
-                            (float) (Units.toPoints((Long) pt.getX()) * scaleW),
-                            (float) (Units.toPoints((Long) pt.getY()) * scaleH));
-                } else if (ch instanceof CTPath2DLineTo){
-                    CTAdjPoint2D pt = ((CTPath2DLineTo)ch).getPt();
-                    path.lineTo((float)Units.toPoints((Long)pt.getX()),
-                                (float)Units.toPoints((Long)pt.getY()));
-                } else if (ch instanceof CTPath2DQuadBezierTo){
-                    CTPath2DQuadBezierTo bez = ((CTPath2DQuadBezierTo)ch);
-                    CTAdjPoint2D pt1 = bez.getPtArray(0);
-                    CTAdjPoint2D pt2 = bez.getPtArray(1);
-                    path.quadTo(
-                            (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));
-                } 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();
+            XmlCursor cursor = spPath.newCursor();
+            try {
+                if (cursor.toFirstChild()) {
+                    do {
+                        final XmlObject ch = cursor.getObject();
+                        if (ch instanceof CTPath2DMoveTo) {
+                            addMoveTo(path, (CTPath2DMoveTo)ch);
+                        } else if (ch instanceof CTPath2DLineTo) {
+                            addLineTo(path, (CTPath2DLineTo)ch);
+                        } else if (ch instanceof CTPath2DQuadBezierTo) {
+                            addQuadBezierTo(path, (CTPath2DQuadBezierTo)ch);
+                        } else if (ch instanceof CTPath2DCubicBezierTo) {
+                            addCubicBezierTo(path, (CTPath2DCubicBezierTo)ch);
+                        } else if (ch instanceof CTPath2DClose) {
+                            addClosePath(path);
+                        } else {
+                            LOG.log(POILogger.WARN, "can't handle path of type "+xo.getClass());
+                        }
+                    } while (cursor.toNextSibling());
                 }
+            } finally {
+                cursor.dispose();
             }
         }
 
         // the created path starts at (x=0, y=0).
-        // The returned path should fit in the bounding rectangle
-        AffineTransform at = new AffineTransform();
-        at.translate(bounds.getX(), bounds.getY());
+        // this used to scale each path element to the path bounding box,
+        // but now the dimensions/relations are kept as-is
+        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));
     }
+
+    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
      */
index c45244ee5dab88d20016117bfe1f8c045533b489..1e3700aa3ce8dfd816de2072acac92274832593c 100644 (file)
 package org.apache.poi.xslf.usermodel;
 
 
-import static org.junit.Assert.assertEquals;
 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.Line2D;
 import java.awt.geom.Path2D;
+import java.awt.geom.PathIterator;
 import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
 import java.io.IOException;
 
 import org.junit.Test;
+import org.mockito.AdditionalAnswers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
 
 public class TestXSLFFreeformShape {
 
@@ -52,4 +63,43 @@ public class TestXSLFFreeformShape {
         
         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);
+    }
 }
\ No newline at end of file