Diagram to SVG enhancements and stabilization
[simantics/platform.git] / bundles / org.simantics.modeling / src / org / simantics / modeling / SCLScenegraph.java
1 package org.simantics.modeling;
2
3 import java.awt.BasicStroke;
4 import java.awt.Dimension;
5 import java.awt.RenderingHints;
6 import java.awt.RenderingHints.Key;
7 import java.awt.geom.AffineTransform;
8 import java.awt.geom.Rectangle2D;
9 import java.io.ByteArrayOutputStream;
10 import java.io.OutputStreamWriter;
11 import java.util.ArrayList;
12 import java.util.Arrays;
13 import java.util.Collection;
14 import java.util.HashMap;
15 import java.util.HashSet;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Random;
19 import java.util.Set;
20 import java.util.UUID;
21 import java.util.stream.Collectors;
22
23 import javax.xml.transform.OutputKeys;
24 import javax.xml.transform.Transformer;
25 import javax.xml.transform.TransformerFactory;
26 import javax.xml.transform.dom.DOMSource;
27 import javax.xml.transform.stream.StreamResult;
28
29 import org.apache.batik.dom.GenericDOMImplementation;
30 import org.apache.batik.svggen.SVGGeneratorContext;
31 import org.apache.batik.svggen.SVGGraphics2D;
32 import org.simantics.Simantics;
33 import org.simantics.db.ReadGraph;
34 import org.simantics.db.Resource;
35 import org.simantics.db.common.request.UnaryRead;
36 import org.simantics.db.exception.DatabaseException;
37 import org.simantics.db.exception.RuntimeDatabaseException;
38 import org.simantics.diagram.elements.DiagramNodeUtil;
39 import org.simantics.diagram.elements.TextGridNode;
40 import org.simantics.diagram.elements.TextNode;
41 import org.simantics.diagram.stubs.DiagramResource;
42 import org.simantics.g2d.canvas.ICanvasContext;
43 import org.simantics.g2d.diagram.DiagramHints;
44 import org.simantics.g2d.diagram.IDiagram;
45 import org.simantics.g2d.diagram.handler.DataElementMap;
46 import org.simantics.g2d.diagram.participant.Selection;
47 import org.simantics.g2d.element.IElement;
48 import org.simantics.g2d.scenegraph.ICanvasSceneGraphProvider;
49 import org.simantics.g2d.utils.CanvasUtils;
50 import org.simantics.layer0.Layer0;
51 import org.simantics.scenegraph.INode;
52 import org.simantics.scenegraph.ParentNode;
53 import org.simantics.scenegraph.g2d.G2DParentNode;
54 import org.simantics.scenegraph.g2d.G2DRenderingHints;
55 import org.simantics.scenegraph.g2d.G2DSceneGraph;
56 import org.simantics.scenegraph.g2d.IG2DNode;
57 import org.simantics.scenegraph.g2d.IG2DNodeVisitor;
58 import org.simantics.scenegraph.g2d.events.command.Commands;
59 import org.simantics.scenegraph.g2d.nodes.BackgroundNode;
60 import org.simantics.scenegraph.g2d.nodes.BoundsNode;
61 import org.simantics.scenegraph.g2d.nodes.ConnectionNode;
62 import org.simantics.scenegraph.g2d.nodes.DataNode;
63 import org.simantics.scenegraph.g2d.nodes.DecorationSVGNode;
64 import org.simantics.scenegraph.g2d.nodes.NavigationNode;
65 import org.simantics.scenegraph.g2d.nodes.SVGNode;
66 import org.simantics.scenegraph.g2d.nodes.SelectionNode;
67 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
68 import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphNode;
69 import org.simantics.scenegraph.g2d.nodes.spatial.RTreeNode;
70 import org.simantics.scenegraph.utils.NodeUtil;
71 import org.simantics.scl.runtime.function.Function1;
72 import org.simantics.trend.impl.ItemNode;
73 import org.simantics.utils.threads.ThreadUtils;
74 import org.slf4j.Logger;
75 import org.slf4j.LoggerFactory;
76 import org.w3c.dom.DOMImplementation;
77 import org.w3c.dom.Document;
78 import org.w3c.dom.Element;
79 import org.w3c.dom.NodeList;
80
81 public class SCLScenegraph {
82
83         private static final Logger LOGGER = LoggerFactory.getLogger(SCLScenegraph.class);
84
85         public static ICanvasSceneGraphProvider getICanvasSceneGraphProvider(Resource model, Resource diagram, String diagramRVI) throws DatabaseException, InterruptedException {
86                 ICanvasSceneGraphProvider provider = DiagramNodeUtil.loadSceneGraphProvider(model, diagram, diagramRVI);
87                 return provider;
88         }
89         
90         public static void disposeSceneGraphProvider(ICanvasSceneGraphProvider provider) {
91                 provider.dispose();
92         }
93         
94         public static String getNodeTransform(ICanvasContext ctx, String name) {
95                 
96                 Set<TextNode> texts = NodeUtil.collectNodes(ctx.getSceneGraph(), TextNode.class);
97                 for (TextNode text : texts) {
98                         String nodeName = NodeUtil.getNodeName(text);
99                         if (nodeName.equals(name)) {
100                                 String transform = text.getTransform().toString();
101                                 return transform;
102                         }
103                 }
104                 return "No node found";
105         }
106         
107         public static String getNodeText(ICanvasContext ctx, String name) {
108                 
109                 Set<TextNode> texts = NodeUtil.collectNodes(ctx.getSceneGraph(), TextNode.class);
110                 for (TextNode text : texts) {
111                         String nodeName = NodeUtil.getNodeName(text);
112                         if (nodeName.equals(name)) {
113                                 String texti = text.getText();
114                                 return texti;
115                         }
116                 }
117                 return "No node found";
118         }
119         
120         public static String getNodeCount(ICanvasContext ctx) {
121                 G2DSceneGraph g2 = ctx.getSceneGraph();
122                 int amount = NodeUtil.countTreeNodes(g2);
123                 return "Node count: " + amount;
124         }
125         
126     public static String getAllNodes (ICanvasContext ctx) {
127         
128         Set<G2DSceneGraph> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), G2DSceneGraph.class);
129         int amount = g2.size() +1;
130         return "All nodes: " + amount;
131     }
132     
133     public static String getBoundsNodes (ICanvasContext ctx) {
134         
135         Set<BoundsNode> bn = NodeUtil.collectNodes(ctx.getSceneGraph(), BoundsNode.class);
136         int amount = bn.size();
137         return "BoundsNodes: " + amount;
138     }
139     
140     public static String getBackgroundNodes (ICanvasContext ctx) {
141         
142         Set<BackgroundNode> bg = NodeUtil.collectNodes(ctx.getSceneGraph(), BackgroundNode.class);
143         int amount = bg.size();
144         return "BackgroundNodes: " + amount;
145     }
146     
147     public static String getDataNodes (ICanvasContext ctx) {
148         
149         Set<DataNode> dn = NodeUtil.collectNodes(ctx.getSceneGraph(), DataNode.class);
150         int amount = dn.size();
151         return "DataNodes: " + amount;
152     }
153     
154     public static String getNavigationNodes (ICanvasContext ctx) {
155         
156         Set<NavigationNode> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), NavigationNode.class);
157         int amount = g2.size();
158         return "NavigationNodes: " + amount;
159     }
160     
161     public static String getParentNodes (ICanvasContext ctx) {
162         
163         Set<G2DParentNode> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), G2DParentNode.class);
164         int amount = g2.size();
165         return "ParentNodes: " + amount;
166     }
167     
168     public static String getDecorationNodes (ICanvasContext ctx) {
169         
170         Set<DecorationSVGNode> deco = NodeUtil.collectNodes(ctx.getSceneGraph(), DecorationSVGNode.class);
171         int amount = deco.size();
172         return "DecorationNodes: " + amount;
173     }
174     
175     public static String getSingleElementNodes (ICanvasContext ctx) {
176         
177         Set<SingleElementNode> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), SingleElementNode.class);
178         int amount = g2.size();
179         return "SingleElementNodes: " + amount;
180     }
181     
182     public static String getConnectionNodes (ICanvasContext ctx) {
183         
184         Set<ConnectionNode> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), ConnectionNode.class);
185         int amount = g2.size();
186         return "ConnectionNodes: " + amount;
187     }
188     
189     public static String getTextNodes (ICanvasContext ctx) {
190         
191         Set<TextNode> tn = NodeUtil.collectNodes(ctx.getSceneGraph(), TextNode.class);
192         Set<TextGridNode> tgn = NodeUtil.collectNodes(ctx.getSceneGraph(), TextGridNode.class);
193         int amount = tn.size() + tgn.size();
194         return "TextNodes: " + amount;
195     }
196     
197     public static String getItemNodes (ICanvasContext ctx) {
198         
199         Set<ItemNode> item = NodeUtil.collectNodes(ctx.getSceneGraph(), ItemNode.class);
200         int amount = item.size();
201         return "ItemNodes: " + amount;
202     }
203   
204     public static String editNodeText (ICanvasContext ctx, String module, String previous_value, String new_value) {
205                 
206         Set<TextNode> textGridNodes = NodeUtil.collectNodes(ctx.getSceneGraph(), TextNode.class);
207         for (TextNode modulenode : textGridNodes) {
208                 if (module.equals(modulenode.getText())) {
209                         //System.out.println("Module what we were looking for: " + module);
210                         //System.out.println("Modulenode: " + modulenode.getText());
211                         
212                         ParentNode<?> parentnode = modulenode.getParent();
213                         //System.out.println("Parentnode: " + parentnode);
214                         
215                         Collection<TextNode> textnodes = (Collection<TextNode>) parentnode.getNodes();
216                         for (TextNode valuenode : textnodes) {
217                                 if (previous_value.equals(valuenode.getText())) {
218                                         //System.out.println("Value what we were looking for: " + previous_value);
219                                         //System.out.println("Valuenode: " + valuenode.getText());
220                                         
221                                         //valuenode.setEditMode(true);
222                                         valuenode.activateEdit(0, null, ctx);
223                                         valuenode.setText(new_value);
224                                         valuenode.fireTextEditingEnded();
225                                         
226                                         //System.out.println("valuenode modified: " + valuenode);
227                                         return "Modified module " + module + " with value " + new_value;
228                                 }
229                         }
230                         return "Not found module : " + module;
231                 }
232         }
233         return "No nodes in scenegraph!";
234     }
235
236     public static String sceneGraphTest (ICanvasContext ctx, String module, String value) {
237         
238         boolean module_founded = false;
239         boolean value_founded = false;
240         
241         Set<G2DSceneGraph> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), G2DSceneGraph.class);
242         System.out.println("Total amount of nodes: " + g2.size() + 1);
243         
244         Set<TextGridNode> grid = NodeUtil.collectNodes(ctx.getSceneGraph(), TextGridNode.class);
245         Integer textGridNodeAmount = grid.size();
246         System.out.println("Amount of TextGridNodes " + textGridNodeAmount);
247         
248         Set<TextNode> texts = NodeUtil.collectNodes(ctx.getSceneGraph(), TextNode.class);
249         Integer textNodeAmount = grid.size();
250         System.out.println("Amount of TextNodes " + textNodeAmount);
251
252         for (TextNode node : texts) {
253             if (module.equals(node.getText())) {
254                 module_founded = true;
255                 System.out.println("Correct module " + module + " founded.");
256             }
257             if (value.equals(node.getText())) {
258                 value_founded = true;
259                 System.out.println("Correct value " + value + " founded.");
260             }
261         }
262         
263         if (value_founded == true && module_founded == true) {
264                 return "Found both correct module " + module + " and value " + value;
265         }
266         if (value_founded == false && module_founded == true) {
267                 return "Found only correct module " + module + " but not value " + value;
268         }
269         if (value_founded == true && module_founded == false) {
270                 return "Found only correct value " + value + " but not module " + module;
271         }
272         else {
273                 return "Didn't found either module " + module + " or value " + value;
274         }
275     }
276     
277     public static boolean copyPaste (final ICanvasContext source_ctx, final ICanvasContext target_ctx, List<Resource> modules) throws DatabaseException {
278         
279         IDiagram idiagram = source_ctx.getDefaultHintContext().getHint(DiagramHints.KEY_DIAGRAM);
280
281                 DataElementMap dem = idiagram.getDiagramClass().getAtMostOneItemOfClass(DataElementMap.class);
282                 if (dem != null) {
283                         final Collection<IElement> newSelection = new ArrayList<IElement>();
284                         for (Resource module : modules) {
285                                 IElement element = dem.getElement(idiagram, module);
286                                 if (element != null) {
287                                         newSelection.add(element);
288                                 } else {
289                                         throw new DatabaseException("Could not find IElement for " + element);
290                                 }
291                         }
292                         
293                         ThreadUtils.syncExec(source_ctx.getThreadAccess(), new Runnable() {
294                     @Override
295                     public void run() {
296                         if (source_ctx.isDisposed())
297                             return;
298                         Selection selection = source_ctx.getAtMostOneItemOfClass(Selection.class);
299                         if (selection != null) {
300                             // This prevents workbench selection from being left over.
301                             // Also prevents scene graph crap from being left on the screen.
302                             selection.setSelection(0, newSelection);
303                         }
304                                 CanvasUtils.sendCommand(source_ctx, Commands.COPY);
305                                 CanvasUtils.sendCommand(target_ctx, Commands.PASTE);
306                     }
307                 });
308                         
309                 //}
310                 
311                 while(source_ctx.getEventQueue().size() > 0) {
312                         try {
313                                 Thread.sleep(10);
314                         } catch (InterruptedException e) {
315                                 throw new DatabaseException(e);
316                         }
317                 }
318
319                 ThreadUtils.syncExec(source_ctx.getThreadAccess(), new Runnable() {
320             @Override
321             public void run() {
322             }
323         });             
324                                 
325                 }
326                 return true;
327     }
328
329         static class Generator extends SVGGraphics2D {
330
331                 int elemLevel = 0;
332                 String newElementId = null;
333                 ArrayList<Element> elements = new ArrayList<Element>();
334
335                 public static final String svgNS = "http://www.w3.org/2000/svg";
336
337                 public Generator(SVGGeneratorContext ctx, boolean joku) {
338                         super(ctx, joku);
339                 }
340
341                 public Generator(Document document) {
342                         super(document);
343                 }
344
345                 @Override
346                 public Element getRoot() {
347                         Element root = super.getRoot();
348                         for(Element e : elements) {
349                                 root.appendChild(e);
350                         }
351                         return root;
352                 }
353
354                 @Override
355                 public void setRenderingHint(Key arg0, Object arg1) {
356                         if(G2DRenderingHints.KEY_BEGIN_ELEMENT == arg0) {
357                                 elemLevel++;
358                         }
359                         if(G2DRenderingHints.KEY_ELEMENT_ID == arg0) {
360                                 if(arg1 != null)
361                                         newElementId = arg1.toString();
362                                 else
363                                         newElementId = UUID.randomUUID().toString();
364                         }
365                         if(G2DRenderingHints.KEY_END_ELEMENT == arg0) {
366                                 elemLevel--;
367                                 if(elemLevel == 0) {
368                                         Element group = getDOMFactory().createElement(SVG_G_TAG);
369                                         //Element group = getDOMFactory().createElementNS(SVG_NAMESPACE_URI, SVG_G_TAG);
370                                         group.setAttributeNS(null, "id", newElementId);
371                                         group.setAttributeNS(null, "class", arg1.toString());
372                                         getRoot(group);
373                                         elements.add(group);
374                                 }
375                         }
376                         super.setRenderingHint(arg0, arg1);
377                 }
378
379         }
380
381         public static Element renderSVGNode(IG2DNode node) {
382
383                 // Get a DOMImplementation.
384                 DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();
385
386                 // Create an instance of org.w3c.dom.Document.
387                 String svgNS = "http://www.w3.org/2000/svg";
388                 Document document = domImpl.createDocument(svgNS, "svg", null);
389
390                 SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(document);
391                 ctx.setComment(null);
392
393                 // Create an instance of the SVG Generator.
394                 SVGGraphics2D svgGenerator = new Generator(ctx, false);
395
396                 try {
397
398                         svgGenerator.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
399                         svgGenerator.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
400                         svgGenerator.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
401
402                         node.render(svgGenerator);
403
404                 } catch (Throwable t) {
405                         LOGGER.error("Problems rendering scene graph to SVG", t);
406                 }
407
408                 return svgGenerator.getRoot();
409         }
410
411         public static String printSVGDocument(Element doce) {
412
413                 StringBuilder result = new StringBuilder();
414
415                 NodeList nl =  doce.getChildNodes();
416
417                 for(int i=0;i<nl.getLength();i++) {
418
419                         ByteArrayOutputStream os = new ByteArrayOutputStream();
420
421                         try {
422
423                                 TransformerFactory tf = TransformerFactory.newInstance();
424                                 Transformer transformer = tf.newTransformer();
425                                 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
426                                 transformer.setOutputProperty(OutputKeys.STANDALONE, "no");
427                                 transformer.setOutputProperty(OutputKeys.METHOD, "xml");
428                                 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
429                                 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
430                                 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
431
432                                 transformer.transform(new DOMSource(nl.item(i)), 
433                                                 new StreamResult(new OutputStreamWriter(os, "UTF-8")));
434
435                                 os.flush();
436                                 os.close();
437
438                         } catch (Throwable t) {
439                                 LOGGER.error("Problems formatting SVGDocument to text.", t);
440                         }
441
442                         result.append(new String(os.toByteArray()));
443
444                 }
445
446                 return result.toString();
447
448         }
449
450         public static String renderSVG3(ICanvasContext ctx) {
451         return renderSVG0(ctx, p0 -> p0.stream().collect(Collectors.toMap(p1 -> p1, p2 -> p2)));
452         }
453
454     /**
455      * @param graph
456      * @param component
457      * @throws DatabaseException
458      */
459     private static Object[] createURIBasedL0Identifier(ReadGraph graph, Resource component) throws DatabaseException {
460         String uri = graph.getPossibleURI(component);
461         int hashCode = uri.hashCode();
462         Random random = new Random(hashCode);
463         long l1 = random.nextLong();
464         long l2 = random.nextLong();
465         return new Object[] { l1, l2 };
466     }
467
468     /**
469      * Default no-op mapper
470      */
471     private static final Function1<Set<?>, Map<?, ?>> mapper = p0 -> p0.stream().collect(Collectors.toMap(p1 -> p1, p2 -> p2));
472     
473     public static String renderSVG(ICanvasContext ctx) {
474         return renderSVG0(ctx, mapper);
475     }
476
477     /**
478      * Renders ICanvasContext into SVG by mapping the SVG id's into URI based
479      * GUID's
480      * 
481      * @param ctx
482      * @return
483      */
484     public static String renderSVGMapIdentifiers(ICanvasContext ctx) {
485         return renderSVG0(ctx, new Function1<Set<?>, Map<?, ?>>() {
486
487             @Override
488             public Map<?, ?> apply(Set<?> p0) {
489                 try {
490                     return Simantics.getSession().syncRequest(new UnaryRead<Set<?>, Map<?, ?>>(p0) {
491
492                         @Override
493                         public Map<?, ?> perform(ReadGraph graph) throws DatabaseException {
494                             ModelingResources MOD = ModelingResources.getInstance(graph);
495                             DiagramResource DIA = DiagramResource.getInstance(graph);
496                             Layer0 L0 = Layer0.getInstance(graph);
497                             return parameter.stream().collect(Collectors.toMap(p -> p, p -> {
498                                 try {
499                                     if (p instanceof Resource) {
500                                         Resource element = (Resource) p;
501                                         if (graph.isInstanceOf(element, DIA.Connection) || graph.isInstanceOf(element, DIA.Terminal)) {
502                                             // Ok, lets create a hashcode for connections and terminals as they do not have identifiers 
503                                             return graph.getURI(element).hashCode();
504                                         } else if (graph.isInstanceOf(element, DIA.Element)) {
505                                             Resource component = graph.getPossibleObject(element, MOD.ElementToComponent);
506                                             if (component != null) {
507                                                 Object relatedValue = graph.getPossibleRelatedValue(component, L0.identifier);
508                                                 if (relatedValue != null) {
509                                                     if (relatedValue instanceof Object[]) {
510                                                         // OK, found guid, lets generate new based on URI
511                                                         return Arrays.toString(createURIBasedL0Identifier(graph, component));
512                                                     } else {
513                                                         System.err.println("Component " + component + " identifeir is not normal: " + relatedValue);
514                                                     }
515                                                 } else {
516                                                     System.err.println("Compoennt " + component + " dooes not have identifier");
517                                                 }
518                                             } else {
519                                                 System.err.println("Element " + element + " does not have component");
520                                             }
521                                         }
522                                     } else {
523                                         System.err.println("p is not resource but is " + p);
524                                     }
525                                     return p;
526                                 } catch (DatabaseException e) {
527                                     throw new RuntimeDatabaseException(e);
528                                 }
529                             }));
530                         }
531                     });
532                 } catch (DatabaseException e) {
533                     LOGGER.error("Could not apply mappings", e);
534                     throw new RuntimeDatabaseException(e);
535                 }
536             }
537         });
538         }
539         
540         private static String renderSVG0(ICanvasContext ctx, Function1<Set<?>, Map<?, ?>> mappingFunction) {
541
542                 // Get a DOMImplementation.
543                 DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();
544
545                 // Create an instance of org.w3c.dom.Document.
546                 String svgNS = "http://www.w3.org/2000/svg";
547                 Document document = domImpl.createDocument(svgNS, "svg", null);
548
549                 // Create an instance of the SVG Generator.
550                 SVGGraphics2D svgGenerator = new Generator(document);
551
552                 StringBuilder result = new StringBuilder();
553
554                 try {
555
556             Selection selection = ctx.getAtMostOneItemOfClass(Selection.class);
557             if (selection != null) {
558                 // This prevents workbench selection from being left over.
559                 // Also prevents scene graph crap from being left on the screen.
560                 IDiagram d = ctx.getDefaultHintContext().getHint(DiagramHints.KEY_DIAGRAM);
561                 selection.setSelection(0, d.getElements());
562             }
563
564                         G2DSceneGraph sg = ctx.getSceneGraph();
565                         G2DParentNode root = (G2DParentNode) sg.getRootNode();
566
567                         // rtree is the actual content of the diagram
568                         RTreeNode rtree = NodeUtil.getNearestChildByClass(root, RTreeNode.class);
569                         Rectangle2D rtreeBounds = NodeUtil.getLocalBounds(rtree);
570
571                         // nav is a node that has zooming functionalities
572                         NavigationNode nav = NodeUtil.getNearestChildByClass(root, NavigationNode.class);
573                         nav.setZoomEnabled(true);
574
575                         // fit view with the contents of rtreeBounds
576                         nav.zoomTo(rtreeBounds);
577
578                         // get the bounds of the content
579                         Rectangle2D content = NodeUtil.getLocalBounds(nav);
580
581                         svgGenerator.scale(3,3);
582
583                         // translate svgGenerator to the x and y coordinates of current content
584                         svgGenerator.translate(-1 * content.getX(), (-1 * content.getY()));
585
586                         Rectangle2D destination = new Rectangle2D.Double(0,0,1000,1000);
587                         double sx = destination.getWidth() / content.getWidth();
588                         double sy = destination.getHeight() / content.getHeight();
589                         double scale = sx < sy ? sx : sy;
590
591                         // Set svgCanvasSize to the given size parameters
592                         svgGenerator.setSVGCanvasSize(new Dimension((int)(scale * content.getWidth()), (int)(scale * content.getHeight())));
593                         svgGenerator.setClip(content);
594
595                         double trX = -1 * content.getX();
596                         double trY = -1 * content.getY();
597
598                         result.append("<svg width=\"100%\" height=\"100%\" stroke=\"black\"><g transform=\"translate(").append(trX).append(' ').append(trY).append(")\">");
599
600                         KeyVisitor keyVisitor = new KeyVisitor();
601                         sg.accept(keyVisitor);
602                         
603                         Set<Object> keys = keyVisitor.getKeys();
604                         
605                         Map<?, ?> mappings = mappingFunction.apply(keys);
606
607                         IG2DNodeVisitor visitor = new PrintingVisitor(result, mappings);
608                         sg.accept(visitor);
609
610                 } catch (Throwable t) {
611                         LOGGER.error("Problems rendering canvas context to SVG", t);
612                 }
613
614                 result.append("</g></svg>");
615                 //System.err.println(" == FINAL RESULT == ");
616                 //System.err.println(b);
617                 return result.toString();
618         }
619         
620         
621         
622         private static class KeyVisitor implements IG2DNodeVisitor {
623
624         private Set<Object> keys = new HashSet<>();
625
626         @Override
627         public void enter(IG2DNode node) {
628             if (node instanceof SingleElementNode) {
629                 Object key = ((SingleElementNode) node).getKey();
630                 if (key != null) {
631                     keys.add(key);
632                 }
633             }
634         }
635
636         @Override
637         public void leave(IG2DNode node) {
638             // Nothing to do
639         }
640
641         public Set<Object> getKeys() {
642             return keys;
643         }
644         }
645
646         private static class PrintingVisitor implements IG2DNodeVisitor {
647
648         int indent = 0;
649
650         HashMap<SingleElementNode,StringBuilder> senBuilders = new HashMap<>();
651
652         private StringBuilder result;
653
654         private Map<?, ?> mappings;
655
656         public PrintingVisitor(StringBuilder result, Map<?, ?> mappings) {
657             this.result = result;
658             this.mappings = mappings;
659         }
660
661         private String getKey(SingleElementNode node) {
662             String key;
663             if (mappings.containsKey(node.getKey()))
664                 key = mappings.get(node.getKey()).toString();
665             else
666                 key = node.getKey().toString();
667             return key;
668         }
669
670         @Override
671         public void enter(IG2DNode node) {
672             
673             StringBuilder parentBuilder = getParentBuilder(node);
674             
675             indent++;
676             if(node instanceof ConnectionNode) {
677                 
678                 for(RouteGraphNode n : NodeUtil.collectNodes(node, RouteGraphNode.class)) {
679                     n.setIgnoreSelection(true);
680                 }
681
682                 String key = getKey((ConnectionNode) node);
683                 parentBuilder.append("\n<g class=\"connection\" id=\"" + key + "\">");
684                 Element doc = renderSVGNode((IG2DNode)node);
685                 String svg = printSVGDocument(doc);
686                 parentBuilder.append(svg);
687
688                 for(RouteGraphNode n : NodeUtil.collectNodes(node, RouteGraphNode.class)) {
689                     n.setIgnoreSelection(false);
690                 }
691
692                 parentBuilder.append("\n<g style=\"visibility:hidden\" class=\"selection\" id=\"" + key + "\">");
693                 doc = renderSVGNode((IG2DNode)node);
694                 svg = printSVGDocument(doc);
695                 parentBuilder.append(svg);
696                 parentBuilder.append("\n</g>");
697
698                 BasicStroke bs = new BasicStroke(5f);
699                 
700                 for(RouteGraphNode n : NodeUtil.collectNodes(node, RouteGraphNode.class)) {
701                     n.setDynamicStroke(bs);
702                 }
703
704                 parentBuilder.append("\n<g class=\"selectionMask\" opacity=\"0.001\" id=\"" + key + "\">");
705                 doc = renderSVGNode((IG2DNode)node);
706                 svg = printSVGDocument(doc);
707                 parentBuilder.append(svg);
708                 parentBuilder.append("\n</g>");
709
710                 parentBuilder.append("\n</g>");
711                 
712             } else if (node instanceof SelectionNode) {
713                 
714                 SelectionNode n = (SelectionNode)node;
715                 SingleElementNode parentSEN = (SingleElementNode)NodeUtil.getNearestParentOfType(node, SingleElementNode.class);
716                 if(parentSEN != null && parentSEN.getKey() != null) {
717                     
718                     StringBuilder parentBuilder2 = getParentBuilder(parentSEN);
719                     
720                     String key = getKey(parentSEN);
721                     Element doc = renderSVGNode((IG2DNode)node);
722                     String svg = printSVGDocument(doc);
723                     parentBuilder2.append("\n<g style=\"visibility:hidden\" class=\"selection\" id=\"" + key + "\">");
724                     parentBuilder2.append(svg);
725                     parentBuilder2.append("\n</g>");
726                     parentBuilder2.append("\n<g class=\"selectionMask\" id=\"" + key + "\">");
727                     Rectangle2D rect = n.getRect();
728                     parentBuilder2.append("<rect style=\"fill:#fff\" opacity=\"0.001\"");
729                     parentBuilder2.append(" x=\"" + rect.getX() + "\" y=\"" + rect.getY() + "\"");
730                     parentBuilder2.append(" width=\"" + rect.getWidth() + "\" height=\"" + rect.getHeight() + "\"");
731                     parentBuilder2.append("></rect>");
732                     parentBuilder2.append("\n</g>");
733                 }
734             } else if (node instanceof SVGNode) {
735                 SVGNode svg = (SVGNode)node;
736                 parentBuilder.append(svg.getSVGText());
737             } else if (node instanceof G2DParentNode) {
738                 AffineTransform at = node.getTransform();
739                 if(node instanceof SingleElementNode) {
740                     SingleElementNode sen = (SingleElementNode)node;
741                     if(sen.getKey() != null) {
742                         String key = getKey(sen);
743                         parentBuilder.append("\n<g class=\"definedElement\" id=\"" + key + "\">");
744                     }
745                     senBuilders.put(sen, new StringBuilder());
746                 }
747                 if(!at.isIdentity()) {
748                     if(at.getScaleX() == 1.0 && at.getScaleY() == 1.0 && at.getShearX() == 0.0 && at.getShearY() == 0.0) {
749                         String m = "translate(" + at.getTranslateX() + " " + at.getTranslateY() + ")";
750                         parentBuilder.append("\n<g transform=\"" + m + "\">");
751                     } else {
752                         double[] ds = new double[6];
753                         at.getMatrix(ds);
754                         String m = "matrix(" + ds[0] + " " + ds[1] + " " + ds[2] + " " + ds[3] + " " + ds[4] + " " + ds[5] + ")";
755                         parentBuilder.append("\n<g transform=\"" + m + "\">");
756                     }
757                 }
758             }
759
760             //enters.put(node, b.length());
761
762         }
763         
764         private StringBuilder getParentBuilder(IG2DNode node) {
765             
766             INode parentSEN = NodeUtil.getNearestParentOfType(node, SingleElementNode.class);
767             if(parentSEN instanceof G2DSceneGraph) return result;
768
769             StringBuilder parentBuilder = senBuilders.get(parentSEN);
770             if(parentBuilder == null) return result;
771             
772             return parentBuilder;
773             
774         }
775
776         @Override
777         public void leave(IG2DNode node) {
778
779             if(node instanceof ConnectionNode || node instanceof SVGNode) {
780                 // We are done
781             } else if (node instanceof G2DParentNode) {
782                 
783                 StringBuilder parentBuilder = getParentBuilder(node);
784                 
785                 if(node instanceof SingleElementNode) {
786                     SingleElementNode sen = (SingleElementNode)node;
787 //                  if(sen.getKey() != null) {
788                         StringBuilder b = senBuilders.get(sen);
789                         String content = b.toString();
790                         if(content.isEmpty()) {
791                             if(sen.getKey() != null) {
792                                 
793                                 for(SelectionNode n : NodeUtil.collectNodes(node, SelectionNode.class)) {
794                                     n.setIgnore(true);
795                                 }
796
797                                 Element doc = renderSVGNode((IG2DNode)node);
798                                 String svg = printSVGDocument(doc);
799                                 parentBuilder.append(svg);
800                             }
801                         } else {
802                             parentBuilder.append(content);
803                         }
804 //                  }
805                 }
806
807                 
808                 AffineTransform at = node.getTransform();
809                 if(!at.isIdentity()) {
810                     parentBuilder.append("</g>");
811                 }
812                 if(node instanceof SingleElementNode) {
813                     SingleElementNode sen = (SingleElementNode)node;
814                     if(sen.getKey() != null) {
815                         parentBuilder.append("</g>");
816                     }
817                 }
818             }
819             indent --;
820         }
821         }
822 }