blob: 4c6c14d09b092aed911986c11741e643ac014acd [file] [log] [blame]
/*
* ====================================================================
* 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>
* &lt;complexType name="CT_Path2DArcTo"&gt;
* &lt;complexContent&gt;
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&gt;
* &lt;attribute name="wR" use="required" type="{http://schemas.openxmlformats.org/drawingml/2006/main}ST_AdjCoordinate" /&gt;
* &lt;attribute name="hR" use="required" type="{http://schemas.openxmlformats.org/drawingml/2006/main}ST_AdjCoordinate" /&gt;
* &lt;attribute name="stAng" use="required" type="{http://schemas.openxmlformats.org/drawingml/2006/main}ST_AdjAngle" /&gt;
* &lt;attribute name="swAng" use="required" type="{http://schemas.openxmlformats.org/drawingml/2006/main}ST_AdjAngle" /&gt;
* &lt;/restriction&gt;
* &lt;/complexContent&gt;
* &lt;/complexType&gt;
* </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);
}
}