]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SVGNode.java
Merge branch 'feature/funcwrite'
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / g2d / nodes / SVGNode.java
index e88c7b94c5fb9dec196eff9216923a6fbf533dd3..cdd6a26458f578d4b30fbe3e5b8099a6191f6550 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
- * in Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- *     VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
 package org.simantics.scenegraph.g2d.nodes;
-\r
-import java.awt.Graphics2D;\r
-import java.awt.Point;\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.Rectangle2D;\r
-import java.io.ByteArrayInputStream;\r
-import java.io.IOException;\r
-import java.lang.ref.WeakReference;\r
-import java.math.BigInteger;\r
-import java.net.URI;\r
-import java.net.URL;\r
-import java.security.MessageDigest;\r
-import java.security.NoSuchAlgorithmException;\r
-import java.util.ArrayList;\r
-import java.util.HashMap;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.WeakHashMap;\r
-\r
-import org.simantics.scenegraph.ExportableWidget.RasterOutputWidget;\r
-import org.simantics.scenegraph.LoaderNode;\r
-import org.simantics.scenegraph.ScenegraphUtils;\r
-import org.simantics.scenegraph.g2d.G2DNode;\r
-import org.simantics.scenegraph.utils.BufferedImage;\r
-import org.simantics.scenegraph.utils.G2DUtils;\r
-import org.simantics.scenegraph.utils.InitValueSupport;\r
-import org.simantics.scenegraph.utils.MipMapBufferedImage;\r
-import org.simantics.scenegraph.utils.MipMapVRamBufferedImage;\r
-import org.simantics.scenegraph.utils.VRamBufferedImage;\r
-import org.simantics.scl.runtime.function.Function1;\r
-import org.simantics.scl.runtime.function.Function2;\r
-import org.simantics.utils.threads.AWTThread;\r
-\r
-import com.kitfox.svg.SVGCache;\r
-import com.kitfox.svg.SVGDiagram;\r
-import com.kitfox.svg.SVGElement;\r
-import com.kitfox.svg.SVGException;\r
-import com.kitfox.svg.SVGRoot;\r
-import com.kitfox.svg.SVGUniverse;\r
-import com.kitfox.svg.Text;\r
-import com.kitfox.svg.Tspan;\r
-import com.kitfox.svg.animation.AnimationElement;\r
-\r
+
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.math.BigInteger;
+import java.net.URI;
+import java.net.URL;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+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.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.VRamBufferedImage;
+import org.simantics.scl.runtime.function.Function1;
+import org.simantics.scl.runtime.function.Function2;
+import org.simantics.utils.threads.AWTThread;
+
+import com.kitfox.svg.SVGCache;
+import com.kitfox.svg.SVGDiagram;
+import com.kitfox.svg.SVGElement;
+import com.kitfox.svg.SVGException;
+import com.kitfox.svg.SVGRoot;
+import com.kitfox.svg.SVGUniverse;
+import com.kitfox.svg.Text;
+import com.kitfox.svg.Tspan;
+import com.kitfox.svg.animation.AnimationElement;
+
 @RasterOutputWidget
 public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode {
-\r
-       public static class SVGNodeAssignment {\r
-               public String elementId;\r
-               public String attributeNameOrId;\r
-               public String value;\r
-               public SVGNodeAssignment(String elementId, String attributeNameOrId, String value) {\r
-                       this.elementId = elementId;\r
-                       this.attributeNameOrId = attributeNameOrId;\r
-                       this.value = value;\r
-               }\r
-               @Override\r
-               public int hashCode() {\r
-                       final int prime = 31;\r
-                       int result = 1;\r
-                       result = prime * result + ((attributeNameOrId == null) ? 0 : attributeNameOrId.hashCode());\r
-                       result = prime * result + ((elementId == null) ? 0 : elementId.hashCode());\r
-                       result = prime * result + ((value == null) ? 0 : value.hashCode());\r
-                       return result;\r
-               }\r
-               @Override\r
-               public boolean equals(Object obj) {\r
-                       if (this == obj)\r
-                               return true;\r
-                       if (obj == null)\r
-                               return false;\r
-                       if (getClass() != obj.getClass())\r
-                               return false;\r
-                       SVGNodeAssignment other = (SVGNodeAssignment) obj;\r
-                       if (attributeNameOrId == null) {\r
-                               if (other.attributeNameOrId != null)\r
-                                       return false;\r
-                       } else if (!attributeNameOrId.equals(other.attributeNameOrId))\r
-                               return false;\r
-                       if (elementId == null) {\r
-                               if (other.elementId != null)\r
-                                       return false;\r
-                       } else if (!elementId.equals(other.elementId))\r
-                               return false;\r
-                       if (value == null) {\r
-                               if (other.value != null)\r
-                                       return false;\r
-                       } else if (!value.equals(other.value))\r
-                               return false;\r
-                       return true;\r
-               }\r
-       }\r
+
+       public static class SVGNodeAssignment {
+               public String elementId;
+               public String attributeNameOrId;
+               public String value;
+               public SVGNodeAssignment(String elementId, String attributeNameOrId, String value) {
+                       this.elementId = elementId;
+                       this.attributeNameOrId = attributeNameOrId;
+                       this.value = value;
+               }
+               @Override
+               public int hashCode() {
+                       final int prime = 31;
+                       int result = 1;
+                       result = prime * result + ((attributeNameOrId == null) ? 0 : attributeNameOrId.hashCode());
+                       result = prime * result + ((elementId == null) ? 0 : elementId.hashCode());
+                       result = prime * result + ((value == null) ? 0 : value.hashCode());
+                       return result;
+               }
+               @Override
+               public boolean equals(Object obj) {
+                       if (this == obj)
+                               return true;
+                       if (obj == null)
+                               return false;
+                       if (getClass() != obj.getClass())
+                               return false;
+                       SVGNodeAssignment other = (SVGNodeAssignment) obj;
+                       if (attributeNameOrId == null) {
+                               if (other.attributeNameOrId != null)
+                                       return false;
+                       } else if (!attributeNameOrId.equals(other.attributeNameOrId))
+                               return false;
+                       if (elementId == null) {
+                               if (other.elementId != null)
+                                       return false;
+                       } else if (!elementId.equals(other.elementId))
+                               return false;
+                       if (value == null) {
+                               if (other.value != null)
+                                       return false;
+                       } else if (!value.equals(other.value))
+                               return false;
+                       return true;
+               }
+       }
        
     private static final long serialVersionUID = 8508750881358776559L;
 
-    protected String          data             = null;\r
-    protected String          defaultData      = null;\r
-    protected Point           targetSize       = null;\r
-    protected Boolean         useMipMap        = true;\r
-    protected Rectangle2D     bounds           = null;\r
-    \r
-    protected List<SVGNodeAssignment> assignments = new ArrayList<SVGNodeAssignment>();\r
-\r
-    transient BufferedImage buffer    = null;\r
-    transient String documentCache    = null;\r
-    transient SVGDiagram diagramCache = null;\r
-    transient String dataHash         = null;\r
-\r
-    static transient Map<String, WeakReference<BufferedImage>> bufferCache = new HashMap<String, WeakReference<BufferedImage>>();\r
-\r
-    @Override\r
-    public void cleanup() {\r
-        cleanDiagramCache();\r
-    }\r
-\r
-    public void setAssignments(List<SVGNodeAssignment> ass) {\r
-       assignments.clear();\r
-       assignments.addAll(ass);\r
-    }\r
-    \r
-    public void cleanDiagramCache() {\r
-        SVGDiagram d = diagramCache;\r
-        if (d != null) {\r
-            diagramCache = null;\r
-            SVGUniverse univ = SVGCache.getSVGUniverse();\r
-            if (univ.decRefCountAndClear(d.getXMLBase()) == 0) {\r
-                // Cleared!\r
-                //System.out.println("cleared: " + d.getXMLBase());\r
-            }\r
-        }\r
-    }\r
-\r
-    static WeakHashMap<String, String> dataCache = new WeakHashMap<String, String>();\r
-\r
+    protected String          data             = null;
+    protected String          defaultData      = null;
+    protected Point           targetSize       = null;
+    protected Boolean         useMipMap        = true;
+    protected Rectangle2D     bounds           = null;
+    
+    protected List<SVGNodeAssignment> assignments = new ArrayList<SVGNodeAssignment>();
+
+    transient BufferedImage buffer    = null;
+    transient String documentCache    = null;
+    transient SVGDiagram diagramCache = null;
+    transient String dataHash         = null;
+
+    static transient Map<String, WeakReference<BufferedImage>> bufferCache = new HashMap<String, WeakReference<BufferedImage>>();
+
+    @Override
+    public void cleanup() {
+        cleanDiagramCache();
+    }
+
+    public void setAssignments(List<SVGNodeAssignment> ass) {
+       assignments.clear();
+       assignments.addAll(ass);
+    }
+    
+    public void cleanDiagramCache() {
+        SVGDiagram d = diagramCache;
+        if (d != null) {
+            diagramCache = null;
+            SVGUniverse univ = SVGCache.getSVGUniverse();
+            if (univ.decRefCountAndClear(d.getXMLBase()) == 0) {
+                // Cleared!
+                //System.out.println("cleared: " + d.getXMLBase());
+            }
+        }
+    }
+
+    static WeakHashMap<String, String> dataCache = new WeakHashMap<String, String>();
+
     @PropertySetter("SVG")
     @SyncField("data")
-    public void setData(String data) {\r
-        String cached = dataCache.get(data);\r
-        if (cached == null) {\r
-            cached = data;\r
-            dataCache.put(data, data);\r
-        }\r
-        this.data = cached;\r
-        this.defaultData = cached;\r
+    public void setData(String data) {
+        String cached = dataCache.get(data);
+        if (cached == null) {
+            cached = data;
+            dataCache.put(data, data);
+        }
+        this.data = cached;
+        this.defaultData = cached;
     }
 
     @SyncField("targetSize")
@@ -168,188 +168,222 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode {
     @SyncField("useMipMap")
     public void useMipMap(Boolean use) {
         this.useMipMap = use;
-    }\r
-\r
+    }
+
     @PropertySetter("Bounds")
     @SyncField("bounds")
     public void setBounds(Rectangle2D bounds) {
         this.bounds = bounds;
     }
 
-    @Override\r
+    @Override
     public Rectangle2D getBoundsInLocal() {
-        if (bounds == null)\r
-            parseSVG();\r
+        if (bounds == null)
+            parseSVG();
         return bounds;
     }
 
     @Override
     public void render(Graphics2D g2d) {
-        if (data == null)\r
-            return; // Not initialized\r
-\r
-        if (!data.equals(documentCache) || diagramCache == null || buffer == null)\r
+        if (data == null)
+            return; // Not initialized
+
+        if (!data.equals(documentCache) || diagramCache == null || buffer == null)
             initBuffer(g2d);
-\r
-        AffineTransform ot = null;\r
-        if (!transform.isIdentity()) {\r
-            ot = g2d.getTransform();\r
-            g2d.transform(transform);\r
+
+        AffineTransform ot = null;
+        if (!transform.isIdentity()) {
+            ot = g2d.getTransform();
+            g2d.transform(transform);
         }
 
-        if (buffer != null)\r
-            buffer.paint(g2d);\r
-\r
-        if (ot != null)\r
-            g2d.setTransform(ot);\r
+        if (buffer != null)
+            buffer.paint(g2d);
+
+        if (ot != null)
+            g2d.setTransform(ot);
     }
 
     protected String parseSVG() {
-        if (data == null)\r
-            return null;\r
-\r
-        try {\r
-               SVGUniverse univ = SVGCache.getSVGUniverse();\r
-               synchronized(univ) {
-                       // NOTE: hard-coded to assume all SVG data is encoded in UTF-8\r
-                       byte[] dataBytes = data.getBytes("UTF-8");
-                       dataHash = digest(dataBytes, assignments);\r
-                       if (diagramCache != null)\r
-                               univ.decRefCount(diagramCache.getXMLBase());\r
-                       URI uri = univ.loadSVG(new ByteArrayInputStream(dataBytes), dataHash);
-                       diagramCache = univ.getDiagram(uri, false);\r
-                       if (diagramCache != null) {\r
-                               if (diagramCache.getRoot() == null) {\r
-                                       diagramCache = univ.getDiagram(univ.loadSVG(BROKEN_SVG_DATA), false);\r
-                                       dataHash = "broken";\r
-                               } else if (diagramCache.getRoot().getBoundingBox().isEmpty()) {\r
-                                       diagramCache = univ.getDiagram(univ.loadSVG(EMPTY_SVG_DATA), false);\r
-                                       dataHash = "empty";\r
-                               } else {\r
-                                       for(SVGNodeAssignment ass : assignments) {\r
-                                               SVGElement e = diagramCache.getElement(ass.elementId);\r
-                                               if(e != null) {\r
-                                                       if("$text".equals(ass.attributeNameOrId)) {\r
-                                                               Tspan t = (Tspan)e;\r
-                                                               t.setText(ass.value);\r
-                                                               Text text = (Text)t.getParent();\r
-                                                               text.rebuild();\r
-                                                       } else {\r
-                                                               e.setAttribute(ass.attributeNameOrId, AnimationElement.AT_AUTO, ass.value);\r
-                                                       }\r
-                                               }\r
-                                       }\r
-                                       if(!assignments.isEmpty())\r
-                                               diagramCache.updateTime(0);\r
-                               }\r
-                       }
-                       documentCache = data;\r
-                       if (diagramCache != null) {\r
-                               setBounds((Rectangle2D) diagramCache.getRoot().getBoundingBox().clone());\r
-                               univ.incRefCount(diagramCache.getXMLBase());\r
-                       } else {\r
-                               setBounds(new Rectangle2D.Double());\r
-                       }\r
-               }\r
-        } catch (SVGException e) {\r
-            setBounds((Rectangle2D) diagramCache.getViewRect().clone());\r
-        } catch (IOException e) {\r
+        if (data == null)
+            return null;
+
+        SVGUniverse univ = SVGCache.getSVGUniverse();
+        try {
+            Rectangle2D bbox = null;
+            synchronized (univ) {
+                // Relinquish reference to current element
+                if (diagramCache != null) {
+                    univ.decRefCount(diagramCache.getXMLBase());
+                    diagramCache = null;
+                }
+
+                // NOTE: hard-coded to assume all SVG data is encoded in UTF-8
+                byte[] dataBytes = data.getBytes("UTF-8");
+                dataHash = digest(dataBytes, assignments);
+                URI uri = univ.loadSVG(new ByteArrayInputStream(dataBytes), dataHash);
+                diagramCache = univ.getDiagram(uri, false);
+
+                if (diagramCache != null) {
+                    univ.incRefCount(diagramCache.getXMLBase());
+
+                    if (diagramCache.getRoot() == null) {
+                        univ.decRefCount(diagramCache.getXMLBase());
+                        diagramCache = univ.getDiagram(univ.loadSVG(BROKEN_SVG_DATA), false);
+                        dataHash = "broken";
+                        univ.incRefCount(diagramCache.getXMLBase());
+                        bbox = (Rectangle2D) diagramCache.getRoot().getBoundingBox().clone();
+                    } else {
+                        bbox = diagramCache.getRoot().getBoundingBox();
+                        if (bbox.isEmpty()) {
+                            univ.decRefCount(diagramCache.getXMLBase());
+                            diagramCache = univ.getDiagram(univ.loadSVG(EMPTY_SVG_DATA), false);
+                            dataHash = "empty";
+                            univ.incRefCount(diagramCache.getXMLBase());
+                            bbox = (Rectangle2D) diagramCache.getRoot().getBoundingBox().clone();
+                        } else {
+                            if (applyAssignments(diagramCache, assignments)) {
+                                bbox = (Rectangle2D) diagramCache.getRoot().getBoundingBox().clone();
+                            } else {
+                                bbox = (Rectangle2D) bbox.clone();
+                            }
+                        }
+                    }
+                } else {
+                    bbox = new Rectangle2D.Double();
+                }
+            }
+
+            documentCache = data;
+            setBounds(bbox);
+        } catch (SVGException e) {
+            // This can only occur if diagramCache != null.
+            setBounds(diagramCache.getViewRect(new Rectangle2D.Double()));
+            univ.decRefCount(diagramCache.getXMLBase());
+            diagramCache = null;
+        } catch (IOException e) {
             diagramCache = null;
         }
 
         return dataHash;
     }
-\r
-    public static Rectangle2D getBounds(String data) {\r
-       return getBounds(data, null);\r
-    }\r
-\r
-    public static Rectangle2D getBounds(String data, List<SVGNodeAssignment> assignments) {\r
-        if (data == null) {\r
-            new Exception("null SVG data").printStackTrace();\r
-            return null;\r
-        }\r
-\r
-        SVGDiagram diagramCache = null;\r
-        try {\r
-            // NOTE: hard-coded to assume all SVG data is encoded in UTF-8\r
-            byte[] dataBytes = data.getBytes("UTF-8");\r
-            String digest = digest(dataBytes, assignments);\r
-\r
-            SVGUniverse univ = SVGCache.getSVGUniverse();\r
-            // TODO: this completely removes any parallel processing from the SVG loading which would be nice to have.\r
-            synchronized (univ) {\r
-                //System.out.println(Thread.currentThread() + ": LOADING SVG: " + digest);\r
-                URI uri = univ.loadSVG(new ByteArrayInputStream(dataBytes), digest);\r
-                diagramCache = univ.getDiagram(uri, false);\r
-                if (diagramCache != null) {\r
-                    if (diagramCache.getRoot() == null) {\r
-                        diagramCache = univ.getDiagram(univ.loadSVG(BROKEN_SVG_DATA));\r
-                    } else if (diagramCache.getRoot().getBoundingBox().isEmpty()) {\r
-                        diagramCache = univ.getDiagram(univ.loadSVG(EMPTY_SVG_DATA));\r
-                    }\r
-                }\r
-            }\r
-\r
-            Rectangle2D rect = null;\r
-            if (diagramCache != null) {\r
-                SVGRoot root = diagramCache.getRoot();\r
-                Rectangle2D bbox = root.getBoundingBox();\r
-                rect = (Rectangle2D) bbox.clone();\r
-            } else {\r
-                rect = new Rectangle2D.Double();\r
-            }\r
-            return rect;\r
-        } catch (SVGException e) {\r
-            return ((Rectangle2D) diagramCache.getViewRect().clone());\r
-        } catch(IOException e) {\r
-        }\r
-        return null;\r
-    }\r
-\r
-    public static Rectangle2D getRealBounds(String data) {\r
-       return getRealBounds(data, null);\r
-    }\r
-
-    public static Rectangle2D getRealBounds(String data, List<SVGNodeAssignment> assignments) {\r
-        if (data == null) {\r
-            new Exception("null SVG data").printStackTrace();\r
-            return null;\r
-        }\r
-\r
-        SVGDiagram diagramCache = null;\r
-        try {\r
-            // NOTE: hard-coded to assume all SVG data is encoded in UTF-8\r
-            byte[] dataBytes = data.getBytes("UTF-8");\r
-            String digest = digest(dataBytes, assignments);\r
-\r
-            SVGUniverse univ = SVGCache.getSVGUniverse();\r
-            // TODO: this completely removes any parallel processing from the SVG loading which would be nice to have.\r
-            synchronized (univ) {\r
-                //System.out.println(Thread.currentThread() + ": LOADING SVG: " + digest);\r
-                URI uri = univ.loadSVG(new ByteArrayInputStream(dataBytes), digest);\r
-                diagramCache = univ.getDiagram(uri, false);\r
-                if (diagramCache != null) {\r
-                       SVGRoot root = diagramCache.getRoot(); \r
-                    if (root == null) return new Rectangle2D.Double();\r
-                    return (Rectangle2D)root.getBoundingBox().clone();\r
-                }\r
-            }\r
-        } catch (SVGException e) {\r
-            return ((Rectangle2D) diagramCache.getViewRect().clone());\r
-        } catch(IOException e) {\r
-        }\r
-        return null;\r
-    }\r
-\r
-    protected void initBuffer(Graphics2D g2d) {\r
-       \r
-        if (!data.equals(documentCache) || diagramCache == null) {\r
+
+    private static boolean applyAssignments(SVGDiagram diagram, List<SVGNodeAssignment> assignments) throws SVGException {
+        if (assignments.isEmpty())
+            return false;
+        boolean changed = false;
+        for (SVGNodeAssignment ass : assignments) {
+            SVGElement e = diagram.getElement(ass.elementId);
+            if (e != null) {
+                if ("$text".equals(ass.attributeNameOrId)) {
+                    if (e instanceof Tspan) {
+                        Tspan t = (Tspan) e;
+                        t.setText(ass.value);
+                        SVGElement parent = t.getParent();
+                        if (parent instanceof Text)
+                            ((Text) parent).rebuild();
+                        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);
+    }
+
+    public static Rectangle2D getBounds(String data, List<SVGNodeAssignment> assignments) {
+        if (data == null) {
+            new Exception("null SVG data").printStackTrace();
+            return null;
+        }
+
+        SVGDiagram diagramCache = null;
+        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);
+
+            SVGUniverse univ = SVGCache.getSVGUniverse();
+            // TODO: this completely removes any parallel processing from the SVG loading which would be nice to have.
+            synchronized (univ) {
+                //System.out.println(Thread.currentThread() + ": LOADING SVG: " + digest);
+                URI uri = univ.loadSVG(new ByteArrayInputStream(dataBytes), digest);
+                diagramCache = univ.getDiagram(uri, false);
+                if (diagramCache != null) {
+                    if (diagramCache.getRoot() == null) {
+                        diagramCache = univ.getDiagram(univ.loadSVG(BROKEN_SVG_DATA));
+                    } else if (diagramCache.getRoot().getBoundingBox().isEmpty()) {
+                        diagramCache = univ.getDiagram(univ.loadSVG(EMPTY_SVG_DATA));
+                    }
+                }
+            }
+
+            Rectangle2D rect = null;
+            if (diagramCache != null) {
+                SVGRoot root = diagramCache.getRoot();
+                Rectangle2D bbox = root.getBoundingBox();
+                rect = (Rectangle2D) bbox.clone();
+            } else {
+                rect = new Rectangle2D.Double();
+            }
+            return rect;
+        } catch (SVGException e) {
+            return diagramCache.getViewRect(new Rectangle2D.Double());
+        } catch(IOException e) {
+        }
+        return null;
+    }
+
+    public static Rectangle2D getRealBounds(String data) {
+       return getRealBounds(data, null);
+    }
+
+    public static Rectangle2D getRealBounds(String data, List<SVGNodeAssignment> assignments) {
+        if (data == null) {
+            new Exception("null SVG data").printStackTrace();
+            return null;
+        }
+
+        SVGDiagram diagramCache = null;
+        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);
+
+            SVGUniverse univ = SVGCache.getSVGUniverse();
+            // TODO: this completely removes any parallel processing from the SVG loading which would be nice to have.
+            synchronized (univ) {
+                //System.out.println(Thread.currentThread() + ": LOADING SVG: " + digest);
+                URI uri = univ.loadSVG(new ByteArrayInputStream(dataBytes), digest);
+                diagramCache = univ.getDiagram(uri, false);
+                if (diagramCache != null) {
+                    SVGRoot root = diagramCache.getRoot(); 
+                    if (root == null) return new Rectangle2D.Double();
+                    return (Rectangle2D)root.getBoundingBox().clone();
+                }
+            }
+        } catch (SVGException e) {
+            return diagramCache.getViewRect(new Rectangle2D.Double());
+        } catch(IOException e) {
+        }
+        return null;
+    }
+
+    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;
-               }\r
+               }
         }
 
         if (buffer != null) {
@@ -360,7 +394,7 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode {
             buffer = bufferCache.get(dataHash).get();
         } else if(diagramCache.getViewRect().getWidth()==0 || diagramCache.getViewRect().getHeight()==0) {
             buffer = null;
-        } else if(useMipMap) {\r
+        } else if(useMipMap) {
                if(G2DUtils.isAccelerated(g2d)) {
                 buffer = new MipMapVRamBufferedImage(diagramCache, bounds, targetSize);
             } else {
@@ -376,72 +410,79 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode {
             bufferCache.put(dataHash, new WeakReference<BufferedImage>(buffer));
         }
     }
-\r
-    public void setProperty(String field, Object value) {\r
-        if("data".equals(field)) {\r
-//             System.out.println("SVGNode data -> " + value);\r
-            this.data = (String)value;\r
-        } else if ("z".equals(field)) {\r
-//             System.out.println("SVGNode z -> " + value);\r
-            setZIndex((Integer)value);\r
-        } else if ("position".equals(field)) {\r
-//             System.out.println("SVGNode position -> " + value);\r
-            Point point = (Point)value;\r
-            setTransform(AffineTransform.getTranslateInstance(point.x, point.y));\r
-//             setPosition(point.x, point.y);\r
-        }\r
-    }\r
-\r
-    @Override\r
-    public void initValues() {\r
-        data = defaultData;\r
-        dataHash = null;\r
-        buffer =  null;\r
-    }\r
-\r
-    static WeakHashMap<String, String> digestCache = new WeakHashMap<String, String>();\r
-    \r
-    static String digest(byte[] dataBytes, List<SVGNodeAssignment> assignments) {\r
-        try {\r
-            MessageDigest md = MessageDigest.getInstance("MD5");\r
-            byte[] messageDigest = md.digest(dataBytes);\r
-            BigInteger number = new BigInteger(1, messageDigest);\r
-            String dataHash = number.toString(16) + (assignments != null ? assignments.hashCode() : 0);\r
-            String result = digestCache.get(dataHash);\r
-            if(result == null) {\r
-               result = dataHash;\r
-               digestCache.put(dataHash,dataHash);\r
-            }\r
-            return result;\r
-        } catch (NoSuchAlgorithmException e) {\r
-            // Shouldn't happen\r
-            throw new Error("MD5 digest must exist.");\r
-        }\r
-    }\r
-\r
-    static URL BROKEN_SVG_DATA = SVGNode.class.getResource("broken.svg");\r
-    static URL EMPTY_SVG_DATA = SVGNode.class.getResource("empty.svg");\r
-\r
-       @Override\r
-       public Function1<Object, Boolean> getPropertyFunction(String propertyName) {\r
-               return ScenegraphUtils.getMethodPropertyFunction(AWTThread.getThreadAccess(), this, propertyName);\r
-       }\r
-       \r
-       @Override\r
-       public <T> T getProperty(String propertyName) {\r
-               return null;\r
-       }\r
-\r
-       @Override\r
-       public void setPropertyCallback(Function2<String, Object, Boolean> callback) {\r
-       }\r
-\r
-       public void synchronizeDocument(String document) {\r
-               setData(document);\r
-       }\r
-\r
-       public void synchronizeTransform(double[] data) {\r
-               this.setTransform(new AffineTransform(data));\r
-       }\r
+
+    public void setProperty(String field, Object value) {
+        if("data".equals(field)) {
+//             System.out.println("SVGNode data -> " + value);
+            this.data = (String)value;
+        } else if ("z".equals(field)) {
+//             System.out.println("SVGNode z -> " + value);
+            setZIndex((Integer)value);
+        } else if ("position".equals(field)) {
+//             System.out.println("SVGNode position -> " + value);
+            Point point = (Point)value;
+            setTransform(AffineTransform.getTranslateInstance(point.x, point.y));
+//             setPosition(point.x, point.y);
+        }
+    }
+
+    @Override
+    public void initValues() {
+        data = defaultData;
+        dataHash = null;
+        buffer =  null;
+    }
+
+    static WeakHashMap<String, String> digestCache = new WeakHashMap<String, String>();
+    
+    static String digest(byte[] dataBytes, List<SVGNodeAssignment> assignments) {
+        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 result = digestCache.get(dataHash);
+            if(result == null) {
+               result = dataHash;
+               digestCache.put(dataHash,dataHash);
+            }
+            return result;
+        } catch (NoSuchAlgorithmException e) {
+            // Shouldn't happen
+            throw new Error("MD5 digest must exist.");
+        }
+    }
+
+    static URL BROKEN_SVG_DATA = SVGNode.class.getResource("broken.svg");
+    static URL EMPTY_SVG_DATA = SVGNode.class.getResource("empty.svg");
+
+       @Override
+       public Function1<Object, Boolean> getPropertyFunction(String propertyName) {
+               return ScenegraphUtils.getMethodPropertyFunction(AWTThread.getThreadAccess(), this, propertyName);
+       }
+       
+       @Override
+       public <T> T getProperty(String propertyName) {
+               return null;
+       }
+
+       @Override
+       public void setPropertyCallback(Function2<String, Object, Boolean> callback) {
+       }
+
+       public void synchronizeDocument(String document) {
+               setData(document);
+       }
+
+       public void synchronizeTransform(double[] data) {
+               this.setTransform(new AffineTransform(data));
+       }
+       
+       public String getSVGText() {
+               String ret = data.replace("<svg", "<g").replaceAll("svg>", "g>");
+               //return diagramCache.toString();
+               //return data.replace("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"><svg xmlns=\"http://www.w3.org/2000/svg\" overflow=\"visible\" version=\"1.1\"", "<g").replaceAll("svg>", "/g>");
+               return ret;
+       }
        
 }