]> source.dussan.org Git - poi.git/commitdiff
#62381 - Fix rendering of AutoShapes
authorAndreas Beeker <kiwiwings@apache.org>
Wed, 16 May 2018 20:30:53 +0000 (20:30 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Wed, 16 May 2018 20:30:53 +0000 (20:30 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1831743 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/poi/sl/draw/DrawShape.java
src/java/org/apache/poi/sl/draw/geom/Context.java
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGroupShape.java
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java

index 9977e667ed17be6c29df2c5b313884a5350ffaa5..9a10ad1db2c260b96c865a98cf328a7cb5e0e6d0 100644 (file)
@@ -45,8 +45,8 @@ public class DrawShape implements Drawable {
      * @param shape the shape to render
      * @return {@code true} if HSLF implementation is used
      */
-    protected static boolean isHSLF(Shape<?,?> shape) {
-        return shape.getClass().getCanonicalName().toLowerCase(Locale.ROOT).contains("hslf");
+    static boolean isHSLF(Object shape) {
+        return shape.getClass().getName().toLowerCase(Locale.ROOT).contains("hslf");
     }
 
     /**
@@ -60,21 +60,19 @@ public class DrawShape implements Drawable {
             return;
         }
 
-        PlaceableShape<?,?> ps = (PlaceableShape<?,?>)shape;
+        final PlaceableShape<?,?> ps = (PlaceableShape<?,?>)shape;
         final boolean isHSLF = isHSLF(shape);
-        AffineTransform tx = (AffineTransform)graphics.getRenderingHint(Drawable.GROUP_TRANSFORM);
-        if (tx == null) {
-            tx = new AffineTransform();
-        }
 
-        // we saw one document failing here, probably the format is slightly broken, but
-        // maybe better to try to handle it more gracefully
-        java.awt.Shape transformedShape = tx.createTransformedShape(ps.getAnchor());
-        if(transformedShape == null) {
-            return;
+        final Rectangle2D anchor = getAnchor(graphics, ps);
+
+        if (shape.getShapeName().startsWith("rotate")) {
+            System.out.println(String.format("%s x: %.3f y: %.3f cx: %.3f cy: %.3f / x: %.3f y: %.3f cx: %.3f cy: %.3f ",
+                shape.getShapeName(), shape.getAnchor().getX(), shape.getAnchor().getY(),
+                shape.getAnchor().getWidth(), shape.getAnchor().getHeight(),
+                    anchor.getX(), anchor.getY(), anchor.getWidth(), anchor.getHeight()
+            ));
         }
 
-        final Rectangle2D anchor = transformedShape.getBounds2D();
 
         char cmds[] = isHSLF ? new char[]{ 'h','v','r' } : new char[]{ 'r','h','v' };
         for (char ch : cmds) {
@@ -103,61 +101,10 @@ public class DrawShape implements Drawable {
                     double centerX = anchor.getCenterX();
                     double centerY = anchor.getCenterY();
 
-                    // normalize rotation
-                    rotation %= 360.;
-                    if (rotation < 0) {
-                        rotation += 360.;
-                    }
-
-                    int quadrant = (((int)rotation+45)/90)%4;
-                    double scaleX = 1.0, scaleY = 1.0;
-
-                    // scale to bounding box (bug #53176)
-                    if (quadrant == 1 || quadrant == 3) {
-                        // In quadrant 1 and 3, which is basically a shape in a more or less portrait orientation
-                        // (45-135 degrees and 225-315 degrees), we need to first rotate the shape by a multiple
-                        // of 90 degrees and then resize the bounding box to its original bbox. After that we can
-                        // rotate the shape to the exact rotation amount.
-                        // It's strange that you'll need to rotate the shape back and forth again, but you can
-                        // think of it, as if you paint the shape on a canvas. First you rotate the canvas, which might
-                        // be already (differently) scaled, so you can paint the shape in its default orientation
-                        // and later on, turn it around again to compare it with its original size ...
-
-                        AffineTransform txs;
-                        if (isHSLF) {
-                            txs = new AffineTransform(tx);
-                        } else {
-                            // this handling is only based on try and error ... not sure why xslf is handled differently.
-                            txs = new AffineTransform();
-                            txs.translate(centerX, centerY);
-                            txs.rotate(Math.PI/2.); // actually doesn't matter if +/- 90 degrees
-                            txs.translate(-centerX, -centerY);
-                            txs.concatenate(tx);
-                        }
-
-                        txs.translate(centerX, centerY);
-                        txs.rotate(Math.PI/2.);
-                        txs.translate(-centerX, -centerY);
-
-                        Rectangle2D anchor2 = txs.createTransformedShape(ps.getAnchor()).getBounds2D();
-
-                        scaleX = safeScale(anchor.getWidth(), anchor2.getWidth());
-                        scaleY = safeScale(anchor.getHeight(), anchor2.getHeight());
-                    } else {
-                        quadrant = 0;
-                    }
 
                     // transformation is applied reversed ...
                     graphics.translate(centerX, centerY);
-                    double rot = Math.toRadians(rotation-quadrant*90.);
-                    if (rot != 0) {
-                        graphics.rotate(rot);
-                    }
-                    graphics.scale(scaleX, scaleY);
-                    rot = Math.toRadians(quadrant*90.);
-                    if (rot != 0) {
-                        graphics.rotate(rot);
-                    }
+                    graphics.rotate(Math.toRadians(rotation));
                     graphics.translate(-centerX, -centerY);
                 }
                 break;
@@ -183,7 +130,85 @@ public class DrawShape implements Drawable {
     }
 
     public static Rectangle2D getAnchor(Graphics2D graphics, PlaceableShape<?,?> shape) {
-        return getAnchor(graphics, shape.getAnchor());
+//        return getAnchor(graphics, shape.getAnchor());
+
+        final boolean isHSLF = isHSLF(shape);
+        AffineTransform tx = graphics == null ? null : (AffineTransform)graphics.getRenderingHint(Drawable.GROUP_TRANSFORM);
+        if (tx == null) {
+            tx = new AffineTransform();
+        }
+
+        final double rotation = ((shape.getRotation() % 360.) + 360.) % 360.;
+        final int quadrant = (((int)rotation+45)/90)%4;
+
+        final Rectangle2D normalizedShape;
+
+        // scale to bounding box (bug #53176)
+        if (quadrant == 1 || quadrant == 3) {
+            // In a rotated quadrant 1 (=45-135 degrees) and 3 (=225-315 degrees), which is basically a shape in a
+            // more or less portrait orientation, Powerpoint doesn't use the normal shape anchor,
+            // but rotate it 90 degress and apply the group transformations.
+            // We try to revert that distortion and return the normalized anchor.
+            // It's strange that you'll need to rotate the shape back and forth again, but you can
+            // think of it, as if you paint the shape on a canvas. First you rotate the canvas, which might
+            // be already (differently) scaled, so you can paint the shape in its default orientation
+            // and later on, turn it around again to compare it with its original size ...
+
+
+            final Rectangle2D shapeAnchor = shape.getAnchor();
+            final Rectangle2D anchorO = tx.createTransformedShape(shapeAnchor).getBounds2D();
+
+            final Rectangle2D anchorT;
+            {
+                final double centerX = anchorO.getCenterX();
+                final double centerY = anchorO.getCenterY();
+                final AffineTransform txs2 = new AffineTransform();
+
+                // this handling is only based on try and error ... not sure why h/xslf is handled differently.
+
+                if (!isHSLF) {
+                    txs2.translate(centerX, centerY);
+                    txs2.quadrantRotate(1);
+                    txs2.translate(-centerX, -centerY);
+                    txs2.concatenate(tx);
+                }
+
+                txs2.translate(centerX, centerY);
+                txs2.quadrantRotate(3);
+                txs2.translate(-centerX, -centerY);
+
+                if (isHSLF) {
+                    txs2.concatenate(tx);
+                }
+
+                anchorT = txs2.createTransformedShape(shapeAnchor).getBounds2D();
+            }
+
+            final double scaleX2 = safeScale(anchorO.getWidth(), anchorT.getWidth());
+            final double scaleY2 = safeScale(anchorO.getHeight(), anchorT.getHeight());
+
+            {
+                double centerX = shapeAnchor.getCenterX();
+                double centerY = shapeAnchor.getCenterY();
+                final AffineTransform txs2 = new AffineTransform();
+                txs2.translate(centerX, centerY);
+                // no need to rotate back and forth, just apply scaling inverted
+                txs2.scale(scaleY2, scaleX2);
+                txs2.translate(-centerX, -centerY);
+
+                normalizedShape = txs2.createTransformedShape(shapeAnchor).getBounds2D();
+            }
+        } else {
+            normalizedShape = shape.getAnchor();
+        }
+
+        if (tx.isIdentity()) {
+            return normalizedShape;
+        }
+
+
+        final java.awt.Shape anc = tx.createTransformedShape(normalizedShape);
+        return (anc != null) ? anc.getBounds2D() : normalizedShape;
     }
 
     public static Rectangle2D getAnchor(Graphics2D graphics, Rectangle2D anchor) {
index 283444a980bd3c6a997d04de5c4823c70125794c..31847ceeff03f1b8063d5dc3da8aed0482b03589 100644 (file)
@@ -44,7 +44,8 @@ public class Context {
     }
 
     public Guide getAdjustValue(String name){
-        return _props.getAdjustValue(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){
index e6425d0bc0a41844e93b7dc884b17254ee26e2a3..adcdad21471d019f9284bfcd4669adeab9d87267 100644 (file)
@@ -65,7 +65,7 @@ implements XSLFShapeContainer, GroupShape<XSLFShape,XSLFTextParagraph> {
 
     protected XSLFGroupShape(CTGroupShape shape, XSLFSheet sheet){
         super(shape,sheet);
-        _shapes = XSLFSheet.buildShapes(shape, sheet);
+        _shapes = XSLFSheet.buildShapes(shape, this);
         _grpSpPr = shape.getGrpSpPr();
     }
 
index e8b4f2cc447169a1297c51bee999516f12817bc7..2d45cb2d134f8024486bffd341e377e2d6772bce 100644 (file)
@@ -105,7 +105,9 @@ implements XSLFShapeContainer, Sheet<XSLFShape,XSLFTextParagraph> {
         throw new IllegalStateException("SlideShow was not found");
     }
 
-    protected static List<XSLFShape> buildShapes(CTGroupShape spTree, XSLFSheet sheet){
+    protected static List<XSLFShape> buildShapes(CTGroupShape spTree, XSLFShapeContainer parent){
+        final XSLFSheet sheet = (parent instanceof XSLFSheet) ? (XSLFSheet)parent : ((XSLFShape)parent).getSheet();
+
         List<XSLFShape> shapes = new ArrayList<>();
         XmlCursor cur = spTree.newCursor();
         try {
@@ -133,7 +135,7 @@ implements XSLFShapeContainer, Sheet<XSLFShape,XSLFTextParagraph> {
                     if (cur.toChild(PackageNamespaces.MARKUP_COMPATIBILITY, "Choice") && cur.toFirstChild()) {
                         try {
                             CTGroupShape grp = CTGroupShape.Factory.parse(cur.newXMLStreamReader());
-                            shapes.addAll(buildShapes(grp, sheet));
+                            shapes.addAll(buildShapes(grp, parent));
                         } catch (XmlException e) {
                             LOG.log(POILogger.DEBUG, "unparsable alternate content", e);
                         }
@@ -145,6 +147,10 @@ implements XSLFShapeContainer, Sheet<XSLFShape,XSLFTextParagraph> {
             cur.dispose();
         }
 
+        for (final XSLFShape s : shapes) {
+            s.setParent(parent);
+        }
+
         return shapes;
     }