1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
|
/*
* ====================================================================
* 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);
}
}
|