/* ==================================================================== | |
Licensed to the Apache Software Foundation (ASF) under one or more | |
contributor license agreements. See the NOTICE file distributed with | |
this work for additional information regarding copyright ownership. | |
The ASF licenses this file to You under the Apache License, Version 2.0 | |
(the "License"); you may not use this file except in compliance with | |
the License. You may obtain a copy of the License at | |
http://www.apache.org/licenses/LICENSE-2.0 | |
Unless required by applicable law or agreed to in writing, software | |
distributed under the License is distributed on an "AS IS" BASIS, | |
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
See the License for the specific language governing permissions and | |
limitations under the License. | |
==================================================================== */ | |
package org.apache.poi.sl.draw; | |
import java.awt.BasicStroke; | |
import java.awt.Graphics2D; | |
import java.awt.geom.AffineTransform; | |
import java.awt.geom.Rectangle2D; | |
import java.util.Locale; | |
import org.apache.poi.sl.usermodel.PlaceableShape; | |
import org.apache.poi.sl.usermodel.Shape; | |
import org.apache.poi.sl.usermodel.StrokeStyle; | |
import org.apache.poi.sl.usermodel.StrokeStyle.LineCap; | |
import org.apache.poi.sl.usermodel.StrokeStyle.LineDash; | |
public class DrawShape implements Drawable { | |
protected final Shape<?,?> shape; | |
public DrawShape(Shape<?,?> shape) { | |
this.shape = shape; | |
} | |
/** | |
* Apply 2-D transforms before drawing this shape. This includes rotation and flipping. | |
* | |
* @param graphics the graphics whos transform matrix will be modified | |
*/ | |
public void applyTransform(Graphics2D graphics) { | |
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(); | |
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(); | |
// 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.translate(-centerX, -centerY); | |
} | |
break; | |
default: | |
throw new RuntimeException("unexpected transform code " + ch); | |
} | |
} | |
} | |
private static double safeScale(double dim1, double dim2) { | |
if (dim1 == 0.) { | |
return 1; | |
} | |
return (dim2 == 0.) ? 1 : dim1/dim2; | |
} | |
public void draw(Graphics2D graphics) { | |
} | |
public void drawContent(Graphics2D graphics) { | |
} | |
public static Rectangle2D getAnchor(Graphics2D graphics, PlaceableShape<?,?> shape) { | |
return getAnchor(graphics, shape.getAnchor()); | |
} | |
public static Rectangle2D getAnchor(Graphics2D graphics, Rectangle2D anchor) { | |
if(graphics == null) { | |
return anchor; | |
} | |
AffineTransform tx = (AffineTransform)graphics.getRenderingHint(Drawable.GROUP_TRANSFORM); | |
if(tx != null) { | |
anchor = tx.createTransformedShape(anchor).getBounds2D(); | |
} | |
return anchor; | |
} | |
protected Shape<?,?> getShape() { | |
return shape; | |
} | |
protected static BasicStroke getStroke(StrokeStyle strokeStyle) { | |
float lineWidth = (float) strokeStyle.getLineWidth(); | |
if (lineWidth == 0.0f) lineWidth = 0.25f; // Both PowerPoint and OOo draw zero-length lines as 0.25pt | |
LineDash lineDash = strokeStyle.getLineDash(); | |
if (lineDash == null) { | |
lineDash = LineDash.SOLID; | |
} | |
int dashPatI[] = lineDash.pattern; | |
final float dash_phase = 0; | |
float[] dashPatF = null; | |
if (dashPatI != null) { | |
dashPatF = new float[dashPatI.length]; | |
for (int i=0; i<dashPatI.length; i++) { | |
dashPatF[i] = dashPatI[i]*Math.max(1, lineWidth); | |
} | |
} | |
LineCap lineCapE = strokeStyle.getLineCap(); | |
if (lineCapE == null) lineCapE = LineCap.FLAT; | |
int lineCap; | |
switch (lineCapE) { | |
case ROUND: | |
lineCap = BasicStroke.CAP_ROUND; | |
break; | |
case SQUARE: | |
lineCap = BasicStroke.CAP_SQUARE; | |
break; | |
default: | |
case FLAT: | |
lineCap = BasicStroke.CAP_BUTT; | |
break; | |
} | |
int lineJoin = BasicStroke.JOIN_ROUND; | |
return new BasicStroke(lineWidth, lineCap, lineJoin, lineWidth, dashPatF, dash_phase); | |
} | |
} |