git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1916144 13f79535-47bb-0310-9956-ffa450edef68pull/604/head
@@ -30,6 +30,8 @@ import com.microsoft.schemas.office.visio.x2012.main.RowType; | |||
public class EllipticalArcTo implements GeometryRow { | |||
private static final double EPS = 1e-10; | |||
EllipticalArcTo _master; | |||
// The x-coordinate of the ending vertex on an arc. | |||
@@ -169,6 +171,13 @@ public class EllipticalArcTo implements GeometryRow { | |||
double x0 = last.getX(); | |||
double y0 = last.getY(); | |||
if (isColinear(x0, y0, x, y, a, b)) { | |||
// All begin, end, and control points lie on the same line. | |||
// Skip calculating an arc and just replace it with line. | |||
path.lineTo(x, y); | |||
return; | |||
} | |||
// translate all of the points to the same angle as the ellipse | |||
AffineTransform at = AffineTransform.getRotateInstance(-c); | |||
double[] pts = new double[] { x0, y0, x, y, a, b }; | |||
@@ -219,6 +228,10 @@ public class EllipticalArcTo implements GeometryRow { | |||
path.append(at.createTransformedShape(arc), false); | |||
} | |||
private static boolean isColinear(double x1, double y1, double x2, double y2, double x3, double y3) { | |||
return Math.abs((y1 - y2) * (x1 - x3) - (y1 - y3) * (x1 - x2)) < EPS; | |||
} | |||
protected static double computeSweep(double startAngle, double endAngle, | |||
double ctrlAngle) { | |||
double sweep; |
@@ -24,6 +24,7 @@ import java.awt.geom.Path2D; | |||
import org.apache.poi.xdgf.usermodel.XDGFShape; | |||
import org.apache.poi.xdgf.usermodel.XDGFText; | |||
import org.apache.poi.xdgf.usermodel.section.GeometrySection; | |||
/** | |||
* To use this to render only particular shapes, override it and provide an | |||
@@ -60,7 +61,27 @@ public class ShapeRenderer extends ShapeVisitor { | |||
} | |||
protected Path2D drawPath(XDGFShape shape) { | |||
Path2D.Double path = shape.getPath(); | |||
Path2D path = null; | |||
for (GeometrySection geometrySection : shape.getGeometrySections()) { | |||
if (geometrySection.getNoShow()) { | |||
continue; | |||
} | |||
// We preserve only first drawn path | |||
if (path == null) { | |||
path = drawPath(geometrySection, shape); | |||
} else { | |||
drawPath(geometrySection, shape); | |||
} | |||
} | |||
return path; | |||
} | |||
private Path2D drawPath(GeometrySection geometrySection, XDGFShape shape) { | |||
Path2D path = geometrySection.getPath(shape); | |||
if (path != null) { | |||
// setup the stroke for this line | |||
@@ -69,7 +90,6 @@ public class ShapeRenderer extends ShapeVisitor { | |||
_graphics.setStroke(shape.getStroke()); | |||
_graphics.draw(path); | |||
} | |||
return path; | |||
} | |||
@@ -0,0 +1,102 @@ | |||
/* ==================================================================== | |||
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.apache.poi.util.LocaleUtil; | |||
import org.junit.jupiter.api.Assertions; | |||
import java.awt.geom.Path2D; | |||
import java.awt.geom.PathIterator; | |||
import java.util.Arrays; | |||
import java.util.Map; | |||
import static org.junit.jupiter.api.Assertions.assertEquals; | |||
public final class GeometryTestUtils { | |||
private static final double EPS = 1e-6; | |||
private GeometryTestUtils() { | |||
} | |||
public static RowType createRow(long index, Map<String, Object> cells) { | |||
RowType row = RowType.Factory.newInstance(); | |||
row.setIX(index); | |||
row.setDel(false); | |||
CellType[] cellsArray = cells | |||
.entrySet() | |||
.stream() | |||
.map(entry -> createCell(entry.getKey(), entry.getValue().toString())) | |||
.toArray(CellType[]::new); | |||
row.setCellArray(cellsArray); | |||
return row; | |||
} | |||
private static CellType createCell(String name, String value) { | |||
CellType cell = CellType.Factory.newInstance(); | |||
cell.setN(name); | |||
cell.setV(value); | |||
return cell; | |||
} | |||
public 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( | |||
LocaleUtil.getUserLocale(), | |||
"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( | |||
LocaleUtil.getUserLocale(), | |||
"expected <%f> but found <%f>", e, a)); | |||
} | |||
} | |||
} | |||
} |
@@ -17,27 +17,20 @@ | |||
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 com.microsoft.schemas.office.visio.x2012.main.SectionType; | |||
import com.microsoft.schemas.office.visio.x2012.main.TriggerType; | |||
import org.apache.poi.util.LocaleUtil; | |||
import org.apache.poi.xdgf.usermodel.section.GeometrySection; | |||
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 java.util.HashMap; | |||
import static org.junit.jupiter.api.Assertions.assertEquals; | |||
import static org.junit.jupiter.api.Assertions.assertNotNull; | |||
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; | |||
@@ -61,7 +54,7 @@ public class TestArcTo { | |||
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); | |||
GeometryTestUtils.assertPath(expectedPath, actualPath); | |||
} | |||
@Test | |||
@@ -80,7 +73,7 @@ public class TestArcTo { | |||
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); | |||
GeometryTestUtils.assertPath(expectedPath, actualPath); | |||
} | |||
@Test | |||
@@ -98,7 +91,7 @@ public class TestArcTo { | |||
expectedPath.moveTo(X0, Y0); | |||
expectedPath.lineTo(X, Y); | |||
assertPath(expectedPath, actualPath); | |||
GeometryTestUtils.assertPath(expectedPath, actualPath); | |||
} | |||
@Test | |||
@@ -119,7 +112,7 @@ public class TestArcTo { | |||
Path2D.Double expectedPath = new Path2D.Double(); | |||
expectedPath.moveTo(X0, Y0); | |||
assertPath(expectedPath, actualPath); | |||
GeometryTestUtils.assertPath(expectedPath, actualPath); | |||
} | |||
// this test is mostly used to trigger inclusion of some | |||
@@ -139,67 +132,15 @@ public class TestArcTo { | |||
} | |||
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); | |||
RowType row = GeometryTestUtils.createRow( | |||
0L, | |||
new HashMap<String, Object>() {{ | |||
put("X", X); | |||
put("Y", Y); | |||
put("A", a); | |||
}} | |||
); | |||
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( | |||
LocaleUtil.getUserLocale(), | |||
"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( | |||
LocaleUtil.getUserLocale(), | |||
"expected <%f> but found <%f>", e, a)); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,93 @@ | |||
/* ==================================================================== | |||
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.RowType; | |||
import org.junit.jupiter.api.Test; | |||
import java.awt.geom.Arc2D; | |||
import java.awt.geom.Path2D; | |||
import java.util.HashMap; | |||
public class TestEllipticalArcTo { | |||
private static final double X0 = 0.0; | |||
private static final double Y0 = 0.0; | |||
private static final double R = 100.0; | |||
private static final double X = R; | |||
private static final double Y = R; | |||
// Rotation angle does not affect the calculation | |||
private static final double C = 0.0; | |||
// Draw a circular arc, it does not matter for this test which type of arc we draw | |||
private static final double D = 1.0; | |||
@Test | |||
public void shouldAddArcToPathWhenControlPointIsNotColinearWithBeginAndEnd() { | |||
double a = R / 2.0; | |||
double b = R - Math.sqrt(R * R - a * a); | |||
EllipticalArcTo ellipticalArcTo = createEllipticalArcTo(a, b); | |||
Path2D.Double actualPath = new Path2D.Double(); | |||
actualPath.moveTo(X0, Y0); | |||
// Shape isn't used while creating an elliptical arc | |||
ellipticalArcTo.addToPath(actualPath, null); | |||
Path2D.Double expectedPath = new Path2D.Double(); | |||
expectedPath.moveTo(X0, Y0); | |||
Arc2D arc = new Arc2D.Double(-R, Y0, R * 2, R * 2, 90, -90, Arc2D.OPEN); | |||
expectedPath.append(arc, false); | |||
GeometryTestUtils.assertPath(expectedPath, actualPath); | |||
} | |||
@Test | |||
public void shouldAddLineToPathWhenControlPointIsColinearWithBeginAndEnd() { | |||
// We artificially set control point that is obviously colinear with begin and end. | |||
// However, when you draw a very small arc, it might happen that all three points are colinear to each other | |||
EllipticalArcTo ellipticalArcTo = createEllipticalArcTo(50.0, 50.0); | |||
Path2D.Double actualPath = new Path2D.Double(); | |||
actualPath.moveTo(X0, Y0); | |||
// Shape isn't used while creating an elliptical arc | |||
ellipticalArcTo.addToPath(actualPath, null); | |||
Path2D.Double expectedPath = new Path2D.Double(); | |||
expectedPath.moveTo(X0, Y0); | |||
expectedPath.lineTo(X, Y); | |||
GeometryTestUtils.assertPath(expectedPath, actualPath); | |||
} | |||
private static EllipticalArcTo createEllipticalArcTo(double a, double b) { | |||
RowType row = GeometryTestUtils.createRow( | |||
0L, | |||
new HashMap<String, Object>() {{ | |||
put("X", X); | |||
put("Y", Y); | |||
put("A", a); | |||
put("B", b); | |||
put("C", C); | |||
put("D", D); | |||
}} | |||
); | |||
return new EllipticalArcTo(row); | |||
} | |||
} |