All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.mihosoft.vrl.v3d.svg.SVGLoad Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
package eu.mihosoft.vrl.v3d.svg;



import org.apache.batik.anim.dom.SAXSVGDocumentFactory;
import org.apache.batik.anim.dom.SVGOMGElement;
import org.apache.batik.anim.dom.SVGOMImageElement;
import org.apache.batik.anim.dom.SVGOMPathElement;
import org.apache.batik.anim.dom.SVGOMPolylineElement;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import javax.vecmath.Matrix4d;

import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.DocumentLoader;
import org.apache.batik.bridge.GVTBuilder;
import org.apache.batik.bridge.UserAgent;
import org.apache.batik.bridge.UserAgentAdapter;
import org.apache.batik.dom.svg.SVGItem;
import org.apache.batik.util.XMLResourceDescriptor;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.svg.SVGImageElement;
import org.w3c.dom.svg.SVGPathSegList;
import org.w3c.dom.svg.SVGPointList;

import com.piro.bezier.BezierPath;
import eu.mihosoft.vrl.v3d.CSG;
import eu.mihosoft.vrl.v3d.Edge;
import eu.mihosoft.vrl.v3d.Extrude;
import eu.mihosoft.vrl.v3d.Polygon;
import eu.mihosoft.vrl.v3d.Transform;
import eu.mihosoft.vrl.v3d.Vector3d;
import javafx.scene.paint.Color;

// CSG.setDefaultOptType(CSG.OptType.CSG_BOUND);
/**
 * Responsible for converting all SVG path elements into MetaPost curves.
 */
public class SVGLoad {
	private static final String PATH_ELEMENT_NAME = "path";
	private static final String GROUP_ELEMENT_NAME = "g";
	private Document svgDocument;
	boolean hp = true;
	private HashMap> polygonByLayers = null;
	private HashMap> csgByLayers = new HashMap>(); 
	private HashMap colors=new HashMap<>();
//	private ArrayList sections = null;
//	private ArrayList holes = null;
//
//	private List polygons = null;
	private ISVGLoadProgress progress = null;
	private double thickness;
	private boolean negativeThickness = false;
	private double height = 0;
	private double width = 0;
	private Double scale=null;
	private HashMap units=new HashMap<>();
//	static {
//		units.put("mm", (1/SVGExporter.Scale));
//		units.put("px", 1.0);
//		units.put("cm", units.get("mm")/10.0);
//		units.put("in", units.get("mm")/25.4);
//		units.put("ft", units.get("in")/12.0);
//		units.put("m", units.get("mm")/1000.0);
//
//	}
	
	private  double toPx(String value) {

		for(String key : units.keySet()) {
			if(value.endsWith(key)) {
				String []split = value.split(key);
				if(key.contentEquals("m")&& split.length>1) {
					// meters but not meters units
					break;
				}
				//System.out.println("Units set to "+key+" for "+value);
				return Double.parseDouble(split[0])/ units.get(key);
			}
		}
		return Double.parseDouble(value);
	}
	private void setScale(double value) {
		
		scale=value;
		if(scale.isInfinite()||scale.isNaN())
			throw new RuntimeException("Scale must be real number");
		units.put("mm", (1/getScale()));
		units.put("px", 1.0);
		units.put("cm", units.get("mm")/10.0);
		units.put("in", units.get("mm")/25.4);
		units.put("ft", units.get("in")/12.0);
		units.put("m", units.get("mm")/1000.0);
	}
	private Double getScale() {
		return scale.doubleValue();
	}
	
	private  double toMM(String value) {
		Double px= toPx(value);
		return px*1/getScale();
	}
	private static ISVGLoadProgress progressDefault = new ISVGLoadProgress() {

		@Override
		public void onShape(CSG newShape) {
			// TODO Auto-generated method stub

		}
	};

	public void setHolePolarity(boolean p) {
		hp = p;
	}

	/**
	 * Responsible for converting an SVG path element to MetaPost. This will convert
	 * just the bezier curve portion of the path element, not its style. Typically
	 * the SVG path data is provided from the "d" attribute of an SVG path node.
	 */
	class MetaPostPath2 {
		private SVGOMPathElement pathElement;
		private String transform;

		/**
		 * Use to create an instance of a class that can parse an SVG path element to
		 * produce MetaPost code.
		 *
		 * @param pathNode
		 *            The path node containing a "d" attribute (output as MetaPost
		 *            code).
		 */
		public MetaPostPath2(Node pathNode) {
			setPathNode(pathNode);
		}

		/**
		 * Converts this object's SVG path to a MetaPost draw statement.
		 * 
		 * @return A string that represents the MetaPost code for a path element.
		 */
		public String toCode() {
			String sb = "";
			SVGOMPathElement pathElement = getPathElement();
			SVGPathSegList pathList = pathElement.getNormalizedPathSegList();
			// String offset = pathElement.getOwnerSVGElement();

			int pathObjects = pathList.getNumberOfItems();
			/*
			 * sb.append( "M "+offset .replaceAll("translate", "") .replaceAll("(", "")
			 * .replaceAll(")", "") +"\n");
			 */
			// sb.append( "//"+getId()+"\n");

			for (int i = 0; i < pathObjects; i++) {
				SVGItem item = (SVGItem) pathList.getItem(i);
				String itemLine = String.format("%s%n", item.getValueAsString());
				sb += itemLine;
			}

			return sb.toString();
		}

		/**
		 * Typecasts the given pathNode to an SVGOMPathElement for later analysis.
		 * 
		 * @param pathNode
		 *            The path element that contains curves, lines, and other SVG
		 *            instructions.
		 */
		private void setPathNode(Node pathNode) {
			this.pathElement = (SVGOMPathElement) pathNode;
		}

		/**
		 * Returns an SVG document element that contains path instructions (usually for
		 * drawing on a canvas).
		 * 
		 * @return An object that contains a list of items representing pen movements.
		 */
		private SVGOMPathElement getPathElement() {
			return this.pathElement;
		}
	}

	/**
	 * Creates an SVG Document given a URI.
	 *
	 * @param uri
	 *            Path to the file.
	 * @throws Exception
	 *             Something went wrong parsing the SVG file.
	 */
	public SVGLoad(URI uri) throws IOException {
		setSVGDocument(createSVGDocument(uri));
	}

	/**
	 * Creates an SVG Document given a URI.
	 *
	 * @param f
	 *            Path to the file.
	 * @throws Exception
	 *             Something went wrong parsing the SVG file.
	 */
	public SVGLoad(File f) throws IOException {
		setSVGDocument(createSVGDocument(f.toURI()));
	}

	/**
	 * Creates an SVG Document String of SVG data.
	 *
	 * @param data
	 *            Contents of an svg file
	 * @throws Exception
	 *             Something went wrong parsing the SVG file.
	 */
	public SVGLoad(String data) throws IOException {
		File tmpsvg = new File(System.getProperty("java.io.tmpdir") + "/" + Math.random());
		tmpsvg.createNewFile();
		FileWriter fw = new FileWriter(tmpsvg.getAbsoluteFile());
		BufferedWriter bw = new BufferedWriter(fw);
		bw.write(data);
		bw.close();
		setSVGDocument(createSVGDocument(tmpsvg.toURI()));
		tmpsvg.deleteOnExit();
	}

	public ArrayList extrude(double thickness) throws IOException {

		return extrude(thickness, 0.005);

	}

	public static ArrayList extrude(File f, double thickness) throws IOException {
		return new SVGLoad(f.toURI()).extrude(thickness);

	}

	/**
	 * This function will create a list of polygons that can be exported back to an
	 * SVG
	 * 
	 * @param f
	 *            the file containing the SVG data
	 * @return
	 * @throws IOException
	 */
	public static HashMap> toPolygons(File f) throws IOException {
		return new SVGLoad(f.toURI()).toPolygons();

	}

	public HashMap> toPolygons(double resolution) {
		if(polygonByLayers==null)
			try {
				loadAllGroups(resolution, new Transform());
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		return getPolygonByLayers();
	}

	public HashMap> toPolygons() {
		return toPolygons(0.001);
	}

	public static ArrayList extrude(File f, double thickness, double resolution) throws IOException {
		return new SVGLoad(f.toURI()).extrude(thickness, resolution);
	}

	public static ArrayList extrude(URI uri, double thickness) throws IOException {

		return new SVGLoad(uri).extrude(thickness);

	}

	public static ArrayList extrude(URI uri, double thickness, double resolution) throws IOException {
		return new SVGLoad(uri).extrude(thickness, resolution);
	}

	private void loadAllGroups(double resolution, Transform startingFrame) {
		
		NodeList pn = getSVGDocument().getDocumentElement().getChildNodes();// .getElementsByTagName("g");
		try {
			String hval = getSVGDocument().getDocumentElement().getAttribute("height");
			String wval = getSVGDocument().getDocumentElement().getAttribute("width");
			String viewbox = getSVGDocument().getDocumentElement().getAttribute("viewBox");
			double viewW = Double.parseDouble(viewbox.split(" ")[2]);
			setScale( 1);// use to compute bounds
			height = toMM(hval);
			width = toMM(wval);
			double value =viewW/width;
			//System.out.println("Page size height = "+height+" width ="+width+" with scale "+(int)(value*25.4)+" DPI ");
			setScale( value);
		} catch (Throwable t) {
			t.printStackTrace();
			height = 0;
			width = 0;
			setScale(  3.543307); // Assume 90 DPI and mm
		}
		// println "Loading groups from "+pn.getClass()
		int pnCount = pn.getLength();
		for (int j = 0; j < pnCount; j++) {
			Node item = pn.item(j);
			//System.out.println("\tTOP LEVEL :"+item);
			if (SVGOMGElement.class.isInstance(item)) {
				
				SVGOMGElement element = (SVGOMGElement) item;
				loadGroup(element, resolution, startingFrame,null);
			} if(SVGOMPathElement.class.isInstance(item) || SVGOMImageElement.class.isInstance(item)) {
				try {
					loadPath(item, resolution, startingFrame,"TOP");
				} catch (Throwable t) {

					t.printStackTrace();
				}
			}
		}

	}

	private void loadGroup(SVGOMGElement element, double resolution, Transform startingFrame, String encapsulatingLayer) {
		Node transforms = element.getAttributes().getNamedItem("transform");
		Transform newFrame = getNewframe(startingFrame, transforms);
		String layername;
		
		try {
			layername=element.getAttributeNS("http://www.inkscape.org/namespaces/inkscape", "label");
			if(layername==null|| layername.length()==0)
				throw new RuntimeException();
		}catch (Throwable t) {
			layername=null;
		}
		if(layername==null) {
			layername=encapsulatingLayer;
		}else {
			//System.out.println("Updated to layer "+layername+" from "+encapsulatingLayer);
		}
		//System.out.println("\tGroup " + element.getAttribute("id") +"\n\t inkscape name: "+layername+ " \n\t root " + newFrame.getX() + " " + newFrame.getY());
		
		NodeList children = element.getChildNodes();
		for (int i = 0; i < children.getLength(); i++) {
			Node n = children.item(i);
			if (SVGOMGElement.class.isInstance(n)) {
				loadGroup((SVGOMGElement) n, resolution, newFrame,layername);
			} else {
				//System.out.println("\tNot group:"+n);
				try {
					loadPath(n, resolution, newFrame,layername);
				} catch (Throwable t) {

					t.printStackTrace();
				}
			}
		}
	}

	private Transform getNewframe(Transform startingFrame, Node transforms) {
		if (transforms == null)
			return startingFrame;
		Transform newFrame = new Transform().apply(startingFrame);
		String transformValue = transforms.getNodeValue();
		//System.out.println("\tApply " + transformValue + " root " + startingFrame.getX() + " " + startingFrame.getY());
		if (transformValue.contains("translate")) {
			String[] transformValues = transformValue.replaceAll("translate", "").replaceAll("\\(", "")
					.replaceAll("\\)", "").split("\\,");
			newFrame.apply(new Transform().translate(toPx(transformValues[0]),
					toPx(transformValues[1]), 0));

		}else if (transformValue.contains("rotate")) {
			String[] rotvals = transformValue.replaceAll("rotate", "").replaceAll("\\(", "")
					.replaceAll("\\)", "").split("\\,");
			newFrame= startingFrame.inverse()
					.apply(new Transform().rotZ(-Double.parseDouble(rotvals[0])))
					.apply(startingFrame);

		}  else if (transformValue.contains("scale")) {
			String[] transformValues = transformValue.replaceAll("scale", "").replaceAll("\\(", "")
					.replaceAll("\\)", "").split("\\,");
			// System.out.println(id.getNodeValue() + " " + transformValues);
			double scalex = toPx(transformValues[0]);
			double scaley = toPx(transformValues.length==2?transformValues[1]:transformValues[0]);
			newFrame.scale(scalex, scaley, 1);

		} else if (transformValue.contains("matrix")) {
			String[] transformValues = transformValue.replaceAll("matrix", "").replaceAll("\\(", "")
					.replaceAll("\\)", "").split("\\,");
			// System.out.println("Matrix found " +new
			// ArrayList<>(Arrays.asList(transformValues)));
			double a = toPx(transformValues[0]);
			double b = toPx(transformValues[1]);
			double c = toPx(transformValues[2]);
			double d = toPx(transformValues[3]);
			double e = toPx(transformValues[4]);
			double f = toPx(transformValues[5]);
			double elemenents[] = { a, c, 0, e, b, d, 0, f, 0, 0, 1, 0, 0, 0, 0, 1 };
			newFrame.apply(new Transform(new Matrix4d(elemenents)));

		}
		return newFrame;
	}

	// SVGOMGElement
	private void loadPath(Node pathNode, double resolution, Transform startingFrame, String encapsulatingLayer) {
		Transform newFrame;
		// NodeList pathNodes = element.getElementsByTagName("path");
		// Node transforms = element.getAttributes().getNamedItem("transform");
		if (pathNode != null) {
			// System.out.println("\tPath
			// "+pathNode.getAttributes().getNamedItem("id").getNodeValue());
				//System.out.println("Path loading "+pathNode);
				

				newFrame = startingFrame;
				try {
					Node transforms = pathNode.getAttributes().getNamedItem("transform");
					newFrame = getNewframe(startingFrame, transforms);
				}catch(Exception ex) {
					// no transform
				}
				try {
					if(SVGOMPathElement.class.isInstance(pathNode)) {
//						NamedNodeMap attribs = pathNode.getAttributes();
//						for(int i=0;i 1) {

					// println "Seperated complex: "
					loadSingle(sectionedPart, resolution, startingFrame,encapsulatingLayer, c);
				}
			}
		}
		// System.out.println("SVG has this many elements loaded: "+sections.size());
		// BowlerStudioController.setCsg(sections,null);
	}
	public static boolean isCCW(Polygon polygon) {
		double runningTotal=0;
		List edges = Edge.fromPolygon(polygon);
		for(Edge e:edges) {
			//runningTotal+=((e.getP1().pos.x-e.getP2().pos.x)*(e.getP1().pos.y-e.getP2().pos.y));
			runningTotal+=e.getP1().pos.x*e.getP2().pos.y;
			runningTotal-=e.getP2().pos.x*e.getP1().pos.y;
		}
		
		return runningTotal<0;
	}
	private void loadSingle(String code, double resolution, Transform startingFrame, String encapsulatingLayer, Color c) {
		// println code
		BezierPath path = new BezierPath();
		path.parsePathString(code);
		
		ArrayList p = path.evaluate();
		for (Vector3d point : p) {
			point.transform(startingFrame);
			point.transform(new Transform().scale((1.0 / getScale())));
			point.transform(new Transform().translate(0, -height, 0));
			point.transform(new Transform().rotZ(-180));
			point.transform(new Transform().rotY(180));
		}

		// System.out.println(" Path " + code);
		Polygon poly = Polygon.fromPoints(p);
		if(getPolygonByLayers()==null)
			setPolygonByLayers(new HashMap>());
		if (getPolygonByLayers().get(encapsulatingLayer) == null)
			getPolygonByLayers().put(encapsulatingLayer, new ArrayList());
		List  list = getPolygonByLayers().get(encapsulatingLayer);
		poly = Polygon.fromPoints(Extrude.toCCW(poly.getPoints()));
		if(c!=null)
			colors.put(poly, c);
		list.add(poly);

	}
	public List getLayers(){
		ArrayList layers= new ArrayList();
		if(getPolygonByLayers()==null) {
			toPolygons();
		}
		for(Object key:getPolygonByLayers().keySet().stream().sorted().toArray() )
			layers.add((String) key);
		return layers;
	}
	public CSG extrudeLayerToCSG(double t,String layer){
		CSG unionAll = CSG.unionAll(extrudeLayers(t,0.01,layer).get(layer));
		unionAll.setName(layer);
		
		return unionAll;
	}
	public ArrayList extrudeLayer(double t,String layer){
		return extrudeLayers(t,0.01,layer).get(layer);
	}
	public HashMap> extrudeLayers(double t){
		return extrudeLayers(t,0.01,null);
	}
	public HashMap> extrudeLayers(double t, double resolution, String targetLayer){
		this.thickness = t;

		if (thickness < 0) {
			thickness = -thickness;
			negativeThickness = true;
		} else {
			negativeThickness = false;
		}

		toPolygons(0.001);
		
		for(String key: getPolygonByLayers().keySet()) {
			if(targetLayer!=null)
				if(!targetLayer.contentEquals(key))
					continue;
			if(csgByLayers.get(key)==null) {
				csgByLayers.put(key, new ArrayList());
			}
			ArrayList parts = csgByLayers.get(key);
			parts.clear();
			for(Polygon p:getPolygonByLayers().get(key)) {
				CSG newbit;
				newbit = Extrude.getExtrusionEngine().extrude(new Vector3d(0, 0, thickness), p);
				if (negativeThickness) {
					newbit = newbit.toZMax();
				}
				if(colors.get(p)!=null) {
					newbit.setColor(colors.get(p));
				}
				parts.add(newbit);
			}
		}
		
		return csgByLayers;
	}

	public ArrayList extrude(double t, double resolution) throws IOException {


		HashMap> layers =extrudeLayers( t,  resolution,null);
		ArrayList all = new ArrayList();
		for(String key:layers.keySet()) {
			all.addAll(layers.get(key));
		}
		
		
		return all;
	}


	/**
	 * This will set the document to parse. This method also initializes the SVG DOM
	 * enhancements, which are necessary to perform SVG and CSS manipulations. The
	 * initialization is also required to extract information from the SVG path
	 * elements.
	 *
	 * @param document
	 *            The document that contains SVG content.
	 */
	public void setSVGDocument(Document document) {
		initSVGDOM(document);
		this.svgDocument = document;
	}

	/**
	 * Returns the SVG document parsed upon instantiating this class.
	 * 
	 * @return A valid, parsed, non-null SVG document instance.
	 */
	public Document getSVGDocument() {
		return this.svgDocument;
	}

	/**
	 * Enhance the SVG DOM for the given document to provide CSS- and SVG-specific
	 * DOM interfaces.
	 * 
	 * @param document
	 *            The document to enhance.
	 * @link http://wiki.apache.org/xmlgraphics-batik/BootSvgAndCssDom
	 */
	private void initSVGDOM(Document document) {
		UserAgent userAgent = new UserAgentAdapter();
		DocumentLoader loader = new DocumentLoader(userAgent);
		BridgeContext bridgeContext = new BridgeContext(userAgent, loader);
		bridgeContext.setDynamicState(BridgeContext.DYNAMIC);

		// Enable CSS- and SVG-specific enhancements.
		(new GVTBuilder()).build(bridgeContext, document);
	}

	/**
	 * Use the SAXSVGDocumentFactory to parse the given URI into a DOM.
	 * 
	 * @param uri
	 *            The path to the SVG file to read.
	 * @return A Document instance that represents the SVG file.
	 * @throws Exception
	 *             The file could not be read.
	 */
	private Document createSVGDocument(URI uri) throws IOException {
		String parser = XMLResourceDescriptor.getXMLParserClassName();
		SAXSVGDocumentFactory factory = new SAXSVGDocumentFactory(parser);
		return factory.createDocument(uri.toString());
	}

	public ISVGLoadProgress getProgress() {
		return progress;
	}

	public void setProgress(ISVGLoadProgress progress) {
		this.progress = progress;
	}

	public static ISVGLoadProgress getProgressDefault() {
		return progressDefault;
	}
	
	private HashMap> getPolygonByLayers() {
		return polygonByLayers;
	}
	private void setPolygonByLayers(HashMap> polygonByLayers) {
		this.polygonByLayers = polygonByLayers;
	}


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy