| /* |
| * ==================================================================== |
| * 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.geom; |
| |
| import static org.apache.poi.sl.draw.geom.Formula.OOXML_DEGREE; |
| |
| import java.awt.geom.Arc2D; |
| import java.awt.geom.Path2D; |
| import java.awt.geom.Point2D; |
| import java.util.Objects; |
| |
| import org.apache.poi.util.Internal; |
| |
| /** |
| * ArcTo command within a shape path in DrawingML: |
| * {@code <arcTo wR="wr" hR="hr" stAng="stAng" swAng="swAng"/>} |
| * <p> |
| * Where {@code wr} and {@code wh} are the height and width radii |
| * of the supposed circle being used to draw the arc. This gives the circle |
| * a total height of (2 * hR) and a total width of (2 * wR) |
| * <p> |
| * stAng is the {@code start} angle and {@code swAng} is the swing angle |
| * <p> |
| * Java class for CT_Path2DArcTo complex type. |
| * |
| * <p>The following schema fragment specifies the expected content contained within this class. |
| * |
| * <pre> |
| * <complexType name="CT_Path2DArcTo"> |
| * <complexContent> |
| * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType"> |
| * <attribute name="wR" use="required" type="{http://schemas.openxmlformats.org/drawingml/2006/main}ST_AdjCoordinate" /> |
| * <attribute name="hR" use="required" type="{http://schemas.openxmlformats.org/drawingml/2006/main}ST_AdjCoordinate" /> |
| * <attribute name="stAng" use="required" type="{http://schemas.openxmlformats.org/drawingml/2006/main}ST_AdjAngle" /> |
| * <attribute name="swAng" use="required" type="{http://schemas.openxmlformats.org/drawingml/2006/main}ST_AdjAngle" /> |
| * </restriction> |
| * </complexContent> |
| * </complexType> |
| * </pre> |
| */ |
| // @XmlAccessorType(XmlAccessType.FIELD) |
| // @XmlType(name = "CT_Path2DArcTo") |
| public class ArcToCommand implements PathCommand { |
| |
| // @XmlAttribute(name = "wR", required = true) |
| private String wr; |
| // @XmlAttribute(name = "hR", required = true) |
| private String hr; |
| // @XmlAttribute(name = "stAng", required = true) |
| private String stAng; |
| // @XmlAttribute(name = "swAng", required = true) |
| private String swAng; |
| |
| public void setHR(String hr) { |
| this.hr = hr; |
| } |
| |
| public void setWR(String wr) { |
| this.wr = wr; |
| } |
| |
| public void setStAng(String stAng) { |
| this.stAng = stAng; |
| } |
| |
| public void setSwAng(String swAng) { |
| this.swAng = swAng; |
| } |
| |
| @Override |
| public void execute(Path2D.Double path, Context ctx){ |
| double rx = ctx.getValue(wr); |
| double ry = ctx.getValue(hr); |
| double ooStart = ctx.getValue(stAng) / OOXML_DEGREE; |
| double ooExtent = ctx.getValue(swAng) / OOXML_DEGREE; |
| |
| // skew the angles for AWT output |
| double awtStart = convertOoxml2AwtAngle(ooStart, rx, ry); |
| double awtSweep = convertOoxml2AwtAngle(ooStart+ooExtent, rx, ry)-awtStart; |
| |
| // calculate the inverse angle - taken from the (reversed) preset definition |
| double radStart = Math.toRadians(ooStart); |
| double invStart = Math.atan2(rx * Math.sin(radStart), ry * Math.cos(radStart)); |
| |
| Point2D pt = path.getCurrentPoint(); |
| // calculate top/left corner |
| double x0 = pt.getX() - rx * Math.cos(invStart) - rx; |
| double y0 = pt.getY() - ry * Math.sin(invStart) - ry; |
| |
| Arc2D arc = new Arc2D.Double(x0, y0, 2 * rx, 2 * ry, awtStart, awtSweep, Arc2D.OPEN); |
| path.append(arc, true); |
| } |
| |
| /** |
| * Arc2D angles are skewed, OOXML aren't ... so we need to unskew them<p> |
| * |
| * Furthermore ooxml angle starts at the X-axis and increases clock-wise, |
| * where as Arc2D api states |
| * "45 degrees always falls on the line from the center of the ellipse to |
| * the upper right corner of the framing rectangle" |
| * so we need to reverse it |
| * |
| * <pre> |
| * AWT: OOXML: |
| * |90/-270 |270/-90 (16200000) |
| * | | |
| * +/-180-----------0 +/-180-----------0 |
| * | (10800000) | |
| * |270/-90 |90/-270 (5400000) |
| * </pre> |
| * |
| * @param ooAngle the angle in OOXML units divided by 60000 |
| * @param width the width of the bounding box |
| * @param height the height of the bounding box |
| * |
| * @return the angle in degrees |
| * |
| * @see <a href="http://www.onlinemathe.de/forum/Problem-bei-Winkelberechnungen-einer-Ellipse">unskew angle</a> |
| **/ |
| @Internal |
| public static double convertOoxml2AwtAngle(double ooAngle, double width, double height) { |
| double aspect = (height / width); |
| // reverse angle for awt |
| double awtAngle = -ooAngle; |
| // normalize angle, in case it's < -360 or > 360 degrees |
| double awtAngle2 = awtAngle%360.; |
| double awtAngle3 = awtAngle-awtAngle2; |
| // because of tangens nature, the values left [90°-270°] and right [270°-90°] of the axis are mirrored/the same |
| // and the result of atan2 need to be justified |
| switch ((int)(awtAngle2 / 90)) { |
| case -3: |
| // -270 to -360 |
| awtAngle3 -= 360; |
| awtAngle2 += 360; |
| break; |
| case -2: |
| case -1: |
| // -90 to -270 |
| awtAngle3 -= 180; |
| awtAngle2 += 180; |
| break; |
| default: |
| case 0: |
| // -90 to 90 |
| break; |
| case 2: |
| case 1: |
| // 90 to 270 |
| awtAngle3 += 180; |
| awtAngle2 -= 180; |
| break; |
| case 3: |
| // 270 to 360 |
| awtAngle3 += 360; |
| awtAngle2 -= 360; |
| break; |
| } |
| |
| // skew |
| awtAngle = Math.toDegrees(Math.atan2(Math.tan(Math.toRadians(awtAngle2)), aspect)) + awtAngle3; |
| return awtAngle; |
| } |
| |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (!(o instanceof ArcToCommand)) return false; |
| ArcToCommand that = (ArcToCommand) o; |
| return Objects.equals(wr, that.wr) && |
| Objects.equals(hr, that.hr) && |
| Objects.equals(stAng, that.stAng) && |
| Objects.equals(swAng, that.swAng); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(wr, hr, stAng, swAng); |
| } |
| } |