diff --git a/src/java/org/apache/poi/sl/draw/DrawFreeformShape.java b/src/java/org/apache/poi/sl/draw/DrawFreeformShape.java index ee3bebb1c..d8f986881 100644 --- a/src/java/org/apache/poi/sl/draw/DrawFreeformShape.java +++ b/src/java/org/apache/poi/sl/draw/DrawFreeformShape.java @@ -18,6 +18,8 @@ package org.apache.poi.sl.draw; import java.awt.Graphics2D; +import java.awt.geom.AffineTransform; +import java.awt.geom.Path2D; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -32,14 +34,23 @@ public class DrawFreeformShape extends DrawAutoShape { public DrawFreeformShape(FreeformShape shape) { super(shape); } - + protected Collection computeOutlines(Graphics2D graphics) { List lst = new ArrayList(); - java.awt.Shape sh = getShape().getPath(); - FillStyle fs = getShape().getFillStyle(); - StrokeStyle ss = getShape().getStrokeStyle(); + FreeformShape fsh = getShape(); + Path2D sh = fsh.getPath(); + + AffineTransform tx = (AffineTransform)graphics.getRenderingHint(Drawable.GROUP_TRANSFORM); + if (tx == null) { + tx = new AffineTransform(); + } + + java.awt.Shape canvasShape = tx.createTransformedShape(sh); + + FillStyle fs = fsh.getFillStyle(); + StrokeStyle ss = fsh.getStrokeStyle(); Path path = new Path(fs != null, ss != null); - lst.add(new Outline(sh, path)); + lst.add(new Outline(canvasShape, path)); return lst; } diff --git a/src/java/org/apache/poi/sl/draw/DrawShape.java b/src/java/org/apache/poi/sl/draw/DrawShape.java index f4f53b52d..ff226be90 100644 --- a/src/java/org/apache/poi/sl/draw/DrawShape.java +++ b/src/java/org/apache/poi/sl/draw/DrawShape.java @@ -47,85 +47,95 @@ public class DrawShape implements Drawable { if (!(shape instanceof PlaceableShape)) return; PlaceableShape ps = (PlaceableShape)shape; + final boolean isHSLF = ps.getClass().getCanonicalName().toLowerCase(Locale.ROOT).contains("hslf"); AffineTransform tx = (AffineTransform)graphics.getRenderingHint(Drawable.GROUP_TRANSFORM); if (tx == null) tx = new AffineTransform(); final Rectangle2D anchor = tx.createTransformedShape(ps.getAnchor()).getBounds2D(); - // rotation - double rotation = ps.getRotation(); - if (rotation != 0.) { - // PowerPoint rotates shapes relative to the geometric center - 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 (ps.getClass().getCanonicalName().toLowerCase(Locale.ROOT).contains("hslf")) { - 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); + char cmds[] = isHSLF ? new char[]{ 'h','v','r' } : new char[]{ 'r','h','v' }; + for (char ch : cmds) { + switch (ch) { + case 'h': + //flip horizontal + if (ps.getFlipHorizontal()) { + graphics.translate(anchor.getX() + anchor.getWidth(), anchor.getY()); + graphics.scale(-1, 1); + graphics.translate(-anchor.getX(), -anchor.getY()); } + break; + case 'v': + //flip vertical + if (ps.getFlipVertical()) { + graphics.translate(anchor.getX(), anchor.getY() + anchor.getHeight()); + graphics.scale(1, -1); + graphics.translate(-anchor.getX(), -anchor.getY()); + } + break; + case 'r': + // rotation + double rotation = ps.getRotation(); + if (rotation != 0.) { + // PowerPoint rotates shapes relative to the geometric center + double centerX = anchor.getCenterX(); + double centerY = anchor.getCenterY(); - txs.translate(centerX, centerY); - txs.rotate(Math.PI/2.); - txs.translate(-centerX, -centerY); + // normalize rotation + rotation %= 360.; + if (rotation < 0) rotation += 360.; - Rectangle2D anchor2 = txs.createTransformedShape(ps.getAnchor()).getBounds2D(); + int quadrant = (((int)rotation+45)/90)%4; + double scaleX = 1.0, scaleY = 1.0; - scaleX = safeScale(anchor.getWidth(), anchor2.getWidth()); - scaleY = safeScale(anchor.getHeight(), anchor2.getHeight()); - } else { - quadrant = 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.translate(-centerX, -centerY); + } + break; } - - // 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.translate(-centerX, -centerY); - } - - //flip horizontal - if (ps.getFlipHorizontal()) { - graphics.translate(anchor.getX() + anchor.getWidth(), anchor.getY()); - graphics.scale(-1, 1); - graphics.translate(-anchor.getX(), -anchor.getY()); - } - - //flip vertical - if (ps.getFlipVertical()) { - graphics.translate(anchor.getX(), anchor.getY() + anchor.getHeight()); - graphics.scale(1, -1); - graphics.translate(-anchor.getX(), -anchor.getY()); } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFreeformShape.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFreeformShape.java index 226b2f8bb..5e045f526 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFreeformShape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFreeformShape.java @@ -230,12 +230,12 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh EscherArrayProperty verticesProp = new EscherArrayProperty((short)(EscherProperties.GEOMETRY__VERTICES + 0x4000), false, null); verticesProp.setNumberOfElementsInArray(pntInfo.size()); verticesProp.setNumberOfElementsInMemory(pntInfo.size()); - verticesProp.setSizeOfElements(0xFFF0); + verticesProp.setSizeOfElements(8); for (int i = 0; i < pntInfo.size(); i++) { Point2D.Double pnt = pntInfo.get(i); - byte[] data = new byte[4]; - LittleEndian.putShort(data, 0, (short)Units.pointsToMaster(pnt.getX() - bounds.getX())); - LittleEndian.putShort(data, 2, (short)Units.pointsToMaster(pnt.getY() - bounds.getY())); + byte[] data = new byte[8]; + LittleEndian.putInt(data, 0, Units.pointsToMaster(pnt.getX() - bounds.getX())); + LittleEndian.putInt(data, 4, Units.pointsToMaster(pnt.getY() - bounds.getY())); verticesProp.setElement(i, data); } opt.addEscherProperty(verticesProp); @@ -282,6 +282,7 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh Iterator vertIter = verticesProp.iterator(); Iterator segIter = segmentsProp.iterator(); + double xyPoints[] = new double[2]; while (vertIter.hasNext() && segIter.hasNext()) { byte[] segElem = segIter.next(); @@ -292,30 +293,30 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh break; } case moveTo: { - byte[] p = vertIter.next(); - double x = Units.masterToPoints(LittleEndian.getShort(p, 0)); - double y = Units.masterToPoints(LittleEndian.getShort(p, 2)); + fillPoint(vertIter.next(), xyPoints); + double x = xyPoints[0]; + double y = xyPoints[1]; path.moveTo(x,y); break; } case curveTo: { - byte[] p1 = vertIter.next(); - double x1 = Units.masterToPoints(LittleEndian.getShort(p1, 0)); - double y1 = Units.masterToPoints(LittleEndian.getShort(p1, 2)); - byte[] p2 = vertIter.next(); - double x2 = Units.masterToPoints(LittleEndian.getShort(p2, 0)); - double y2 = Units.masterToPoints(LittleEndian.getShort(p2, 2)); - byte[] p3 = vertIter.next(); - double x3 = Units.masterToPoints(LittleEndian.getShort(p3, 0)); - double y3 = Units.masterToPoints(LittleEndian.getShort(p3, 2)); + fillPoint(vertIter.next(), xyPoints); + double x1 = xyPoints[0]; + double y1 = xyPoints[1]; + fillPoint(vertIter.next(), xyPoints); + double x2 = xyPoints[0]; + double y2 = xyPoints[1]; + fillPoint(vertIter.next(), xyPoints); + double x3 = xyPoints[0]; + double y3 = xyPoints[1]; path.curveTo(x1,y1,x2,y2,x3,y3); break; } case lineTo: if (vertIter.hasNext()) { - byte[] p = vertIter.next(); - double x = Units.masterToPoints(LittleEndian.getShort(p, 0)); - double y = Units.masterToPoints(LittleEndian.getShort(p, 2)); + fillPoint(vertIter.next(), xyPoints); + double x = xyPoints[0]; + double y = xyPoints[1]; path.lineTo(x,y); } break; @@ -344,6 +345,25 @@ public final class HSLFFreeformShape extends HSLFAutoShape implements FreeformSh return new Path2D.Double(at.createTransformedShape(path)); } + private void fillPoint(byte xyMaster[], double xyPoints[]) { + if (xyMaster == null || xyPoints == null || (xyMaster.length != 4 && xyMaster.length != 8) || xyPoints.length != 2) { + logger.log(POILogger.WARN, "Invalid number of master bytes for a single point - ignore point"); + return; + } + + int x, y; + if (xyMaster.length == 4) { + x = LittleEndian.getShort(xyMaster, 0); + y = LittleEndian.getShort(xyMaster, 2); + } else { + x = LittleEndian.getInt(xyMaster, 0); + y = LittleEndian.getInt(xyMaster, 4); + } + + xyPoints[0] = Units.masterToPoints(x); + xyPoints[1] = Units.masterToPoints(y); + } + private static T getShapeProp(AbstractEscherOptRecord opt, int propId) { T prop = getEscherProperty(opt, (short)(propId + 0x4000)); if (prop == null) {