]> source.dussan.org Git - poi.git/commitdiff
Import poi-visio codebase
authorDustin Spicuzza <virtuald@apache.org>
Mon, 19 Oct 2015 05:52:35 +0000 (05:52 +0000)
committerDustin Spicuzza <virtuald@apache.org>
Mon, 19 Oct 2015 05:52:35 +0000 (05:52 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1709354 13f79535-47bb-0310-9956-ffa450edef68

58 files changed:
src/ooxml/java/org/apache/poi/xdgf/exceptions/XDGFException.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/geom/Dimension2dDouble.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/geom/SplineCollector.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/geom/SplineRenderer.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFBaseContents.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFCell.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFConnection.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFDocument.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFFactory.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFMaster.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFMasterContents.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFMasters.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFPage.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFPageContents.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFPageSheet.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFPages.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFRelation.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFShape.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFSheet.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFStyleSheet.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFText.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/XmlVisioDocument.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/section/CharacterSection.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/section/CombinedIterable.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/section/GenericSection.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/section/GeometrySection.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/section/XDGFSection.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/ArcTo.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/Ellipse.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/EllipticalArcTo.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/GeometryRow.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/GeometryRowFactory.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/InfiniteLine.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/LineTo.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/MoveTo.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/NURBSTo.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/PolyLineTo.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/RelCubBezTo.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/RelEllipticalArcTo.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/RelLineTo.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/RelMoveTo.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/RelQuadBezTo.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/SplineKnot.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/SplineStart.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/shape/ShapeDataAcceptor.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/shape/ShapeDebuggerRenderer.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/shape/ShapeRenderer.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/shape/ShapeVisitor.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/shape/ShapeVisitorAcceptor.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/shape/exceptions/StopVisiting.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/usermodel/shape/exceptions/StopVisitingThisBranch.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/util/HierarchyPrinter.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/util/ObjectFactory.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/util/Util.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/util/VsdxToPng.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xdgf/xml/XDGFXMLDocumentPart.java [new file with mode: 0644]
src/ooxml/testcases/org/apache/poi/xdgf/usermodel/section/CombinedIteratorTest.java [new file with mode: 0644]
src/resources/devtools/unpack_ooxml.sh [new file with mode: 0755]

diff --git a/src/ooxml/java/org/apache/poi/xdgf/exceptions/XDGFException.java b/src/ooxml/java/org/apache/poi/xdgf/exceptions/XDGFException.java
new file mode 100644 (file)
index 0000000..3e17377
--- /dev/null
@@ -0,0 +1,50 @@
+/* ====================================================================
+   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.exceptions;
+
+import org.apache.poi.POIXMLDocumentPart;
+import org.apache.poi.POIXMLException;
+
+public class XDGFException {
+
+       /**
+        * Creates an error message to be thrown
+        */
+       public static POIXMLException error(String message, Object o) {
+               return new POIXMLException(o.toString() + ": " + message);
+       }
+       
+       public static POIXMLException error(String message, Object o, Throwable t) {
+               return new POIXMLException(o.toString() + ": " + message, t);
+       }
+       
+       //
+       // Use these to wrap error messages coming up so that we have at least
+       // some idea where the error was located
+       //
+
+       public static POIXMLException wrap(POIXMLDocumentPart part, POIXMLException e) {
+               return new POIXMLException(part.getPackagePart().getPartName().toString() + ": " + e.getMessage(),
+                                                                 e.getCause() == null ? e : e.getCause());
+       }
+       
+       public static POIXMLException wrap(String where, POIXMLException e) {
+               return new POIXMLException(where + ": " + e.getMessage(),
+                                                                 e.getCause() == null ? e : e.getCause());
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/geom/Dimension2dDouble.java b/src/ooxml/java/org/apache/poi/xdgf/geom/Dimension2dDouble.java
new file mode 100644 (file)
index 0000000..63a5031
--- /dev/null
@@ -0,0 +1,73 @@
+/* ====================================================================
+   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.geom;
+
+import java.awt.geom.Dimension2D;
+
+public class Dimension2dDouble extends Dimension2D {
+
+       double width;
+       double height;
+       
+       public Dimension2dDouble() {
+               width = 0d;
+               height = 0d;
+       }
+       
+       public Dimension2dDouble(double width, double height) {
+               this.width = width;
+               this.height = height;
+       }
+       
+       @Override
+       public double getWidth() {
+               return width;
+       }
+
+       @Override
+       public double getHeight() {
+               return height;
+       }
+
+       @Override
+       public void setSize(double width, double height) {
+               this.width = width;
+               this.height = height;
+       }
+       
+       @Override
+       public boolean equals(Object obj) {
+               if (obj instanceof Dimension2dDouble) {
+                       Dimension2dDouble other = (Dimension2dDouble)obj;
+                       return width == other.width && height == other.height;
+               }
+               
+               return false;
+       }
+       
+       @Override
+       public int hashCode() {
+               double sum = width + height;
+        return (int)Math.ceil(sum * (sum + 1)/2 + width);
+       }
+       
+       @Override
+       public String toString() {
+               return "Dimension2dDouble[" + width + ", " + height + "]";
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/geom/SplineCollector.java b/src/ooxml/java/org/apache/poi/xdgf/geom/SplineCollector.java
new file mode 100644 (file)
index 0000000..23f2636
--- /dev/null
@@ -0,0 +1,79 @@
+/* ====================================================================
+   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.geom;
+
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+import org.apache.poi.xdgf.usermodel.section.geometry.SplineKnot;
+import org.apache.poi.xdgf.usermodel.section.geometry.SplineStart;
+
+import com.graphbuilder.curve.ControlPath;
+import com.graphbuilder.curve.ShapeMultiPath;
+import com.graphbuilder.curve.ValueVector;
+import com.graphbuilder.geom.PointFactory;
+
+public class SplineCollector {
+
+       SplineStart _start;
+       ArrayList<SplineKnot> _knots = new ArrayList<>();
+
+       public SplineCollector(SplineStart start) {
+               _start = start;
+       }
+       
+       public void addKnot(SplineKnot knot) {
+               if (!knot.getDel())
+                       _knots.add(knot);
+       }
+       
+       public void addToPath(java.awt.geom.Path2D.Double path, XDGFShape parent) {
+               // ok, we have the start, and all knots... do something with this
+               
+               Point2D last = path.getCurrentPoint();
+               
+               // create a control path and knots
+               ControlPath controlPath = new ControlPath();
+               ValueVector knots = new ValueVector(_knots.size() + 3);
+               
+               double firstKnot = _start.getB();
+               double lastKnot = _start.getC();
+               int degree = _start.getD();
+               
+               // first/second knot
+               knots.add(firstKnot);
+               knots.add(_start.getA());
+               
+               // first/second control point
+               controlPath.addPoint(PointFactory.create(last.getX(), last.getY()));
+               controlPath.addPoint(PointFactory.create(_start.getX(), _start.getY()));
+               
+               // middle knots/control points
+               for (SplineKnot knot: _knots) {
+                       knots.add(knot.getA());
+                       controlPath.addPoint(PointFactory.create(knot.getX(), knot.getY()));
+               }
+               
+               // last knot
+               knots.add(lastKnot);
+               
+               ShapeMultiPath shape = SplineRenderer.createNurbsSpline(controlPath, knots, null, degree);
+               path.append(shape, true);
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/geom/SplineRenderer.java b/src/ooxml/java/org/apache/poi/xdgf/geom/SplineRenderer.java
new file mode 100644 (file)
index 0000000..060555d
--- /dev/null
@@ -0,0 +1,68 @@
+/* ====================================================================
+   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.geom;
+
+import com.graphbuilder.curve.ControlPath;
+import com.graphbuilder.curve.GroupIterator;
+import com.graphbuilder.curve.NURBSpline;
+import com.graphbuilder.curve.ShapeMultiPath;
+import com.graphbuilder.curve.ValueVector;
+
+public class SplineRenderer {
+
+       public static ShapeMultiPath createNurbsSpline(ControlPath controlPoints,
+                                                                                                  ValueVector knots,
+                                                                                                  ValueVector weights,
+                                                                                                  int degree) {
+               
+               double firstKnot = knots.get(0);
+               double lastKnot = knots.get(knots.size()-1);
+               
+               // scale knots to [0, 1] based on first/last knots
+               for (int i = 0; i < knots.size(); i++) {
+                       knots.set((knots.get(i) - firstKnot)/lastKnot, i);
+               }
+               
+               // if we don't have enough knots, duplicate the last knot until we do
+               for (int i = knots.size(); i < controlPoints.numPoints() + degree + 1; i++) {
+                       knots.add(1);
+               }
+               
+               GroupIterator gi = new GroupIterator("0:n-1", controlPoints.numPoints());
+               
+               NURBSpline spline = new NURBSpline(controlPoints, gi);
+               
+               spline.setDegree(degree);
+               spline.setKnotVectorType(NURBSpline.NON_UNIFORM);
+               spline.setKnotVector(knots);
+               
+               if (weights == null) {
+                       spline.setUseWeightVector(false);
+               } else {
+                       spline.setWeightVector(weights);
+               }
+               
+               // now that this is done, add it to the path
+               ShapeMultiPath shape = new ShapeMultiPath();
+               shape.setFlatness(0.01);
+               
+               spline.appendTo(shape);
+               return shape;
+       }
+       
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFBaseContents.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFBaseContents.java
new file mode 100644 (file)
index 0000000..79edbd9
--- /dev/null
@@ -0,0 +1,159 @@
+/* ====================================================================
+   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;
+
+import java.awt.Graphics2D;
+import java.awt.geom.AffineTransform;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.poi.POIXMLException;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.PackageRelationship;
+import org.apache.poi.util.Internal;
+import org.apache.poi.xdgf.exceptions.XDGFException;
+import org.apache.poi.xdgf.usermodel.shape.ShapeRenderer;
+import org.apache.poi.xdgf.usermodel.shape.ShapeVisitor;
+import org.apache.poi.xdgf.usermodel.shape.exceptions.StopVisiting;
+import org.apache.poi.xdgf.xml.XDGFXMLDocumentPart;
+
+import com.microsoft.schemas.office.visio.x2012.main.ConnectType;
+import com.microsoft.schemas.office.visio.x2012.main.PageContentsType;
+import com.microsoft.schemas.office.visio.x2012.main.ShapeSheetType;
+
+/**
+ * Container of shapes for a page in a visio document
+ */
+public class XDGFBaseContents extends XDGFXMLDocumentPart {
+
+       protected PageContentsType _pageContents;
+       
+       // shapes without parents
+       protected List<XDGFShape> _toplevelShapes = new ArrayList<>();
+       protected Map<Long, XDGFShape> _shapes = new HashMap<>();
+       protected List<XDGFConnection> _connections = new ArrayList<>();
+       
+       public XDGFBaseContents(PackagePart part, PackageRelationship rel, XDGFDocument document) {
+               super(part, rel, document);
+       }
+       
+       @Internal
+       public PageContentsType getXmlObject() {
+               return _pageContents;
+       }
+       
+       
+       @Override
+       protected void onDocumentRead() {
+               
+               if (_pageContents.isSetShapes()) {
+                       for (ShapeSheetType shapeSheet: _pageContents.getShapes().getShapeArray()) {
+                               XDGFShape shape = new XDGFShape(shapeSheet, this, _document);
+                               _toplevelShapes.add(shape);
+                               addToShapeIndex(shape);
+                       }
+               }
+               
+               if (_pageContents.isSetConnects()) {
+                       for (ConnectType connect: _pageContents.getConnects().getConnectArray()) {
+                               
+                               XDGFShape from = _shapes.get(connect.getFromSheet());
+                               XDGFShape to = _shapes.get(connect.getToSheet());
+                               
+                               if (from == null)
+                                       throw new POIXMLException(this.toString() + "; Connect; Invalid from id: " + connect.getFromSheet());
+                               
+                               if (to == null)
+                                       throw new POIXMLException(this.toString() + "; Connect; Invalid to id: " + connect.getToSheet());
+                               
+                               _connections.add(new XDGFConnection(connect, from, to));
+                       }
+               }
+       }
+       
+       protected void addToShapeIndex(XDGFShape shape) {
+               _shapes.put(shape.getID(), shape);
+               
+               List<XDGFShape> shapes = shape.getShapes();
+               if (shapes == null)
+                       return;
+               
+               for (XDGFShape subshape: shapes)
+                       addToShapeIndex(subshape);
+       }
+       
+       //
+       // API
+       //
+       
+       /**
+        * 
+        * @param graphics
+        */
+       public void draw(Graphics2D graphics) {
+               visitShapes(new ShapeRenderer(graphics));
+       }
+       
+       
+       public XDGFShape getShapeById(long id) {
+               return _shapes.get(id);
+       }
+       
+       public Map<Long, XDGFShape> getShapesMap() {
+               return Collections.unmodifiableMap(_shapes);
+       }
+       
+       public Collection<XDGFShape> getShapes() {
+               return _shapes.values();
+       }
+       
+       public List<XDGFShape> getTopLevelShapes() {
+               return Collections.unmodifiableList(_toplevelShapes);
+       }
+       
+       // get connections
+       public List<XDGFConnection> getConnections() {
+               return Collections.unmodifiableList(_connections);
+       }
+       
+       @Override
+       public String toString() {
+               return getPackagePart().getPartName().toString();
+       }
+       
+
+       /**
+        * Provides iteration over the shapes using the visitor pattern, and provides
+        * an easy way to convert shape coordinates into global coordinates
+        */
+       public void visitShapes(ShapeVisitor visitor) {
+               try {
+                       for (XDGFShape shape: _toplevelShapes) {
+                               shape.visitShapes(visitor, new AffineTransform(), 0);
+                       }
+               } catch (StopVisiting e) {
+                       // intentionally empty
+               } catch (POIXMLException e) {
+                       throw XDGFException.wrap(this, e);
+               }
+       }
+       
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFCell.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFCell.java
new file mode 100644 (file)
index 0000000..ef16a54
--- /dev/null
@@ -0,0 +1,150 @@
+/* ====================================================================
+   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;
+
+import java.util.Map;
+
+import org.apache.poi.POIXMLException;
+import org.apache.poi.util.Internal;
+
+import com.microsoft.schemas.office.visio.x2012.main.CellType;
+
+/**
+ * There are a lot of different cell types. Cell is really just an attribute
+ * of the thing that it's attached to. Will probably refactor this once I
+ * figure out a better way to use them
+ * 
+ * The various attributes of a Cell are constrained, and are better defined
+ * in the XSD 1.1 visio schema 
+ */
+public class XDGFCell {
+
+       public static Boolean maybeGetBoolean(Map<String, XDGFCell> cells, String name) {
+               XDGFCell cell = cells.get(name);
+               if (cell == null)
+                       return null;
+                       
+               if (cell.getValue().equals("0"))
+                       return false;
+               if (cell.getValue().equals("1"))
+                       return true;
+               
+               throw new POIXMLException("Invalid boolean value for '" + cell.getName() + "'");
+       }
+       
+       public static Double maybeGetDouble(Map<String, XDGFCell> cells, String name) {
+               XDGFCell cell = cells.get(name);
+               if (cell != null)
+                       return parseDoubleValue(cell._cell);
+               return null;
+       }
+       
+       public static Integer maybeGetInteger(Map<String, XDGFCell> cells, String name) {
+               XDGFCell cell = cells.get(name);
+               if (cell != null)
+                       return parseIntegerValue(cell._cell);
+               return null;
+       }
+       
+       public static String maybeGetString(Map<String, XDGFCell> cells, String name) {
+               XDGFCell cell = cells.get(name);
+               if (cell != null) {
+                       String v = cell._cell.getV();
+                       if (v.equals("Themed"))
+                               return null;
+                       return v;
+               }
+               return null;
+       }
+       
+       public static Double parseDoubleValue(CellType cell) {
+               try {
+                       return Double.parseDouble(cell.getV());
+               } catch (NumberFormatException e) {
+                       if (cell.getV().equals("Themed"))
+                               return null;
+                       throw new POIXMLException("Invalid float value for '" + cell.getN() + "': " + e);
+               }
+       }
+       
+       public static Integer parseIntegerValue(CellType cell) {
+               try {
+                       return Integer.parseInt(cell.getV());
+               } catch (NumberFormatException e) {
+                       if (cell.getV().equals("Themed"))
+                               return null;
+                       throw new POIXMLException("Invalid integer value for '" + cell.getN() + "': " + e);
+               }
+       }
+       
+       // returns a length, converts it to inches?
+       public static Double parseVLength(CellType cell) {
+               try {
+                       return Double.parseDouble(cell.getV());
+               } catch (NumberFormatException e) {
+                       if (cell.getV().equals("Themed"))
+                               return null;
+                       throw new POIXMLException("Invalid float value for '" + cell.getN() + "': " + e);
+               }
+       }
+       
+       
+       CellType _cell;
+       
+       public XDGFCell(CellType cell) {
+               _cell = cell;
+       }
+       
+       @Internal
+       CellType getXmlObject() {
+               return _cell;
+       }
+       
+       /**
+        * Represents the name of the ShapeSheet cell.
+        */
+       public String getName() {
+               return _cell.getN();
+       }
+       
+       /**
+        * Represents the value of the cell.
+        */
+       public String getValue() {
+               return _cell.getV();
+       }
+       
+       /**
+        * Represents the element’s formula. This attribute can contain one of the following strings:
+        * - â€˜(some formula)’ if the formula exists locally
+        * - No Formula if the formula is locally deleted or blocked
+        * - Inh if the formula is inherited.
+        */
+       public String getFormula() {
+               return _cell.getF();
+       }
+       
+       /*
+        * Indicates that the formula evaluates to an error. The value of E is the
+        * current value (an error message string); the value of the V attribute is
+        * the last valid value.
+        */
+       public String getError() {
+               return _cell.getE();
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFConnection.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFConnection.java
new file mode 100644 (file)
index 0000000..6e9f108
--- /dev/null
@@ -0,0 +1,134 @@
+/* ====================================================================
+   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;
+
+import com.microsoft.schemas.office.visio.x2012.main.ConnectType;
+
+public class XDGFConnection {
+
+       // comments on frompart/topart taken from pkgVisio 
+       
+       // https://msdn.microsoft.com/en-us/library/ms367611(v=office.12).aspx
+       
+    //    The following constants declared by the Microsoft Office Visio type 
+    //       library show return values for the FromPart property.
+    // Constant                  Value  
+    // visConnectFromError         -1
+    // visFromNone                 0
+    // visLeftEdge                 1
+    // visCenterEdge               2
+    // visRightEdge                3
+    // visBottomEdge               4
+    // visMiddleEdge               5
+    // visTopEdge                  6
+    // visBeginX                   7
+    // visBeginY                   8
+    // visBegin                    9
+    // visEndX                     10
+    // visEndY                     11
+    // visEnd                      12
+    // visFromAngle                13
+    // visFromPin                  14
+    // visControlPoint             100 + zero-based row index (for example, visControlPoint = 100 if the control point is in row 0; visControlPoint = 101 if the control point is in row 1)
+       
+       public static final int visConnectFromError        = -1;
+       public static final int visFromNone                = 0;
+       public static final int visLeftEdge                = 1;
+       public static final int visCenterEdge              = 2;
+       public static final int visRightEdge               = 3;
+       public static final int visBottomEdge              = 4;
+       public static final int visMiddleEdge              = 5;
+       public static final int visTopEdge                 = 6;
+       public static final int visBeginX                  = 7;
+       public static final int visBeginY                  = 8;
+       public static final int visBegin                   = 9;
+       public static final int visEndX                    = 10;
+       public static final int visEndY                    = 11;
+       public static final int visEnd                     = 12;
+       public static final int visFromAngle               = 13;
+       public static final int visFromPin                 = 14;
+       
+       
+       //  The ToPart property identifies the part of a shape to which another 
+       //    shape is glued, such as its begin point or endpoint, one of its edges, 
+       //    or a connection point. The following constants declared by the Visio type library in member VisToParts show possible return values for the ToPart property.
+       // Constant               Value  
+       // visConnectToError       -1
+       // visToNone               0
+       // visGuideX               1
+       // visGuideY               2
+       // visWholeShape           3
+       // visGuideIntersect       4
+       // visToAngle              7
+       // visConnectionPoint      100 + row index of connection point
+       
+       public static final int visConnectToError          = -1;
+       public static final int visToNone                  = 0;
+       public static final int visGuideX                  = 1;
+       public static final int visGuideY                  = 2;
+       public static final int visWholeShape              = 3;
+       public static final int visGuideIntersect          = 4;
+       public static final int visToAngle                 = 7;
+       
+       private ConnectType _connect;
+       private XDGFShape _from;
+       private XDGFShape _to;
+       
+       
+       public XDGFConnection(ConnectType connect, XDGFShape from, XDGFShape to) {
+               _connect = connect;
+               _from = from;
+               _to = to;
+       }
+       
+       public XDGFShape getFromShape() {
+               return _from;
+       }
+       
+       public XDGFCell getFromCell() {
+               return _from.getCell(_connect.getFromCell());
+       }
+       
+       public String getFromCellName() {
+               return _connect.getFromCell();
+       }
+       
+       public XDGFShape getToShape() {
+               return _to;
+       }
+       
+       public String getToCellName() {
+               return _connect.getToCell();
+       }
+       
+       // see constants above
+       public Integer getFromPart() {
+               if (_connect.isSetFromPart())
+                       return _connect.getFromPart();
+               else
+                       return null;
+       }
+       
+       // see constants above
+       public Integer getToPart() {
+               if (_connect.isSetToPart())
+                       return _connect.getToPart();
+               else
+                       return null;
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFDocument.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFDocument.java
new file mode 100644 (file)
index 0000000..b38c879
--- /dev/null
@@ -0,0 +1,116 @@
+/* ====================================================================
+   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;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.poi.POIXMLException;
+import org.apache.poi.util.Internal;
+
+import com.microsoft.schemas.office.visio.x2012.main.DocumentSettingsType;
+import com.microsoft.schemas.office.visio.x2012.main.StyleSheetType;
+import com.microsoft.schemas.office.visio.x2012.main.VisioDocumentType;
+
+/**
+ * Represents the root document: /visio/document.xml
+ */
+public class XDGFDocument {
+
+       protected VisioDocumentType _document;
+
+       Map<Long, XDGFStyleSheet> _styleSheets = new HashMap<>();
+       
+       // defaults
+       long _defaultFillStyle = 0;
+       long _defaultGuideStyle = 0;
+       long _defaultLineStyle = 0;
+       long _defaultTextStyle = 0;
+       
+       
+       public XDGFDocument(VisioDocumentType document) {
+               
+               _document = document;
+               
+               if (!_document.isSetDocumentSettings())
+                       throw new POIXMLException("Document settings not found");
+               
+               DocumentSettingsType docSettings = _document.getDocumentSettings();
+               
+               if (docSettings.isSetDefaultFillStyle())
+                       _defaultFillStyle = docSettings.getDefaultFillStyle();
+               
+               if (docSettings.isSetDefaultGuideStyle())
+                       _defaultGuideStyle = docSettings.getDefaultGuideStyle();
+               
+               if (docSettings.isSetDefaultLineStyle())
+                       _defaultLineStyle = docSettings.getDefaultLineStyle();
+               
+               if (docSettings.isSetDefaultTextStyle())
+                       _defaultTextStyle = docSettings.getDefaultTextStyle();
+               
+               if (_document.isSetStyleSheets()) {
+                       
+                       for (StyleSheetType styleSheet: _document.getStyleSheets().getStyleSheetArray()) { 
+                               _styleSheets.put(styleSheet.getID(), new XDGFStyleSheet(styleSheet, this));
+                       }
+               }
+       }
+       
+
+       @Internal
+       public VisioDocumentType getXmlObject() {
+               return _document;
+       }
+       
+
+       public XDGFStyleSheet getStyleById(long id) {
+               return _styleSheets.get(id);
+       }
+
+
+       public XDGFStyleSheet getDefaultFillStyle() {
+               XDGFStyleSheet style = getStyleById(_defaultFillStyle);
+               if (style == null)
+                       throw new POIXMLException("No default fill style found!");
+               return style;
+       }
+       
+       public XDGFStyleSheet getDefaultGuideStyle() {
+               XDGFStyleSheet style = getStyleById(_defaultGuideStyle);
+               if (style == null)
+                       throw new POIXMLException("No default guide style found!");
+               return style;
+       }
+       
+       public XDGFStyleSheet getDefaultLineStyle() {
+               XDGFStyleSheet style = getStyleById(_defaultLineStyle);
+               if (style == null)
+                       throw new POIXMLException("No default line style found!");
+               return style;
+       }
+       
+       public XDGFStyleSheet getDefaultTextStyle() {
+               XDGFStyleSheet style = getStyleById(_defaultTextStyle);
+               if (style == null)
+                       throw new POIXMLException("No default text style found!");
+               return style;
+       }
+
+       
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFFactory.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFFactory.java
new file mode 100644 (file)
index 0000000..80acd09
--- /dev/null
@@ -0,0 +1,75 @@
+/* ====================================================================
+   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;
+
+import java.lang.reflect.Constructor;
+
+import org.apache.poi.POIXMLDocumentPart;
+import org.apache.poi.POIXMLException;
+import org.apache.poi.POIXMLFactory;
+import org.apache.poi.POIXMLRelation;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.PackageRelationship;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+
+public class XDGFFactory extends POIXMLFactory {
+
+    private static final POILogger logger = POILogFactory.getLogger(XDGFFactory.class);
+    
+       private XDGFDocument _document;
+       
+       public XDGFFactory(XDGFDocument document){
+               _document = document;
+    }
+       
+       @Override
+       public POIXMLDocumentPart createDocumentPart(POIXMLDocumentPart parent,
+                       PackageRelationship rel, PackagePart part) {
+               POIXMLRelation descriptor = XDGFRelation.getInstance(rel.getRelationshipType());
+        if(descriptor == null || descriptor.getRelationClass() == null){
+            logger.log(POILogger.DEBUG, "using default POIXMLDocumentPart for " + rel.getRelationshipType());
+            return new POIXMLDocumentPart(part, rel);
+        }
+
+        try {
+            Class<? extends POIXMLDocumentPart> cls = descriptor.getRelationClass();
+            try {
+                Constructor<? extends POIXMLDocumentPart> constructor = cls.getDeclaredConstructor(POIXMLDocumentPart.class, PackagePart.class, PackageRelationship.class, XDGFDocument.class);
+                return constructor.newInstance(parent, part, rel, _document);
+            } catch (NoSuchMethodException e) {
+                Constructor<? extends POIXMLDocumentPart> constructor = cls.getDeclaredConstructor(PackagePart.class, PackageRelationship.class, XDGFDocument.class);
+                return constructor.newInstance(part, rel, _document);
+            }
+        } catch (Exception e){
+            throw new POIXMLException(e);
+        }
+       }
+
+       @Override
+       public POIXMLDocumentPart newDocumentPart(POIXMLRelation descriptor) {
+        try {
+            Class<? extends POIXMLDocumentPart> cls = descriptor.getRelationClass();
+            Constructor<? extends POIXMLDocumentPart> constructor = cls.getDeclaredConstructor();
+            return constructor.newInstance();
+        } catch (Exception e){
+            throw new POIXMLException(e);
+        }
+       }
+
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFMaster.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFMaster.java
new file mode 100644 (file)
index 0000000..17736f8
--- /dev/null
@@ -0,0 +1,68 @@
+/* ====================================================================
+   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;
+
+import org.apache.poi.util.Internal;
+
+import com.microsoft.schemas.office.visio.x2012.main.MasterType;
+
+/**
+ * Provides the API to work with an underlying master
+ */
+public class XDGFMaster {
+
+       private MasterType _master;
+       private XDGFMasterContents _content;
+       XDGFSheet _pageSheet = null;
+       
+       public XDGFMaster(MasterType master, XDGFMasterContents content, XDGFDocument document) {
+               _master = master;
+               _content = content;
+               content.setMaster(this);
+               
+               if (master.isSetPageSheet())
+                       _pageSheet = new XDGFPageSheet(master.getPageSheet(), document);
+       }
+       
+       @Internal
+       MasterType getXmlObject() {
+               return _master;
+       }
+       
+       @Override
+       public String toString() {
+               return "<Master ID=\"" + getID() + "\" " + _content + ">";
+       }
+       
+       public long getID() {
+               return _master.getID();
+       }
+       
+       public String getName() {
+               return _master.getName();
+       }
+       
+       public XDGFMasterContents getContent() {
+               return _content;
+       }
+       
+       public XDGFSheet getPageSheet() {
+               return _pageSheet;
+       }
+       
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFMasterContents.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFMasterContents.java
new file mode 100644 (file)
index 0000000..ddeaa8c
--- /dev/null
@@ -0,0 +1,64 @@
+/* ====================================================================
+   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;
+
+import java.io.IOException;
+
+import org.apache.poi.POIXMLException;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.PackageRelationship;
+import org.apache.poi.xdgf.exceptions.XDGFException;
+import org.apache.xmlbeans.XmlException;
+
+import com.microsoft.schemas.office.visio.x2012.main.MasterContentsDocument;
+
+public class XDGFMasterContents extends XDGFBaseContents {
+
+       private XDGFMaster _master;
+       
+       public XDGFMasterContents(PackagePart part, PackageRelationship rel, XDGFDocument document) {
+               super(part, rel, document);
+       }
+       
+       @Override
+       protected void onDocumentRead() {
+
+               try {
+               
+                       try {
+                               _pageContents = MasterContentsDocument.Factory.parse(getPackagePart().getInputStream()).getMasterContents();
+                       } catch (XmlException | IOException e) {
+                               throw new POIXMLException(e);
+                       }
+                       
+                       super.onDocumentRead();
+                       
+               } catch (POIXMLException e) {
+                       throw XDGFException.wrap(this, e);
+               }
+       }
+
+       public XDGFMaster getMaster() {
+               return _master;
+       }
+
+       protected void setMaster(XDGFMaster master) {
+               _master = master;
+       }
+
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFMasters.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFMasters.java
new file mode 100644 (file)
index 0000000..ddfca13
--- /dev/null
@@ -0,0 +1,99 @@
+/* ====================================================================
+   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;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.poi.POIXMLDocumentPart;
+import org.apache.poi.POIXMLException;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.PackageRelationship;
+import org.apache.poi.util.Internal;
+import org.apache.poi.xdgf.exceptions.XDGFException;
+import org.apache.poi.xdgf.xml.XDGFXMLDocumentPart;
+import org.apache.xmlbeans.XmlException;
+
+import com.microsoft.schemas.office.visio.x2012.main.MasterType;
+import com.microsoft.schemas.office.visio.x2012.main.MastersDocument;
+import com.microsoft.schemas.office.visio.x2012.main.MastersType;
+
+public class XDGFMasters extends XDGFXMLDocumentPart {
+
+       MastersType _mastersObject;
+       
+       // key: id of master
+       Map<Long, XDGFMaster> _masters = new HashMap<>();
+       
+       public XDGFMasters(PackagePart part, PackageRelationship rel, XDGFDocument document) {
+               super(part, rel, document);
+       }
+       
+       @Internal
+       MastersType getXmlObject() {
+               return _mastersObject;
+       }
+       
+       @Override
+       protected void onDocumentRead() {
+               try {
+                       try {
+                               _mastersObject = MastersDocument.Factory.parse(getPackagePart().getInputStream()).getMasters();
+                       } catch (XmlException | IOException e) {
+                               throw new POIXMLException(e);
+                       }
+                       
+                       Map<String, MasterType> masterSettings = new HashMap<>();
+                       for (MasterType master: _mastersObject.getMasterArray()) {
+                               masterSettings.put(master.getRel().getId(), master);
+                       }
+                       
+                       // create the masters
+                       for (POIXMLDocumentPart part: getRelations()) {
+                               
+                               String relId = part.getPackageRelationship().getId();
+                               MasterType settings = masterSettings.get(relId);
+                               
+                               if (settings == null)
+                                       throw new POIXMLException("Master relationship for " + relId + " not found");
+                               
+                               if (!(part instanceof XDGFMasterContents))
+                                       throw new POIXMLException("Unexpected masters relationship for " + relId + ": " + part);
+                               
+                               XDGFMasterContents contents = (XDGFMasterContents)part;
+                               contents.onDocumentRead();
+                               
+                               XDGFMaster master = new XDGFMaster(settings, contents, _document);
+                               _masters.put(master.getID(), master);
+                       }
+               } catch (POIXMLException e) {
+                       throw XDGFException.wrap(this, e);
+               }
+       }
+       
+       public Collection<XDGFMaster> getMastersList() {
+               return Collections.unmodifiableCollection(_masters.values());
+       }
+       
+       public XDGFMaster getMasterById(long masterId) {
+               return _masters.get(masterId);
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFPage.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFPage.java
new file mode 100644 (file)
index 0000000..762c963
--- /dev/null
@@ -0,0 +1,113 @@
+/* ====================================================================
+   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;
+
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+
+import org.apache.poi.POIXMLException;
+import org.apache.poi.util.Internal;
+import org.apache.poi.xdgf.geom.Dimension2dDouble;
+
+import com.microsoft.schemas.office.visio.x2012.main.PageType;
+
+/**
+ * Provides the API to work with an underlying page
+ */
+public class XDGFPage {
+
+       PageType _page;
+       XDGFPageContents _content;
+       XDGFPages _pages;
+       XDGFSheet _pageSheet = null;
+       
+       public XDGFPage(PageType page, XDGFPageContents content, XDGFDocument document, XDGFPages pages) {
+               _page = page;
+               _content = content;
+               _pages = pages;
+               content.setPage(this);
+               
+               if (page.isSetPageSheet())
+                       _pageSheet = new XDGFPageSheet(page.getPageSheet(), document);
+       }
+       
+       @Internal
+       PageType getXmlObject() {
+               return _page;
+       }
+       
+       public long getID() {
+               return _page.getID();
+       }
+       
+       public String getName() {
+               return _page.getName();
+       }
+       
+       public XDGFPageContents getContent() {
+               return _content;
+       }
+       
+       public XDGFSheet getPageSheet() {
+               return _pageSheet;
+       }
+       
+       public long getPageNumber() {
+               return _pages.getPageList().indexOf(this) + 1;
+       }
+       
+       // height/width of page
+       public Dimension2dDouble getPageSize() {
+               XDGFCell w = _pageSheet.getCell("PageWidth");
+               XDGFCell h = _pageSheet.getCell("PageHeight");
+               
+               if (w == null || h == null)
+                       throw new POIXMLException("Cannot determine page size");
+               
+               return new Dimension2dDouble(Double.parseDouble(w.getValue()),
+                                                                        Double.parseDouble(h.getValue()));
+       }
+       
+       // origin of coordinate system
+       public Point2D.Double getPageOffset() {
+               XDGFCell xoffcell = _pageSheet.getCell("XRulerOrigin");
+               XDGFCell yoffcell = _pageSheet.getCell("YRulerOrigin");
+               
+               double xoffset = 0;
+               double yoffset = 0;
+               
+               if (xoffcell != null)
+                       xoffset = Double.parseDouble(xoffcell.getValue());
+               
+               if (xoffcell != null)
+                       yoffset = Double.parseDouble(yoffcell.getValue());
+               
+               return new Point2D.Double(xoffset, yoffset);
+       }
+       
+       // bounding box of page
+       public Rectangle2D getBoundingBox() {
+               Dimension2dDouble sz = getPageSize();
+               Point2D.Double offset = getPageOffset();
+               
+               return new Rectangle2D.Double(-offset.getX(),
+                                                                         -offset.getY(),
+                                                                         sz.getWidth(),
+                                                                         sz.getHeight());
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFPageContents.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFPageContents.java
new file mode 100644 (file)
index 0000000..392ec48
--- /dev/null
@@ -0,0 +1,83 @@
+/* ====================================================================
+   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;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.poi.POIXMLDocumentPart;
+import org.apache.poi.POIXMLException;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.PackageRelationship;
+import org.apache.poi.xdgf.exceptions.XDGFException;
+import org.apache.xmlbeans.XmlException;
+
+import com.microsoft.schemas.office.visio.x2012.main.PageContentsDocument;
+
+public class XDGFPageContents extends XDGFBaseContents {
+
+       Map<Long, XDGFMaster> _masters = new HashMap<>();
+       XDGFPage _page;
+       
+       public XDGFPageContents(PackagePart part, PackageRelationship rel, XDGFDocument document) {
+               super(part, rel, document);
+       }
+
+       @Override
+       protected void onDocumentRead() {
+               try {
+                       try {
+                               _pageContents = PageContentsDocument.Factory.parse(getPackagePart().getInputStream()).getPageContents();
+                       } catch (XmlException | IOException e) {
+                               throw new POIXMLException(e);
+                       }
+                       
+                       for (POIXMLDocumentPart part: getRelations()) {
+                               if (!(part instanceof XDGFMasterContents))
+                                       continue;
+                                       //throw new POIXMLException("Unexpected page relation: " + part);
+                               
+                               XDGFMaster master = ((XDGFMasterContents)part).getMaster();
+                               _masters.put(master.getID(), master);
+                       }
+                       
+                       super.onDocumentRead();
+                       
+                       for (XDGFShape shape: _shapes.values()) {
+                               if (shape.isTopmost())
+                                       shape.setupMaster(this, null);
+                       }
+               
+               } catch (POIXMLException e) {
+                       throw XDGFException.wrap(this, e);
+               }
+       }
+
+       public XDGFPage getPage() {
+               return _page;
+       }
+       
+       protected void setPage(XDGFPage page) {
+               _page = page;
+       }
+       
+       public XDGFMaster getMasterById(long id) {
+               return _masters.get(id);
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFPageSheet.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFPageSheet.java
new file mode 100644 (file)
index 0000000..2bf8324
--- /dev/null
@@ -0,0 +1,36 @@
+/* ====================================================================
+   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;
+
+import com.microsoft.schemas.office.visio.x2012.main.PageSheetType;
+
+public class XDGFPageSheet extends XDGFSheet {
+
+       PageSheetType _pageSheet;
+       
+       public XDGFPageSheet(PageSheetType sheet, XDGFDocument document) {
+               super(sheet, document);
+               _pageSheet = sheet;
+       }
+
+       @Override
+       PageSheetType getXmlObject() {
+               return _pageSheet;
+       }
+
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFPages.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFPages.java
new file mode 100644 (file)
index 0000000..c8447f5
--- /dev/null
@@ -0,0 +1,95 @@
+/* ====================================================================
+   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;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.poi.POIXMLDocumentPart;
+import org.apache.poi.POIXMLException;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.PackageRelationship;
+import org.apache.poi.util.Internal;
+import org.apache.poi.xdgf.exceptions.XDGFException;
+import org.apache.poi.xdgf.xml.XDGFXMLDocumentPart;
+import org.apache.xmlbeans.XmlException;
+
+import com.microsoft.schemas.office.visio.x2012.main.PageType;
+import com.microsoft.schemas.office.visio.x2012.main.PagesDocument;
+import com.microsoft.schemas.office.visio.x2012.main.PagesType;
+
+
+/**
+ * Contains a list of Page objects (not page content!)
+ */
+public class XDGFPages extends XDGFXMLDocumentPart {
+
+       PagesType _pagesObject;
+       
+       // ordered by page number
+       List<XDGFPage> _pages = new ArrayList<>();
+       
+       public XDGFPages(PackagePart part, PackageRelationship rel, XDGFDocument document) {
+               super(part, rel, document);
+       }
+       
+       @Internal
+       PagesType getXmlObject() {
+               return _pagesObject;
+       }
+       
+       @Override
+       protected void onDocumentRead() {
+               try {
+                       try {
+                               _pagesObject = PagesDocument.Factory.parse(getPackagePart().getInputStream()).getPages();
+                       } catch (XmlException | IOException e) {
+                               throw new POIXMLException(e);
+                       }
+                       
+                       // this iteration is ordered by page number
+                       for (PageType pageSettings: _pagesObject.getPageArray()) {
+                               
+                               String relId = pageSettings.getRel().getId();
+                               
+                               POIXMLDocumentPart pageContentsPart = getRelationById(relId);
+                               if (pageContentsPart == null)
+                                       throw new POIXMLException("PageSettings relationship for " + relId + " not found");
+                               
+                               if (!(pageContentsPart instanceof XDGFPageContents))
+                                       throw new POIXMLException("Unexpected pages relationship for " + relId + ": " + pageContentsPart);
+                               
+                               XDGFPageContents contents = (XDGFPageContents)pageContentsPart;
+                               XDGFPage page = new XDGFPage(pageSettings, contents, _document, this);
+                               
+                               contents.onDocumentRead();
+                               
+                               _pages.add(page);
+                       }
+                       
+               } catch (POIXMLException e) {
+                       throw XDGFException.wrap(this, e);
+               }
+       }
+       
+       // ordered by page number
+       public List<XDGFPage> getPageList() {
+               return Collections.unmodifiableList(_pages);
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFRelation.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFRelation.java
new file mode 100644 (file)
index 0000000..1090377
--- /dev/null
@@ -0,0 +1,99 @@
+/* ====================================================================
+   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;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.poi.POIXMLRelation;
+import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;
+import org.apache.poi.xdgf.xml.XDGFXMLDocumentPart;
+
+public class XDGFRelation extends POIXMLRelation {
+       
+       /**
+     * A map to lookup POIXMLRelation by its relation type
+     */
+    protected static final Map<String, XDGFRelation> _table = new HashMap<String, XDGFRelation>();
+
+    public static final XDGFRelation DOCUMENT = new XDGFRelation(
+            "application/vnd.ms-visio.drawing.main+xml",
+            "http://schemas.microsoft.com/visio/2010/relationships/document",
+            "/visio/document.xml",
+            null
+    );
+    
+    public static final XDGFRelation MASTERS = new XDGFRelation(
+            "application/vnd.ms-visio.masters+xml",
+            "http://schemas.microsoft.com/visio/2010/relationships/masters",
+            "/visio/masters/masters.xml",
+            XDGFMasters.class
+    );
+    
+    public static final XDGFRelation MASTER = new XDGFRelation(
+            "application/vnd.ms-visio.master+xml",
+            "http://schemas.microsoft.com/visio/2010/relationships/master",
+            "/visio/masters/master#.xml",
+            XDGFMasterContents.class
+    );
+    
+    public static final XDGFRelation IMAGES = new XDGFRelation(
+            null,
+            PackageRelationshipTypes.IMAGE_PART,
+            null,
+            null // XSSFPictureData.class
+        );
+    
+    public static final XDGFRelation PAGES = new XDGFRelation(
+            "application/vnd.ms-visio.pages+xml",
+            "http://schemas.microsoft.com/visio/2010/relationships/pages",
+            "/visio/pages/pages.xml",
+            XDGFPages.class
+    );
+    
+    public static final XDGFRelation PAGE = new XDGFRelation(
+            "application/vnd.ms-visio.page+xml",
+            "http://schemas.microsoft.com/visio/2010/relationships/page",
+            "/visio/pages/page#.xml",
+            XDGFPageContents.class
+    );
+    
+    public static final XDGFRelation WINDOW = new XDGFRelation(
+            "application/vnd.ms-visio.windows+xml",
+            "http://schemas.microsoft.com/visio/2010/relationships/windows",
+            "/visio/windows.xml",
+            null
+    );
+    
+    private XDGFRelation(String type, String rel, String defaultName, Class<? extends XDGFXMLDocumentPart> cls) {
+        super(type, rel, defaultName, cls);
+
+        if (cls != null && !_table.containsKey(rel)) _table.put(rel, this);
+    }
+
+    /**
+     * Get POIXMLRelation by relation type
+     *
+     * @param rel relation type, for example,
+     *            <code>http://schemas.openxmlformats.org/officeDocument/2006/relationships/image</code>
+     * @return registered POIXMLRelation or null if not found
+     */
+    public static XDGFRelation getInstance(String rel) {
+        return _table.get(rel);
+    }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFShape.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFShape.java
new file mode 100644 (file)
index 0000000..493f049
--- /dev/null
@@ -0,0 +1,943 @@
+/* ====================================================================
+   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;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Stroke;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Path2D;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map.Entry;
+
+import org.apache.poi.POIXMLException;
+import org.apache.poi.util.Internal;
+import org.apache.poi.xdgf.exceptions.XDGFException;
+import org.apache.poi.xdgf.usermodel.section.CombinedIterable;
+import org.apache.poi.xdgf.usermodel.section.GeometrySection;
+import org.apache.poi.xdgf.usermodel.section.XDGFSection;
+import org.apache.poi.xdgf.usermodel.shape.ShapeVisitor;
+import org.apache.poi.xdgf.usermodel.shape.exceptions.StopVisitingThisBranch;
+
+import com.microsoft.schemas.office.visio.x2012.main.ShapeSheetType;
+import com.microsoft.schemas.office.visio.x2012.main.TextType;
+
+/**
+ * A shape is a collection of Geometry Visualization, Format, Text, Images,
+ * and Shape Data in a Drawing Page.
+ */
+public class XDGFShape extends XDGFSheet {
+
+       XDGFBaseContents _parentPage;
+       XDGFShape _parent;                      // only non-null if a subshape
+       
+       XDGFMaster _master = null;
+       XDGFShape _masterShape = null;
+       
+       XDGFText _text = null;
+       
+       // subshapes if they exist
+       List<XDGFShape> _shapes = null;
+       
+       // properties specific to shapes
+       
+       // center of rotation relative to origin of parent
+       Double _pinX = null;
+       Double _pinY = null;
+       
+       Double _width = null;
+       Double _height = null;
+       
+       // center of rotation relative to self
+       Double _locPinX = null;
+       Double _locPinY = null;
+       
+       // start x coordinate, relative to parent
+       // -> one dimensional shapes only
+       Double _beginX = null;
+       Double _beginY = null;
+       
+       // end x coordinate, relative to parent
+       // -> one dimensional shapes only
+       Double _endX = null;
+       Double _endY = null;
+       
+       Double _angle = null;
+       Double _rotationXAngle = null;
+       Double _rotationYAngle = null;
+       Double _rotationZAngle = null;
+       
+       // end x coordinate, relative to parent
+       Boolean _flipX = null;
+       Boolean _flipY = null;
+       
+       // center of text relative to this shape
+       Double _txtPinX = null;
+       Double _txtPinY = null;
+       
+       // center of text relative to text block
+       Double _txtLocPinX = null;
+       Double _txtLocPinY = null;
+       
+       Double _txtAngle = null;
+       
+       Double _txtWidth = null;
+       Double _txtHeight = null;
+       
+       public XDGFShape(ShapeSheetType shapeSheet, XDGFBaseContents parentPage, XDGFDocument document) {
+               this(null, shapeSheet, parentPage, document);
+       }
+       
+       public XDGFShape(XDGFShape parent, ShapeSheetType shapeSheet, XDGFBaseContents parentPage, XDGFDocument document) {
+               
+               super(shapeSheet, document);
+               
+               _parent = parent;
+               _parentPage = parentPage;
+               
+               TextType text = shapeSheet.getText();
+               if (text != null)
+                       _text = new XDGFText(text, this);
+               
+               if (shapeSheet.isSetShapes()) {
+                       _shapes = new ArrayList<XDGFShape>();
+                       for (ShapeSheetType shape: shapeSheet.getShapes().getShapeArray())
+                               _shapes.add(new XDGFShape(this, shape, parentPage, document));
+               }
+               
+               readProperties();
+       }
+
+       @Override
+       public String toString() {
+               if (_parentPage instanceof XDGFMasterContents)
+                       return _parentPage + ": <Shape ID=\"" + getID() + "\">";
+               else
+                       return "<Shape ID=\"" + getID() + "\">";
+       }
+       
+       protected void readProperties() {
+               
+               _pinX = XDGFCell.maybeGetDouble(_cells, "PinX");
+               _pinY = XDGFCell.maybeGetDouble(_cells, "PinY");
+               _width = XDGFCell.maybeGetDouble(_cells, "Width");
+               _height = XDGFCell.maybeGetDouble(_cells, "Height");
+               _locPinX = XDGFCell.maybeGetDouble(_cells, "LocPinX");
+               _locPinY = XDGFCell.maybeGetDouble(_cells, "LocPinY");
+               _beginX = XDGFCell.maybeGetDouble(_cells, "BeginX");
+               _beginY = XDGFCell.maybeGetDouble(_cells, "BeginY");
+               _endX = XDGFCell.maybeGetDouble(_cells, "EndX");
+               _endY = XDGFCell.maybeGetDouble(_cells, "EndY");
+               
+               _angle = XDGFCell.maybeGetDouble(_cells, "Angle");
+               _rotationXAngle = XDGFCell.maybeGetDouble(_cells, "RotationXAngle");
+               _rotationYAngle = XDGFCell.maybeGetDouble(_cells, "RotationYAngle");
+               _rotationZAngle = XDGFCell.maybeGetDouble(_cells, "RotationZAngle");
+               
+               _flipX = XDGFCell.maybeGetBoolean(_cells, "FlipX");
+               _flipY = XDGFCell.maybeGetBoolean(_cells, "FlipY");
+               
+               _txtPinX = XDGFCell.maybeGetDouble(_cells, "TxtPinX");
+               _txtPinY = XDGFCell.maybeGetDouble(_cells, "TxtPinY");
+               _txtLocPinX = XDGFCell.maybeGetDouble(_cells, "TxtLocPinX");
+               _txtLocPinY = XDGFCell.maybeGetDouble(_cells, "TxtLocPinY");
+               _txtWidth = XDGFCell.maybeGetDouble(_cells, "TxtWidth");
+               _txtHeight = XDGFCell.maybeGetDouble(_cells, "TxtHeight");
+               
+               _txtAngle = XDGFCell.maybeGetDouble(_cells, "TxtAngle");
+       }
+       
+       /**
+        * Setup top level shapes
+        * 
+        * Shapes that have a 'Master' attribute refer to a specific master in 
+        * the page, whereas shapes with a 'MasterShape' attribute refer to a 
+        * subshape of a Master.
+        * 
+        * 
+        */
+       protected void setupMaster(XDGFPageContents pageContents, XDGFMasterContents master) {
+               
+               ShapeSheetType obj = getXmlObject();
+               
+               if (obj.isSetMaster()) {
+                       _master = pageContents.getMasterById(obj.getMaster());
+                       if (_master == null)
+                               throw XDGFException.error("refers to non-existant master " + obj.getMaster(),
+                                                                                 this);
+                       
+                       /*
+                        * If a master has one top-level shape, a shape that inherits from
+                        * that master inherits the descendant elements of that master
+                        * shape. If a master has more than one master shape, a shape that
+                        * inherits from that master inherits those master shapes as
+                        * subshapes.
+                        */
+                       
+                       Collection<XDGFShape> masterShapes = _master.getContent().getTopLevelShapes();
+                       
+                       switch (masterShapes.size()) {
+                       case 0:
+                               throw XDGFException.error("Could not retrieve master shape from " + _master, this);
+                       case 1:
+                               _masterShape = masterShapes.iterator().next();
+                               break;
+                       default:
+                               break;
+                       }
+                       
+               } else if (obj.isSetMasterShape()) {
+                       _masterShape = master.getShapeById(obj.getMasterShape());
+                       if (_masterShape == null)
+                               throw XDGFException.error("refers to non-existant master shape " + obj.getMasterShape(),
+                                                                                 this);
+                       
+               }
+               
+               setupSectionMasters();
+               
+               if (_shapes != null) {
+                       for (XDGFShape shape: _shapes) {
+                               shape.setupMaster(pageContents,
+                                                                 _master == null ? master : _master.getContent());
+                       }
+               }
+       }
+       
+       protected void setupSectionMasters() {
+               
+               if (_masterShape == null)
+                       return;
+               
+               try {
+                       for (Entry<String, XDGFSection> section: _sections.entrySet()) {
+                               XDGFSection master = _masterShape.getSection(section.getKey());
+                               if (master != null)
+                                       section.getValue().setupMaster(master);
+                       }
+                       
+                       for (Entry<Long, GeometrySection> section: _geometry.entrySet()) {
+                               GeometrySection master = _masterShape.getGeometryByIdx(section.getKey());
+                               if (master != null)
+                                       section.getValue().setupMaster(master);
+                       }
+               } catch (POIXMLException e) {
+                       throw XDGFException.wrap(this.toString(), e);
+               }
+       }
+       
+       @Internal
+       public ShapeSheetType getXmlObject() {
+               return (ShapeSheetType)_sheet;
+       }
+       
+       public long getID() {
+               return getXmlObject().getID();
+       }
+       
+       public String getType() {
+               return getXmlObject().getType();
+       }
+       
+       public String getTextAsString() {
+               XDGFText text = getText();
+               if (text == null)
+                       return "";
+               
+               return text.getTextContent();
+       }
+       
+       public boolean hasText() {
+               return _text != null || (_masterShape != null && _masterShape._text != null);
+       }
+
+       @Override
+       public XDGFCell getCell(String cellName) {
+               XDGFCell _cell = super.getCell(cellName);
+               
+               // if not found, ask the master
+               if (_cell == null && _masterShape != null) {
+                       _cell = _masterShape.getCell(cellName);
+               }
+               
+               return _cell;
+       }
+
+       public GeometrySection getGeometryByIdx(long idx) {
+               return _geometry.get(idx);
+       }
+       
+       // only available if this is a shape group
+       // -> May be null
+       public List<XDGFShape> getShapes() {
+               return _shapes;
+       }
+
+       // unique to this shape on the page?
+       public String getName() {
+               String name = getXmlObject().getName();
+               if (name == null)
+                       return "";
+               return name;
+       }
+       
+       // unique to this shape on the page?
+       public String getShapeType() {
+               String type = getXmlObject().getType();
+               if (type == null)
+                       return "";
+               return type;
+       }
+       
+       // name of the symbol that this was derived from
+       public String getSymbolName() {
+               
+               if (_master == null)
+                       return "";
+               
+               String name = _master.getName();
+               if (name == null)
+                       return "";
+               
+               return name;
+       }
+       
+       public XDGFShape getMasterShape() {
+               return _masterShape;
+       }
+       
+       // returns the parent shape of this shape, if its in a subshape
+       public XDGFShape getParentShape() {
+               return _parent;
+       }
+       
+       public XDGFShape getTopmostParentShape() {
+               XDGFShape top = null;
+               if (_parent != null) {
+                       top = _parent.getTopmostParentShape();
+                       if (top == null)
+                               top = _parent;  
+               }
+                                       
+               return top;
+       }
+       
+       public boolean hasMaster() {
+               return _master != null;
+       }
+       
+       public boolean hasMasterShape() {
+               return _masterShape != null;
+       }
+       
+       public boolean hasParent() {
+               return _parent != null;
+       }
+       
+       public boolean hasShapes() {
+               return _shapes != null;
+       }
+       
+       public boolean isTopmost() {
+               return _parent == null;
+       }
+
+       public boolean isShape1D() {
+               return getBeginX() != null;
+       }
+       
+       public boolean isDeleted() {
+               return getXmlObject().isSetDel() ? getXmlObject().getDel() : false;
+       }
+       
+       public XDGFText getText() {
+               if (_text == null && _masterShape != null)
+                       return _masterShape.getText();
+               
+               return _text;
+       }
+
+       public Double getPinX() {
+               if (_pinX == null && _masterShape != null)
+                       return _masterShape.getPinX();
+               
+               if (_pinX == null)
+                       throw XDGFException.error("PinX not set!", this);
+               
+               return _pinX;
+       }
+       
+       public Double getPinY() {
+               if (_pinY == null && _masterShape != null)
+                       return _masterShape.getPinY();
+               
+               if (_pinY == null)
+                       throw XDGFException.error("PinY not specified!", this);
+                       
+               return _pinY;
+       }
+       
+       public Double getWidth() {
+               if (_width == null && _masterShape != null)
+                       return _masterShape.getWidth();
+               
+               if (_width == null)
+                       throw XDGFException.error("Width not specified!", this);
+               
+               return _width;
+       }
+       
+       public Double getHeight() {
+               if (_height == null && _masterShape != null)
+                       return _masterShape.getHeight();
+               
+               if (_height == null)
+                       throw XDGFException.error("Height not specified!", this);
+               
+               return _height;
+       }
+       
+       public Double getLocPinX() {
+               if (_locPinX == null && _masterShape != null)
+                       return _masterShape.getLocPinX();
+               
+               if (_locPinX == null)
+                       throw XDGFException.error("LocPinX not specified!", this);
+               
+               return _locPinX;
+       }
+       
+       public Double getLocPinY() {
+               if (_locPinY == null && _masterShape != null)
+                       return _masterShape.getLocPinY();
+               
+               if (_locPinY == null)
+                       throw XDGFException.error("LocPinY not specified!", this);
+               
+               return _locPinY;
+       }
+       
+       public Double getBeginX() {
+               if (_beginX == null && _masterShape != null)
+                       return _masterShape.getBeginX();
+               
+               return _beginX;
+       }
+       
+       public Double getBeginY() {
+               if (_beginY == null && _masterShape != null)
+                       return _masterShape.getBeginY();
+               
+               return _beginY;
+       }
+       
+       public Double getEndX() {
+               if (_endX == null && _masterShape != null)
+                       return _masterShape.getEndX();
+               
+               return _endX;
+       }
+       
+       public Double getEndY() {
+               if (_endY == null && _masterShape != null)
+                       return _masterShape.getEndY();
+               
+               return _endY;
+       }
+       
+       public Double getAngle() {
+               if (_angle == null && _masterShape != null)
+                       return _masterShape.getAngle();
+               
+               return _angle;
+       }
+       
+       public Boolean getFlipX() {
+               if (_flipX == null && _masterShape != null)
+                       return _masterShape.getFlipX();
+               
+               return _flipX;
+       }
+       
+       public Boolean getFlipY() {
+               if (_flipY == null && _masterShape != null)
+                       return _masterShape.getFlipY();
+               
+               return _flipY;
+       }
+       
+    public Double getTxtPinX() {
+       if (_txtPinX == null &&
+               _masterShape != null && _masterShape._txtPinX != null)
+            return _masterShape._txtPinX;
+        
+        if (_txtPinX == null)
+               return getWidth()*0.5;
+        
+        return _txtPinX;
+    }
+
+
+    public Double getTxtPinY() {
+       if (_txtLocPinY == null &&
+               _masterShape != null && _masterShape._txtLocPinY != null)
+            return _masterShape._txtLocPinY;
+        
+        if (_txtPinY == null)
+               return getHeight()*0.5;
+        
+        return _txtPinY;
+    }
+
+
+    public Double getTxtLocPinX() {
+        if (_txtLocPinX == null &&
+               _masterShape != null && _masterShape._txtLocPinX != null)
+            return _masterShape._txtLocPinX;
+        
+        if (_txtLocPinX == null)
+               return getTxtWidth()*0.5;
+        
+        return _txtLocPinX;
+    }
+
+
+    public Double getTxtLocPinY() {
+        if (_txtLocPinY == null &&
+               _masterShape != null && _masterShape._txtLocPinY != null)
+            return _masterShape._txtLocPinY;
+        
+        if (_txtLocPinY == null)
+               return getTxtHeight()*0.5;
+        
+        return _txtLocPinY;
+    }
+    
+    public Double getTxtAngle() {
+               if (_txtAngle == null && _masterShape != null)
+                       return _masterShape.getTxtAngle();
+               
+               return _txtAngle;
+       }
+    
+    public Double getTxtWidth() {
+       if (_txtWidth == null &&
+               _masterShape != null && _masterShape._txtWidth != null)
+            return _masterShape._txtWidth;
+        
+        if (_txtWidth == null)
+            return getWidth();
+        
+        return _txtWidth;
+    }
+
+
+    public Double getTxtHeight() {
+        if (_txtHeight == null &&
+               _masterShape != null && _masterShape._txtHeight != null)
+            return _masterShape._txtHeight;
+        
+        if (_txtHeight == null)
+            return getHeight();
+        
+        return _txtHeight;
+    }
+    
+    @Override
+       public Integer getLineCap() {
+               
+       Integer lineCap = super.getLineCap();
+               if (lineCap != null)
+                       return lineCap;
+               
+               // get from master
+               if (_masterShape != null) {
+                       return _masterShape.getLineCap();
+               }
+               
+               // get default
+               XDGFStyleSheet style = _document.getDefaultLineStyle();
+               if (style != null)
+                       return style.getLineCap();
+                                       
+               return null;
+       }
+    
+    @Override
+       public Color getLineColor() {
+               
+       Color lineColor = super.getLineColor();
+               if (lineColor != null)
+                       return lineColor;
+               
+               // get from master
+               if (_masterShape != null) {
+                       return _masterShape.getLineColor();
+               }
+               
+               // get default
+               XDGFStyleSheet style = _document.getDefaultLineStyle();
+               if (style != null)
+                       return style.getLineColor();
+                                       
+               return null;
+       }
+      
+    @Override
+       public Integer getLinePattern() {
+               
+       Integer linePattern = super.getLinePattern();
+               if (linePattern != null)
+                       return linePattern;
+               
+               // get from master
+               if (_masterShape != null) {
+                       return _masterShape.getLinePattern();
+               }
+               
+               // get default
+               XDGFStyleSheet style = _document.getDefaultLineStyle();
+               if (style != null)
+                       return style.getLinePattern();
+                                       
+               return null;
+       }
+    
+    @Override
+       public Double getLineWeight() {
+               
+       Double lineWeight = super.getLineWeight();
+               if (lineWeight != null)
+                       return lineWeight;
+               
+               // get from master
+               if (_masterShape != null) {
+                       return _masterShape.getLineWeight();
+               }
+               
+               // get default
+               XDGFStyleSheet style = _document.getDefaultLineStyle();
+               if (style != null)
+                       return style.getLineWeight();
+                                       
+               return null;
+       }
+    
+    public Color getFontColor() {
+               
+       Color fontColor = super.getFontColor();
+               if (fontColor != null)
+                       return fontColor;
+               
+               // get from master
+               if (_masterShape != null) {
+                       return _masterShape.getFontColor();
+               }
+               
+               // get default
+               XDGFStyleSheet style = _document.getDefaultTextStyle();
+               if (style != null)
+                       return style.getFontColor();
+                                       
+               return null;
+       }
+    
+       public Double getFontSize() {
+               
+               Double fontSize = super.getFontSize();
+               if (fontSize != null)
+                       return fontSize;
+               
+               // get from master
+               if (_masterShape != null) {
+                       return _masterShape.getFontSize();
+               }
+               
+               // get default
+               XDGFStyleSheet style = _document.getDefaultTextStyle();
+               if (style != null)
+                       return style.getFontSize();
+                                       
+               return null;
+       }
+       
+       public Stroke getStroke() {
+               
+               float lineWeight = getLineWeight().floatValue();
+               int cap;
+               int join = BasicStroke.JOIN_MITER;
+               float miterlimit = 10.0f;
+               
+               switch (getLineCap()) {
+               case 0:
+                       cap = BasicStroke.CAP_ROUND;
+                       break;
+               case 1:
+                       cap = BasicStroke.CAP_SQUARE;
+                       break;
+               case 2:
+                       cap = BasicStroke.CAP_BUTT; // TODO: what does extended mean?
+                       break;
+               default:
+                       throw new POIXMLException("Invalid line cap specified");
+               }
+               
+               float[] dash = null;
+               
+               // these line patterns are just approximations
+               switch (getLinePattern()) {
+               case 0: // transparent
+                       break;
+               case 1: // solid
+                       break;
+               case 2:
+                       dash = new float[]{5,3};
+                       break;
+               case 3:
+                       dash = new float[]{1,4};
+                       break;
+               case 4:
+                       dash = new float[]{6,3,1,3};
+                       break;
+               case 5:
+                       dash = new float[]{6,3,1,3,1,3};
+                       break;
+               case 6:
+                       dash = new float[]{1,3,6,3,6,3};
+                       break;
+               case 7:
+                       dash = new float[]{15,3,6,3};
+                       break;
+               case 8:
+                       dash = new float[]{6,3,6,3};
+                       break;
+               case 9:
+                       dash = new float[]{3,2};
+                       break;
+               case 10:
+                       dash = new float[]{1,2};
+                       break;
+               case 11:
+                       dash = new float[]{3,2,1,2};
+                       break;
+               case 12:
+                       dash = new float[]{3,2,1,2,1};
+                       break;
+               case 13:
+                       dash = new float[]{1,2,3,2,3,2};
+                       break;
+               case 14:
+                       dash = new float[]{3,2,7,2};
+                       break;
+               case 15:
+                       dash = new float[]{7,2,3,2,3,2};
+                       break;
+               case 16:
+                       dash = new float[]{12,6};
+                       break;
+               case 17:
+                       dash = new float[]{1,6};
+                       break;
+               case 18:
+                       dash = new float[]{1,6,12,6};
+                       break;
+               case 19:
+                       dash = new float[]{1,6,1,6,12,6};
+                       break;
+               case 20:
+                       dash = new float[]{1,6,12,6,12,6};
+                       break;
+               case 21:
+                       dash = new float[]{30,6,12,6};
+                       break;
+               case 22:
+                       dash = new float[]{30,6,12,6,12,6};
+                       break;
+               case 23:
+                       dash = new float[]{1};
+                       break;
+               case 254:
+                       throw new POIXMLException("Unsupported line pattern value");
+               default:
+                       throw new POIXMLException("Invalid line pattern value");
+               }
+               
+               // dashes are in units of line width
+               if (dash != null) {
+                       for (int i = 0; i < dash.length; i++) {
+                               dash[i] *= lineWeight;
+                       }
+               }
+               
+               return new BasicStroke(lineWeight, cap, join, miterlimit, dash, 0);
+       }
+       
+       
+       //
+       // Geometry
+       //
+       
+       public Iterable<GeometrySection> getGeometrySections() {
+               return new CombinedIterable<>(_geometry,
+                                                                         _masterShape != null ? _masterShape._geometry : null);
+       }
+       
+
+       // returns a rectangle in local coordinates
+       public Rectangle2D.Double getBounds() {
+               return new Rectangle2D.Double(0, 0, getWidth(),
+                                                                                       getHeight());
+       }
+       
+       // returns bounds as a path in local coordinates
+       // -> useful if you need to transform to global coordinates
+       // -> Don't use for 1d objects, fails for infinite line objects
+       public Path2D.Double getBoundsAsPath() {
+               
+               Double w = getWidth();
+               Double h = getHeight();
+               
+               Path2D.Double bounds = new Path2D.Double();
+               bounds.moveTo(0, 0);
+               bounds.lineTo(w, 0);
+               bounds.lineTo(w, h);
+               bounds.lineTo(0, h);
+               bounds.lineTo(0, 0);
+               
+               return bounds;
+       }
+       
+       // returns the path in local coordinates
+       public Path2D.Double getPath() {
+               for (GeometrySection geoSection: getGeometrySections()) {
+                       if (geoSection.getNoShow() == true)
+                               continue;
+                       
+                       return geoSection.getPath(this);
+               }
+               
+               return null;
+       }
+       
+       /*
+        * Returns true if the shape has a drawable geometry associated with it
+        */
+       public boolean hasGeometry() {
+               for (GeometrySection geoSection: getGeometrySections()) {
+                       if (geoSection.getNoShow() == false)
+                               return true;
+               }
+               return false;
+       }
+       
+       /**
+        * Returns a transform that can translate shape-local coordinates
+        * to the coordinates of its parent shape
+        */
+       protected AffineTransform getParentTransform() {
+               // TODO: There's probably a better way to do this
+               AffineTransform tr = new AffineTransform();
+               
+               Double locX = getLocPinX();
+               Double locY = getLocPinY();
+               Boolean flipX = getFlipX();
+               Boolean flipY = getFlipY();
+               Double angle = getAngle();
+               
+               tr.translate(-locX, -locY);
+                               
+               tr.translate(getPinX(), getPinY());
+
+               // rotate about the origin
+               if (angle != null && Math.abs(angle) > 0.001) {
+                       tr.rotate(angle, locX, locY);
+               }
+               
+               // flip if necessary
+               
+               if (flipX != null && flipX) {
+                       tr.scale(-1, 1);
+                       tr.translate(-getWidth(), 0);
+               }
+               
+               if (flipY != null && flipY) {
+                       tr.scale(1, -1);
+                       tr.translate(0, -getHeight());
+               }
+               
+               return tr;
+       }
+
+       
+
+    /**
+     * The visitor will first visit this shape, then it's children
+     * 
+     * This is useful because exceptions will be marked with the shapes as it
+     * propagates up the shape hierarchy.
+     */
+       public void visitShapes(ShapeVisitor visitor, AffineTransform tr, int level) {
+       
+               tr = (AffineTransform)tr.clone();
+               tr.concatenate(getParentTransform());
+               
+               try {
+                       if (visitor.accept(this))
+                               visitor.visit(this, tr, level);
+                       
+                       if (_shapes != null) {
+                               for (XDGFShape shape: _shapes) {
+                                       shape.visitShapes(visitor, tr, level + 1);
+                               }
+                       }
+               } catch (StopVisitingThisBranch e) {
+                       // intentionally empty
+               } catch (POIXMLException e) {
+                       throw XDGFException.wrap(this.toString(), e);
+               }
+       }
+       
+       /**
+     * The visitor will first visit this shape, then it's children. No transform 
+     * is calculated for this visit
+     * 
+     * This is useful because exceptions will be marked with the shapes as it
+     * propagates up the shape hierarchy.
+     */
+       public void visitShapes(ShapeVisitor visitor, int level) {
+       
+               try {
+                       if (visitor.accept(this))
+                               visitor.visit(this, null, level);
+                       
+                       if (_shapes != null) {
+                               for (XDGFShape shape: _shapes) {
+                                       shape.visitShapes(visitor, level + 1);
+                               }
+                       }
+               } catch (StopVisitingThisBranch e) {
+                       // intentionally empty
+               } catch (POIXMLException e) {
+                       throw XDGFException.wrap(this.toString(), e);
+               }
+       }
+       
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFSheet.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFSheet.java
new file mode 100644 (file)
index 0000000..f460319
--- /dev/null
@@ -0,0 +1,204 @@
+/* ====================================================================
+   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;
+
+import java.awt.Color;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.apache.poi.POIXMLException;
+import org.apache.poi.xdgf.exceptions.XDGFException;
+import org.apache.poi.xdgf.usermodel.section.CharacterSection;
+import org.apache.poi.xdgf.usermodel.section.GeometrySection;
+import org.apache.poi.xdgf.usermodel.section.XDGFSection;
+
+import com.microsoft.schemas.office.visio.x2012.main.CellType;
+import com.microsoft.schemas.office.visio.x2012.main.SectionType;
+import com.microsoft.schemas.office.visio.x2012.main.SheetType;
+
+/**
+ * A sheet is a collection of properties that specify information for a shape,
+ * master, drawing page, style, or web drawing.
+ */
+public abstract class XDGFSheet {
+
+       protected XDGFDocument _document;
+       protected SheetType _sheet;
+
+       // cells
+       protected Map<String, XDGFCell> _cells = new HashMap<>();
+       
+       // sections
+       protected Map<String, XDGFSection> _sections = new HashMap<>();
+       
+       // special: geometry sections (key: index, value: section)
+       protected SortedMap<Long, GeometrySection> _geometry = new TreeMap<>();
+       
+       // special: character section
+       protected CharacterSection _character = null;
+       
+       public XDGFSheet(SheetType sheet, XDGFDocument document) {
+               try {
+                       _sheet = sheet;
+                       _document = document;
+                       
+                       for (CellType cell: sheet.getCellArray()) {
+                               if (_cells.containsKey(cell.getN()))
+                                       throw new POIXMLException("Unexpected duplicate cell " + cell.getN()); // this shouldn't happen
+                               
+                               _cells.put(cell.getN(), new XDGFCell(cell));
+                       }
+                       
+                       // only geometry sections can have duplicate names
+                       // sections can be found in the master too, if there are no attributes here!
+                       
+                       // no idea if I have a master in this space. go figure.
+                       
+                       for (SectionType section: sheet.getSectionArray()) {
+                               String name = section.getN();
+                               if (name.equals("Geometry")) {
+                                       _geometry.put(section.getIX(), new GeometrySection(section, this));
+                               } else if (name.equals("Character")) {
+                                       _character = new CharacterSection(section, this);
+                               } else {
+                                       _sections.put(name, XDGFSection.load(section, this));
+                               }
+                       }
+               } catch (POIXMLException e) {
+                       throw XDGFException.wrap(this.toString(), e);
+               }
+       }
+       
+       abstract SheetType getXmlObject();
+       
+       public XDGFDocument getDocument() {
+               return _document;
+       }
+       
+       // A cell is really just a setting
+       public XDGFCell getCell(String cellName) {
+               return _cells.get(cellName);
+       }
+       
+       public XDGFSection getSection(String sectionName) {
+               return _sections.get(sectionName);
+       }
+       
+       public XDGFStyleSheet getLineStyle() {
+               if (!_sheet.isSetLineStyle())
+                       return null;
+               
+               return _document.getStyleById(_sheet.getLineStyle());
+       }
+       
+       public XDGFStyleSheet getFillStyle() {
+               if (!_sheet.isSetFillStyle())
+                       return null;
+               
+               return _document.getStyleById(_sheet.getFillStyle());
+       }
+       
+       public XDGFStyleSheet getTextStyle() {
+               if (!_sheet.isSetTextStyle())
+                       return null;
+               
+               return _document.getStyleById(_sheet.getTextStyle());
+       }
+       
+       public Color getFontColor() {
+               Color fontColor = null;
+               
+               if (_character != null) {
+                       fontColor = _character.getFontColor();
+                       if (fontColor != null)
+                               return fontColor;
+               }
+               
+               XDGFStyleSheet style = getTextStyle();
+               if (style != null)
+                       return style.getFontColor();
+               
+               return null;
+       }
+       
+       public Double getFontSize() {
+               Double fontSize = null;
+               
+               if (_character != null) {
+                       fontSize = _character.getFontSize();
+                       if (fontSize != null)
+                               return fontSize;
+               }
+               
+               XDGFStyleSheet style = getTextStyle();
+               if (style != null)
+                       return style.getFontSize();
+               
+               return null;
+       }
+       
+       public Integer getLineCap() {
+               Integer lineCap = XDGFCell.maybeGetInteger(_cells, "LineCap");
+               if (lineCap != null)
+                       return lineCap;
+               
+               XDGFStyleSheet style = getLineStyle();
+               if (style != null)
+                       return style.getLineCap();
+               
+               return null;
+       }
+       
+       public Color getLineColor() {
+               String lineColor = XDGFCell.maybeGetString(_cells, "LineColor");
+               if (lineColor != null)
+                       return Color.decode(lineColor);
+               
+               XDGFStyleSheet style = getLineStyle();
+               if (style != null)
+                       return style.getLineColor();
+               
+               return null;
+       }
+
+       public Integer getLinePattern() {
+               Integer linePattern = XDGFCell.maybeGetInteger(_cells, "LinePattern");
+               if (linePattern != null)
+                       return linePattern;
+               
+               XDGFStyleSheet style = getLineStyle();
+               if (style != null)
+                       return style.getLinePattern();
+               
+               return null;
+       }
+       
+       public Double getLineWeight() {
+               Double lineWeight = XDGFCell.maybeGetDouble(_cells, "LineWeight");
+               if (lineWeight != null)
+                       return lineWeight;
+               
+               XDGFStyleSheet style = getLineStyle();
+               if (style != null)
+                       return style.getLineWeight();
+               
+               return null;
+       }       
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFStyleSheet.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFStyleSheet.java
new file mode 100644 (file)
index 0000000..39df4a9
--- /dev/null
@@ -0,0 +1,36 @@
+/* ====================================================================
+   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;
+
+import org.apache.poi.util.Internal;
+
+import com.microsoft.schemas.office.visio.x2012.main.StyleSheetType;
+
+public class XDGFStyleSheet extends XDGFSheet {
+       
+       public XDGFStyleSheet(StyleSheetType styleSheet, XDGFDocument document) {
+               super(styleSheet, document);
+       }
+       
+       @Internal
+       public StyleSheetType getXmlObject() {
+               return (StyleSheetType)_sheet;
+       }
+       
+       
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFText.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFText.java
new file mode 100644 (file)
index 0000000..a5c89fc
--- /dev/null
@@ -0,0 +1,150 @@
+/* ====================================================================
+   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;
+
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextLayout;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+
+import org.apache.poi.util.Internal;
+
+import com.microsoft.schemas.office.visio.x2012.main.TextType;
+import com.microsoft.schemas.office.visio.x2012.main.impl.TextTypeImpl;
+
+public class XDGFText {
+
+       TextType _text;
+       XDGFShape _parent;
+       
+       public XDGFText(TextType text, XDGFShape parent) {
+               _text = text;
+               _parent = parent;
+       }
+       
+       @Internal
+       TextType getXmlObject() {
+               return _text;
+       }
+       
+       public String getTextContent() {
+               // casting here is wrong, but there's no other way of getting the value,
+               // as it doesn't seem to be exposed by complex types (even though this
+               // is a mixed type)
+               return ((TextTypeImpl)_text).getStringValue();
+       }
+       
+       // these are in the shape coordinate system
+       // -> See https://msdn.microsoft.com/en-us/library/hh644132(v=office.12).aspx
+       public Rectangle2D.Double getTextBounds() {
+               
+               double txtPinX = _parent.getTxtPinX();
+               double txtPinY = _parent.getTxtPinY();
+               
+               double txtLocPinX = _parent.getTxtLocPinX();
+               double txtLocPinY = _parent.getTxtLocPinY();
+               
+               double txtWidth = _parent.getTxtWidth();
+               double txtHeight = _parent.getTxtHeight();
+               
+               double x = txtPinX - txtLocPinX;
+               double y = txtPinY - txtLocPinY;
+               
+               return new Rectangle2D.Double(x, y, txtWidth, txtHeight);
+       }
+       
+       // returns bounds as a path in local coordinates
+       // -> useful if you need to transform to global coordinates
+       public Path2D.Double getBoundsAsPath() {
+               
+               Rectangle2D.Double rect = getTextBounds();
+               Double w = rect.getWidth();
+               Double h = rect.getHeight();
+               
+               Path2D.Double bounds = new Path2D.Double();
+               bounds.moveTo(0, 0);
+               bounds.lineTo(w, 0);
+               bounds.lineTo(w, h);
+               bounds.lineTo(0, h);
+               bounds.lineTo(0, 0);
+               
+               return bounds;
+       }
+       
+       // center of text in local coordinates
+       public Point2D.Double getTextCenter() {
+               return new Point2D.Double(_parent.getTxtLocPinX(), _parent.getTxtLocPinY());
+       }
+       
+       // assumes graphics is set properly to draw in the right style
+       public void draw(Graphics2D graphics) {
+
+               String textContent = getTextContent();
+               if (textContent.length() == 0)
+                       return;
+               
+               Rectangle2D.Double bounds = getTextBounds();
+               
+               String[] lines = textContent.trim().split("\n");
+               FontRenderContext frc = graphics.getFontRenderContext();
+               Font font = graphics.getFont();
+               
+               AffineTransform oldTr = graphics.getTransform();
+               
+               // visio is in flipped coordinates, so translate the text to be in the right place
+               Boolean flipX = _parent.getFlipX();
+               Boolean flipY = _parent.getFlipY();
+               
+               if (flipY == null || !_parent.getFlipY()) {
+                       graphics.translate(bounds.x, bounds.y);
+                       graphics.scale(1, -1);
+                       graphics.translate(0, -bounds.height + graphics.getFontMetrics().getMaxCharBounds(graphics).getHeight());
+               }
+               
+               if (flipX != null && _parent.getFlipX()) {
+                       graphics.scale(-1, 1);
+                       graphics.translate(-bounds.width, 0);
+               }
+               
+               Double txtAngle = _parent.getTxtAngle();
+               if (txtAngle != null && Math.abs(txtAngle) > 0.01)
+                       graphics.rotate(txtAngle);
+                       
+               float nextY = 0;
+               for (String line: lines) {
+                       
+                       if (line.length() == 0)
+                               continue;
+                       
+                       TextLayout layout = new TextLayout(line, font, frc);
+                       
+                       if (layout.isLeftToRight())
+                               layout.draw(graphics, 0, nextY);
+                       else
+                               layout.draw(graphics, (float)(bounds.width - layout.getAdvance()), nextY);
+                       
+                       nextY += layout.getAscent() + layout.getDescent() + layout.getLeading();
+               }
+               
+               graphics.setTransform(oldTr);
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/XmlVisioDocument.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/XmlVisioDocument.java
new file mode 100644 (file)
index 0000000..83adc18
--- /dev/null
@@ -0,0 +1,113 @@
+/* ====================================================================
+   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;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.poi.POIXMLDocument;
+import org.apache.poi.POIXMLDocumentPart;
+import org.apache.poi.POIXMLException;
+import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.util.PackageHelper;
+import org.apache.xmlbeans.XmlException;
+
+import com.microsoft.schemas.office.visio.x2012.main.VisioDocumentDocument1;
+import com.microsoft.schemas.office.visio.x2012.main.VisioDocumentType;
+
+public class XmlVisioDocument extends POIXMLDocument {
+
+       public static String CORE_DOCUMENT = "http://schemas.microsoft.com/visio/2010/relationships/document";
+       
+       XDGFPages _pages;
+       XDGFMasters _masters;
+       XDGFDocument _document;
+       
+       
+       public XmlVisioDocument(OPCPackage pkg) throws IOException {
+               super(pkg, CORE_DOCUMENT);
+               
+               VisioDocumentType document;
+               
+               try {
+                       document = VisioDocumentDocument1.Factory.parse(getPackagePart().getInputStream()).getVisioDocument();  
+               } catch (XmlException | IOException e) {
+                       throw new POIXMLException(e);
+               }
+               
+               _document = new XDGFDocument(document);
+               
+               //build a tree of POIXMLDocumentParts, this document being the root
+        load(new XDGFFactory(_document));
+       }
+       
+       public XmlVisioDocument(InputStream is) throws IOException {
+               this(PackageHelper.open(is));
+       }
+       
+       
+       @Override
+    protected void onDocumentRead() throws IOException {
+               
+               // by the time this gets called, all other document parts should
+               // have been loaded, so it's safe to build the document structure
+               
+               // note that in other onDocumentRead(), relations/etc may not have
+               // loaded yet, so it's not quite safe
+               
+               for (POIXMLDocumentPart part: getRelations()) {
+                       
+                       // organize the document pieces
+                       if (part instanceof XDGFPages)
+                               _pages = (XDGFPages)part;
+                       
+                       else if (part instanceof XDGFMasters)
+                               _masters = (XDGFMasters)part;
+               }
+               
+               if (_masters != null)
+                       _masters.onDocumentRead();
+               
+               _pages.onDocumentRead();
+       }
+
+       
+
+       @Override
+       public List<PackagePart> getAllEmbedds() throws OpenXML4JException {
+               throw new UnsupportedOperationException("Not implemented");
+       }
+       
+       //
+       // Useful public API goes here
+       //
+
+       public Collection<XDGFPage> getPages() {
+               return _pages.getPageList();
+       }
+       
+
+       public XDGFStyleSheet getStyleById(long id) {
+               return _document.getStyleById(id);
+       }
+       
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/CharacterSection.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/CharacterSection.java
new file mode 100644 (file)
index 0000000..b5407cb
--- /dev/null
@@ -0,0 +1,71 @@
+/* ====================================================================
+   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;
+
+import java.awt.Color;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.poi.xdgf.usermodel.XDGFCell;
+import org.apache.poi.xdgf.usermodel.XDGFSheet;
+
+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;
+
+public class CharacterSection extends XDGFSection {
+       
+       Double _fontSize = null;
+       Color _fontColor = null;
+       
+       Map<String, XDGFCell> _characterCells = new HashMap<>();
+       
+       public CharacterSection(SectionType section, XDGFSheet containingSheet) {
+               super(section, containingSheet);
+               
+               // there aren't cells for this, just a single row
+               RowType row = section.getRowArray(0);
+               
+               for (CellType cell: row.getCellArray()) {
+                       _characterCells.put(cell.getN(), new XDGFCell(cell));
+               }
+               
+               if (row != null) {
+                       _fontSize = XDGFCell.maybeGetDouble(_characterCells, "Size");
+                       
+                       String tmpColor = XDGFCell.maybeGetString(_characterCells, "Color");
+                       if (tmpColor != null)
+                               _fontColor = Color.decode(tmpColor);
+               }
+       }
+       
+       public Double getFontSize() {
+               return _fontSize;
+       }
+       
+       public Color getFontColor() {
+               return _fontColor;
+       }
+
+       @Override
+       public void setupMaster(XDGFSection section) {
+               
+       }
+       
+}
+
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/CombinedIterable.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/CombinedIterable.java
new file mode 100644 (file)
index 0000000..b1fb4c6
--- /dev/null
@@ -0,0 +1,136 @@
+/* ====================================================================
+   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;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.SortedMap;
+import java.util.Map.Entry;
+
+// iterates over the base and master
+public class CombinedIterable<T> implements Iterable<T> {
+
+       final SortedMap<Long, T> _baseItems;
+       final SortedMap<Long, T> _masterItems;
+       
+       public CombinedIterable(SortedMap<Long, T> baseItems, 
+                                                       SortedMap<Long, T> masterItems) {
+               _baseItems = baseItems;
+               _masterItems = masterItems;
+       }
+       
+       @Override
+       public Iterator<T> iterator() {
+               
+               final Iterator<Entry<Long, T>> vmasterI;
+               
+               if (_masterItems != null)
+                       vmasterI = _masterItems.entrySet().iterator();
+               else
+                       vmasterI = Collections.emptyIterator();
+               
+               return new Iterator<T>() {
+
+                       Long lastI = Long.MIN_VALUE;
+                       
+                       Entry<Long, T> currentBase = null;
+                       Entry<Long, T> currentMaster = null;
+                       
+                       // grab the iterator for both
+                       Iterator<Entry<Long, T>> baseI = _baseItems.entrySet().iterator();
+                       Iterator<Entry<Long, T>> masterI = vmasterI;
+                       
+                       @Override
+                       public boolean hasNext() {
+                               return currentBase != null || currentMaster != null || baseI.hasNext() || masterI.hasNext();
+                       }
+
+                       @Override
+                       public T next() {
+                               
+                               // TODO: This seems far more complex than it needs to be
+                               
+                               long baseIdx = Long.MAX_VALUE;
+                               long masterIdx = Long.MAX_VALUE;
+                               
+                               if (currentBase == null) {
+                                       while (baseI.hasNext()) {
+                                               currentBase = baseI.next();
+                                               if (currentBase.getKey() > lastI) {
+                                                       baseIdx = currentBase.getKey();
+                                                       break;
+                                               }
+                                       }
+                               } else {
+                                       baseIdx = currentBase.getKey();
+                               }
+                               
+                               if (currentMaster == null) {
+                                       while (masterI.hasNext()) {
+                                               currentMaster = masterI.next();
+                                               if (currentMaster.getKey() > lastI) {
+                                                       masterIdx = currentMaster.getKey();
+                                                       break;
+                                               }
+                                       }
+                               } else {
+                                       masterIdx = currentMaster.getKey();
+                               }
+                               
+                               T val;
+                               
+                               if (currentBase != null) {
+                                       
+                                       if (baseIdx <= masterIdx) {
+                                               lastI = baseIdx;
+                                               val = currentBase.getValue();
+                                               
+                                               // discard master if same as base
+                                               if (masterIdx == baseIdx) {
+                                                       currentMaster = null;
+                                               }
+                                               
+                                               currentBase = null;
+                                               
+                                       } else {
+                                               lastI = masterIdx;
+                                               val = currentMaster.getValue();
+                                               currentMaster = null;
+                                       }
+                                       
+                               } else if (currentMaster != null) {
+                                       lastI = currentMaster.getKey();
+                                       val = currentMaster.getValue();
+                                       
+                                       currentMaster = null;
+                               } else {
+                                       throw new NoSuchElementException();
+                               }
+                               
+                               return val;
+                       }
+
+                       @Override
+                       public void remove() {
+                               throw new UnsupportedOperationException();
+                       }
+               };
+       }
+
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/GenericSection.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/GenericSection.java
new file mode 100644 (file)
index 0000000..0a35fc0
--- /dev/null
@@ -0,0 +1,33 @@
+/* ====================================================================
+   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;
+
+import org.apache.poi.xdgf.usermodel.XDGFSheet;
+
+import com.microsoft.schemas.office.visio.x2012.main.SectionType;
+
+public class GenericSection extends XDGFSection {
+
+       public GenericSection(SectionType section, XDGFSheet containingSheet) {
+               super(section, containingSheet);
+       }
+
+       @Override
+       public void setupMaster(XDGFSection section) {
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/GeometrySection.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/GeometrySection.java
new file mode 100644 (file)
index 0000000..079b06e
--- /dev/null
@@ -0,0 +1,155 @@
+/* ====================================================================
+   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;
+
+import java.awt.geom.Path2D;
+import java.util.Iterator;
+import java.util.Map.Entry;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.apache.poi.POIXMLException;
+import org.apache.poi.xdgf.geom.SplineCollector;
+import org.apache.poi.xdgf.usermodel.XDGFCell;
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+import org.apache.poi.xdgf.usermodel.XDGFSheet;
+import org.apache.poi.xdgf.usermodel.section.geometry.Ellipse;
+import org.apache.poi.xdgf.usermodel.section.geometry.GeometryRowFactory;
+import org.apache.poi.xdgf.usermodel.section.geometry.InfiniteLine;
+import org.apache.poi.xdgf.usermodel.section.geometry.GeometryRow;
+import org.apache.poi.xdgf.usermodel.section.geometry.SplineKnot;
+import org.apache.poi.xdgf.usermodel.section.geometry.SplineStart;
+
+import com.microsoft.schemas.office.visio.x2012.main.RowType;
+import com.microsoft.schemas.office.visio.x2012.main.SectionType;
+
+public class GeometrySection extends XDGFSection {
+
+       GeometrySection _master = null;
+       
+       // rows
+       SortedMap<Long, GeometryRow> _rows = new TreeMap<>();
+       
+       public GeometrySection(SectionType section, XDGFSheet containingSheet) {
+               super(section, containingSheet);
+               
+               for (RowType row: section.getRowArray()) {
+                       if (_rows.containsKey(row.getIX()))
+                               throw new POIXMLException("Index element '" + row.getIX() + "' already exists");
+                       
+                       _rows.put(row.getIX(), GeometryRowFactory.load(row));
+               }
+       }
+       
+       @Override
+       public void setupMaster(XDGFSection master) {
+               
+               _master = (GeometrySection)master;
+               
+               for (Entry<Long, GeometryRow> entry : _rows.entrySet()) {
+                       GeometryRow masterRow = _master._rows.get(entry.getKey());
+                       if (masterRow != null) {
+                               try {
+                                       entry.getValue().setupMaster(masterRow);
+                               } catch (ClassCastException e) {
+                                       // this can happen when a dynamic connector overrides its master's geometry
+                                       // .. probably can happen elsewhere too, I imagine.
+                                       //throw XDGFException.error("Mismatched geometry section '" + entry.getKey() + "' in master", this, e);
+                               }
+                       } 
+               }
+       }
+       
+       // returns True if this row shouldn't be displayed
+       public Boolean getNoShow() {
+               Boolean noShow = XDGFCell.maybeGetBoolean(_cells, "NoShow");
+               if (noShow == null) {
+                       if (_master != null)
+                               return _master.getNoShow();
+                       
+                       return false;
+               }
+               
+               return noShow;
+       }
+       
+       public Iterable<GeometryRow> getCombinedRows() {
+               return new CombinedIterable<>(_rows,
+                                                                         _master == null ? null : _master._rows);
+       }
+
+       public Path2D.Double getPath(XDGFShape parent) {
+               
+               Iterator<GeometryRow> rows = getCombinedRows().iterator();
+               
+               // special cases
+               GeometryRow first = rows.next();
+               
+               if (first instanceof Ellipse) {
+                       return ((Ellipse)first).getPath();
+               } else if (first instanceof InfiniteLine) {
+                       return ((InfiniteLine)first).getPath();
+               } else if (first instanceof SplineStart) {
+                       throw new POIXMLException("SplineStart must be preceded by another type");
+               } else {
+               
+                       // everything else is a path
+                       Path2D.Double path = new Path2D.Double();
+                       
+                       // dealing with splines makes this more complex
+                       SplineCollector renderer = null;
+                       GeometryRow row;
+                       
+                       while (true) {
+                               
+                               if (first != null) {
+                                       row = first;
+                                       first = null;
+                               } else {
+                                       if (!rows.hasNext())
+                                               break;
+                                       row = rows.next();
+                               }
+                               
+                               if (row instanceof SplineStart) {
+                                       if (renderer != null)
+                                               throw new POIXMLException("SplineStart found multiple times!");
+                                       renderer = new SplineCollector((SplineStart) row);
+                               } else if (row instanceof SplineKnot) {
+                                       if (renderer == null)
+                                               throw new POIXMLException("SplineKnot found without SplineStart!");
+                                       renderer.addKnot((SplineKnot) row);
+                               } else {
+                                       if (renderer != null) {
+                                               renderer.addToPath(path, parent);
+                                               renderer = null;
+                                       }
+                                       
+                                       row.addToPath(path, parent);
+                               }
+                       }
+                       
+                       // just in case we end iteration
+                       if (renderer != null)
+                               renderer.addToPath(path, parent);
+                       
+                       return path;
+               }
+       }
+       
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/XDGFSection.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/XDGFSection.java
new file mode 100644 (file)
index 0000000..835dade
--- /dev/null
@@ -0,0 +1,98 @@
+/* ====================================================================
+   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;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.poi.POIXMLException;
+import org.apache.poi.util.Internal;
+import org.apache.poi.xdgf.usermodel.XDGFCell;
+import org.apache.poi.xdgf.usermodel.XDGFSheet;
+import org.apache.poi.xdgf.util.ObjectFactory;
+
+import com.microsoft.schemas.office.visio.x2012.main.CellType;
+import com.microsoft.schemas.office.visio.x2012.main.SectionType;
+
+public abstract class XDGFSection {
+
+       static final ObjectFactory<XDGFSection, SectionType> _sectionTypes;
+       
+       static {
+               _sectionTypes = new ObjectFactory<>();
+               try {
+                       _sectionTypes.put("LineGradient",       GenericSection.class, SectionType.class, XDGFSheet.class);
+                       _sectionTypes.put("FillGradient",       GenericSection.class, SectionType.class, XDGFSheet.class);
+                       _sectionTypes.put("Character",          CharacterSection.class, SectionType.class, XDGFSheet.class);
+                       _sectionTypes.put("Paragraph",          GenericSection.class, SectionType.class, XDGFSheet.class);
+                       _sectionTypes.put("Tabs",                       GenericSection.class, SectionType.class, XDGFSheet.class);
+                       _sectionTypes.put("Scratch",            GenericSection.class, SectionType.class, XDGFSheet.class);
+                       _sectionTypes.put("Connection",         GenericSection.class, SectionType.class, XDGFSheet.class);
+                       _sectionTypes.put("ConnectionABCD", GenericSection.class, SectionType.class, XDGFSheet.class);
+                       _sectionTypes.put("Field",                      GenericSection.class, SectionType.class, XDGFSheet.class);
+                       _sectionTypes.put("Control",            GenericSection.class, SectionType.class, XDGFSheet.class);
+                       _sectionTypes.put("Geometry",           GeometrySection.class, SectionType.class, XDGFSheet.class);
+                       _sectionTypes.put("Actions",            GenericSection.class, SectionType.class, XDGFSheet.class);
+                       _sectionTypes.put("Layer",                      GenericSection.class, SectionType.class, XDGFSheet.class);
+                       _sectionTypes.put("User",                       GenericSection.class, SectionType.class, XDGFSheet.class);
+                       _sectionTypes.put("Property",           GenericSection.class, SectionType.class, XDGFSheet.class);
+                       _sectionTypes.put("Hyperlink",          GenericSection.class, SectionType.class, XDGFSheet.class);
+                       _sectionTypes.put("Reviewer",           GenericSection.class, SectionType.class, XDGFSheet.class);
+                       _sectionTypes.put("Annotation",         GenericSection.class, SectionType.class, XDGFSheet.class);
+                       _sectionTypes.put("ActionTag",          GenericSection.class, SectionType.class, XDGFSheet.class);
+               } catch (NoSuchMethodException | SecurityException e) {
+                       throw new POIXMLException("Internal error");
+               }
+               
+       }
+       
+       public static XDGFSection load(SectionType section, XDGFSheet containingSheet) {
+               return _sectionTypes.load(section.getN(), section, containingSheet);
+       }
+       
+       
+       protected SectionType _section;
+       protected XDGFSheet _containingSheet;
+       
+       protected Map<String, XDGFCell> _cells = new HashMap<>();
+       
+       
+       public XDGFSection(SectionType section, XDGFSheet containingSheet) {
+               _section = section;
+               _containingSheet = containingSheet;
+               
+               // only store cells in the base, not rows -- because rows are handled
+               // specially for geometry sections
+               for (CellType cell: section.getCellArray()) {
+                       _cells.put(cell.getN(), new XDGFCell(cell));
+               }
+       }
+       
+       @Internal
+       public SectionType getXmlObject() {
+               return _section;
+       }
+       
+       @Override
+       public String toString() {
+               return "<Section type=" + _section.getN() + " from " + _containingSheet + ">";
+       }
+
+       public abstract void setupMaster(XDGFSection section);
+       
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/ArcTo.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/ArcTo.java
new file mode 100644 (file)
index 0000000..243b388
--- /dev/null
@@ -0,0 +1,131 @@
+/* ====================================================================
+   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 java.awt.geom.AffineTransform;
+import java.awt.geom.Arc2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+
+import org.apache.poi.POIXMLException;
+import org.apache.poi.xdgf.usermodel.XDGFCell;
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+
+import com.microsoft.schemas.office.visio.x2012.main.CellType;
+import com.microsoft.schemas.office.visio.x2012.main.RowType;
+
+public class ArcTo implements GeometryRow {
+       
+       ArcTo _master = null;
+       
+       // The x-coordinate of the ending vertex of an arc.
+       Double x = null;
+       
+       // The y-coordinate of the ending vertex of an arc.
+       Double y = null;
+       
+       // The distance from the arc's midpoint to the midpoint of its chord.
+       Double a = null;
+       
+       Boolean deleted = null;
+       
+       // TODO: support formulas
+       
+       public ArcTo(RowType row) {
+               
+               if (row.isSetDel()) deleted = row.getDel();
+               
+               for (CellType cell: row.getCellArray()) {
+                       String cellName = cell.getN();
+                       
+                       if (cellName.equals("X")) {
+                               x = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("Y")) {
+                               y = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("A")) {
+                               a = XDGFCell.parseDoubleValue(cell);
+                       } else {
+                               throw new POIXMLException("Invalid cell '" + cellName + "' in ArcTo row");
+                       }
+               }
+       }
+       
+       public boolean getDel() {
+               if (deleted != null)
+                       return deleted;
+               
+               if (_master != null)
+                       return _master.getDel();
+                       
+               return false;
+       }
+       
+       public Double getX() {
+               return x == null ? _master.x : x;
+       }
+       
+       public Double getY() {
+               return y == null ? _master.y : y;
+       }
+       
+       public Double getA() {
+               return a == null ? _master.a : a;
+       }
+
+       @Override
+       public void setupMaster(GeometryRow row) {
+               _master = (ArcTo) row;
+       }
+
+       @Override
+       public void addToPath(Path2D.Double path, XDGFShape parent) {
+               
+               if (getDel()) return;
+               
+               Point2D last = path.getCurrentPoint();
+               
+               // intentionally shadowing variables here
+               double x = getX();
+               double y = getY();
+               double a = getA();
+               
+               if (a == 0) {
+                       path.lineTo(x, y);
+                       return;
+               }
+               
+               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));
+               
+               // center point
+               double cx = x0 + (x - x0) / 2.0;
+               double cy = y0 + (y - y0) / 2.0;
+               
+               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);
+               
+               path.append(AffineTransform.getRotateInstance(rotate, x0, y0).createTransformedShape(arc), true);
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/Ellipse.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/Ellipse.java
new file mode 100644 (file)
index 0000000..0e4124e
--- /dev/null
@@ -0,0 +1,156 @@
+/* ====================================================================
+   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 java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Path2D;
+
+import org.apache.poi.POIXMLException;
+import org.apache.poi.xdgf.usermodel.XDGFCell;
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+
+import com.microsoft.schemas.office.visio.x2012.main.CellType;
+import com.microsoft.schemas.office.visio.x2012.main.RowType;
+
+public class Ellipse implements GeometryRow {
+       
+       Ellipse _master = null;
+       
+       // x coordinate of center point
+       Double x = null;
+       // y coordinate of center point
+       Double y = null;
+       
+       // x coordinate of first point on ellipse
+       Double a = null;
+       // y coordinate of first point on ellipse
+       Double b = null;
+       
+       // x coordinate of second point on ellipse
+       Double c = null;
+       // y coordinate of second point on ellipse
+       Double d = null;
+       
+       Boolean deleted = null;
+       
+       // TODO: support formulas
+       
+       public Ellipse(RowType row) {
+               
+               if (row.isSetDel()) deleted = row.getDel();
+               
+               for (CellType cell: row.getCellArray()) {
+                       String cellName = cell.getN();
+                       
+                       if (cellName.equals("X")) {
+                               x = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("Y")) {
+                               y = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("A")) {
+                               a = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("B")) {
+                               b = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("C")) {
+                               c = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("D")) {
+                               d = XDGFCell.parseDoubleValue(cell);
+                       } else {
+                               throw new POIXMLException("Invalid cell '" + cellName + "' in Ellipse row");
+                       }
+               }
+       }
+       
+       public boolean getDel() {
+               if (deleted != null)
+                       return deleted;
+               
+               if (_master != null)
+                       return _master.getDel();
+                       
+               return false;
+       }
+       
+       public Double getX() {
+               return x == null ? _master.x : x;
+       }
+       
+       public Double getY() {
+               return y == null ? _master.y : y;
+       }
+       
+       public Double getA() {
+               return a == null ? _master.a : a;
+       }
+       
+       public Double getB() {
+               return b == null ? _master.b : b;
+       }
+       
+       public Double getC() {
+               return c == null ? _master.c : c;
+       }
+       
+       public Double getD() {
+               return d == null ? _master.d : d;
+       }
+
+       @Override
+       public void setupMaster(GeometryRow row) {
+               _master = (Ellipse) row;
+       }
+
+       public Path2D.Double getPath() {
+               
+               if (getDel()) return null;
+               
+               // intentionally shadowing variables here
+               double cx = getX(); // center
+               double cy = getY();
+               double a = getA(); // left
+               double b = getB();
+               double c = getC(); // top
+               double d = getD();
+               
+               // compute radius
+               double rx = Math.hypot(a - cx, b - cy);
+               double ry = Math.hypot(c - cx, d - cy);
+               
+               // compute angle of ellipse
+               double angle = (2.0*Math.PI + (cy > b ? 1.0 : -1.0) * Math.acos((cx - a) / rx)) % (2.0*Math.PI);
+               
+               // create ellipse
+               Ellipse2D.Double ellipse = new Ellipse2D.Double(cx - rx,
+                                                                                                               cy - ry,
+                                                                                                               rx*2, ry*2);
+                               
+               // create a path, rotate it about its center
+               Path2D.Double path = new Path2D.Double(ellipse);
+               
+               AffineTransform tr = new AffineTransform();
+               tr.rotate(angle, cx, cy);
+               path.transform(tr);
+               
+               return path;
+       }
+
+       @Override
+       public void addToPath(java.awt.geom.Path2D.Double path, XDGFShape parent) {
+               throw new POIXMLException("Ellipse elements cannot be part of a path");
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/EllipticalArcTo.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/EllipticalArcTo.java
new file mode 100644 (file)
index 0000000..f7b3bca
--- /dev/null
@@ -0,0 +1,231 @@
+/* ====================================================================
+   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 java.awt.geom.AffineTransform;
+import java.awt.geom.Arc2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+
+import org.apache.poi.POIXMLException;
+import org.apache.poi.xdgf.usermodel.XDGFCell;
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+
+import com.microsoft.schemas.office.visio.x2012.main.CellType;
+import com.microsoft.schemas.office.visio.x2012.main.RowType;
+
+public class EllipticalArcTo implements GeometryRow {
+       
+       EllipticalArcTo _master = null;
+       
+       // The x-coordinate of the ending vertex on an arc.
+       Double x = null;
+       
+       // The y-coordinate of the ending vertex on an arc.
+       Double y = null;
+       
+       // The x-coordinate of the arc's control point; a point on the arc. The
+       // control point is best located about halfway between the beginning and
+       // ending vertices of the arc. Otherwise, the arc may grow to an extreme
+       // size in order to pass through the control point, with unpredictable
+       // results.
+       Double a = null;
+       
+       // The y-coordinate of an arc's control point.
+       Double b = null;
+       
+       // The angle of an arc's major axis relative to the x-axis of its parent shape.
+       Double c = null;
+       
+       // The ratio of an arc's major axis to its minor axis. Despite the usual
+       // meaning of these words, the "major" axis does not have to be greater than
+       // the "minor" axis, so this ratio does not have to be greater than 1.
+       // Setting this cell to a value less than or equal to 0 or greater than 1000
+       // can lead to unpredictable results.
+       Double d = null;
+       
+       Boolean deleted = null;
+       
+       // TODO: support formulas
+       
+       public EllipticalArcTo(RowType row) {
+
+               if (row.isSetDel()) deleted = row.getDel();
+               
+               for (CellType cell: row.getCellArray()) {
+                       String cellName = cell.getN();
+                       
+                       if (cellName.equals("X")) {
+                               x = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("Y")) {
+                               y = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("A")) {
+                               a = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("B")) {
+                               b = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("C")) {
+                               c = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("D")) {
+                               d = XDGFCell.parseDoubleValue(cell);
+                       } else {
+                               throw new POIXMLException("Invalid cell '" + cellName + "' in EllipticalArcTo row");
+                       }
+               }
+       }
+       
+       public boolean getDel() {
+               if (deleted != null)
+                       return deleted;
+               
+               if (_master != null)
+                       return _master.getDel();
+                       
+               return false;
+       }
+       
+       public Double getX() {
+               return x == null ? _master.x : x;
+       }
+       
+       public Double getY() {
+               return y == null ? _master.y : y;
+       }
+       
+       public Double getA() {
+               return a == null ? _master.a : a;
+       }
+       
+       public Double getB() {
+               return b == null ? _master.b : b;
+       }
+       
+       public Double getC() {
+               return c == null ? _master.c : c;
+       }
+       
+       public Double getD() {
+               return d == null ? _master.d : d;
+       }
+       
+       
+       @Override
+       public void setupMaster(GeometryRow row) {
+               _master = (EllipticalArcTo) row;
+       }
+       
+       public static int draw = 0;
+
+       @Override
+       public void addToPath(java.awt.geom.Path2D.Double path, XDGFShape parent) {
+       
+               if (getDel()) return;
+               
+               // intentionally shadowing variables here
+               double x = getX();
+               double y = getY();
+               double a = getA();
+               double b = getB();
+               double c = getC();
+               double d = getD();
+               
+               createEllipticalArc(x, y, a, b, c, d, path);
+       }
+               
+       public static void createEllipticalArc(double x, double y, double a, double b,
+                                                                                  double c, double d,
+                                                                                  java.awt.geom.Path2D.Double path) {
+               
+               // Formula for center of ellipse by Junichi Yoda & nashwaan:
+               // -> From http://visguy.com/vgforum/index.php?topic=2464.0
+               //
+               // x1,y1 = start; x2,y2 = end; x3,y3 = control point
+               //
+               // x0 = ((x1-x2)*(x1+x2)*(y2-y3)-(x2-x3)*(x2+x3)*(y1-y2)+D^2*(y1-y2)*(y2-y3)*(y1-y3))/(2*((x1-x2)*(y2-y3)-(x2-x3)*(y1-y2)))
+               // y0 = ((x1-x2)*(x2-x3)*(x1-x3)/D^2+(x2-x3)*(y1-y2)*(y1+y2)-(x1-x2)*(y2-y3)*(y2+y3))/(2*((x2-x3)*(y1-y2)-(x1-x2)*(y2-y3)))
+               // radii along axis:   a = sqrt{ (x1-x0)^2 + (y1-y0)^2 * D^2 }
+               //
+               
+               Point2D last = path.getCurrentPoint();
+               double x0 = last.getX();
+               double y0 = last.getY();
+               
+               // 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};
+               at.transform(pts, 0, pts, 0, 3);
+               
+               
+               x0 = pts[0]; y0 = pts[1];
+               x = pts[2]; y = pts[3];
+               a = pts[4]; b = pts[5];
+               
+               // nasty math time
+               
+               double d2 = d*d;
+               double cx = ((x0-x)*(x0+x)*(y-b)-(x-a)*(x+a)*(y0-y)+d2*(y0-y)*(y-b)*(y0-b))/(2.0*((x0-x)*(y-b)-(x-a)*(y0-y)));
+               double cy = ((x0-x)*(x-a)*(x0-a)/d2+(x-a)*(y0-y)*(y0+y)-(x0-x)*(y-b)*(y+b))/(2.0*((x-a)*(y0-y)-(x0-x)*(y-b)));
+               
+               // calculate radii of ellipse
+               double rx = Math.sqrt(Math.pow(x0-cx, 2) + Math.pow(y0-cy,2) * d2);
+               double ry = rx / d;
+               
+               // Arc2D requires us to draw an arc from one point to another, so we
+               // need to calculate the angle of the start point and end point along the ellipse
+               // - Derived from parametric form of ellipse: x = h + a*cos(t); y = k + b*sin(t) 
+               
+               double ctrlAngle = Math.toDegrees(Math.atan2((b-cy)/ry, (a-cx)/rx));
+               double startAngle = Math.toDegrees(Math.atan2((y0-cy)/ry, (x0-cx)/rx));
+               double endAngle = Math.toDegrees(Math.atan2((y-cy)/ry, (x-cx)/rx));
+               
+               double sweep = computeSweep(startAngle, endAngle, ctrlAngle);
+               
+               // Now we have enough information to go on. Create the arc.
+               Arc2D arc = new Arc2D.Double(cx-rx, cy-ry,
+                                                                        rx*2, ry*2, -startAngle, sweep, Arc2D.OPEN);
+               
+               // rotate the arc back to the original coordinate system
+               at.setToRotation(c);
+               path.append(at.createTransformedShape(arc), false);
+       }
+       
+       protected static double computeSweep(double startAngle, double endAngle, double ctrlAngle) {
+               double sweep;
+               
+               startAngle = (360.0 + startAngle) % 360.0;
+               endAngle = (360.0 + endAngle) % 360.0;
+               ctrlAngle = (360.0 + ctrlAngle) % 360.0;
+               
+               // different sweeps depending on where the control point is
+               
+               if (startAngle < endAngle) {
+                       if (startAngle < ctrlAngle && ctrlAngle < endAngle) {
+                               sweep = startAngle - endAngle;
+                       } else {
+                               sweep = 360 + (startAngle - endAngle);
+                       }       
+               } else {
+                       if (endAngle < ctrlAngle && ctrlAngle < startAngle) {
+                               sweep = startAngle - endAngle;
+                       } else {
+                               sweep = - (360 - (startAngle - endAngle));
+                       }
+               }
+               
+               return sweep;
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/GeometryRow.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/GeometryRow.java
new file mode 100644 (file)
index 0000000..fcd488f
--- /dev/null
@@ -0,0 +1,31 @@
+/* ====================================================================
+   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 java.awt.geom.Path2D;
+
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+
+
+public interface GeometryRow {
+       
+       public void setupMaster(GeometryRow row);
+
+       public void addToPath(Path2D.Double path, XDGFShape parent);
+       
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/GeometryRowFactory.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/GeometryRowFactory.java
new file mode 100644 (file)
index 0000000..57e21d1
--- /dev/null
@@ -0,0 +1,57 @@
+/* ====================================================================
+   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 org.apache.poi.POIXMLException;
+import org.apache.poi.xdgf.util.ObjectFactory;
+
+import com.microsoft.schemas.office.visio.x2012.main.RowType;
+
+public class GeometryRowFactory {
+
+       static final ObjectFactory<GeometryRow, RowType> _rowTypes;
+       
+       static {
+               _rowTypes = new ObjectFactory<>();
+               try {
+                       _rowTypes.put("ArcTo", ArcTo.class, RowType.class);
+                       _rowTypes.put("Ellipse", Ellipse.class, RowType.class);
+                       _rowTypes.put("EllipticalArcTo", EllipticalArcTo.class, RowType.class);
+                       _rowTypes.put("InfiniteLine", InfiniteLine.class, RowType.class);
+                       _rowTypes.put("LineTo", LineTo.class, RowType.class);
+                       _rowTypes.put("MoveTo", MoveTo.class, RowType.class);
+                       _rowTypes.put("NURBSTo", NURBSTo.class, RowType.class);
+                       _rowTypes.put("PolyLineTo", PolyLineTo.class, RowType.class);
+                       _rowTypes.put("RelCubBezTo", RelCubBezTo.class, RowType.class);
+                       _rowTypes.put("RelEllipticalArcTo", RelEllipticalArcTo.class, RowType.class);
+                       _rowTypes.put("RelLineTo", RelLineTo.class, RowType.class);
+                       _rowTypes.put("RelMoveTo", RelMoveTo.class, RowType.class);
+                       _rowTypes.put("RelQuadBezTo", RelQuadBezTo.class, RowType.class);
+                       _rowTypes.put("SplineKnot", SplineKnot.class, RowType.class);
+                       _rowTypes.put("SplineStart", SplineStart.class, RowType.class);
+               } catch (NoSuchMethodException | SecurityException e) {
+                       throw new POIXMLException("Internal error", e);
+               }
+               
+       }
+       
+       public static GeometryRow load(RowType row) {
+               return _rowTypes.load(row.getT(), row);
+       }
+       
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/InfiniteLine.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/InfiniteLine.java
new file mode 100644 (file)
index 0000000..584acb8
--- /dev/null
@@ -0,0 +1,151 @@
+/* ====================================================================
+   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 java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+
+import org.apache.poi.POIXMLException;
+import org.apache.poi.xdgf.geom.Dimension2dDouble;
+import org.apache.poi.xdgf.usermodel.XDGFCell;
+import org.apache.poi.xdgf.usermodel.XDGFDocument;
+import org.apache.poi.xdgf.usermodel.XDGFPage;
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+
+import com.microsoft.schemas.office.visio.x2012.main.CellType;
+import com.microsoft.schemas.office.visio.x2012.main.RowType;
+
+/**
+ * Contains the x- and y-coordinates of two points on an infinite line.
+ */
+public class InfiniteLine implements GeometryRow {
+       
+       InfiniteLine _master = null;
+       
+       // An x-coordinate of a point on the infinite line; paired with y-coordinate represented by the Y cell.
+       Double x = null;
+       
+       // A y-coordinate of a point on the infinite line; paired with x-coordinate represented by the X cell.
+       Double y = null;
+       
+       // An x-coordinate of a point on the infinite line; paired with y-coordinate represented by the B cell.
+       Double a = null;
+       
+       // A y-coordinate of a point on an infinite line; paired with x-coordinate represented by the A cell.
+       Double b = null;
+       
+       Boolean deleted = null;
+       
+       // TODO: support formulas
+       
+       public InfiniteLine(RowType row) {
+
+               if (row.isSetDel()) deleted = row.getDel();
+               
+               for (CellType cell: row.getCellArray()) {
+                       String cellName = cell.getN();
+                       
+                       if (cellName.equals("X")) {
+                               x = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("Y")) {
+                               y = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("A")) {
+                               a = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("B")) {
+                               b = XDGFCell.parseDoubleValue(cell);
+                       } else {
+                               throw new POIXMLException("Invalid cell '" + cellName + "' in InfiniteLine row");
+                       }
+               }
+       }
+       
+       public boolean getDel() {
+               if (deleted != null)
+                       return deleted;
+               
+               if (_master != null)
+                       return _master.getDel();
+                       
+               return false;
+       }
+       
+       public Double getX() {
+               return x == null ? _master.x : x;
+       }
+       
+       public Double getY() {
+               return y == null ? _master.y : y;
+       }
+       
+       public Double getA() {
+               return a == null ? _master.a : a;
+       }
+       
+       public Double getB() {
+               return b == null ? _master.b : b;
+       }
+       
+       @Override
+       public void setupMaster(GeometryRow row) {
+               _master = (InfiniteLine) row;
+       }
+
+       @Override
+       public void addToPath(java.awt.geom.Path2D.Double path, XDGFShape parent) {
+               
+               if (getDel()) return;
+               
+               throw new POIXMLException("InfiniteLine elements cannot be part of a path");
+       }
+       
+       // returns this object as a line that extends between the boundaries of
+       // the document
+       public Path2D.Double getPath() {
+               Path2D.Double path = new Path2D.Double();
+               
+               // this is a bit of a hack, but it works
+               double max_val = 100000;
+               
+               // compute slope..
+               double x0 = getX();
+               double y0 = getY();
+               double x1 = getA(); // second x
+               double y1 = getB(); // second y
+               
+               if (x0 == x1) {
+                       path.moveTo(x0, -max_val);
+                       path.lineTo(x0, max_val);
+               } else if (y0 == y1) {
+                       path.moveTo(-max_val, y0);
+                       path.lineTo(max_val, y0);
+               } else {
+
+                       // normal case: compute slope/intercept
+                       double m = (y1 - y0) / (x1 - x0);
+                       double c = y0 - m*x0;
+                       
+                       // y = mx + c
+                       
+                       path.moveTo(max_val, m*max_val + c);
+                       path.lineTo(max_val, (max_val - c)/m);
+               }
+               
+               return path;
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/LineTo.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/LineTo.java
new file mode 100644 (file)
index 0000000..e3bfe62
--- /dev/null
@@ -0,0 +1,88 @@
+/* ====================================================================
+   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 org.apache.poi.POIXMLException;
+import org.apache.poi.xdgf.usermodel.XDGFCell;
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+
+import com.microsoft.schemas.office.visio.x2012.main.CellType;
+import com.microsoft.schemas.office.visio.x2012.main.RowType;
+
+public class LineTo implements GeometryRow {
+
+       LineTo _master = null;
+       
+       Double x = null;
+       Double y = null;
+       
+       Boolean deleted = null;
+       
+       // TODO: support formulas
+       
+       public LineTo(RowType row) {
+
+               if (row.isSetDel()) deleted = row.getDel();
+               
+               for (CellType cell: row.getCellArray()) {
+                       String cellName = cell.getN();
+                       
+                       if (cellName.equals("X")) {
+                               x = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("Y")) {
+                               y = XDGFCell.parseDoubleValue(cell);
+                       } else {
+                               throw new POIXMLException("Invalid cell '" + cellName + "' in LineTo row");
+                       }
+               }
+       }
+       
+       @Override
+       public String toString() {
+               return "LineTo: x=" + getX() + "; y=" + getY();
+       }
+       
+       public boolean getDel() {
+               if (deleted != null)
+                       return deleted;
+               
+               if (_master != null)
+                       return _master.getDel();
+                       
+               return false;
+       }
+       
+       public Double getX() {
+               return x == null ? _master.x : x;
+       }
+       
+       public Double getY() {
+               return y == null ? _master.y : y;
+       }
+       
+       @Override
+       public void setupMaster(GeometryRow row) {
+               _master = (LineTo) row;
+       }
+
+       @Override
+       public void addToPath(java.awt.geom.Path2D.Double path, XDGFShape parent) {
+               if (getDel()) return;
+               path.lineTo(getX(), getY());
+       }       
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/MoveTo.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/MoveTo.java
new file mode 100644 (file)
index 0000000..fa704c8
--- /dev/null
@@ -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 org.apache.poi.POIXMLException;
+import org.apache.poi.xdgf.usermodel.XDGFCell;
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+
+import com.microsoft.schemas.office.visio.x2012.main.CellType;
+import com.microsoft.schemas.office.visio.x2012.main.RowType;
+
+/**
+ * Contains the x- and y-coordinates of the first vertex of a shape or the x-
+ * and y-coordinates of the first vertex after a break in a path, relative to
+ * the height and width of the shape.
+ */
+public class MoveTo implements GeometryRow {
+       
+       MoveTo _master = null;
+       
+       Double x = null;
+       Double y = null;
+       
+       Boolean deleted = null;
+       
+       // TODO: support formulas
+       
+       public MoveTo(RowType row) {
+
+               if (row.isSetDel()) deleted = row.getDel();
+               
+               for (CellType cell: row.getCellArray()) {
+                       String cellName = cell.getN();
+                       
+                       if (cellName.equals("X")) {
+                               x = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("Y")) {
+                               y = XDGFCell.parseDoubleValue(cell);
+                       } else {
+                               throw new POIXMLException("Invalid cell '" + cellName + "' in MoveTo row");
+                       }
+               }
+       }
+       
+       @Override
+       public String toString() {
+               return "MoveTo: x=" + getX() + "; y=" + getY();
+       }
+       
+       public boolean getDel() {
+               if (deleted != null)
+                       return deleted;
+               
+               if (_master != null)
+                       return _master.getDel();
+                       
+               return false;
+       }
+       
+       public Double getX() {
+               return x == null ? _master.x : x;
+       }
+       
+       public Double getY() {
+               return y == null ? _master.y : y;
+       }
+       
+       @Override
+       public void setupMaster(GeometryRow row) {
+               _master = (MoveTo) row;
+       }
+
+       @Override
+       public void addToPath(java.awt.geom.Path2D.Double path, XDGFShape parent) {
+               if (getDel()) return;
+               path.moveTo(getX(), getY());
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/NURBSTo.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/NURBSTo.java
new file mode 100644 (file)
index 0000000..3916523
--- /dev/null
@@ -0,0 +1,205 @@
+/* ====================================================================
+   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 java.awt.geom.Point2D;
+
+import org.apache.poi.POIXMLException;
+import org.apache.poi.xdgf.geom.SplineRenderer;
+import org.apache.poi.xdgf.usermodel.XDGFCell;
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+
+import com.graphbuilder.curve.ControlPath;
+import com.graphbuilder.curve.ShapeMultiPath;
+import com.graphbuilder.curve.ValueVector;
+import com.graphbuilder.geom.PointFactory;
+import com.microsoft.schemas.office.visio.x2012.main.CellType;
+import com.microsoft.schemas.office.visio.x2012.main.RowType;
+
+public class NURBSTo implements GeometryRow {
+       
+       NURBSTo _master = null;
+       
+       // The x-coordinate of the last control point of a NURBS.
+       Double x = null;
+       
+       // The y-coordinate of the last control point of a NURBS.
+       Double y = null;
+       
+       // The second to the last knot of the NURBS.
+       Double a = null;
+       
+       // The last weight of the NURBS.
+       Double b = null;
+       
+       // The first knot of the NURBS.
+       Double c = null;
+       
+       // The first weight of the NURBS.
+       Double d = null;
+       
+       // A NURBS formula.
+       String e = null;
+       
+       Boolean deleted = null;
+       
+       // TODO: support formulas
+       
+       public NURBSTo(RowType row) {
+
+               if (row.isSetDel()) deleted = row.getDel();
+               
+               for (CellType cell: row.getCellArray()) {
+                       String cellName = cell.getN();
+                       
+                       if (cellName.equals("X")) {
+                               x = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("Y")) {
+                               y = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("A")) {
+                               a = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("B")) {
+                               b = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("C")) {
+                               c = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("D")) {
+                               d = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("E")) {
+                               e = cell.getV();
+                       } else {
+                               throw new POIXMLException("Invalid cell '" + cellName + "' in NURBS row");
+                       }
+               }
+       }
+       
+       public boolean getDel() {
+               if (deleted != null)
+                       return deleted;
+               
+               if (_master != null)
+                       return _master.getDel();
+                       
+               return false;
+       }
+       
+       public Double getX() {
+               return x == null ? _master.x : x;
+       }
+       
+       public Double getY() {
+               return y == null ? _master.y : y;
+       }
+       
+       public Double getA() {
+               return a == null ? _master.a : a;
+       }
+       
+       public Double getB() {
+               return b == null ? _master.b : b;
+       }
+       
+       public Double getC() {
+               return c == null ? _master.c : c;
+       }
+       
+       public Double getD() {
+               return d == null ? _master.d : d;
+       }
+       
+       public String getE() {
+               return e == null ? _master.e : e;
+       }
+       
+       @Override
+       public void setupMaster(GeometryRow row) {
+               _master = (NURBSTo) row;
+       }
+
+       @Override
+       public void addToPath(java.awt.geom.Path2D.Double path, XDGFShape parent) {
+               if (getDel()) return;
+               
+               Point2D last = path.getCurrentPoint();
+               
+               // A NURBS formula: knotLast, degree, xType, yType, x1, y1, knot1, weight1, ..
+               String formula = getE().trim();
+               if (!formula.startsWith("NURBS(") || !formula.endsWith(")"))
+                       throw new POIXMLException("Invalid NURBS formula: " + formula);
+               
+               String [] components = formula.substring(6, formula.length()-1).split(",");
+               
+               if (components.length < 8)
+                       throw new POIXMLException("Invalid NURBS formula (not enough arguments)");
+               
+               if ((components.length - 4) % 4 != 0)
+                       throw new POIXMLException("Invalid NURBS formula -- need 4 + n*4 arguments, got " + components.length);
+               
+               double lastControlX = getX();
+               double lastControlY = getY();
+               double secondToLastKnot = getA();
+               double lastWeight = getB();
+               double firstKnot = getC();
+               double firstWeight = getD();
+               
+               double lastKnot = Double.parseDouble(components[0].trim());
+               int degree = Integer.parseInt(components[1].trim());
+               int xType = Integer.parseInt(components[2].trim());
+               int yType = Integer.parseInt(components[3].trim());
+               
+               double xScale = 1;
+               double yScale = 1;
+               
+               if (xType == 0)
+                       xScale = parent.getWidth();
+               if (yType == 0)
+                       yScale = parent.getHeight();
+               
+               // setup first knots/weights/control point
+               ControlPath controlPath = new ControlPath();
+               ValueVector knots = new ValueVector();
+               ValueVector weights = new ValueVector();
+               
+               knots.add(firstKnot);
+               weights.add(firstWeight);
+               controlPath.addPoint(PointFactory.create(last.getX(), last.getY()));
+               
+               // iterate get knots/weights
+               int sets = (components.length - 4) / 4;
+               for (int i = 0; i < sets; i++) {
+                       double x1 = Double.parseDouble(components[4 + i*4 + 0].trim());
+                       double y1 = Double.parseDouble(components[4 + i*4 + 1].trim());
+                       double k = Double.parseDouble(components[4 + i*4 + 2].trim());
+                       double w = Double.parseDouble(components[4 + i*4 + 3].trim());
+                       
+                       controlPath.addPoint(PointFactory.create(x1*xScale, y1*yScale));
+                       knots.add(k);
+                       weights.add(w);
+               }
+               
+               // last knots/weights/control point
+               knots.add(secondToLastKnot);
+               knots.add(lastKnot);
+               
+               weights.add(lastWeight);
+               
+               controlPath.addPoint(PointFactory.create(lastControlX, lastControlY));
+               
+               ShapeMultiPath shape = SplineRenderer.createNurbsSpline(controlPath, knots, weights, degree);
+               path.append(shape, true);
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/PolyLineTo.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/PolyLineTo.java
new file mode 100644 (file)
index 0000000..7ce70d5
--- /dev/null
@@ -0,0 +1,95 @@
+/* ====================================================================
+   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 org.apache.poi.POIXMLException;
+import org.apache.poi.xdgf.usermodel.XDGFCell;
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+
+import com.microsoft.schemas.office.visio.x2012.main.CellType;
+import com.microsoft.schemas.office.visio.x2012.main.RowType;
+
+public class PolyLineTo implements GeometryRow {
+       
+       PolyLineTo _master = null;
+       
+       // The x-coordinate of the ending vertex of a polyline.
+       Double x = null;
+       
+       // The y-coordinate of the ending vertex of a polyline.
+       Double y = null;
+       
+       // The polyline formula
+       String a = null;
+       
+       Boolean deleted = null;
+       
+       // TODO: support formulas
+       
+       public PolyLineTo(RowType row) {
+
+               if (row.isSetDel()) deleted = row.getDel();
+               
+               for (CellType cell: row.getCellArray()) {
+                       String cellName = cell.getN();
+                       
+                       if (cellName.equals("X")) {
+                               x = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("Y")) {
+                               y = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("A")) {
+                               a = cell.getV();
+                       } else {
+                               throw new POIXMLException("Invalid cell '" + cellName + "' in ArcTo row");
+                       }
+               }
+       }
+       
+       public boolean getDel() {
+               if (deleted != null)
+                       return deleted;
+               
+               if (_master != null)
+                       return _master.getDel();
+                       
+               return false;
+       }
+       
+       public Double getX() {
+               return x == null ? _master.x : x;
+       }
+       
+       public Double getY() {
+               return y == null ? _master.y : y;
+       }
+       
+       public String getA() {
+               return a == null ? _master.a : a;
+       }
+       
+       @Override
+       public void setupMaster(GeometryRow row) {
+               _master = (PolyLineTo) row;
+       }
+
+       @Override
+       public void addToPath(java.awt.geom.Path2D.Double path, XDGFShape parent) {
+               if (getDel()) return;
+               throw new POIXMLException("Polyline support not implemented");
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/RelCubBezTo.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/RelCubBezTo.java
new file mode 100644 (file)
index 0000000..4b0ad47
--- /dev/null
@@ -0,0 +1,127 @@
+/* ====================================================================
+   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 org.apache.poi.POIXMLException;
+import org.apache.poi.xdgf.usermodel.XDGFCell;
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+
+import com.microsoft.schemas.office.visio.x2012.main.CellType;
+import com.microsoft.schemas.office.visio.x2012.main.RowType;
+
+public class RelCubBezTo implements GeometryRow {
+       
+       RelCubBezTo _master = null;
+       
+       // The x-coordinate of the ending vertex of a cubic Bézier curve relative to the width of the shape.
+       Double x = null;
+       
+       // The y-coordinate of the ending vertex of a cubic Bézier curve relative to the height of the shape.
+       Double y = null;
+       
+       // The x-coordinate of the curve’s beginning control point relative to the shape’s width; a point on the arc. The control point is best located between the beginning and ending vertices of the arc.
+       Double a = null;
+       
+       // The y-coordinate of a curve’s beginning control point relative to the shape’s height.
+       Double b = null;
+       
+       // The x-coordinate of the curve’s ending control point relative to the shape’s width; a point on the arc. The control point is best located between the beginning control point and ending vertices of the arc.
+       Double c = null;
+       
+       // The y-coordinate of a curve's ending control point relative to the shape’s height.
+       Double d = null;
+       
+       Boolean deleted = null;
+       
+       // TODO: support formulas
+       
+       public RelCubBezTo(RowType row) {
+
+               if (row.isSetDel()) deleted = row.getDel();
+               
+               for (CellType cell: row.getCellArray()) {
+                       String cellName = cell.getN();
+                       
+                       if (cellName.equals("X")) {
+                               x = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("Y")) {
+                               y = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("A")) {
+                               a = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("B")) {
+                               b = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("C")) {
+                               c = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("D")) {
+                               d = XDGFCell.parseDoubleValue(cell);
+                       } else {
+                               throw new POIXMLException("Invalid cell '" + cellName + "' in RelCubBezTo row");
+                       }
+               }
+       }
+       
+       public boolean getDel() {
+               if (deleted != null)
+                       return deleted;
+               
+               if (_master != null)
+                       return _master.getDel();
+                       
+               return false;
+       }
+       
+       public Double getX() {
+               return x == null ? _master.x : x;
+       }
+       
+       public Double getY() {
+               return y == null ? _master.y : y;
+       }
+       
+       public Double getA() {
+               return a == null ? _master.a : a;
+       }
+       
+       public Double getB() {
+               return b == null ? _master.b : b;
+       }
+       
+       public Double getC() {
+               return c == null ? _master.c : c;
+       }
+       
+       public Double getD() {
+               return d == null ? _master.d : d;
+       }
+       
+       @Override
+       public void setupMaster(GeometryRow row) {
+               _master = (RelCubBezTo) row;
+       }
+
+       @Override
+       public void addToPath(java.awt.geom.Path2D.Double path, XDGFShape parent) {
+               
+               if (getDel()) return;
+               
+               double w = parent.getWidth();
+               double h = parent.getHeight();
+               
+               path.curveTo(getA()*w, getB()*h, getC()*w, getD()*h, getX()*w, getY()*h);
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/RelEllipticalArcTo.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/RelEllipticalArcTo.java
new file mode 100644 (file)
index 0000000..99ae22b
--- /dev/null
@@ -0,0 +1,141 @@
+/* ====================================================================
+   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 org.apache.poi.POIXMLException;
+import org.apache.poi.xdgf.usermodel.XDGFCell;
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+
+import com.microsoft.schemas.office.visio.x2012.main.CellType;
+import com.microsoft.schemas.office.visio.x2012.main.RowType;
+
+public class RelEllipticalArcTo implements GeometryRow {
+       
+       RelEllipticalArcTo _master = null;
+       
+       // The x-coordinate of the ending vertex on an arc relative to the width of
+       // the shape.
+       Double x = null;
+
+       // The y-coordinate of the ending vertex on an arc relative to the height of
+       // the shape.
+       Double y = null;
+
+       // The x-coordinate of the arc's control point relative to the shape’s
+       // width; a point on the arc.
+       Double a = null;
+
+       // The y-coordinate of an arc's control point relative to the shape’s width.
+       Double b = null;
+
+       // The angle of an arc's major axis relative to the x-axis of its parent.
+       Double c = null;
+
+       // The ratio of an arc's major axis to its minor axis. Despite the usual
+       // meaning of these words, the "major" axis does not have to be greater than
+       // the "minor" axis, so this ratio does not have to be greater than 1.
+       Double d = null;
+       
+       Boolean deleted = null;
+       
+       // TODO: support formulas
+       
+       public RelEllipticalArcTo(RowType row) {
+
+               if (row.isSetDel()) deleted = row.getDel();
+               
+               for (CellType cell: row.getCellArray()) {
+                       String cellName = cell.getN();
+                       
+                       if (cellName.equals("X")) {
+                               x = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("Y")) {
+                               y = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("A")) {
+                               a = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("B")) {
+                               b = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("C")) {
+                               c = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("D")) {
+                               d = XDGFCell.parseDoubleValue(cell);
+                       } else {
+                               throw new POIXMLException("Invalid cell '" + cellName + "' in RelEllipticalArcTo row");
+                       }
+               }
+       }
+       
+       public boolean getDel() {
+               if (deleted != null)
+                       return deleted;
+               
+               if (_master != null)
+                       return _master.getDel();
+                       
+               return false;
+       }
+       
+       public Double getX() {
+               return x == null ? _master.x : x;
+       }
+       
+       public Double getY() {
+               return y == null ? _master.y : y;
+       }
+       
+       public Double getA() {
+               return a == null ? _master.a : a;
+       }
+       
+       public Double getB() {
+               return b == null ? _master.b : b;
+       }
+       
+       public Double getC() {
+               return c == null ? _master.c : c;
+       }
+       
+       public Double getD() {
+               return d == null ? _master.d : d;
+       }
+       
+       @Override
+       public void setupMaster(GeometryRow row) {
+               _master = (RelEllipticalArcTo) row;
+       }
+
+       @Override
+       public void addToPath(java.awt.geom.Path2D.Double path, XDGFShape parent) {
+               
+               if (getDel()) return;
+               
+               double w = parent.getWidth();
+               double h = parent.getHeight();
+               
+               // intentionally shadowing variables here
+               double x = getX()*w;
+               double y = getY()*h;
+               double a = getA()*w;
+               double b = getB()*h;
+               double c = getC();
+               double d = getD();
+               
+               EllipticalArcTo.createEllipticalArc(x, y, a, b, c, d, path);
+               
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/RelLineTo.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/RelLineTo.java
new file mode 100644 (file)
index 0000000..e2cbeb4
--- /dev/null
@@ -0,0 +1,90 @@
+/* ====================================================================
+   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 org.apache.poi.POIXMLException;
+import org.apache.poi.xdgf.usermodel.XDGFCell;
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+
+import com.microsoft.schemas.office.visio.x2012.main.CellType;
+import com.microsoft.schemas.office.visio.x2012.main.RowType;
+
+/**
+ * Contains x-and y-coordinates of the ending vertex of a straight line segment
+ * relative to a shape’s width and height.
+ */
+public class RelLineTo implements GeometryRow {
+       
+       RelLineTo _master = null;
+       
+       Double x = null;
+       Double y = null;
+       
+       Boolean deleted = null;
+       
+       // TODO: support formulas
+       
+       public RelLineTo(RowType row) {
+
+               if (row.isSetDel()) deleted = row.getDel();
+               
+               for (CellType cell: row.getCellArray()) {
+                       String cellName = cell.getN();
+                       
+                       if (cellName.equals("X")) {
+                               x = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("Y")) {
+                               y = XDGFCell.parseDoubleValue(cell);
+                       } else {
+                               throw new POIXMLException("Invalid cell '" + cellName + "' in RelLineTo row");
+                       }
+               }
+       }
+       
+       public boolean getDel() {
+               if (deleted != null)
+                       return deleted;
+               
+               if (_master != null)
+                       return _master.getDel();
+                       
+               return false;
+       }
+       
+       public Double getX() {
+               return x == null ? _master.x : x;
+       }
+       
+       public Double getY() {
+               return y == null ? _master.y : y;
+       }
+       
+       @Override
+       public void setupMaster(GeometryRow row) {
+               _master = (RelLineTo) row;
+       }
+
+       @Override
+       public void addToPath(java.awt.geom.Path2D.Double path, XDGFShape parent) {
+               
+               if (getDel()) return;
+               
+               path.lineTo(getX()*parent.getWidth(),
+                                   getY()*parent.getHeight());
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/RelMoveTo.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/RelMoveTo.java
new file mode 100644 (file)
index 0000000..5413e37
--- /dev/null
@@ -0,0 +1,86 @@
+/* ====================================================================
+   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 org.apache.poi.POIXMLException;
+import org.apache.poi.xdgf.usermodel.XDGFCell;
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+
+import com.microsoft.schemas.office.visio.x2012.main.CellType;
+import com.microsoft.schemas.office.visio.x2012.main.RowType;
+
+public class RelMoveTo implements GeometryRow {
+       
+       RelMoveTo _master = null;
+       
+       Double x = null;
+       Double y = null;
+       
+       Boolean deleted = null;
+       
+       // TODO: support formulas
+       
+       public RelMoveTo(RowType row) {
+
+               if (row.isSetDel()) deleted = row.getDel();
+               
+               for (CellType cell: row.getCellArray()) {
+                       String cellName = cell.getN();
+                       
+                       if (cellName.equals("X")) {
+                               x = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("Y")) {
+                               y = XDGFCell.parseDoubleValue(cell);
+                       } else {
+                               throw new POIXMLException("Invalid cell '" + cellName + "' in RelMoveTo row");
+                       }
+               }
+       }
+       
+       public boolean getDel() {
+               if (deleted != null)
+                       return deleted;
+               
+               if (_master != null)
+                       return _master.getDel();
+                       
+               return false;
+       }
+       
+       public Double getX() {
+               return x == null ? _master.x : x;
+       }
+       
+       public Double getY() {
+               return y == null ? _master.y : y;
+       }
+       
+       @Override
+       public void setupMaster(GeometryRow row) {
+               _master = (RelMoveTo) row;
+       }
+
+       @Override
+       public void addToPath(java.awt.geom.Path2D.Double path, XDGFShape parent) {
+               
+               if (getDel()) return;
+               
+               path.moveTo(getX()*parent.getWidth(),
+                                   getY()*parent.getHeight());
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/RelQuadBezTo.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/RelQuadBezTo.java
new file mode 100644 (file)
index 0000000..ba715b1
--- /dev/null
@@ -0,0 +1,114 @@
+/* ====================================================================
+   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 org.apache.poi.POIXMLException;
+import org.apache.poi.xdgf.usermodel.XDGFCell;
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+
+import com.microsoft.schemas.office.visio.x2012.main.CellType;
+import com.microsoft.schemas.office.visio.x2012.main.RowType;
+
+/**
+ * Contains the x- and y-coordinates of the endpoint of a quadratic Bézier curve
+ * relative to the shape’s width and height and the x- and y-coordinates of the
+ * control point of the curve relative shape’s width and height.
+ */
+public class RelQuadBezTo implements GeometryRow {
+       
+       RelQuadBezTo _master = null;
+       
+       // The x-coordinate of the ending vertex of a quadratic Bézier curve relative to the width of the shape.
+       Double x = null;
+       
+       // The y-coordinate of the ending vertex of a quadratic Bézier curve relative to the height of the shape.
+       Double y = null;
+       
+       // The x-coordinate of the curve’s control point relative to the shape’s width; a point on the arc. The control point is best located about halfway between the beginning and ending vertices of the arc.
+       Double a = null;
+       
+       // The y-coordinate of a curve’s control point relative to the shape’s height.
+       Double b = null;
+       
+       Boolean deleted = null;
+       
+       // TODO: support formulas
+       
+       public RelQuadBezTo(RowType row) {
+
+               if (row.isSetDel()) deleted = row.getDel();
+               
+               for (CellType cell: row.getCellArray()) {
+                       String cellName = cell.getN();
+                       
+                       if (cellName.equals("X")) {
+                               x = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("Y")) {
+                               y = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("A")) {
+                               a = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("B")) {
+                               b = XDGFCell.parseDoubleValue(cell);
+                       } else {
+                               throw new POIXMLException("Invalid cell '" + cellName + "' in RelQuadBezTo row");
+                       }
+               }
+       }
+       
+       public boolean getDel() {
+               if (deleted != null)
+                       return deleted;
+               
+               if (_master != null)
+                       return _master.getDel();
+                       
+               return false;
+       }
+       
+       public Double getX() {
+               return x == null ? _master.x : x;
+       }
+       
+       public Double getY() {
+               return y == null ? _master.y : y;
+       }
+       
+       public Double getA() {
+               return a == null ? _master.a : a;
+       }
+       
+       public Double getB() {
+               return b == null ? _master.b : b;
+       }
+       
+       @Override
+       public void setupMaster(GeometryRow row) {
+               _master = (RelQuadBezTo) row;
+       }
+
+       @Override
+       public void addToPath(java.awt.geom.Path2D.Double path, XDGFShape parent) {
+               
+               if (getDel()) return;
+               
+               double w = parent.getWidth();
+               double h = parent.getHeight();
+               
+               path.quadTo(getA()*w, getB()*h, getX()*w, getY()*h);
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/SplineKnot.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/SplineKnot.java
new file mode 100644 (file)
index 0000000..254fae5
--- /dev/null
@@ -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 org.apache.poi.POIXMLException;
+import org.apache.poi.xdgf.usermodel.XDGFCell;
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+
+import com.microsoft.schemas.office.visio.x2012.main.CellType;
+import com.microsoft.schemas.office.visio.x2012.main.RowType;
+
+/**
+ * Contains x- and y-coordinates for a spline's control point and a spline's knot.
+ */
+public class SplineKnot implements GeometryRow {
+       
+       SplineKnot _master = null;
+       
+       // The x-coordinate of a control point.
+       Double x = null;
+       
+       // The y-coordinate of a control point.
+       Double y = null;
+       
+       // One of the spline's knots (other than the last one or the first two).
+       Double a = null;
+       
+       Boolean deleted = null;
+       
+       // TODO: support formulas
+       
+       public SplineKnot(RowType row) {
+
+               if (row.isSetDel()) deleted = row.getDel();
+               
+               for (CellType cell: row.getCellArray()) {
+                       String cellName = cell.getN();
+                       
+                       if (cellName.equals("X")) {
+                               x = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("Y")) {
+                               y = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("A")) {
+                               a = XDGFCell.parseDoubleValue(cell);
+                       } else {
+                               throw new POIXMLException("Invalid cell '" + cellName + "' in SplineKnot row");
+                       }
+               }
+       }
+       
+       public boolean getDel() {
+               if (deleted != null)
+                       return deleted;
+               
+               if (_master != null)
+                       return _master.getDel();
+                       
+               return false;
+       }
+       
+       public Double getX() {
+               return x == null ? _master.x : x;
+       }
+       
+       public Double getY() {
+               return y == null ? _master.y : y;
+       }
+       
+       public Double getA() {
+               return a == null ? _master.a : a;
+       }
+       
+       @Override
+       public void setupMaster(GeometryRow row) {
+               _master = (SplineKnot) row;
+       }
+
+       @Override
+       public void addToPath(java.awt.geom.Path2D.Double path, XDGFShape parent) {
+               throw new POIXMLException("Error: Use SplineRenderer!");
+       }
+       
+       @Override
+       public String toString() {
+               return "{SplineKnot x=" + getX() + " y=" + getY() + " a=" + getA() + "}";
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/SplineStart.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/section/geometry/SplineStart.java
new file mode 100644 (file)
index 0000000..b485eb7
--- /dev/null
@@ -0,0 +1,132 @@
+/* ====================================================================
+   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 org.apache.poi.POIXMLException;
+import org.apache.poi.xdgf.usermodel.XDGFCell;
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+
+import com.microsoft.schemas.office.visio.x2012.main.CellType;
+import com.microsoft.schemas.office.visio.x2012.main.RowType;
+
+/**
+ * Contains x- and y-coordinates for a spline's second control point, its second
+ * knot, its first knot, the last knot, and the degree of the spline.
+ */
+public class SplineStart implements GeometryRow {
+       
+       SplineStart _master = null;
+       
+       // The x-coordinate of a spline's second control point.
+       Double x = null;
+       
+       // The y-coordinate of a spline's second control point.
+       Double y = null;
+       
+       // The second knot of the spline.
+       Double a = null;
+       
+       // The first knot of a spline.
+       Double b = null;
+       
+       // The last knot of a spline.
+       Double c = null;
+       
+       // The degree of a spline (an integer from 1 to 25).
+       Integer d = null;
+       
+       Boolean deleted = null;
+       
+       // TODO: support formulas
+       
+       public SplineStart(RowType row) {
+
+               if (row.isSetDel()) deleted = row.getDel();
+               
+               for (CellType cell: row.getCellArray()) {
+                       String cellName = cell.getN();
+                       
+                       if (cellName.equals("X")) {
+                               x = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("Y")) {
+                               y = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("A")) {
+                               a = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("B")) {
+                               b = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("C")) {
+                               c = XDGFCell.parseDoubleValue(cell);
+                       } else if (cellName.equals("D")) {
+                               d = XDGFCell.parseIntegerValue(cell);
+                       } else {
+                               throw new POIXMLException("Invalid cell '" + cellName + "' in SplineStart row");
+                       }
+               }
+       }
+       
+       public boolean getDel() {
+               if (deleted != null)
+                       return deleted;
+               
+               if (_master != null)
+                       return _master.getDel();
+                       
+               return false;
+       }
+       
+       public Double getX() {
+               return x == null ? _master.x : x;
+       }
+       
+       public Double getY() {
+               return y == null ? _master.y : y;
+       }
+       
+       public Double getA() {
+               return a == null ? _master.a : a;
+       }
+       
+       public Double getB() {
+               return b == null ? _master.b : b;
+       }
+       
+       public Double getC() {
+               return c == null ? _master.c : c;
+       }
+       
+       public Integer getD() {
+               return d == null ? _master.d : d;
+       }
+       
+       @Override
+       public void setupMaster(GeometryRow row) {
+               _master = (SplineStart) row;
+       }
+
+       @Override
+       public void addToPath(java.awt.geom.Path2D.Double path, XDGFShape parent) {
+               throw new POIXMLException("Error: Use SplineRenderer!");
+       }
+       
+       @Override
+       public String toString() {
+               return "{SplineStart x=" + getX() + " y=" + getY() +
+                               " a=" + getA() + " b=" + getB() +
+                               " c=" + getC() + " d=" + getD() +
+                               "}";
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/shape/ShapeDataAcceptor.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/shape/ShapeDataAcceptor.java
new file mode 100644 (file)
index 0000000..5090699
--- /dev/null
@@ -0,0 +1,69 @@
+/* ====================================================================
+   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.shape;
+
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+
+/**
+ * This acceptor only allows traversal to shapes that have useful data
+ * associated with them, and tries to elide details that aren't useful
+ * when analyzing the content of a document.
+ * 
+ * Useful is subjective of course, and is defined as any of:
+ * 
+ * - Has non-empty text
+ * - Is a 1d shape, such as a line
+ * - User specified shapes
+ * - The outline of stencil objects
+ * - TODO
+ */
+public class ShapeDataAcceptor implements ShapeVisitorAcceptor {
+
+       @Override
+       public boolean accept(XDGFShape shape) {
+               
+               if (shape.isDeleted())
+                       return false;
+               
+               // text is interesting
+               if (shape.hasText() && shape.getTextAsString().length() != 0)
+                       return true;
+               
+               // 1d shapes are interesting, they create connections
+               if (shape.isShape1D())
+                       return true;
+               
+               // User specified shapes are interesting
+               if (!shape.hasMaster() && !shape.hasMasterShape())
+                       return true;
+               
+               if (shape.hasMaster() && !shape.hasMasterShape())
+                       return true;
+               
+               // include stencil content, but try to elide stencil interiors
+               //if (shape.getXmlObject().isSetMaster())
+               //      return true;
+               
+               if (shape.hasMasterShape() && shape.getMasterShape().isTopmost())
+                       return true;
+               
+               return false;
+       }
+       
+       
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/shape/ShapeDebuggerRenderer.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/shape/ShapeDebuggerRenderer.java
new file mode 100644 (file)
index 0000000..2f32337
--- /dev/null
@@ -0,0 +1,70 @@
+/* ====================================================================
+   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.shape;
+
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.geom.Path2D;
+
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+
+public class ShapeDebuggerRenderer extends ShapeRenderer {
+
+       ShapeVisitorAcceptor _debugAcceptor = null;
+       
+       public ShapeDebuggerRenderer() {
+               super();
+       }
+       
+       public ShapeDebuggerRenderer(Graphics2D g) {
+               super(g);
+       }
+       
+       public void setDebugAcceptor(ShapeVisitorAcceptor acceptor) {
+               _debugAcceptor = acceptor;
+       }
+       
+       @Override
+       protected Path2D drawPath(XDGFShape shape){
+               
+               Path2D path = super.drawPath(shape);
+               if (_debugAcceptor == null || _debugAcceptor.accept(shape)) {
+               
+                       // show numbers to associate shapes with ids.. doesn't always work
+                       Font f = _graphics.getFont();
+                       _graphics.scale(1, -1);
+                       _graphics.setFont(f.deriveFont(0.05F));
+                       
+                       String shapeId = "" +  shape.getID();
+                       float shapeOffset = -0.1F;
+                       
+                       if (shape.hasMasterShape()) {
+                               shapeId += " MS:" + shape.getMasterShape().getID();
+                               shapeOffset -= 0.15F;
+                       }
+                       
+                       
+                       _graphics.drawString(shapeId, shapeOffset, 0);
+                       _graphics.setFont(f);
+                       _graphics.scale(1, -1);
+               }
+               
+               return path;
+       }
+
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/shape/ShapeRenderer.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/shape/ShapeRenderer.java
new file mode 100644 (file)
index 0000000..1386368
--- /dev/null
@@ -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.shape;
+
+import java.awt.BasicStroke;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Path2D;
+
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+import org.apache.poi.xdgf.usermodel.XDGFText;
+
+/**
+ * To use this to render only particular shapes, override it and provide an
+ * appropriate implementation of getAcceptor() or accept()
+ */
+public class ShapeRenderer extends ShapeVisitor {
+
+       protected Graphics2D _graphics;
+       
+       public ShapeRenderer() {
+               _graphics = null;
+       }
+       
+       public ShapeRenderer(Graphics2D g) {
+               _graphics = g;
+       }
+       
+       public void setGraphics(Graphics2D g) {
+               _graphics = g;
+       }
+       
+       @Override
+       public void visit(XDGFShape shape, AffineTransform globalTransform, int level) {
+               
+               AffineTransform savedTr = _graphics.getTransform();
+               _graphics.transform(globalTransform);
+               
+               drawPath(shape);
+               drawText(shape);
+               
+               // we're done, undo the transforms
+               _graphics.setTransform(savedTr);
+       }
+       
+       protected Path2D drawPath(XDGFShape shape) {
+               Path2D.Double path = shape.getPath();
+               if (path != null) {
+                       
+                       // setup the stroke for this line
+                       
+                       _graphics.setColor(shape.getLineColor());
+                       _graphics.setStroke(shape.getStroke());
+                       _graphics.draw(path);
+               }
+               
+               return path;
+       }
+       
+       protected void drawText(XDGFShape shape) {
+               XDGFText text = shape.getText();
+               if (text != null) {
+                       
+                       if (text.getTextContent().equals("Header"))
+                               text.getTextBounds();
+                       
+                       Font oldFont = _graphics.getFont();
+                       
+                       _graphics.setFont(oldFont.deriveFont(shape.getFontSize().floatValue()));
+                       _graphics.setColor(shape.getFontColor());
+                       
+                       text.draw(_graphics);
+                       _graphics.setFont(oldFont);
+               }
+       }
+
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/shape/ShapeVisitor.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/shape/ShapeVisitor.java
new file mode 100644 (file)
index 0000000..cf4eee1
--- /dev/null
@@ -0,0 +1,68 @@
+/* ====================================================================
+   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.shape;
+
+import java.awt.geom.AffineTransform;
+
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+
+/**
+ * Used to iterate through shapes
+ * 
+ * To change the behavior of a particular visitor, you can override either
+ * accept() or getAcceptor() [preferred]
+ * 
+ * If accept() or visit() throw StopVisitingThisBranch, the iteration will
+ * not visit subshapes of the shape.
+ */
+public abstract class ShapeVisitor{
+
+       ShapeVisitorAcceptor _acceptor;
+       
+       public ShapeVisitor() {
+               _acceptor = getAcceptor();
+       }
+       
+       // is only called on construction of the visitor, allows
+       // mixing visitors and acceptors
+       public ShapeVisitorAcceptor getAcceptor() {
+               return new ShapeVisitorAcceptor() {
+                       @Override
+                       public boolean accept(XDGFShape shape) {
+                               return !shape.isDeleted();
+                       }
+               };
+       }
+       
+       public void setAcceptor(ShapeVisitorAcceptor acceptor) {
+               _acceptor = acceptor;
+       }
+       
+       
+       public boolean accept(XDGFShape shape) {
+               return _acceptor.accept(shape);
+       }
+       
+       /**
+        * @param shape                         Current shape
+        * @param globalTransform   A transform that can convert the shapes points to global coordinates
+        * @param level             Level in the tree (0 is topmost, 1 is next level...
+        */
+       public abstract void visit(XDGFShape shape, AffineTransform globalTransform, int level);
+       
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/shape/ShapeVisitorAcceptor.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/shape/ShapeVisitorAcceptor.java
new file mode 100644 (file)
index 0000000..f9fb982
--- /dev/null
@@ -0,0 +1,26 @@
+/* ====================================================================
+   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.shape;
+
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+
+public interface ShapeVisitorAcceptor {
+
+       public boolean accept(XDGFShape shape);
+       
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/shape/exceptions/StopVisiting.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/shape/exceptions/StopVisiting.java
new file mode 100644 (file)
index 0000000..9a5d377
--- /dev/null
@@ -0,0 +1,24 @@
+/* ====================================================================
+   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.shape.exceptions;
+
+public class StopVisiting extends RuntimeException {
+
+       private static final long serialVersionUID = -4651207777092840750L;
+
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/shape/exceptions/StopVisitingThisBranch.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/shape/exceptions/StopVisitingThisBranch.java
new file mode 100644 (file)
index 0000000..576e062
--- /dev/null
@@ -0,0 +1,24 @@
+/* ====================================================================
+   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.shape.exceptions;
+
+public class StopVisitingThisBranch extends RuntimeException {
+
+       private static final long serialVersionUID = 5262319077534717862L;
+
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/util/HierarchyPrinter.java b/src/ooxml/java/org/apache/poi/xdgf/util/HierarchyPrinter.java
new file mode 100644 (file)
index 0000000..ae48c21
--- /dev/null
@@ -0,0 +1,84 @@
+/* ====================================================================
+   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.util;
+
+import java.awt.geom.AffineTransform;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+import org.apache.poi.xdgf.usermodel.XDGFPage;
+import org.apache.poi.xdgf.usermodel.XDGFShape;
+import org.apache.poi.xdgf.usermodel.XmlVisioDocument;
+import org.apache.poi.xdgf.usermodel.shape.ShapeVisitor;
+
+public class HierarchyPrinter {
+
+       public static void printHierarchy(XDGFPage page, File outDir) throws FileNotFoundException {
+               
+               File pageFile = new File(outDir, "page" + page.getPageNumber() + "-" + Util.sanitizeFilename(page.getName()) + ".txt");
+               
+               OutputStream os = new FileOutputStream(pageFile);
+               PrintStream pos = new PrintStream(os);
+               
+               printHierarchy(page, pos);
+               
+               pos.close();
+       }
+       
+       public static void printHierarchy(XDGFPage page, final PrintStream os) {
+
+               page.getContent().visitShapes(new ShapeVisitor() {
+                       
+                       @Override
+                       public void visit(XDGFShape shape, AffineTransform globalTransform, int level) {
+                               for (int i = 0; i < level; i++) {
+                                       os.append("  ");
+                               }
+                               // TODO: write text?
+                               os.println(shape.toString() + " [" + shape.getShapeType() + ", " + shape.getSymbolName() + "] " + shape.getMasterShape() + " " + shape.getTextAsString().trim());
+                       }
+               });     
+       }
+       
+       public static void printHierarchy(XmlVisioDocument document, String outDirname) throws FileNotFoundException {
+               
+               File outDir = new File(outDirname);
+               
+               for (XDGFPage page: document.getPages()) {
+                       printHierarchy(page, outDir);
+               }
+       }
+       
+       
+       public static void main(String [] args) throws Exception {
+               if (args.length != 2) {
+                       System.err.println("Usage: in.vsdx outdir");
+                       System.exit(1);
+               }
+               
+               String inFilename = args[0];
+               String outDir = args[1];
+               
+               XmlVisioDocument doc = new XmlVisioDocument(new FileInputStream(inFilename));
+               printHierarchy(doc, outDir);
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/util/ObjectFactory.java b/src/ooxml/java/org/apache/poi/xdgf/util/ObjectFactory.java
new file mode 100644 (file)
index 0000000..561e0c3
--- /dev/null
@@ -0,0 +1,57 @@
+/* ====================================================================
+   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.util;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.poi.POIXMLException;
+import org.apache.xmlbeans.XmlObject;
+
+
+public class ObjectFactory<T, X extends XmlObject> {
+       
+       Map<String, Constructor<? extends T>> _types = new HashMap<>();
+       
+       public void put(String typeName, Class<? extends T> cls, Class<?>... varargs) throws NoSuchMethodException, SecurityException {         
+               _types.put(typeName, cls.getDeclaredConstructor(varargs));
+       }
+       
+       public T load(String name, Object... varargs) {
+               Constructor<? extends T> constructor = _types.get(name);
+               if (constructor == null) {
+                       
+                       @SuppressWarnings("unchecked")
+                       X xmlObject = (X) varargs[0];
+                       
+                       String typeName = xmlObject.schemaType().getName().getLocalPart();
+                       throw new POIXMLException("Invalid '" + typeName + "' name '" + name + "'");
+               }
+               
+               try {
+                       return constructor.newInstance(varargs);
+               } catch (InvocationTargetException e) {
+                       throw new POIXMLException(e.getCause());
+               } catch (Exception e) {
+                       throw new POIXMLException(e);
+               }
+       }
+       
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/util/Util.java b/src/ooxml/java/org/apache/poi/xdgf/util/Util.java
new file mode 100644 (file)
index 0000000..3c67253
--- /dev/null
@@ -0,0 +1,37 @@
+/* ====================================================================
+   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.util;
+
+public class Util {
+       
+       public static int countLines(String str) {
+           int lines = 1;
+           int pos = 0;
+           while ((pos = str.indexOf("\n", pos) + 1) != 0) {
+               lines++;
+           }
+           return lines;
+       }
+       
+       // this probably isn't 100% correct, so don't use it in security-sensitive
+       // applications!
+       // from: http://www.rgagnon.com/javadetails/java-0662.html
+       public static String sanitizeFilename(String name) {
+               return name.replaceAll("[:\\\\/*\"?|<>]", "_");
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/util/VsdxToPng.java b/src/ooxml/java/org/apache/poi/xdgf/util/VsdxToPng.java
new file mode 100644 (file)
index 0000000..1bf6cf3
--- /dev/null
@@ -0,0 +1,118 @@
+/* ====================================================================
+   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.util;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.geom.Point2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import javax.imageio.ImageIO;
+
+import org.apache.poi.xdgf.geom.Dimension2dDouble;
+import org.apache.poi.xdgf.usermodel.XDGFPage;
+import org.apache.poi.xdgf.usermodel.XmlVisioDocument;
+import org.apache.poi.xdgf.usermodel.shape.ShapeDebuggerRenderer;
+import org.apache.poi.xdgf.usermodel.shape.ShapeRenderer;
+
+public class VsdxToPng {
+
+       
+       public static void renderToPng(XDGFPage page, String outFilename, double scale, ShapeRenderer renderer) throws IOException {
+               renderToPng(page, new File(outFilename), scale, renderer);
+       }
+       
+       public static void renderToPngDir(XDGFPage page, File outDir, double scale, ShapeRenderer renderer) throws IOException {
+               
+               File pageFile = new File(outDir, "page" + page.getPageNumber() + "-" + Util.sanitizeFilename(page.getName()) + ".png");
+               System.out.println("** Writing image to " + pageFile);
+               
+               renderToPng(page, pageFile, scale, renderer);
+               
+       }
+       
+       public static void renderToPng(XDGFPage page, File outFile, double scale, ShapeRenderer renderer) throws IOException {
+               
+               Dimension2dDouble sz = page.getPageSize();
+               
+               int width = (int)(scale * sz.getWidth());
+               int height = (int)(scale * sz.getHeight());
+               
+               BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+        final Graphics2D graphics = img.createGraphics();
+
+        // default rendering options
+        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+        graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+        graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+
+        graphics.setColor(Color.black);
+        graphics.setBackground(Color.white);
+        graphics.clearRect(0, 0, width, height);
+        
+        // Visio's coordinate system is flipped, so flip the image vertically
+        graphics.translate(0,  img.getHeight());
+        graphics.scale(scale, -scale);
+        
+        // toplevel shapes only
+        renderer.setGraphics(graphics);
+        page.getContent().visitShapes(renderer);
+        
+        graphics.dispose();
+        
+        FileOutputStream out = new FileOutputStream(outFile);
+        ImageIO.write(img, "png", out);
+        out.close();
+       }
+       
+       public static void renderToPng(XmlVisioDocument document, String outDirname, double scale, ShapeRenderer renderer) throws IOException {
+               
+               File outDir = new File(outDirname);
+               
+               for (XDGFPage page: document.getPages()) {
+                       renderToPngDir(page, outDir, scale, renderer);
+               }
+       }
+       
+       public static void main(String [] args) throws Exception {
+               if (args.length > 2) {
+                       System.err.println("Usage: [--debug] in.vsdx outdir");
+                       System.exit(1);
+               }
+               
+               ShapeRenderer renderer = new ShapeRenderer();
+               
+               String inFilename = args[0];
+               String pngDir = args[1];
+               
+               if (args[0].equals("--debug")) {
+                       inFilename = args[1];
+                       pngDir = args[2];
+                       renderer = new ShapeDebuggerRenderer();
+               }
+               
+               XmlVisioDocument doc = new XmlVisioDocument(new FileInputStream(inFilename));
+               renderToPng(doc, pngDir, 2000/11.0, renderer);
+       }
+}
diff --git a/src/ooxml/java/org/apache/poi/xdgf/xml/XDGFXMLDocumentPart.java b/src/ooxml/java/org/apache/poi/xdgf/xml/XDGFXMLDocumentPart.java
new file mode 100644 (file)
index 0000000..afe3489
--- /dev/null
@@ -0,0 +1,33 @@
+/* ====================================================================
+   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.xml;
+
+import org.apache.poi.POIXMLDocumentPart;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.PackageRelationship;
+import org.apache.poi.xdgf.usermodel.XDGFDocument;
+
+public class XDGFXMLDocumentPart extends POIXMLDocumentPart {
+
+       protected XDGFDocument _document;
+       
+       public XDGFXMLDocumentPart(PackagePart part, PackageRelationship rel, XDGFDocument document) {
+               super(part, rel);
+               _document = document;
+       }
+       
+}
diff --git a/src/ooxml/testcases/org/apache/poi/xdgf/usermodel/section/CombinedIteratorTest.java b/src/ooxml/testcases/org/apache/poi/xdgf/usermodel/section/CombinedIteratorTest.java
new file mode 100644 (file)
index 0000000..c48ab62
--- /dev/null
@@ -0,0 +1,159 @@
+/* ====================================================================
+   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;
+
+import java.util.Iterator;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class CombinedIteratorTest {
+
+       void testIteration(CombinedIterable<String> iterable, String... expected) {
+               
+               Iterator<String> iter = iterable.iterator();
+               
+               for (int i = 0; i < expected.length; i++) {
+                       Assert.assertEquals(true, iter.hasNext());
+                       Assert.assertEquals(expected[i], iter.next());
+               }
+               
+               Assert.assertEquals(false, iter.hasNext());
+       }
+       
+       @Test
+       public void testNullMaster() {
+               
+               SortedMap<Long, String> base = new TreeMap<>();
+               base.put(1L, "B1");
+               base.put(2L, "B2");
+               base.put(3L, "B3");
+               
+               testIteration(new CombinedIterable<>(base, null),
+                                         "B1", "B2", "B3");
+       }
+       
+       @Test
+       public void testNoMatchesBaseFirst() {
+               
+               SortedMap<Long, String> base = new TreeMap<>();
+               base.put(1L, "B1");
+               base.put(2L, "B2");
+               base.put(3L, "B3");
+               
+               SortedMap<Long, String> master = new TreeMap<>();
+               master.put(4L, "M4");
+               master.put(5L, "M5");
+               master.put(6L, "M6");
+               
+               testIteration(new CombinedIterable<>(base, master),
+                                 "B1", "B2", "B3", "M4", "M5", "M6");  
+       }
+       
+       @Test
+       public void testNoMatchesMasterFirst() {
+               
+               SortedMap<Long, String> base = new TreeMap<>();
+               base.put(4L, "B4");
+               base.put(5L, "B5");
+               base.put(6L, "B6");
+               
+               SortedMap<Long, String> master = new TreeMap<>();
+               master.put(1L, "M1");
+               master.put(2L, "M2");
+               master.put(3L, "M3");
+               
+               testIteration(new CombinedIterable<>(base, master),
+                                 "M1", "M2", "M3", "B4", "B5", "B6");  
+       }
+       
+       @Test
+       public void testInterleaved1() {
+               
+               SortedMap<Long, String> base = new TreeMap<>();
+               base.put(1L, "B1");
+               base.put(3L, "B3");
+               base.put(5L, "B5");
+               
+               SortedMap<Long, String> master = new TreeMap<>();
+               master.put(2L, "M2");
+               master.put(4L, "M4");
+               master.put(6L, "M6");
+               
+               testIteration(new CombinedIterable<>(base, master),
+                                 "B1", "M2", "B3", "M4", "B5", "M6");  
+       }
+       
+       @Test
+       public void testInterleaved2() {
+               
+               SortedMap<Long, String> base = new TreeMap<>();
+               base.put(1L, "B1");
+               base.put(2L, "B2");
+               base.put(5L, "B5");
+               base.put(6L, "B6");
+               
+               SortedMap<Long, String> master = new TreeMap<>();
+               master.put(3L, "M3");
+               master.put(4L, "M4");
+               master.put(7L, "M7");
+               master.put(8L, "M8");
+               
+               testIteration(new CombinedIterable<>(base, master),
+                                 "B1", "B2", "M3", "M4", "B5", "B6", "M7", "M8");      
+       }
+       
+       @Test
+       public void testAllMatching() {
+               
+               SortedMap<Long, String> base = new TreeMap<>();
+               base.put(1L, "B1");
+               base.put(2L, "B2");
+               base.put(3L, "B3");
+               
+               SortedMap<Long, String> master = new TreeMap<>();
+               master.put(1L, "M1");
+               master.put(2L, "M2");
+               master.put(3L, "M3");
+               
+               testIteration(new CombinedIterable<>(base, master),
+                                 "B1", "B2", "B3");    
+       }
+       
+       @Test
+       public void testAllMatching2() {
+               
+               SortedMap<Long, String> base = new TreeMap<>();
+               base.put(1L, "B1");
+               base.put(2L, "B2");
+               base.put(3L, "B3");
+               
+               SortedMap<Long, String> master = new TreeMap<>();
+               master.put(1L, "M1");
+               master.put(2L, "M2");
+               master.put(3L, "M3");
+               master.put(4L, "M4");
+               
+               testIteration(new CombinedIterable<>(base, master),
+                                 "B1", "B2", "B3", "M4");      
+       }
+       
+       
+}
diff --git a/src/resources/devtools/unpack_ooxml.sh b/src/resources/devtools/unpack_ooxml.sh
new file mode 100755 (executable)
index 0000000..a4b8703
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/bash -e
+#
+#   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.
+
+# basename it
+
+FILENAME="$1"
+BASENAME="${FILENAME##*/}"
+OUTDIR="${BASENAME%.*}"
+
+if [ ! -f "$FILENAME" ]; then
+    echo "File $FILENAME does not exist"
+    exit 1
+fi
+
+# if dir exists, fail
+if [ -d "$OUTDIR" ]; then
+    echo "Directory '$OUTDIR' already exists!"
+    exit 1
+fi
+
+mkdir "$OUTDIR"
+unzip -d "$OUTDIR" "$FILENAME"
+
+pushd "$OUTDIR"
+
+find . -type f \( -iname "*.xml" -or -iname "*.rels" \) -exec python -c "import os, sys, xml.dom.minidom
+with open('{}', 'r') as fp:
+    s = fp.read()
+with open('{}.bak', 'w') as fp:
+    fp.write(xml.dom.minidom.parseString(s).toprettyxml().encode('utf-8'))
+os.rename('{}.bak', '{}')" \;