X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.scenegraph%2Fsrc%2Forg%2Fsimantics%2Fscenegraph%2Fg2d%2Fnodes%2FSVGNode.java;h=42ae2e4c93d34c9b8cdb48f502fb653a6975856f;hb=0a40f3664e55387464c9490bbbcd563fddefd451;hp=ec85fd75c86e7893a8b7341bcfde6641e380d244;hpb=2e21c89c81d449bcc2301b3cf3cce4f2cd403a60;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SVGNode.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SVGNode.java index ec85fd75c..42ae2e4c9 100644 --- a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SVGNode.java +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SVGNode.java @@ -17,6 +17,8 @@ import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; import java.lang.ref.WeakReference; import java.math.BigInteger; import java.net.URI; @@ -24,26 +26,42 @@ import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + import org.simantics.scenegraph.ExportableWidget.RasterOutputWidget; import org.simantics.scenegraph.LoaderNode; import org.simantics.scenegraph.ScenegraphUtils; import org.simantics.scenegraph.g2d.G2DNode; +import org.simantics.scenegraph.g2d.G2DRenderingHints; import org.simantics.scenegraph.utils.BufferedImage; import org.simantics.scenegraph.utils.G2DUtils; import org.simantics.scenegraph.utils.InitValueSupport; import org.simantics.scenegraph.utils.MipMapBufferedImage; import org.simantics.scenegraph.utils.MipMapVRamBufferedImage; +import org.simantics.scenegraph.utils.SVGPassthruShape; import org.simantics.scenegraph.utils.VRamBufferedImage; import org.simantics.scl.runtime.function.Function1; import org.simantics.scl.runtime.function.Function2; import org.simantics.utils.threads.AWTThread; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import com.kitfox.svg.RenderableElement; import com.kitfox.svg.SVGCache; import com.kitfox.svg.SVGDiagram; import com.kitfox.svg.SVGElement; @@ -64,16 +82,21 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { protected Point targetSize = null; protected Boolean useMipMap = true; protected Rectangle2D bounds = null; - + protected List assignments = new ArrayList(); - transient BufferedImage buffer = null; - transient String documentCache = null; - transient SVGDiagram diagramCache = null; - transient String dataHash = null; + protected transient BufferedImage buffer = null; + protected transient String documentCache = null; + protected transient SVGDiagram diagramCache = null; + protected transient String dataHash = null; static transient Map> bufferCache = new HashMap>(); + @Override + public void init() { + super.init(); + } + @Override public void cleanup() { cleanDiagramCache(); @@ -143,22 +166,34 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { if (data == null) return; // Not initialized - if (!data.equals(documentCache) || diagramCache == null || buffer == null) - initBuffer(g2d); - AffineTransform ot = null; if (!transform.isIdentity()) { ot = g2d.getTransform(); g2d.transform(transform); } - if (buffer != null) - buffer.paint(g2d); + if (g2d.getRenderingHint(G2DRenderingHints.KEY_SVG_PASSTHRU) == Boolean.TRUE) { + SVGPassthruShape.resetG2D(g2d); + String svg = assignments.isEmpty() ? data : applyAssigments(data, assignments); + if (svg != null) { + g2d.fill(new SVGPassthruShape(svg)); + } + } else { + if (!data.equals(documentCache) || diagramCache == null || buffer == null) + initBuffer(g2d); + + if (buffer != null) + buffer.paint(g2d); + } if (ot != null) g2d.setTransform(ot); } + protected int dynamicHash() { + return 0; + } + protected String parseSVG() { if (data == null) return null; @@ -173,7 +208,6 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { diagramCache = null; } - // Lets check for rootAssignment that contributes the whole SVG SVGNodeAssignment rootAssignment = null; if (!assignments.isEmpty()) { @@ -191,7 +225,7 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { // NOTE: hard-coded to assume all SVG data is encoded in UTF-8 dataBytes = data.getBytes("UTF-8"); } - dataHash = digest(dataBytes, assignments); + dataHash = digest(dataBytes, assignments, dynamicHash()); URI uri = univ.loadSVG(new ByteArrayInputStream(dataBytes), dataHash); diagramCache = univ.getDiagram(uri, false); @@ -208,7 +242,7 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { bbox = root.getBoundingBox(); if (bbox.isEmpty()) { // Lets check if this should be visible or not - Set presentationAttributes = root.getPresentationAttributes(); + Set presentationAttributes = root.getPresentationAttributes(); if (!presentationAttributes.contains("display")) { // TODO: fix this - How can one read values of attributes in SVG salamander??? univ.decRefCount(diagramCache.getXMLBase()); @@ -246,14 +280,72 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { return dataHash; } - private static boolean applyAssignments(SVGDiagram diagram, List assignments) throws SVGException { + private static DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + static { + dbf.setValidating(false); + try { + dbf.setFeature("http://xml.org/sax/features/namespaces", false); + dbf.setFeature("http://xml.org/sax/features/validation", false); + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false); + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + } catch (ParserConfigurationException e) { + // Nothing to do + } + } + + // Notice: Remember to change both implementations of applyAssigments when modifying the functionality. + protected static String applyAssigments(String svg, List assignments) { + try { + DocumentBuilder db = dbf.newDocumentBuilder(); + Document doc = db.parse(new InputSource(new StringReader(svg))); + + NodeList entries = doc.getElementsByTagName("*"); + for (int i=0; i assignments) throws SVGException { if (assignments.isEmpty()) return false; + boolean changed = false; + for (SVGNodeAssignment ass : assignments) { -// System.err.println("assign: " + ass.elementId + " " + ass.attributeNameOrId + " " + ass.value); -// if("opacity".equals(ass.attributeNameOrId)) -// System.err.println("faaf"); SVGElement e = diagram.getElement(ass.elementId); if (e != null) { if ("$text".equals(ass.attributeNameOrId)) { @@ -269,21 +361,28 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { ((Text) parent).rebuild(); changed = true; } + } else if (ass.attributeNameOrId.startsWith("#")) { + e.setAttribute(ass.attributeNameOrId.substring(1), AnimationElement.AT_CSS, ass.value); + changed = true; } else { e.setAttribute(ass.attributeNameOrId, AnimationElement.AT_AUTO, ass.value); changed = true; } } } - diagram.updateTime(0); + return changed; } public static Rectangle2D getBounds(String data) { - return getBounds(data, null); + return getBounds(data, 0); } - public static Rectangle2D getBounds(String data, List assignments) { + public static Rectangle2D getBounds(String data, int dynamicHash) { + return getBounds(data, Collections.emptyList(), dynamicHash); + } + + public static Rectangle2D getBounds(String data, List assignments, int dynamicHash) { if (data == null) { new Exception("null SVG data").printStackTrace(); return null; @@ -293,7 +392,7 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { try { // NOTE: hard-coded to assume all SVG data is encoded in UTF-8 byte[] dataBytes = data.getBytes("UTF-8"); - String digest = digest(dataBytes, assignments); + String digest = digest(dataBytes, assignments, dynamicHash); SVGUniverse univ = SVGCache.getSVGUniverse(); // TODO: this completely removes any parallel processing from the SVG loading which would be nice to have. @@ -327,10 +426,10 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { } public static Rectangle2D getRealBounds(String data) { - return getRealBounds(data, null); + return getRealBounds(data, Collections.emptyList(), 0); } - public static Rectangle2D getRealBounds(String data, List assignments) { + public static Rectangle2D getRealBounds(String data, List assignments, int dynamicHash) { if (data == null) { new Exception("null SVG data").printStackTrace(); return null; @@ -340,7 +439,7 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { try { // NOTE: hard-coded to assume all SVG data is encoded in UTF-8 byte[] dataBytes = data.getBytes("UTF-8"); - String digest = digest(dataBytes, assignments); + String digest = digest(dataBytes, assignments, dynamicHash); SVGUniverse univ = SVGCache.getSVGUniverse(); // TODO: this completely removes any parallel processing from the SVG loading which would be nice to have. @@ -362,13 +461,12 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { } protected void initBuffer(Graphics2D g2d) { - if (!data.equals(documentCache) || diagramCache == null) { - dataHash = parseSVG(); - if (diagramCache == null) { - System.err.println("UNABLE TO PARSE SVG:\n" + data); - return; - } + dataHash = parseSVG(); + if (diagramCache == null) { + System.err.println("UNABLE TO PARSE SVG:\n" + data); + return; + } } if (buffer != null) { @@ -380,14 +478,14 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { } else if(diagramCache.getViewRect().getWidth()==0 || diagramCache.getViewRect().getHeight()==0) { buffer = null; } else if(useMipMap) { - if(G2DUtils.isAccelerated(g2d)) { + if(G2DUtils.isAccelerated(g2d)) { buffer = new MipMapVRamBufferedImage(diagramCache, bounds, targetSize); } else { buffer = new MipMapBufferedImage(diagramCache, bounds, targetSize); } bufferCache.put(dataHash, new WeakReference(buffer)); } else { - if(G2DUtils.isAccelerated(g2d)) { + if(G2DUtils.isAccelerated(g2d)) { buffer = new VRamBufferedImage(diagramCache, bounds, targetSize); } else { buffer = new BufferedImage(diagramCache, bounds, targetSize); @@ -419,13 +517,13 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { } static WeakHashMap digestCache = new WeakHashMap(); - - static String digest(byte[] dataBytes, List assignments) { + + static String digest(byte[] dataBytes, List assignments, int dynamicHash) { try { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] messageDigest = md.digest(dataBytes); BigInteger number = new BigInteger(1, messageDigest); - String dataHash = number.toString(16) + (assignments != null ? assignments.hashCode() : 0); + String dataHash = number.toString(16) + (assignments != null ? assignments.hashCode() : 0) + 31 * dynamicHash; String result = digestCache.get(dataHash); if(result == null) { result = dataHash; @@ -462,12 +560,21 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { public void synchronizeTransform(double[] data) { this.setTransform(new AffineTransform(data)); } - + public String getSVGText() { String ret = data.replace("", "g>"); //return diagramCache.toString(); //return data.replace("", "/g>"); return ret; } - + + public Rectangle2D getElementBounds(String id) throws SVGException { + SVGElement e = diagramCache.getElement(id); + if (e instanceof RenderableElement) { + return ((RenderableElement)e).getBoundingBox(); + } else { + return null; + } + } + }