#62381 - Fix rendering of AutoShapes

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1831743 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2018-05-16 20:30:53 +00:00
parent 1af53bbf7f
commit e371ce46cc
4 changed files with 103 additions and 71 deletions

View 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();
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) {

View 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){

View 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();
}

View 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;
}