Commits

Anonymous committed 657d482 Draft

edited pom to reflect ami2

  • Participants
  • Parent commits 8412ea8

Comments (0)

Files changed (16)

 	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 	<modelVersion>4.0.0</modelVersion>
 
-	<groupId>org.apache.pdfbox</groupId>
-	<artifactId>pdfbox-pmr</artifactId>
-	<version>1.6.0-pmr</version>
-	<name>PDFBox</name>
+<!--  changed project -->
+    <parent>
+        <groupId>uk.ac.cam.ch.wwmm</groupId>
+        <artifactId>wwmm-parent</artifactId>
+        <version>4</version> 
+    </parent>
+    
+	<groupId>org.xml-cml</groupId>
+	<artifactId>ami2</artifactId>
+	<version>0.1-SNAPSHOT</version>
+	<name>AMI2</name>
+	<description>Takes PDF or SVG from PDF and analyzes the semantics</description>
+	
 
 	<properties>
 		<jumbo.groupId>org.xml-cml</jumbo.groupId>
 	</properties>
 
 	<dependencies>
-		<!-- 
-		<dependency>
-		 <groupId>org.apache.pdfbox</groupId>
-		 <artifactId>fontbox</artifactId> 
-		 <version>1.6.0</version> 
-		</dependency> 
-		<dependency> 
-		 <groupId>org.apache.pdfbox</groupId> 
-		 <artifactId>jempbox</artifactId>
-		 <version>1.6.0</version>
-		</dependency> 
-   	    <dependency> 
-		  <groupId>org.apache.pdfbox</groupId> 
-		  <artifactId>pdfbox</artifactId> 
-		  <version>1.6.0</version> 
-		</dependency> -->
-		<!-- 
-		<dependency> 
-		 <groupId>org.apache.pdfbox</groupId> 
-		 <artifactId>pdfbox</artifactId> 
-		 <version>1.7.0-SNAPSHOT</version>
-		</dependency> -->
 		<dependency>
 			<groupId>org.apache.pdfbox</groupId>
 			<artifactId>pdfbox</artifactId>
 			<version>4.8.1</version>
 			<scope>test</scope>
 		</dependency>
+
+		<dependency>
+			<groupId>org.xml-cml</groupId>
+			<artifactId>euclid</artifactId>
+			<version>1.1-SNAPSHOT</version>
+<!-- 			<scope>test</scope> -->
+		</dependency>
+		<dependency>
+			<groupId>org.xml-cml</groupId>
+			<artifactId>euclid-testutil</artifactId>
+			<version>1.1-SNAPSHOT</version>
+			<scope>test</scope>
+		</dependency>
 	</dependencies>
 
 
 				<configuration>
 					<argLine>-Xmx64m</argLine>
 					<includes>
-                        <include>**/*Test.*</include>
+						<include>**/*Test.*</include>
 					</includes>
 				</configuration>
 			</plugin>

File src/main/java/org/xmlcml/graphics/cml/AMIEdge.java

 
 	AMIEdge(AMIGraph graph, SVGLine line) {
 		this.add(line);
+		this.setId(line.getId());
 		ensureNodeList();
 		this.graph = graph;
 	}
 							LOG.debug("looking "+thisPoint+" "+deltaXY);
 							node = graph.lookupNode(nodeList, thisPoint, deltaXY);
 							if (node == null) {
-								node = AMINode.createAndAddNewNode(nodeList, thisPoint);
+								node = AMINode.createAndAddNewNode(graph, nodeList, thisPoint);
 							} else {
 								LOG.debug("existing node: "+node);
 							}
 	public String toString() {
 		ensureNodeList();
 		String s = ""+id+" ";
-		s += nodeList.get(0)+" "+lineList.size()+" "+nodeList.get(1);
+		s += "{"+nodeList.get(0)+"}"+" {"+lineList.size()+" "+((lineList.size() > 0) ? lineList.get(0).getEuclidLine() : "null")+"} {"+nodeList.get(1)+"}";
 		return s;
 	}
 
 		Real2 projected0 = addMissingNodeByExtension(0, length);
 		Real2 projected1 = addMissingNodeByExtension(1, length);
 		if (projected0 != null) {
-			AMINode newNode = new AMINode(projected0, NodeType.EXTENDED_EDGE);
+			AMINode newNode = graph.createNode(projected0, NodeType.EXTENDED_EDGE);
 			nodeList.set(0, newNode);
 		}
 		if (projected1 != null) {
-			AMINode newNode = new AMINode(projected1, NodeType.EXTENDED_EDGE);
+			AMINode newNode = graph.createNode(projected1, NodeType.EXTENDED_EDGE);
 			nodeList.set(1, newNode);
 		}
-
 	}
 
 	public Real2 addMissingNodeByExtension(int nodeSerial, Double extensionLength) {

File src/main/java/org/xmlcml/graphics/cml/AMIGraph.java

 
 import org.apache.log4j.Logger;
 import org.xmlcml.euclid.Real2;
+import org.xmlcml.euclid.Real2Range;
 import org.xmlcml.euclid.RealArray;
-import org.xmlcml.graphics.svg.LinePrimitive;
+import org.xmlcml.graphics.cml.AMINode.NodeType;
+import org.xmlcml.graphics.pdf2svg.BoundingBoxManager;
 import org.xmlcml.graphics.svg.SVGLine;
+import org.xmlcml.graphics.svg.SVGText;
 
 public class AMIGraph {
 	private static Logger LOG = Logger.getLogger(AMIGraph.class);
 		}
 	}
 	
+	private AMINode createNode() {
+		AMINode node = new AMINode(this);
+		ensureNodeList();
+		nodeList.add(node);
+		return node;
+	}
+	
+	AMINode createNode(Real2 xy, NodeType nodeType) {
+		AMINode node = new AMINode(this);
+		ensureNodeList();
+		nodeList.add(node);
+		node.setXY(xy);
+		node.setType(nodeType);
+		return node;
+	}
+	
 	private void ensureNodeList() {
 		if (nodeList == null) {
 			nodeList = new ArrayList<AMINode>();
 		}
 		return realArray.size() == 0 ? null : realArray.getMean();
 	}
+
+	public void addTextAsNodes(List<SVGText> textList) {
+		for (SVGText text : textList) {
+			Real2Range bbox = BoundingBoxManager.createExtendedBox(text, text.getFontSize()/2.);
+			LOG.debug("T "+text.getString()+"/"+bbox);
+			for (AMIEdge edge : edgeList) {
+				for (int i = 0; i < 2; i++) {
+					AMINode node = edge.getNode(i);
+					Real2 point = (node != null) ? node.getXY() : edge.getEndPoints(i).get(0);
+					if (bbox.includes(point)) {
+						if (node == null) {
+							node = this.createNode(point, NodeType.LINE_END);
+							node.setText(text);
+						} else if (node.getText() == null) {
+							node.setText(text);
+						} else if (node.getText().equals(text)) {
+							// shouldn't get here but...
+						} else {
+							throw new RuntimeException("overlapping textNodes "+node.getText() +" -- " +text);
+						}
+					}
+				}
+			}
+		}
+	}
 }

File src/main/java/org/xmlcml/graphics/cml/AMINode.java

 import java.util.List;
 
 import org.xmlcml.euclid.Real2;
-import org.xmlcml.graphics.cml.AMINode.NodeType;
+import org.xmlcml.graphics.svg.SVGText;
 
 public class AMINode {
 
 	private List<AMIEdge> edgeList;
 	private String id;
 	private NodeType nodeType;
+	private SVGText text; // node can only have one text
+	private AMIGraph graph;
 
-	public AMINode(Real2 xy, NodeType nodeType) {
+	
+	private AMINode(AMIGraph graph, Real2 xy, NodeType nodeType) {
+		this(graph);
 		this.xy = xy;
 		this.nodeType = nodeType;
 	}
+	
+	public AMINode(AMIGraph graph) {
+		this.graph = graph;
+	}
 
 	public Real2 getXY() {
 		return xy;
 		this.nodeType = nodeType;
 	}
 
-	public static AMINode createAndAddNewNode(List<AMINode> nodeList, Real2 thisPoint) {
-		AMINode node = new AMINode(thisPoint, NodeType.LINE_END);
+	public static AMINode createAndAddNewNode(AMIGraph graph, List<AMINode> nodeList, Real2 thisPoint) {
+		AMINode node = new AMINode(graph, thisPoint, NodeType.LINE_END);
 		node.setId("node"+nodeList.size());
 		nodeList.add(node);
 		return node;
 	}
 
+	public SVGText getText() {
+		return text;
+	}
+
+	public void setText(SVGText  text) {
+		this.text = text;
+	}
+
+	public void setXY(Real2 xy) {
+		this.xy = xy;
+	}
+
 }

File src/main/java/org/xmlcml/graphics/paths/PathAnalyzer.java

  */
 public class PathAnalyzer extends AbstractSVGAnalyzer {
 
-	private static final String STROKE_NONE = "none";
+	private static final String NONE = "none";
 
 	private static final String DEFAULT_STROKE = "gray";
 
 		interpretPathsAsRectCirclePolylineAndReplace(pathList);
 	}
 
+	/** with help from
+http://stackoverflow.com/questions/4958161/determine-the-centre-center-of-a-circle-using-multiple-points
+	 * @param p1
+	 * @param p2
+	 * @param p3
+	 * @return
+	 */
+	public static SVGCircle findCircleFrom3Points(Real2 p1, Real2 p2, Real2 p3, Double eps) {
+		SVGCircle circle = null;
+		if (p1 != null && p2 != null && p3 != null) {
+			Double d2 = p2.x * p2.x + p2.y * p2.y;
+			Double bc = (p1.x * p1.x + p1.y * p1.y - d2) / 2;
+			Double cd = (d2 - p3.x * p3.x - p3.y * p3.y) / 2;
+			Double det = (p1.x - p2.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p2.y);
+			if (Math.abs(det) > eps) {
+				Real2 center = new Real2(
+						(bc * (p2.y - p3.y) - cd * (p1.y - p2.y)) / det,
+						((p1.x - p2.x) * cd - (p2.x - p3.x) * bc) / det);
+				Double rad = center.getDistance(p1);
+				circle = new SVGCircle(center, rad);
+			}
+		}
+		return circle;
+	}
+
 	/** main routine?
 	 * 
 	 * @param pathList
 	}
 
 	public void enforceVisibility() {
-		List<SVGPath> paths = SVGPath.extractPaths(SVGUtil.getQuerySVGElements(svgPage, "//svg:path"));
-		for (SVGPath path : paths) {
-			String stroke = path.getStroke();
-			if (stroke == null || STROKE_NONE.equals(stroke)) {
-				path.setStroke(DEFAULT_STROKE);
+		List<SVGElement> elements = SVGUtil.getQuerySVGElements(
+				svgPage, "//svg:path | //svg:polygon | //svg:line | //svg:polyline | //svg:circle | //svg:rect");
+		for (SVGElement element : elements) {
+			String stroke = element.getStroke();
+			String fill = element.getFill();
+			if ((stroke == null || NONE.equals(stroke)) && (fill == null || NONE.equals(fill))) {
+				element.setStroke(DEFAULT_STROKE);
 			}
 		}
 	}

File src/main/resources/org/xmlcml/graphics/schema.xsd

+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright 2011 Peter Murray-Rust et. al. Licensed 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. -->
+
+<xsd:schema targetNamespace="http://www.xml-cml.org/schema"
+	xmlns:h="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+	xmlns="http://www.xml-cml.org/schema">
+	<xsd:simpleType id="st.actionOrderType" name="actionOrderType">
+		<xsd:annotation>
+			<xsd:documentation>
+				<h:div class="summary">Describes whether child elements are sequential
+					or parallel.</h:div>
+				<h:div class="description">There is no default.</h:div>
+			</xsd:documentation>
+		</xsd:annotation>
+		<xsd:restriction base="xsd:string">
+			<xsd:enumeration value="sequential" />
+			<xsd:enumeration value="parallel" />
+		</xsd:restriction>
+	</xsd:simpleType>
+	<xsd:attributeGroup id="attGp.abbreviation" name="abbreviation">
+		<xsd:attribute id="att.abbreviation" name="abbreviation"
+			type="xsd:string">
+			<xsd:annotation>
+				<xsd:documentation>
+					<h:div class="summary">Abbreviation.</h:div>
+					<h:div class="description">Abbreviation for units, terms, etc.</h:div>
+				</xsd:documentation>
+			</xsd:annotation>
+		</xsd:attribute>
+	</xsd:attributeGroup>
+	<xsd:attributeGroup id="attGp.actionOrder" name="actionOrder">
+		<xsd:attribute name="order" id="att.actionOrder" type="actionOrderType">
+			<xsd:annotation>
+				<xsd:documentation>
+					<h:div class="summary">Describes whether child elements are sequential
+						or parallel.</h:div>
+					<h:div class="description">There is no default.</h:div>
+				</xsd:documentation>
+			</xsd:annotation>
+		</xsd:attribute>
+	</xsd:attributeGroup>
+	<xsd:element name="abundance" id="el.abundance">
+		<xsd:annotation>
+			<xsd:documentation>
+				<h:div class="summary">The abundance of an isotope.</h:div>
+				<h:div class="description">The abundance of an isotope in an isotopeList.
+					Values are expressed in percentages.
+				</h:div>
+				<h:div class="example" href="isotope1.xml" />
+			</xsd:documentation>
+		</xsd:annotation>
+		<xsd:complexType>
+			<xsd:simpleContent>
+				<xsd:extension base="xsd:double">
+					<xsd:attributeGroup ref="title" />
+					<xsd:attributeGroup ref="id" />
+					<xsd:attributeGroup ref="convention" />
+					<xsd:attributeGroup ref="dictRef" />
+					<xsd:attributeGroup ref="min" />
+					<xsd:attributeGroup ref="max" />
+					<xsd:attributeGroup ref="units" />
+				</xsd:extension>
+			</xsd:simpleContent>
+		</xsd:complexType>
+	</xsd:element>
+	<xsd:element name="action" id="el.action">
+		<xsd:annotation>
+			<xsd:documentation>
+				<h:div class="summary">An action which might occur in scientific data or
+					narrative.</h:div>
+				<h:div class="description">
+					An action which might occur in scientific data or narrative. The
+					definition is deliberately vague, intending to collect examples of
+					possible usage. Thus an action could be addition of materials,
+					measurement, application of heat or radiation. The content model is
+					unrestricted. _action_ iself is normally a child of _actionList_.
+
+					<h:p>The start, end and duration attributes should be interpreted
+						as
+					</h:p>
+					<h:ul>
+						<h:li>
+							XSD dateTimes and XSD durations. This allows precise recording of
+							time of day, etc, or duration after start of actionList. A
+							<h:tt>convention="xsd"</h:tt>
+							attribute should be used to enforce XSD.
+						</h:li>
+						<h:li>a numerical value, with a units attribute linked to a
+							dictionary.</h:li>
+						<h:li>a human-readable string (unlikely to be machine processable)
+						</h:li>
+					</h:ul>
+					<h:p>
+						<h:tt>startCondition</h:tt>
+						and
+						<h:tt>endCondition</h:tt>
+						values are not constrained, which allows XSL-like
+						<h:tt>test</h:tt>
+						attribute values. The semantics of the conditions are yet to be
+						defined and at present are simply human readable.
+					</h:p>
+					<h:p>
+						The order of the
+						<h:tt>action</h:tt>
+						elements in the document may, but will not always, define
+						the order that they actually occur in.
+					</h:p>
+					<h:p>
+						A delay can be shown by an
+						<h:tt>action</h:tt>
+						with no content. Repeated actions or
+						actionLists are indicated through the count attribute.
+					</h:p>
+				</h:div>
+
+				<h:div class="example" href="action1.xml" />
+				<!-- <h:div class="example" href="action2.xml"></h:div> -->
+			</xsd:documentation>
+		</xsd:annotation>
+
+		<xsd:complexType mixed="true">
+			<xsd:sequence minOccurs="0" maxOccurs="unbounded">
+				<xsd:any processContents="lax" />
+			</xsd:sequence>
+
+			<xsd:attributeGroup ref="title" />
+			<xsd:attributeGroup ref="id" />
+			<xsd:attributeGroup ref="convention" />
+			<xsd:attributeGroup ref="dictRef" />
+			<xsd:attributeGroup ref="units" />
+			<xsd:attributeGroup ref="start" />
+			<xsd:attributeGroup ref="startCondition" />
+			<xsd:attributeGroup ref="duration" />
+			<xsd:attributeGroup ref="end" />
+			<xsd:attributeGroup ref="endCondition" />
+			<xsd:attributeGroup ref="type" />
+			<xsd:attributeGroup ref="actionOrder" />
+			<xsd:attributeGroup ref="count">
+				<xsd:annotation>
+					<xsd:documentation>
+						<h:div class="specific">Number of times the action should be repeated.
+						</h:div>
+					</xsd:documentation>
+				</xsd:annotation>
+			</xsd:attributeGroup>
+			<xsd:attributeGroup ref="ref" />
+		</xsd:complexType>
+
+	</xsd:element>
+</xsd:schema>

File src/main/resources/org/xmlcml/graphics/styles/ajc/basic.xml

+<semanticDocument debug="true">
+  <documentIterator max="999" skipIfExists="raw">
+    <documentActionList outDir="${d.rawDirectory}/../out" skipIfExists="${d.outDir}">
+      <fontManager name="simpleFont" filename="src/main/resources/org/xmlcml/graphics/font/simpleFont.xml"/>
+      <documentReader filename="${d.rawDirectory}" format="svgPages"/>
+      <pageSelector/>
+      <pageIterator>
+        <pageAnalyzer timeout="15000" maxMbyte="5.0">
+          <pageVariable name="p.root" value="${d.outDir}/${p.page}"/>
+          <pageWriter filename="${p.root}start.svg"/>
+          <pageNormalizer 
+              denormalizeG="true"
+              normalizeHighCodePoints="true"
+              denormalizeFontSizes="true"
+              removeUnwantedAttributes="true" 
+              applyAndRemoveCumulativeTransforms="true"
+              removeImageData="true"
+              cleanSVGStyles="true"
+              canRotateLandscape="true"
+              removeUnitTransforms="true"
+              formatDecimalPlaces="3"/>
+          <pageStyleProcessor translateClipPathsToPhysicalStyles="true" removeDefs="true"/> 
+          <pageWriter filename="target/pageStyle1clipPaths.svg"/>              
+          <pathNormalizer 
+              removeDuplicatePaths="true" 
+              createHigherPrimitives="true" 
+              removeEmptySVGG="true" 
+              minLinesInPolyline="8"
+              joinPolylines="true" 
+              enforceVisibility="true"
+              />
+          <pageWriter filename="target/pathNorm99.svg"/>              
+          <whitespaceChunker depth="3"/>
+          <pageWriter filename="target/whiteSpace99.svg"/>              
+          <deleteNodes xpath="//svg:rect[@title='leafNodes']"/>
+          <pageWriter filename="target/textChunker99.svg"/>              
+          <chunkAnalyzer xpath="//svg:g[@LEAF]" 
+              subSup="true" 
+              removeNumericTSpans="true"/>
+              splitAtSpaces="true"/>
+          <pageWriter filename="target/subSup.svg"/>
+          <deleteNodes xpath="//@*[local-name()='oldFontSize' or local-name()='physicalStyle']"/>
+          <pageNormalizer 
+              removeUnitTransforms="true"
+              formatDecimalPlaces="3"/>
+          <pageWriter filename="${p.root}end.svg"/>
+        </pageAnalyzer>
+      </pageIterator>
+      <documentWriter filename="${d.outDir}" format="htmlMenu" regex="((.*chunk.*\.xml)|(.*figure.*\.svg))"/>
+    </documentActionList> 
+  </documentIterator>
+</semanticDocument>
+
+
+

File src/main/resources/org/xmlcml/graphics/styles/basic.xml

           <pageVariable name="p.root" value="${d.outDir}/${p.page}"/>
           <pageWriter filename="${p.root}start.svg"/>
           <pageNormalizer 
+              denormalizeG="true"
               normalizeHighCodePoints="true"
               denormalizeFontSizes="true"
               removeUnwantedAttributes="true" 

File src/test/java/org/xmlcml/graphics/cml/AMIEdgeTest.java

 		SVGLine line = new SVGLine(new Real2(0.,0.), new Real2(1., 2.));
 		AMIEdge edge = graph.createEdge(line);
 		edge.setId("e00");
-		Assert.assertEquals("no nodes", "e00 null 1 null", edge.toString());
+		Assert.assertEquals("no nodes", "e00 {null} {1 line: from((0.0,0.0)) to((1.0,2.0)) v((1.0,2.0))} {null}", edge.toString());
 	}
 	
 	@Test
 		SVGLine line = new SVGLine(point, new Real2(3., 5.));
 		AMIEdge edge = graph.createEdge(line);
 		edge.setId("e10");
-		AMINode newNode = AMINode.createAndAddNewNode(nodeList, point);
+		AMINode newNode = AMINode.createAndAddNewNode(graph, nodeList, point);
 		edge.addNodeToEdgeAndEdgeToNode(0, newNode);
-		Assert.assertEquals("no nodes", "e10 node: node0 (1.0,2.0) 1 null", edge.toString());
+		Assert.assertEquals("no nodes", "e10 {node: node0 (1.0,2.0)} {1 line: from((1.0,2.0)) to((3.0,5.0)) v((2.0,3.0))} {null}", edge.toString());
 	}
 	
 	@Test
 		SVGLine line = new SVGLine(point0, point1);
 		AMIEdge edge = new AMIEdge(graph, line);
 		edge.setId("e11");
-		AMINode newNode = AMINode.createAndAddNewNode(nodeList, point0);
+		AMINode newNode = AMINode.createAndAddNewNode(graph, nodeList, point0);
 		edge.addNodeToEdgeAndEdgeToNode(0, newNode);
-		AMINode newNode1 = AMINode.createAndAddNewNode(nodeList, point1);
+		AMINode newNode1 = AMINode.createAndAddNewNode(graph, nodeList, point1);
 		edge.addNodeToEdgeAndEdgeToNode(1, newNode1);
-		Assert.assertEquals("no nodes", "e11 node: node0 (1.0,2.0) 1 node: node1 (3.0,5.0)", edge.toString());
+		Assert.assertEquals("no nodes", "e11 {node: node0 (1.0,2.0)} {1 line: from((1.0,2.0)) to((3.0,5.0)) v((2.0,3.0))} {node: node1 (3.0,5.0)}", edge.toString());
 	}
 	
 	@Test
 		SVGLine line = new SVGLine(point0, point1);
 		AMIEdge edge = new AMIEdge(graph, line);
 		edge.setId("e10");
-		AMINode node0 = AMINode.createAndAddNewNode(nodeList, point0);
+		AMINode node0 = AMINode.createAndAddNewNode(graph, nodeList, point0);
 		edge.addNodeToEdgeAndEdgeToNode(0, node0);
 		edge.addMissingNodesByExtension(10.);
 		AMINode node1 = edge.getNode(1);
 		SVGLine line = new SVGLine(point0, point1);
 		AMIEdge edge = new AMIEdge(graph, line);
 		edge.setId("e11");
-		AMINode newNode = AMINode.createAndAddNewNode(nodeList, point0);
+		AMINode newNode = AMINode.createAndAddNewNode(graph, nodeList, point0);
 		edge.addNodeToEdgeAndEdgeToNode(0, newNode);
-		AMINode newNode1 = AMINode.createAndAddNewNode(nodeList, point1);
+		AMINode newNode1 = AMINode.createAndAddNewNode(graph, nodeList, point1);
 		edge.addNodeToEdgeAndEdgeToNode(1, newNode1);
 		edge.addMissingNodesByExtension(10.);
-		Assert.assertEquals("no nodes", "e11 node: node0 (1.0,2.0) 1 node: node1 (3.0,5.0)", edge.toString());
+		Assert.assertEquals("no nodes ", "e11 {node: node0 (1.0,2.0)} {1 line: from((1.0,2.0)) to((3.0,5.0)) v((2.0,3.0))} {node: node1 (3.0,5.0)}", edge.toString());
 	}
 	
 }

File src/test/java/org/xmlcml/graphics/cml/AMIGraphTest.java

 import org.junit.Test;
 import org.xmlcml.euclid.Real2;
 import org.xmlcml.graphics.svg.SVGLine;
+import org.xmlcml.graphics.svg.SVGText;
 
 public class AMIGraphTest {
 
 	private static final Logger LOG = Logger.getLogger(AMIGraph.class);
 	
 	List<Real2> pointList;
+	List<SVGText> textList;
 	List<SVGLine> isopentane;
-	List<SVGLine> methylisopropylamine;
+	List<SVGLine> hydroxymethylisopropylamine;
 	
 	@Before
 	public void setup() {
 		pointList = new ArrayList<Real2>();
-		pointList.add(new Real2(0., 0.));
-		pointList.add(new Real2(5., 10.));
-		pointList.add(new Real2(5., -10.));
-		pointList.add(new Real2(-15., 0.));
-		pointList.add(new Real2(-20., 10.));
-		pointList.add(new Real2(-12., 0.));
-		pointList.add(new Real2(-16., 2.));
+		pointList.add(new Real2(0., 0.)); // 0
+		pointList.add(new Real2(5., 10.)); // 1
+		pointList.add(new Real2(5., -10.)); // 2
+		pointList.add(new Real2(-15., 0.)); // 3
+		pointList.add(new Real2(-20., 10.)); // 4
+		pointList.add(new Real2(-12., 0.)); // 5
+		pointList.add(new Real2(-16., 2.)); // 6
+		pointList.add(new Real2(4., 8.)); // 7
 		SVGLine line01 = new SVGLine(pointList.get(0), pointList.get(1));
+		SVGLine line01a = new SVGLine(pointList.get(0), pointList.get(7));
 		SVGLine line02 = new SVGLine(pointList.get(0), pointList.get(2));
 		SVGLine line03 = new SVGLine(pointList.get(0), pointList.get(3));
 		SVGLine line03a = new SVGLine(pointList.get(0), pointList.get(5));
 		SVGLine line34 = new SVGLine(pointList.get(3), pointList.get(4));
-		SVGLine line34a = new SVGLine(pointList.get(3), pointList.get(6));
+		SVGLine line34a = new SVGLine(pointList.get(6), pointList.get(4));
 		line01.setId("l01");
+		line01a.setId("l01a");
 		line02.setId("l02");
 		line03.setId("l03");
 		line03a.setId("l03a");
 		line34.setId("l34");
-		line34a.setId("l3a");
+		line34a.setId("l34a");
+//		line34a.debug("l34a");
+		textList = new ArrayList<SVGText>();
+		Double f = 4.;
+		Double f2 = f/2.;
+		SVGText nText = new SVGText(pointList.get(3).plus(new Real2(-f2, f2)), "N");
+		nText.setFontSize(f);
+		nText.setId("n");
+		textList.add(nText);
+		SVGText oText = new SVGText(pointList.get(1).plus(new Real2(-f2, f2)), "O");
+		oText.setFontSize(f);
+		oText.setId("o");
+		textList.add(oText);
+		
 		
 		isopentane = new ArrayList<SVGLine>();
 		isopentane.add(line01);
 		isopentane.add(line03);
 		isopentane.add(line34);
 		
-		methylisopropylamine = new ArrayList<SVGLine>();
-		methylisopropylamine.add(line01);		
-		methylisopropylamine.add(line02);		
-		methylisopropylamine.add(line03a);		
-		methylisopropylamine.add(line34a);		
+		hydroxymethylisopropylamine = new ArrayList<SVGLine>();
+		hydroxymethylisopropylamine.add(line01a);		
+		hydroxymethylisopropylamine.add(line02);		
+		hydroxymethylisopropylamine.add(line03a);		
+		hydroxymethylisopropylamine.add(line34a);		
 	}
 	
 	@Test
 	}
 
 	@Test
-	@Ignore // FIXME
+	@Ignore // probably not best approach
 	public void testAddNodesByExtension() {
 		AMIGraph graph = new AMIGraph();
-		graph.createEdges(methylisopropylamine);
+		graph.createEdges(hydroxymethylisopropylamine);
 		List<AMIEdge> edges = graph.getEdges();
 		graph.createIntersectionNodes(AMIGraph.NODE_SPREAD);
-		LOG.debug("graph "+graph.toString());
 		List<AMIEdge> nullNodeEdges = graph.createListOfNullNodeEdges();
 		Double length = graph.getAverageEdgeLengthBetweenNodes();
-		Assert.assertNotNull("length", length);
-		for (AMIEdge edge : nullNodeEdges) {
-			edge.addMissingNodesByExtension(length);
-		}
+		Assert.assertNull("length", length);
+//		for (AMIEdge edge : nullNodeEdges) {
+//			edge.addMissingNodesByExtension(length);
+//		}
+	}
+
+	@Test
+	public void testAddTextNodes() {
+		AMIGraph graph = new AMIGraph();
+		graph.createEdges(hydroxymethylisopropylamine);
+		List<AMIEdge> edges = graph.getEdges();
+		graph.createIntersectionNodes(AMIGraph.NODE_SPREAD);
+		graph.addTextAsNodes(textList);
 	}
 
 }

File src/test/java/org/xmlcml/graphics/misc/SchemaTest.java

+package org.xmlcml.graphics.misc;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+
+import org.apache.xerces.parsers.DOMParser;
+import org.apache.xml.utils.DefaultErrorHandler;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+public class SchemaTest {
+	
+	@Test
+	public void dummy() {
+		// to keep maven happy
+	}
+	// Why can't I validate XML? these tests should work
+	
+	@Test
+	public void xomValidationTest() {
+		String filename = "src/test/resources/org/xmlcml/graphics/misc/test1.xml";
+		try {
+			XMLReader xerces = XMLReaderFactory
+					.createXMLReader("org.apache.xerces.parsers.SAXParser");
+			xerces.setFeature(
+					"http://apache.org/xml/features/validation/schema", true);
+
+			nu.xom.Builder parser = new nu.xom.Builder(xerces, true);
+			parser.build(new File(filename));
+			System.out.println(filename + " is schema valid.");
+		} catch (SAXException ex) {
+			System.out.println("Could not load Xerces.");
+			System.out.println(ex.getMessage());
+		} catch (nu.xom.ParsingException ex) {
+			System.out.println("doc" + " is not schema valid.");
+			System.out.println(ex.getMessage());
+			System.out.println(" at line " + ex.getLineNumber() + ", column "
+					+ ex.getColumnNumber());
+		} catch (IOException ex) {
+			System.out.println("Due to an IOException, Xerces could not check "
+					+ filename);
+		}
+	}
+
+	@Test
+	@Ignore
+	public void testValidationW3C() throws Exception {
+		javax.xml.parsers.DocumentBuilderFactory factory = 
+				javax.xml.parsers.DocumentBuilderFactory.newInstance();
+
+		factory.setValidating(true);
+
+		factory.setAttribute(
+				"http://java.sun.com/xml/jaxp/properties/schemaLanguage",
+				"http://www.w3.org/2001/XMLSchema");
+		factory.setAttribute(
+				"http://java.sun.com/xml/jaxp/properties/schemaSource",
+				"src/test/resources/org/xmlcml/graphics/misc/test1.xsd");
+		org.w3c.dom.Document doc = null;
+		try {
+			javax.xml.parsers.DocumentBuilder parser = factory.newDocumentBuilder();
+			doc = parser.parse("src/test/resources/org/xmlcml/graphics/misc/test1.xsd");
+		} catch (Exception e) {
+			throw e;
+		}
+	}
+	
+	@Test
+	@Ignore
+	  public void testDOMParser() { 
+	      try
+	      {
+	           DOMParser parser = new DOMParser();
+	           parser.setFeature("http://xml.org/sax/features/validation", true);
+	           parser.setProperty(
+	             "http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation", 
+	                        "test1.xsd");
+//	           ErrorChecker errors = new ErrorChecker();
+//	           parser.setErrorHandler(errors);
+	           parser.parse("src/test/resources/org/xmlcml/graphics/misc/test1.xml");
+	     }
+	     catch (Exception e) 
+	     {
+	    	 throw new RuntimeException("failed:", e);
+	     }
+	  }
+	
+	@Test
+	@Ignore
+	public void testValidatingParser() {
+		try {
+			String SCHEMA_PATH = "test1.xsd";
+			String SCHEMA_FILE = "src/test/resources/org/xmlcml/graphics/misc/test1.xsd";
+			InputStream SCHEMA_STREAM = getClass().getResourceAsStream(SCHEMA_PATH);
+			StreamSource SCHEMA_SOURCE = new StreamSource(SCHEMA_STREAM);
+			File xmlFile = new File("src/test/resources/org/xmlcml/graphics/misc/test1.xml");
+			
+			final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+			factory.setNamespaceAware(true);
+			SchemaFactory sf = 
+			        SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+//			Schema schema = sf.newSchema(SCHEMA_SOURCE);
+			Schema schema = sf.newSchema(new File(SCHEMA_FILE));
+			factory.setSchema(schema);
+			DocumentBuilder builder = factory.newDocumentBuilder();
+			builder.setErrorHandler(new DefaultErrorHandler());	
+			builder.parse(xmlFile);
+		} catch (Exception e) {
+			e.printStackTrace();
+			Assert.fail("failed: "+e);
+		}
+	}
+	
+	@Test
+	@Ignore
+	public void testValidatingParser1() throws Exception {
+		String SCHEMA_PATH = "test1.xsd";
+		InputStream SCHEMA_STREAM = getClass().getResourceAsStream(SCHEMA_PATH);
+		StreamSource SCHEMA_SOURCE = new StreamSource(SCHEMA_STREAM);
+		SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+		Schema schema = sf.newSchema(SCHEMA_SOURCE);
+		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+		factory.setSchema(schema);
+	}
+}

File src/test/java/org/xmlcml/graphics/paths/PathAnalyzerTest.java

+package org.xmlcml.graphics.paths;
+
+import junit.framework.Assert;
+
+
+import org.junit.Test;
+import org.xmlcml.euclid.Real2;
+//import org.xmlcml.euclid.testutil.Real2TestUtil;
+import org.xmlcml.graphics.svg.SVGCircle;
+
+public class PathAnalyzerTest {
+
+	static double EPS = 1E-8;
+	@Test
+	public void testFindCircleFrom3Points() {
+		
+		Real2 p1 = new Real2(1., 0.);
+		Real2 p2 = new Real2(0., 1.);
+		Real2 p3 = new Real2(-1., 0.);
+		SVGCircle circle = PathAnalyzer.findCircleFrom3Points(p1, p2, p3, 1e-10);
+		Assert.assertNotNull(circle);
+		Assert.assertEquals("rad", 1.0, circle.getRad(), EPS);
+//		Real2TestUtil.assertEquals("center", new Real2(0., 0.), circle.getCXY(), EPS);
+	}
+}

File src/test/java/org/xmlcml/graphics/pdf2svg/GeneralPathTest.java

+package org.xmlcml.graphics.pdf2svg;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.PathIterator;
+
+import org.apache.log4j.Logger;
+import org.junit.Test;
+
+public class GeneralPathTest {
+
+	static final Logger LOG = Logger.getLogger(GeneralPathTest.class);
+	@Test
+	public void testGeneralPath() {
+		GeneralPath generalPath = new GeneralPath();
+		generalPath.moveTo(1.0d, 2.0d);
+		generalPath.lineTo(3.0d, 4.0d);
+		generalPath.quadTo(5.0d, 6.0d, 7.0d, 8.0d);
+		generalPath.curveTo(9.0d, 10.0d, 11.0d, 12.0d, 13.0d, 14.0d);
+		generalPath.closePath();
+		AffineTransform at = new AffineTransform();
+		LOG.debug("AT "+at);
+		PathIterator pathIterator = generalPath.getPathIterator(at);
+		double[] coords = new double[6];
+		while (!pathIterator.isDone()) {
+			int segType = pathIterator.currentSegment(coords);
+			if (PathIterator.SEG_MOVETO == segType) {
+				LOG.debug("MOVE "+coords[0]+"/"+coords[1]);
+			} else if (PathIterator.SEG_LINETO == segType) {
+				LOG.debug("LINE "+coords[0]+"/"+coords[1]);
+			} else if (PathIterator.SEG_QUADTO == segType) {
+				LOG.debug("QUAD "+coords[0]+"/"+coords[1]+"/"+coords[2]+"/"+coords[3]);
+			} else if (PathIterator.SEG_CUBICTO == segType) {
+				LOG.debug("CUBIC "+coords[0]+"/"+coords[1]+"/"+coords[2]+"/"+coords[3]+"/"+coords[4]+"/"+coords[5]);
+			} else if (PathIterator.SEG_CLOSE == segType) {
+				LOG.debug("CLOSE ");
+			} else {
+				LOG.error("UNKNOWN "+segType);
+			}
+			pathIterator.next();
+		}
+	}
+}

File src/test/resources/org/xmlcml/graphics/misc/test1.xml

+<?xml version="1.0" encoding="UTF-8"?>
+<p:customers 
+    xmlns:p="http://www.xml-cml.org/schema/ami2" 
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
+  xsi:schemaLocation="http://www.xml-cml.org/schema/ami2 src/test/resources/org/xmlcml/graphics/misc/test1.xsd">
+  <p:customer>
+    <p:name salutation="Mr."/>
+    <p:age>25</p:age>
+    <p:discount>6</p:discount>
+  </p:customer>
+  <p:customer>
+    <p:name salutation="Ms."/>
+    <p:age>35</p:age>
+    <p:discount>10</p:discount>
+  </p:customer>
+</p:customers>

File src/test/resources/org/xmlcml/graphics/misc/test1.xsd

+<?xml version="1.0"?>
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+	
+	<xsd:complexType name="foo">
+		<xsd:attribute name="bar" type="xsd:string" />
+	</xsd:complexType>
+	
+</xsd:schema>

File src/test/resources/org/xmlcml/graphics/misc/test1a.xsd

+<xs:schema 
+    xmlns:xs="http://www.w3.org/2001/XMLSchema"
+	elementFormDefault="qualified" 
+	targetNamespace="http://www.xml-cml.org/schema/ami2"
+	xmlns:p="http://www.xml-cml.org/schema/ami2"
+	>
+
+	<xs:complexType name="customerName">
+		<xs:attribute name="salutation" type="xs:string" default="Mr."></xs:attribute>
+	</xs:complexType>
+
+	<xs:simpleType name="customerAge">
+		<xs:restriction base="xs:integer">
+			<xs:minInclusive value="18"></xs:minInclusive>
+			<xs:maxInclusive value="60"></xs:maxInclusive>
+		</xs:restriction>
+	</xs:simpleType>
+
+	<xs:simpleType name="customerdiscount">
+		<xs:restriction base="xs:integer">
+			<xs:minInclusive value="5"></xs:minInclusive>
+			<xs:maxInclusive value="30"></xs:maxInclusive>
+		</xs:restriction>
+	</xs:simpleType>
+
+	<xs:complexType name="customer">
+		<xs:sequence>
+			<xs:element name="name" type="p:customerName"></xs:element>
+			<xs:element name="age" type="p:customerAge"></xs:element>
+			<xs:element name="discount" type="p:customerdiscount"></xs:element>
+		</xs:sequence>
+	</xs:complexType>
+
+	<xs:element name="customers">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:element name="customer" type="p:customer"></xs:element>
+				<xs:element name="customer" type="p:customer"></xs:element>
+				<xs:element name="customer" type="p:customer"></xs:element>
+			</xs:sequence>
+		</xs:complexType>
+	</xs:element>
+-->
+	
+</xs:schema>