diff --git a/src/java/org/apache/poi/sl/draw/DrawShape.java b/src/java/org/apache/poi/sl/draw/DrawShape.java index 9977e667ed..9a10ad1db2 100644 --- a/src/java/org/apache/poi/sl/draw/DrawShape.java +++ b/src/java/org/apache/poi/sl/draw/DrawShape.java @@ -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(); + + 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() + )); } - // 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 = 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) { diff --git a/src/java/org/apache/poi/sl/draw/geom/Context.java b/src/java/org/apache/poi/sl/draw/geom/Context.java index 283444a980..31847ceeff 100644 --- a/src/java/org/apache/poi/sl/draw/geom/Context.java +++ b/src/java/org/apache/poi/sl/draw/geom/Context.java @@ -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){ diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGroupShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGroupShape.java index e6425d0bc0..adcdad2147 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGroupShape.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGroupShape.java @@ -65,7 +65,7 @@ implements XSLFShapeContainer, GroupShape { protected XSLFGroupShape(CTGroupShape shape, XSLFSheet sheet){ super(shape,sheet); - _shapes = XSLFSheet.buildShapes(shape, sheet); + _shapes = XSLFSheet.buildShapes(shape, this); _grpSpPr = shape.getGrpSpPr(); } diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java index e8b4f2cc44..2d45cb2d13 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java @@ -105,7 +105,9 @@ implements XSLFShapeContainer, Sheet { throw new IllegalStateException("SlideShow was not found"); } - protected static List buildShapes(CTGroupShape spTree, XSLFSheet sheet){ + protected static List buildShapes(CTGroupShape spTree, XSLFShapeContainer parent){ + final XSLFSheet sheet = (parent instanceof XSLFSheet) ? (XSLFSheet)parent : ((XSLFShape)parent).getSheet(); + List shapes = new ArrayList<>(); XmlCursor cur = spTree.newCursor(); try { @@ -133,7 +135,7 @@ implements XSLFShapeContainer, Sheet { 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 { cur.dispose(); } + for (final XSLFShape s : shapes) { + s.setParent(parent); + } + return shapes; }