aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--poi-ooxml/src/main/java/org/apache/poi/xdgf/usermodel/section/geometry/ArcTo.java27
-rw-r--r--poi-ooxml/src/test/java/org/apache/poi/xdgf/usermodel/section/geometry/TestArcTo.java179
2 files changed, 192 insertions, 14 deletions
diff --git a/poi-ooxml/src/main/java/org/apache/poi/xdgf/usermodel/section/geometry/ArcTo.java b/poi-ooxml/src/main/java/org/apache/poi/xdgf/usermodel/section/geometry/ArcTo.java
index 4547229d4a..531b8eb0fd 100644
--- a/poi-ooxml/src/main/java/org/apache/poi/xdgf/usermodel/section/geometry/ArcTo.java
+++ b/poi-ooxml/src/main/java/org/apache/poi/xdgf/usermodel/section/geometry/ArcTo.java
@@ -17,8 +17,6 @@
package org.apache.poi.xdgf.usermodel.section.geometry;
-import java.awt.geom.AffineTransform;
-import java.awt.geom.Arc2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
@@ -112,20 +110,21 @@ public class ArcTo implements GeometryRow {
double x0 = last.getX();
double y0 = last.getY();
- double chordLength = Math.hypot(y - y0, x - x0);
- double radius = (4 * a * a + chordLength * chordLength)
- / (8 * Math.abs(a));
+ // Find a normal to the chord of the circle.
+ double nx = y - y0;
+ double ny = x0 - x;
+ double nLength = Math.sqrt(nx * nx + ny * ny);
- // center point
- double cx = x0 + (x - x0) / 2.0;
- double cy = y0 + (y - y0) / 2.0;
+ // Follow the normal with the height of the arc to get the third point on the circle.
+ double x1 = (x0 + x) / 2 + a * nx / nLength;
+ double y1 = (y0 + y) / 2 + a * ny / nLength;
- double rotate = Math.atan2(y - cy, x - cx);
-
- Arc2D arc = new Arc2D.Double(x0, y0 - radius, chordLength, 2 * radius,
- 180, x0 < x ? 180 : -180, Arc2D.OPEN);
+ // Add an elliptical arc with rx / ry = 1 to the path because it's a circle.
+ EllipticalArcTo.createEllipticalArc(x, y, x1, y1, 0.0, 1.0, path);
+ }
- path.append(AffineTransform.getRotateInstance(rotate, x0, y0)
- .createTransformedShape(arc), true);
+ @Override
+ public String toString() {
+ return String.format("ArcTo: x=%f; y=%f; a=%f", x, y, a);
}
}
diff --git a/poi-ooxml/src/test/java/org/apache/poi/xdgf/usermodel/section/geometry/TestArcTo.java b/poi-ooxml/src/test/java/org/apache/poi/xdgf/usermodel/section/geometry/TestArcTo.java
new file mode 100644
index 0000000000..5ee3dd0535
--- /dev/null
+++ b/poi-ooxml/src/test/java/org/apache/poi/xdgf/usermodel/section/geometry/TestArcTo.java
@@ -0,0 +1,179 @@
+/* ====================================================================
+ 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.xdgf.usermodel.section.geometry;
+
+import com.microsoft.schemas.office.visio.x2012.main.CellType;
+import com.microsoft.schemas.office.visio.x2012.main.RowType;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.awt.geom.Path2D;
+import java.awt.geom.PathIterator;
+import java.util.Arrays;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class TestArcTo {
+
+ private static final double EPS = 0.000001;
+
+ // We draw a circular arc with radius 100 from (0, 0) to (100, 100)
+ private static final double X0 = 0.0;
+ private static final double Y0 = 0.0;
+ private static final double X = 100.0;
+ private static final double Y = 100.0;
+ private static final double A = 29.289322; // a = radius - sqrt(((x + x0) / 2) ^ 2 + ((y + y0) / 2) ^2)
+
+ @Test
+ public void shouldDrawCircularArcWhenArcHeightMoreThanZero() {
+ ArcTo arcTo = createArcTo(A);
+
+ Path2D.Double actualPath = new Path2D.Double();
+ actualPath.moveTo(X0, Y0);
+
+ // Shape isn't used while creating a circular arc
+ arcTo.addToPath(actualPath, null);
+
+ // This path can be used to draw a curve that approximates calculated arc.
+ Path2D.Double expectedPath = new Path2D.Double();
+ expectedPath.moveTo(X0, Y0);
+ expectedPath.curveTo(26.521649, 0.0, 51.957040, 10.535684, 70.710678, 29.289321);
+ expectedPath.curveTo(89.464316, 48.042960, 100.000000, 73.478351, X, Y);
+
+ assertPath(expectedPath, actualPath);
+ }
+
+ @Test
+ public void shouldDrawCircularArcWhenArcHeightLessThanZero() {
+ ArcTo arcTo = createArcTo(-A);
+
+ Path2D.Double actualPath = new Path2D.Double();
+ actualPath.moveTo(X0, Y0);
+
+ // Shape isn't used while creating a circular arc
+ arcTo.addToPath(actualPath, null);
+
+ // This path can be used to draw a curve that approximates calculated arc.
+ Path2D.Double expectedPath = new Path2D.Double();
+ expectedPath.moveTo(X0, Y0);
+ expectedPath.curveTo(0.0, 26.521649, 10.535684, 51.957040, 29.289321, 70.710678);
+ expectedPath.curveTo(48.042960, 89.464316, 73.478351, 100.000000, X, Y);
+
+ assertPath(expectedPath, actualPath);
+ }
+
+ @Test
+ public void shouldDrawLineInsteadOfArcWhenArcHeightIsZero() {
+ ArcTo arcTo = createArcTo(0.0);
+
+ Path2D.Double actualPath = new Path2D.Double();
+ actualPath.moveTo(X0, Y0);
+
+ // Shape isn't used while creating a circular arc
+ arcTo.addToPath(actualPath, null);
+
+ // This path can be used to draw a curve that approximates calculated arc.
+ Path2D.Double expectedPath = new Path2D.Double();
+ expectedPath.moveTo(X0, Y0);
+ expectedPath.lineTo(X, Y);
+
+ assertPath(expectedPath, actualPath);
+ }
+
+ @Test
+ public void shouldNotDrawAnythingWhenArcIsDeleted() {
+ RowType row = RowType.Factory.newInstance();
+ row.setIX(0L);
+ row.setDel(true);
+
+ ArcTo arcTo = new ArcTo(row);
+
+ Path2D.Double actualPath = new Path2D.Double();
+ actualPath.moveTo(X0, Y0);
+
+ // Shape isn't used while creating a circular arc
+ arcTo.addToPath(actualPath, null);
+
+ // This path can be used to draw a curve that approximates calculated arc.
+ Path2D.Double expectedPath = new Path2D.Double();
+ expectedPath.moveTo(X0, Y0);
+
+ assertPath(expectedPath, actualPath);
+ }
+
+ private static ArcTo createArcTo(double a) {
+ RowType row = RowType.Factory.newInstance();
+ row.setIX(0L);
+ row.setDel(false);
+
+ CellType xCell = CellType.Factory.newInstance();
+ xCell.setN("X");
+ xCell.setV(Double.toString(X));
+
+ CellType yCell = CellType.Factory.newInstance();
+ yCell.setN("Y");
+ yCell.setV(Double.toString(Y));
+
+
+ CellType aCell = CellType.Factory.newInstance();
+ aCell.setN("A");
+ aCell.setV(Double.toString(a));
+
+ CellType[] cells = new CellType[] { xCell , yCell, aCell };
+ row.setCellArray(cells);
+
+ return new ArcTo(row);
+ }
+
+ private static void assertPath(Path2D expected, Path2D actual) {
+ PathIterator expectedIterator = expected.getPathIterator(null);
+ PathIterator actualIterator = actual.getPathIterator(null);
+
+ double[] expectedCoordinates = new double[6];
+ double[] actualCoordinates = new double[6];
+ while (!expectedIterator.isDone() && !actualIterator.isDone()) {
+ int expectedSegmentType = expectedIterator.currentSegment(expectedCoordinates);
+ int actualSegmentType = actualIterator.currentSegment(actualCoordinates);
+
+ assertEquals(expectedSegmentType, actualSegmentType);
+ assertCoordinates(expectedCoordinates, actualCoordinates);
+
+ expectedIterator.next();
+ actualIterator.next();
+ }
+
+ if (!expectedIterator.isDone() || !actualIterator.isDone()) {
+ Assertions.fail("Path iterators have different number of segments");
+ }
+ }
+
+ private static void assertCoordinates(double[] expected, double[] actual) {
+ if (expected.length != actual.length) {
+ Assertions.fail(String.format("Given coordinates arrays have different length: expected=%s, actual=%s",
+ Arrays.toString(expected), Arrays.toString(actual)));
+ }
+ for (int i = 0; i < expected.length; i++) {
+ double e = expected[i];
+ double a = actual[i];
+
+ if (Math.abs(e - a) > EPS) {
+ Assertions.fail(String.format("expected <%f> but found <%f>", e, a));
+ }
+ }
+ }
+}