| /* ==================================================================== |
| 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; |
| } |
| |
| /** |
| * Sometimes it's necessary to distinguish between XSLF/HSLF for the rendering. |
| * Use this method on the shape to determine, if we work on the BIFF implementation |
| * |
| * @param shape the shape to render |
| * @return {@code true} if HSLF implementation is used |
| */ |
| static boolean isHSLF(Object shape) { |
| return shape.getClass().getName().toLowerCase(Locale.ROOT).contains("hslf"); |
| } |
| |
| /** |
| * Apply 2-D transforms before drawing this shape. This includes rotation and flipping. |
| * |
| * @param graphics the graphics whos transform matrix will be modified |
| */ |
| @Override |
| public void applyTransform(Graphics2D graphics) { |
| if (!(shape instanceof PlaceableShape) || graphics == null) { |
| return; |
| } |
| |
| final Rectangle2D anchor = getAnchor(graphics, (PlaceableShape<?,?>)shape); |
| if (anchor == null) { |
| return; |
| } |
| |
| if (isHSLF(shape)) { |
| flipHorizontal(graphics, anchor); |
| flipVertical(graphics, anchor); |
| rotate(graphics, anchor); |
| } else { |
| rotate(graphics, anchor); |
| flipHorizontal(graphics, anchor); |
| flipVertical(graphics, anchor); |
| } |
| } |
| |
| private void flipHorizontal(Graphics2D graphics, Rectangle2D anchor) { |
| assert(shape instanceof PlaceableShape && anchor != null); |
| if (((PlaceableShape<?,?>)shape).getFlipHorizontal()) { |
| graphics.translate(anchor.getX() + anchor.getWidth(), anchor.getY()); |
| graphics.scale(-1, 1); |
| graphics.translate(-anchor.getX(), -anchor.getY()); |
| } |
| } |
| |
| private void flipVertical(Graphics2D graphics, Rectangle2D anchor) { |
| assert(shape instanceof PlaceableShape && anchor != null); |
| if (((PlaceableShape<?,?>)shape).getFlipVertical()) { |
| graphics.translate(anchor.getX(), anchor.getY() + anchor.getHeight()); |
| graphics.scale(1, -1); |
| graphics.translate(-anchor.getX(), -anchor.getY()); |
| } |
| } |
| |
| private void rotate(Graphics2D graphics, Rectangle2D anchor) { |
| assert(shape instanceof PlaceableShape && anchor != null); |
| double rotation = ((PlaceableShape<?,?>)shape).getRotation(); |
| if (rotation != 0.) { |
| // PowerPoint rotates shapes relative to the geometric center |
| graphics.rotate(Math.toRadians(rotation), anchor.getCenterX(), anchor.getCenterY()); |
| } |
| |
| } |
| |
| private static double safeScale(double dim1, double dim2) { |
| return (dim1 == 0. || dim2 == 0.) ? 1 : dim1/dim2; |
| } |
| |
| @Override |
| public void draw(Graphics2D graphics) { |
| } |
| |
| @Override |
| public void drawContent(Graphics2D graphics) { |
| } |
| |
| public static Rectangle2D getAnchor(Graphics2D graphics, PlaceableShape<?,?> shape) { |
| final Rectangle2D shapeAnchor = shape.getAnchor(); |
| if (shapeAnchor == null) { |
| return null; |
| } |
| |
| 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 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.quadrantRotate(1, centerX, centerY); |
| txs2.concatenate(tx); |
| } |
| |
| txs2.quadrantRotate(3, 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 = shapeAnchor; |
| } |
| |
| 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) { |
| if(graphics == null) { |
| return anchor; |
| } |
| |
| AffineTransform tx = (AffineTransform)graphics.getRenderingHint(Drawable.GROUP_TRANSFORM); |
| if(tx != null && !tx.isIdentity() && tx.createTransformedShape(anchor) != 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) { |
| // Both PowerPoint and OOo draw zero-length lines as 0.25pt |
| lineWidth = 0.25f; |
| } |
| |
| 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, 10, dashPatF, dash_phase); |
| } |
| } |