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