1 package org.simantics.modeling;
3 import java.awt.BasicStroke;
5 import java.awt.Dimension;
6 import java.awt.RenderingHints;
7 import java.awt.RenderingHints.Key;
8 import java.awt.geom.AffineTransform;
9 import java.awt.geom.Rectangle2D;
10 import java.io.ByteArrayOutputStream;
11 import java.io.OutputStreamWriter;
12 import java.nio.charset.Charset;
13 import java.util.ArrayList;
14 import java.util.Arrays;
15 import java.util.Collection;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.List;
20 import java.util.Random;
22 import java.util.UUID;
23 import java.util.function.Function;
24 import java.util.stream.Collectors;
26 import javax.xml.transform.OutputKeys;
27 import javax.xml.transform.Transformer;
28 import javax.xml.transform.TransformerFactory;
29 import javax.xml.transform.dom.DOMSource;
30 import javax.xml.transform.stream.StreamResult;
32 import org.apache.batik.dom.GenericDOMImplementation;
33 import org.apache.batik.svggen.SVGGeneratorContext;
34 import org.apache.batik.svggen.SVGGraphics2D;
35 import org.apache.batik.svggen.SVGIDGenerator;
36 import org.eclipse.swt.SWT;
37 import org.simantics.Simantics;
38 import org.simantics.datatypes.literal.GUID;
39 import org.simantics.db.ReadGraph;
40 import org.simantics.db.Resource;
41 import org.simantics.db.common.request.IndexRoot;
42 import org.simantics.db.common.request.ResourceRead;
43 import org.simantics.db.common.request.UnaryRead;
44 import org.simantics.db.exception.DatabaseException;
45 import org.simantics.db.exception.RuntimeDatabaseException;
46 import org.simantics.db.layer0.variable.Variables;
47 import org.simantics.diagram.elements.DecorationSVGNode;
48 import org.simantics.diagram.elements.DiagramNodeUtil;
49 import org.simantics.diagram.elements.TextGridNode;
50 import org.simantics.diagram.elements.TextNode;
51 import org.simantics.diagram.stubs.DiagramResource;
52 import org.simantics.diagram.ui.DiagramModelHints;
53 import org.simantics.g2d.canvas.Hints;
54 import org.simantics.g2d.canvas.ICanvasContext;
55 import org.simantics.g2d.diagram.DiagramHints;
56 import org.simantics.g2d.diagram.IDiagram;
57 import org.simantics.g2d.diagram.handler.DataElementMap;
58 import org.simantics.g2d.diagram.participant.ElementPainter.SelectionShapeNode;
59 import org.simantics.g2d.diagram.participant.Selection;
60 import org.simantics.g2d.element.IElement;
61 import org.simantics.g2d.scenegraph.ICanvasSceneGraphProvider;
62 import org.simantics.g2d.utils.CanvasUtils;
63 import org.simantics.layer0.Layer0;
64 import org.simantics.scenegraph.INode;
65 import org.simantics.scenegraph.ParentNode;
66 import org.simantics.scenegraph.g2d.G2DParentNode;
67 import org.simantics.scenegraph.g2d.G2DRenderingHints;
68 import org.simantics.scenegraph.g2d.G2DSceneGraph;
69 import org.simantics.scenegraph.g2d.IG2DNode;
70 import org.simantics.scenegraph.g2d.IG2DNodeVisitor;
71 import org.simantics.scenegraph.g2d.events.command.Commands;
72 import org.simantics.scenegraph.g2d.nodes.BackgroundNode;
73 import org.simantics.scenegraph.g2d.nodes.BoundsNode;
74 import org.simantics.scenegraph.g2d.nodes.ConnectionNode;
75 import org.simantics.scenegraph.g2d.nodes.DataNode;
76 import org.simantics.scenegraph.g2d.nodes.LinkNode;
77 import org.simantics.scenegraph.g2d.nodes.NavigationNode;
78 import org.simantics.scenegraph.g2d.nodes.SVGNode;
79 import org.simantics.scenegraph.g2d.nodes.SelectionNode;
80 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
81 import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphNode;
82 import org.simantics.scenegraph.g2d.nodes.spatial.RTreeNode;
83 import org.simantics.scenegraph.utils.NodeUtil;
84 import org.simantics.scl.runtime.function.Function1;
85 import org.simantics.scl.runtime.tuple.Tuple2;
86 import org.simantics.trend.impl.ItemNode;
87 import org.simantics.utils.threads.ThreadUtils;
88 import org.slf4j.Logger;
89 import org.slf4j.LoggerFactory;
90 import org.w3c.dom.DOMImplementation;
91 import org.w3c.dom.Document;
92 import org.w3c.dom.Element;
93 import org.w3c.dom.Node;
94 import org.w3c.dom.NodeList;
96 public class SCLScenegraph {
98 private static final Logger LOGGER = LoggerFactory.getLogger(SCLScenegraph.class);
100 private static final String MAIN_SECTION = "main";
101 private static final String SELECTION_SECTION = "selection";
102 private static final String SELECTION_MASK_SECTION = "selectionMask";
104 private static final String[] ALL_SECTIONS = { MAIN_SECTION, SELECTION_SECTION, SELECTION_MASK_SECTION };
106 // Changed from 0.001 to 0.0001 to prevent creation of huge BufferedImage's when
107 // generating PDF from SVG. If SVG contains any transparency then Batik uses
108 // bitmap-rendering which remarkably slows things down
109 // See org.apache.batik.gvt.AbstractGraphicsNode.paint(Graphics2D) where decisions are made
110 // if AlphaComposite should be painted
111 private static final String OPACITY = "0.0001";
114 public static ICanvasSceneGraphProvider getICanvasSceneGraphProvider(Resource model, Resource diagram, String diagramRVI) throws DatabaseException, InterruptedException {
115 ICanvasSceneGraphProvider provider = DiagramNodeUtil.loadSceneGraphProvider(model, diagram, diagramRVI);
120 public static void disposeSceneGraphProvider(ICanvasSceneGraphProvider provider) {
124 public static <T> T doWithICanvasSceneGraphProvider(Resource diagram, Function1<ICanvasSceneGraphProvider, T> func) throws DatabaseException {
125 return doWithICanvasSceneGraphProvider(diagram, (Function<ICanvasSceneGraphProvider, T>) provider -> func.apply(provider));
128 public static <T> T doWithICanvasSceneGraphProvider(Resource diagram, Function<ICanvasSceneGraphProvider, T> func) throws DatabaseException {
129 Tuple2 result = Simantics.getSession().syncRequest(new ResourceRead<Tuple2>(diagram) {
132 public Tuple2 perform(ReadGraph graph) throws DatabaseException {
133 Resource indexRoot = graph.syncRequest(new IndexRoot(resource));
134 String diagramRVI = Variables.getRVI(graph, resource);
135 return new Tuple2(indexRoot, diagramRVI);
138 ICanvasSceneGraphProvider provider = DiagramNodeUtil.loadSceneGraphProvider((Resource) result.c0, diagram, (String) result.c1);
140 return func.apply(provider);
146 public static <T> T doWithCanvasContext(Resource diagram, Function1<ICanvasContext, T> func) throws DatabaseException {
147 return doWithCanvasContext(diagram, (Function<ICanvasContext, T>) canvasContext -> func.apply(canvasContext));
150 public static <T> T doWithCanvasContext(Resource diagram, Function<ICanvasContext, T> func) throws DatabaseException {
151 return doWithICanvasSceneGraphProvider(diagram, (Function<ICanvasSceneGraphProvider, T>) provider -> func.apply(provider.getCanvasContext()));
154 public static String getNodeTransform(ICanvasContext ctx, String name) {
156 Set<TextNode> texts = NodeUtil.collectNodes(ctx.getSceneGraph(), TextNode.class);
157 for (TextNode text : texts) {
158 String nodeName = NodeUtil.getNodeName(text);
159 if (nodeName.equals(name)) {
160 String transform = text.getTransform().toString();
164 return "No node found";
167 public static String getNodeText(ICanvasContext ctx, String name) {
169 Set<TextNode> texts = NodeUtil.collectNodes(ctx.getSceneGraph(), TextNode.class);
170 for (TextNode text : texts) {
171 String nodeName = NodeUtil.getNodeName(text);
172 if (nodeName.equals(name)) {
173 String texti = text.getText();
177 return "No node found";
180 public static String getNodeCount(ICanvasContext ctx) {
181 G2DSceneGraph g2 = ctx.getSceneGraph();
182 int amount = NodeUtil.countTreeNodes(g2);
183 return "Node count: " + amount;
186 public static String getAllNodes (ICanvasContext ctx) {
188 Set<G2DSceneGraph> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), G2DSceneGraph.class);
189 int amount = g2.size() +1;
190 return "All nodes: " + amount;
193 public static String getBoundsNodes (ICanvasContext ctx) {
195 Set<BoundsNode> bn = NodeUtil.collectNodes(ctx.getSceneGraph(), BoundsNode.class);
196 int amount = bn.size();
197 return "BoundsNodes: " + amount;
200 public static String getBackgroundNodes (ICanvasContext ctx) {
202 Set<BackgroundNode> bg = NodeUtil.collectNodes(ctx.getSceneGraph(), BackgroundNode.class);
203 int amount = bg.size();
204 return "BackgroundNodes: " + amount;
207 public static String getDataNodes (ICanvasContext ctx) {
209 Set<DataNode> dn = NodeUtil.collectNodes(ctx.getSceneGraph(), DataNode.class);
210 int amount = dn.size();
211 return "DataNodes: " + amount;
214 public static String getNavigationNodes (ICanvasContext ctx) {
216 Set<NavigationNode> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), NavigationNode.class);
217 int amount = g2.size();
218 return "NavigationNodes: " + amount;
221 public static String getParentNodes (ICanvasContext ctx) {
223 Set<G2DParentNode> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), G2DParentNode.class);
224 int amount = g2.size();
225 return "ParentNodes: " + amount;
228 public static String getDecorationNodes (ICanvasContext ctx) {
230 Set<DecorationSVGNode> deco = NodeUtil.collectNodes(ctx.getSceneGraph(), DecorationSVGNode.class);
231 int amount = deco.size();
232 return "DecorationNodes: " + amount;
235 public static String getSingleElementNodes (ICanvasContext ctx) {
237 Set<SingleElementNode> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), SingleElementNode.class);
238 int amount = g2.size();
239 return "SingleElementNodes: " + amount;
242 public static String getConnectionNodes (ICanvasContext ctx) {
244 Set<ConnectionNode> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), ConnectionNode.class);
245 int amount = g2.size();
246 return "ConnectionNodes: " + amount;
249 public static String getTextNodes (ICanvasContext ctx) {
251 Set<TextNode> tn = NodeUtil.collectNodes(ctx.getSceneGraph(), TextNode.class);
252 Set<TextGridNode> tgn = NodeUtil.collectNodes(ctx.getSceneGraph(), TextGridNode.class);
253 int amount = tn.size() + tgn.size();
254 return "TextNodes: " + amount;
257 public static String getItemNodes (ICanvasContext ctx) {
259 Set<ItemNode> item = NodeUtil.collectNodes(ctx.getSceneGraph(), ItemNode.class);
260 int amount = item.size();
261 return "ItemNodes: " + amount;
264 public static String editNodeText (ICanvasContext ctx, String module, String previous_value, String new_value) {
266 Set<TextNode> textGridNodes = NodeUtil.collectNodes(ctx.getSceneGraph(), TextNode.class);
267 for (TextNode modulenode : textGridNodes) {
268 if (module.equals(modulenode.getText())) {
269 //System.out.println("Module what we were looking for: " + module);
270 //System.out.println("Modulenode: " + modulenode.getText());
272 ParentNode<?> parentnode = modulenode.getParent();
273 //System.out.println("Parentnode: " + parentnode);
275 Collection<TextNode> textnodes = (Collection<TextNode>) parentnode.getNodes();
276 for (TextNode valuenode : textnodes) {
277 if (previous_value.equals(valuenode.getText())) {
278 //System.out.println("Value what we were looking for: " + previous_value);
279 //System.out.println("Valuenode: " + valuenode.getText());
281 //valuenode.setEditMode(true);
282 valuenode.activateEdit(0, null, ctx);
283 valuenode.setText(new_value);
284 valuenode.fireTextEditingEnded();
286 //System.out.println("valuenode modified: " + valuenode);
287 return "Modified module " + module + " with value " + new_value;
290 return "Not found module : " + module;
293 return "No nodes in scenegraph!";
297 * Returns background color of a canvasContext or null.
299 * @return color in RGBA List<Integer> format, or null.
301 public static List<Integer> getBackgroundColor(ICanvasContext ctx) {
302 Color color = ctx.getDefaultHintContext().getHint(Hints.KEY_BACKGROUND_COLOR);
305 ArrayList<Integer> rgba = new ArrayList<>(4);
306 rgba.add(color.getRed());
307 rgba.add(color.getGreen());
308 rgba.add(color.getBlue());
309 rgba.add(color.getAlpha());
313 public static String sceneGraphTest (ICanvasContext ctx, String module, String value) {
315 boolean module_founded = false;
316 boolean value_founded = false;
318 Set<G2DSceneGraph> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), G2DSceneGraph.class);
319 System.out.println("Total amount of nodes: " + g2.size() + 1);
321 Set<TextGridNode> grid = NodeUtil.collectNodes(ctx.getSceneGraph(), TextGridNode.class);
322 Integer textGridNodeAmount = grid.size();
323 System.out.println("Amount of TextGridNodes " + textGridNodeAmount);
325 Set<TextNode> texts = NodeUtil.collectNodes(ctx.getSceneGraph(), TextNode.class);
326 Integer textNodeAmount = grid.size();
327 System.out.println("Amount of TextNodes " + textNodeAmount);
329 for (TextNode node : texts) {
330 if (module.equals(node.getText())) {
331 module_founded = true;
332 System.out.println("Correct module " + module + " founded.");
334 if (value.equals(node.getText())) {
335 value_founded = true;
336 System.out.println("Correct value " + value + " founded.");
340 if (value_founded == true && module_founded == true) {
341 return "Found both correct module " + module + " and value " + value;
343 if (value_founded == false && module_founded == true) {
344 return "Found only correct module " + module + " but not value " + value;
346 if (value_founded == true && module_founded == false) {
347 return "Found only correct value " + value + " but not module " + module;
350 return "Didn't found either module " + module + " or value " + value;
354 public static boolean copyPaste (final ICanvasContext source_ctx, final ICanvasContext target_ctx, List<Resource> modules) throws DatabaseException {
356 IDiagram idiagram = source_ctx.getDefaultHintContext().getHint(DiagramHints.KEY_DIAGRAM);
358 DataElementMap dem = idiagram.getDiagramClass().getAtMostOneItemOfClass(DataElementMap.class);
360 final Collection<IElement> newSelection = new ArrayList<IElement>();
361 for (Resource module : modules) {
362 IElement element = dem.getElement(idiagram, module);
363 if (element != null) {
364 newSelection.add(element);
366 throw new DatabaseException("Could not find IElement for " + element);
370 ThreadUtils.syncExec(source_ctx.getThreadAccess(), new Runnable() {
373 if (source_ctx.isDisposed())
375 Selection selection = source_ctx.getAtMostOneItemOfClass(Selection.class);
376 if (selection != null) {
377 // This prevents workbench selection from being left over.
378 // Also prevents scene graph crap from being left on the screen.
379 selection.setSelection(0, newSelection);
381 CanvasUtils.sendCommand(source_ctx, Commands.COPY);
382 CanvasUtils.sendCommand(target_ctx, Commands.PASTE);
388 while(source_ctx.getEventQueue().size() > 0) {
391 } catch (InterruptedException e) {
392 throw new DatabaseException(e);
396 ThreadUtils.syncExec(source_ctx.getThreadAccess(), new Runnable() {
407 static class UniqueIDGenerator extends SVGIDGenerator{
410 public UniqueIDGenerator(String overallId) {
412 this.overallId = overallId;
416 public String generateID(String prefix) {
417 return super.generateID(overallId+prefix);
422 static class Generator extends SVGGraphics2D {
425 String newElementId = null;
426 ArrayList<Element> elements = new ArrayList<Element>();
428 public static final String svgNS = "http://www.w3.org/2000/svg";
430 public Generator(SVGGeneratorContext ctx, boolean textAsShapes) {
431 super(ctx, textAsShapes);
434 public Generator(Document document) {
437 public Generator(Document document, String id) {
439 // prevent batik comment in each g-element
440 getGeneratorContext().setComment(null);
442 getGeneratorContext().setIDGenerator(new UniqueIDGenerator(id));
446 public Element getRoot() {
447 Element root = super.getRoot();
448 for(Element e : elements) {
455 public void setRenderingHint(Key arg0, Object arg1) {
456 if(G2DRenderingHints.KEY_BEGIN_ELEMENT == arg0) {
459 if(G2DRenderingHints.KEY_ELEMENT_ID == arg0) {
461 newElementId = arg1.toString();
463 newElementId = UUID.randomUUID().toString();
465 if(G2DRenderingHints.KEY_END_ELEMENT == arg0) {
468 Element group = getDOMFactory().createElement(SVG_G_TAG);
469 //Element group = getDOMFactory().createElementNS(SVG_NAMESPACE_URI, SVG_G_TAG);
470 group.setAttributeNS(null, "id", newElementId);
471 group.setAttributeNS(null, "class", arg1.toString());
476 super.setRenderingHint(arg0, arg1);
481 public static Element renderSVGNode(SVGGraphics2D svgGenerator, IG2DNode node) {
485 svgGenerator.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
486 svgGenerator.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
487 svgGenerator.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
489 node.render(svgGenerator);
491 } catch (Throwable t) {
492 LOGGER.error("Problems rendering scene graph to SVG", t);
495 return svgGenerator.getRoot();
498 public static String printSVGDocument(Element doce) {
500 StringBuilder result = new StringBuilder();
502 NodeList nl = doce.getChildNodes();
504 for(int i=0;i<nl.getLength();i++) {
506 ByteArrayOutputStream os = new ByteArrayOutputStream();
510 TransformerFactory tf = TransformerFactory.newInstance();
511 Transformer transformer = tf.newTransformer();
512 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
513 transformer.setOutputProperty(OutputKeys.STANDALONE, "no");
514 transformer.setOutputProperty(OutputKeys.METHOD, "xml");
515 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
516 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
517 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
519 transformer.transform(new DOMSource(nl.item(i)),
520 new StreamResult(new OutputStreamWriter(os, "UTF-8")));
525 } catch (Throwable t) {
526 LOGGER.error("Problems formatting SVGDocument to text.", t);
529 result.append(new String(os.toByteArray(), Charset.forName("UTF-8")));
533 return result.toString();
537 public static String renderSVG3(ICanvasContext ctx) {
538 return renderSVG3(ctx, -1, -1);
541 public static String renderSVG3(ICanvasContext ctx, double width, double height) {
542 return renderSVG0(width, height, SWT.LEFT, SWT.TOP, ctx, p0 -> p0.stream().collect(Collectors.toMap(p1 -> p1, p2 -> p2)));
548 * @throws DatabaseException
550 private static Object[] createURIBasedL0Identifier(ReadGraph graph, Resource component) throws DatabaseException {
551 String uri = graph.getPossibleURI(component);
552 int hashCode = uri.hashCode();
553 Random random = new Random(hashCode);
554 long l1 = random.nextLong();
555 long l2 = random.nextLong();
556 return new Object[] { l1, l2 };
560 * Default no-op mapper
562 private static final Function1<Set<?>, Map<?, ?>> mapper = p0 -> p0.stream().collect(Collectors.toMap(p1 -> p1, p2 -> p2));
566 * Renders CanvasContext to SVG.
568 * @param width Width of output image. Use -1 for autosize.
569 * @param height Height of output image. Use -1 for autosize.
570 * @param ax horizontal alignment. SWT.LEFT SWT.CENTER SWT.RIGHT are accepted values. Value is not used with autosize.
571 * @param ay vertical alignment. SWT.TOP SWT.CENTER SWT.BOTTOM are accepted values. Value is not used with autosize.
574 public static String renderSVG(ICanvasContext ctx, double width, double height, int ax, int ay) {
575 return renderSVG0(width, height, ax, ay, ctx, mapper);
578 public static String renderSVG(ICanvasContext ctx, double width, double height) {
579 return renderSVG(ctx,width,height, SWT.LEFT, SWT.TOP);
582 public static String renderSVG(ICanvasContext ctx) {
583 return renderSVG(ctx, -1, -1, SWT.LEFT, SWT.TOP);
586 public static String renderSVGMapIdentifiers(ICanvasContext ctx) {
587 return renderSVGMapIdentifiers(ctx, -1, -1);
591 * Renders ICanvasContext into SVG by mapping the SVG id's into URI based
597 public static String renderSVGMapIdentifiers(ICanvasContext ctx, double width, double height) {
598 return renderSVG0(width, height, SWT.LEFT, SWT.TOP, ctx, new Function1<Set<?>, Map<?, ?>>() {
601 public Map<?, ?> apply(Set<?> p0) {
603 return Simantics.getSession().syncRequest(new UnaryRead<Set<?>, Map<?, ?>>(p0) {
606 public Map<?, ?> perform(ReadGraph graph) throws DatabaseException {
607 ModelingResources MOD = ModelingResources.getInstance(graph);
608 DiagramResource DIA = DiagramResource.getInstance(graph);
609 Layer0 L0 = Layer0.getInstance(graph);
610 return parameter.stream().collect(Collectors.toMap(p -> p, p -> {
612 if (p instanceof Resource) {
613 Resource element = (Resource) p;
614 if (graph.isInstanceOf(element, DIA.Element)) {
615 Resource component = graph.getPossibleObject(element, MOD.ElementToComponent);
616 if (component != null) {
617 GUID identifier = graph.getPossibleRelatedValue(component, L0.identifier, GUID.BINDING);
618 if (identifier != null) {
619 return Arrays.toString(createURIBasedL0Identifier(graph, component));
621 LOGGER.error("Component {} does not have GUID identifier!", component);
623 } else if (graph.isInstanceOf(element, DIA.Connection) || graph.isInstanceOf(element, DIA.Terminal)) {
624 // Ok, lets create a hashcode for connections and terminals as they do not have identifiers
625 return graph.getURI(element).hashCode();
627 // Ok, lets create a hashcode or either return empty string in cases where no ID is required
628 if (graph.hasStatement(element, L0.HasName))
629 return graph.getURI(element).hashCode();
634 LOGGER.error("Parameter p {} is not resource but it is {}", p, p.getClass());
637 } catch (DatabaseException e) {
638 throw new RuntimeDatabaseException(e);
643 } catch (DatabaseException e) {
644 LOGGER.error("Could not apply mappings", e);
645 throw new RuntimeDatabaseException(e);
651 static class RenderSVGContext {
653 Map<String,StringBuilder> documents = new HashMap<>();
655 public void append(String[] keys, String svgText) {
656 for(String key : keys) append(key, svgText);
659 public void append(String key, String svgText) {
660 StringBuilder builder = documents.get(key);
661 if(builder == null) {
662 builder = new StringBuilder();
663 documents.put(key, builder);
665 builder.append(svgText);
668 public void append(RenderSVGContext other) {
669 for(String key : other.documents.keySet()) {
670 append(key, other.get(key));
674 public String get(String key) {
675 StringBuilder builder = documents.get(key);
676 if(builder == null) return "";
677 else return builder.toString();
684 private static String renderSVG0(double width, double height, int ax, int ay, ICanvasContext ctx, Function1<Set<?>, Map<?, ?>> mappingFunction) {
686 // Get a DOMImplementation.
687 DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();
689 // Create an instance of org.w3c.dom.Document.
690 String svgNS = "http://www.w3.org/2000/svg";
691 Document document = domImpl.createDocument(svgNS, "svg", null);
693 // Create an instance of the SVG Generator.
694 IDiagram d = ctx.getDefaultHintContext().getHint(DiagramHints.KEY_DIAGRAM);
695 Resource r = d.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);
696 SVGGraphics2D svgGenerator;
698 svgGenerator = new Generator(document,r.toString());
700 svgGenerator = new Generator(document);
702 RenderSVGContext result = new RenderSVGContext();
704 double[] matrix = new double[6];
707 Selection selection = ctx.getAtMostOneItemOfClass(Selection.class);
708 if (selection != null) {
709 // This prevents workbench selection from being left over.
710 // Also prevents scene graph crap from being left on the screen.
711 selection.setSelection(0, d.getElements());
714 G2DSceneGraph sg = ctx.getSceneGraph();
716 G2DParentNode root = (G2DParentNode) sg.getRootNode();
718 // rtree is the actual content of the diagram
719 RTreeNode rtree = NodeUtil.getNearestChildByClass(root, RTreeNode.class);
720 Rectangle2D rtreeBounds = NodeUtil.getLocalBounds(rtree);
722 // get the bounds of the content
723 Rectangle2D content = rtreeBounds;
725 // int ax = SWT.LEFT;
727 // int ax = SWT.CENTER;
728 // int ay = SWT.CENTER;
730 if (content != null) {
731 // To account for dynamic padding of selection rectangles (5 units + stroke width)
734 double scale = width < 0 || height < 0 ? 1.0 : Math.min((width - 2*offset) / content.getWidth(), (height - 2*offset) / content.getHeight());
736 AffineTransform tr = new AffineTransform();
737 tr.translate(offset, offset);
738 tr.scale(scale, scale);
739 double dx = -content.getX();
740 double dy = -content.getY();
741 if (width > 0.0 && height > 0.0) {
742 if (ax == SWT.LEFT) {
743 dx = -content.getX();
744 } else if (ax == SWT.RIGHT) {
745 double t = ((width - 2*offset)/scale - content.getWidth());
746 dx = -content.getX() + t;
748 double t = ((width - 2*offset)/scale - content.getWidth()) *0.5;
749 dx = -content.getX() + t;
752 dy = -content.getY();
753 } else if (ay == SWT.BOTTOM) {
754 double t = ((height - 2*offset)/scale - content.getHeight());
755 dy = -content.getY() + t;
757 double t = ((height - 2*offset)/scale - content.getHeight()) * 0.5;
758 dy = -content.getY() + t;
761 tr.translate(dx, dy);
762 tr.getMatrix(matrix);
763 svgGenerator.setSVGCanvasSize(new Dimension((int)Math.ceil(scale * content.getWidth()) + 2*offset, (int)Math.ceil(scale * content.getHeight()) + 2*offset));
765 svgGenerator.setSVGCanvasSize(new Dimension(100, 100));
767 //svgGenerator.translate(offset, offset);
768 //svgGenerator.scale(scale, scale);
769 // translate svgGenerator to the x and y coordinates of current content
770 //svgGenerator.translate(-content.getX(), -content.getY());
773 //svgGenerator.setClip(content);
775 result.append(MAIN_SECTION, "<g class=\"symbols\">");
776 result.append(SELECTION_SECTION, "<g class=\"selections\">");
777 result.append(SELECTION_MASK_SECTION, "<g class=\"selectionMasks\">");
779 KeyVisitor keyVisitor = new KeyVisitor();
780 sg.accept(keyVisitor);
782 Set<Object> keys = keyVisitor.getKeys();
784 Map<?, ?> mappings = mappingFunction.apply(keys);
786 IG2DNodeVisitor visitor = new PrintingVisitor(svgGenerator, result, mappings);
790 } catch (Throwable t) {
791 LOGGER.error("Problems rendering canvas context to SVG", t);
795 result.append(ALL_SECTIONS, "</g>");
797 StringBuilder res = new StringBuilder();
798 if (width > 0 && height > 0 ) {
799 res.append("<svg width=\"" + width +"px\" height=\""+height+"px\" stroke=\"black\" xmlns=\"http://www.w3.org/2000/svg\">");
801 res.append("<svg width=\"100%\" height=\"100%\" stroke=\"black\" xmlns=\"http://www.w3.org/2000/svg\">");
803 res.append("<g transform=\"matrix(").append(matrix[0]).append(",").append(matrix[1]).append(",").append(matrix[2]).append(",").append(matrix[3]).append(",").append(matrix[4]).append(",").append(matrix[5]).append(")\">");
804 res.append(result.get(MAIN_SECTION));
805 res.append(result.get(SELECTION_SECTION));
806 res.append(result.get(SELECTION_MASK_SECTION));
808 res.append("</svg>");
810 // System.err.println(" == FINAL RESULT == ");
811 // System.err.println(res.toString());
813 return res.toString();
819 private static class KeyVisitor implements IG2DNodeVisitor {
821 private Set<Object> keys = new HashSet<>();
824 public void enter(IG2DNode node) {
825 if (node instanceof SingleElementNode) {
826 Object key = ((SingleElementNode) node).getKey();
834 public void leave(IG2DNode node) {
838 public Set<Object> getKeys() {
843 private static class PrintingVisitor implements IG2DNodeVisitor {
847 HashMap<SingleElementNode,RenderSVGContext> senBuilders = new HashMap<>();
849 private RenderSVGContext result;
850 private SVGGraphics2D svgGenerator;
852 private Map<?, ?> mappings;
854 public PrintingVisitor(SVGGraphics2D svgGenerator, RenderSVGContext result, Map<?, ?> mappings) {
855 this.result = result;
856 this.mappings = mappings;
857 this.svgGenerator = svgGenerator;
860 private String getKey(SingleElementNode node) {
862 if(node.getKey() != null) {
863 if (mappings.containsKey(node.getKey())) {
864 key = mappings.get(node.getKey()).toString();
867 key = node.getKey().toString();
871 key = Long.toString(node.getId());
876 private String escape(String key) {
877 // Keys may contain '<' '>' characters, which causes errors in browser SVG handling.
878 return org.apache.commons.lang.StringEscapeUtils.escapeHtml(key);
881 private String removeElem(String xml, String elemStart) {
882 // FIXME: This is rather nasty and error prone way of removing elements from XML string.
883 // This only supports elements with /> end element tag. Elements ends with </elem name> are not supported!
884 int start = xml.indexOf("<"+elemStart);
886 int end = xml.indexOf(">",start);
889 return xml.substring(0,start)+xml.substring(end+1);
891 return xml.substring(end+1);
898 public void enter(IG2DNode node) {
900 RenderSVGContext parentBuilder = getParentBuilder(node);
903 if(node instanceof ConnectionNode) {
905 for(RouteGraphNode n : NodeUtil.collectNodes(node, RouteGraphNode.class)) {
906 n.setIgnoreSelection(true);
909 String key = getKey((ConnectionNode) node);
910 parentBuilder.append(MAIN_SECTION, "\n<g class=\"connection\" id=\"" + key + "\">");
911 parentBuilder.append(SELECTION_SECTION, "\n<g style=\"visibility:hidden\" class=\"selection\" id=\"" + key + "\">");
912 parentBuilder.append(SELECTION_MASK_SECTION, "\n<g class=\"selectionMask\" opacity=\"" + OPACITY + "\" id=\"" + key + "\">");
914 Element doc = renderSVGNode(svgGenerator, (IG2DNode)node);
915 String svg = printSVGDocument(doc);
916 parentBuilder.append(MAIN_SECTION, svg);
918 for(RouteGraphNode n : NodeUtil.collectNodes(node, RouteGraphNode.class)) {
919 n.setIgnoreSelection(false);
922 doc = renderSVGNode(svgGenerator, (IG2DNode)node);
923 svg = printSVGDocument(doc);
924 parentBuilder.append(SELECTION_SECTION, svg);
926 BasicStroke bs = new BasicStroke(10f);
928 for(RouteGraphNode n : NodeUtil.collectNodes(node, RouteGraphNode.class)) {
929 n.setDynamicStroke(bs);
932 doc = renderSVGNode(svgGenerator, (IG2DNode)node);
933 svg = printSVGDocument(doc);
934 parentBuilder.append(SELECTION_MASK_SECTION, svg);
936 senBuilders.put((ConnectionNode)node, new RenderSVGContext());
939 } else if (isSelection0(node)) {
941 SelectionNode n = (SelectionNode)node;
942 SingleElementNode parentSEN = (SingleElementNode)NodeUtil.getNearestParentOfType(node, SingleElementNode.class);
943 if(parentSEN != null) {
945 RenderSVGContext parentBuilder2 = getParentBuilder(parentSEN);
947 String key = getKey(parentSEN);
949 Element doc = renderSVGNode(svgGenerator, (IG2DNode)node);
951 String svg = printSVGDocument(doc);
952 parentBuilder2.append(SELECTION_SECTION, "\n<g style=\"visibility:hidden\" class=\"selection\" id=\"" + key + "\">");
953 parentBuilder2.append(SELECTION_SECTION, svg);
954 parentBuilder2.append(SELECTION_SECTION, "\n</g>");
955 parentBuilder2.append(SELECTION_MASK_SECTION, "\n<g class=\"selectionMask\" id=\"" + key /*+ "\" transform=\"" + matrixString + "\"*/+ "\">");
956 Rectangle2D rect = n.getRect();
958 if(rect.getHeight() == rect.getHeight() && rect.getWidth() == rect.getWidth()) {
959 parentBuilder2.append(SELECTION_MASK_SECTION,"<rect style=\"fill:#fff\" opacity=\"" + OPACITY + "\"");
960 parentBuilder2.append(SELECTION_MASK_SECTION," x=\"" + rect.getX() + "\" y=\"" + rect.getY() + "\"");
961 parentBuilder2.append(SELECTION_MASK_SECTION," width=\"" + rect.getWidth() + "\" height=\"" + rect.getHeight() + "\"");
962 parentBuilder2.append(SELECTION_MASK_SECTION,"></rect>");
964 parentBuilder2.append(SELECTION_MASK_SECTION,"\n</g>");
967 } else if (node instanceof SelectionNode) {
969 Element doc = renderSVGNode(svgGenerator, (IG2DNode)node);
970 String svg = printSVGDocument(doc);
971 parentBuilder.append(MAIN_SECTION, "<g>");
972 parentBuilder.append(MAIN_SECTION, svg);
973 parentBuilder.append(MAIN_SECTION, "\n</g>");
975 } else if (node instanceof SVGNode) {
976 SVGNode svg = (SVGNode)node;
977 AffineTransform at = svg.getTransform();
978 if (!at.isIdentity()) {
979 if(at.getScaleX() == 1.0 && at.getScaleY() == 1.0 && at.getShearX() == 0.0 && at.getShearY() == 0.0) {
980 String m = "translate(" + at.getTranslateX() + " " + at.getTranslateY() + ")";
981 parentBuilder.append(MAIN_SECTION, "\n<g transform=\"" + m + "\">");
983 double[] ds = new double[6];
985 String m = "matrix(" + ds[0] + " " + ds[1] + " " + ds[2] + " " + ds[3] + " " + ds[4] + " " + ds[5] + ")";
986 parentBuilder.append(MAIN_SECTION, "\n<g transform=\"" + m + "\">");
989 String svgContent = svg.getSVGText();
990 // SVGNode content may contain SVG/XML elements, which break browser compatibility
991 int start = svgContent.indexOf("<svg");
993 svgContent = svgContent.substring(start);
995 String s = removeElem(svgContent, "?xml");
997 svgContent = "<g>"+s+"</g>";
998 s = removeElem(svgContent, "!DOCTYPE");
1003 parentBuilder.append(MAIN_SECTION, svgContent);
1004 if (!at.isIdentity()) {
1005 parentBuilder.append(MAIN_SECTION, "\n</g>");
1007 } else if (node instanceof G2DParentNode) {
1008 AffineTransform at = node.getTransform();
1009 if(node instanceof SingleElementNode) {
1010 SingleElementNode sen = (SingleElementNode)node;
1011 String key = getKey(sen);
1012 String typeClass = sen.getTypeClass();
1013 String clazz = "definedElement";
1014 if(typeClass != null && !typeClass.isEmpty())
1015 clazz = clazz + " " + typeClass;
1017 parentBuilder.append(MAIN_SECTION, "\n<g class=\""+clazz+"\" id=\"" + key + "\">");
1018 senBuilders.put(sen, new RenderSVGContext());
1020 if(!at.isIdentity()) {
1021 if(at.getScaleX() == 1.0 && at.getScaleY() == 1.0 && at.getShearX() == 0.0 && at.getShearY() == 0.0) {
1022 String m = "translate(" + at.getTranslateX() + " " + at.getTranslateY() + ")";
1023 parentBuilder.append(ALL_SECTIONS, "\n<g transform=\"" + m + "\">");
1025 double[] ds = new double[6];
1027 String m = "matrix(" + ds[0] + " " + ds[1] + " " + ds[2] + " " + ds[3] + " " + ds[4] + " " + ds[5] + ")";
1028 parentBuilder.append(ALL_SECTIONS, "\n<g transform=\"" + m + "\">");
1031 } else if (node instanceof TextNode) {
1032 TextNode text = (TextNode)node;
1034 SingleElementNode parentSEN = (SingleElementNode)NodeUtil.getNearestParentOfType(node, SingleElementNode.class);
1035 if(parentSEN != null) {
1037 text.setShowSelection(false);
1038 Element doc = renderSVGNode(svgGenerator, (IG2DNode)node);
1039 String svg = printSVGDocument(doc);
1040 parentBuilder.append(MAIN_SECTION, svg);
1042 RenderSVGContext parentBuilder2 = getParentBuilder(parentSEN);
1044 String key = getKey(parentSEN);
1045 text.setShowSelection(true);
1046 doc = renderSVGNode(svgGenerator, (IG2DNode)node);
1047 svg = printSVGDocument(doc);
1049 parentBuilder2.append(SELECTION_SECTION, "\n<g style=\"visibility:hidden\" class=\"selection\" id=\"" + key + "\">");
1050 parentBuilder2.append(SELECTION_SECTION, svg);
1051 parentBuilder2.append(SELECTION_SECTION, "\n</g>");
1052 parentBuilder2.append(SELECTION_MASK_SECTION, "\n<g class=\"selectionMask\" id=\"" + key /*+ "\" transform=\"" + matrixString + "\"*/+ "\">");
1053 Rectangle2D rect = text.getBounds();
1055 if(rect.getHeight() == rect.getHeight() && rect.getWidth() == rect.getWidth()) {
1056 parentBuilder2.append(SELECTION_MASK_SECTION,"<rect style=\"fill:#fff\" opacity=\"" + OPACITY + "\"");
1057 parentBuilder2.append(SELECTION_MASK_SECTION," x=\"" + rect.getX() + "\" y=\"" + rect.getY() + "\"");
1058 parentBuilder2.append(SELECTION_MASK_SECTION," width=\"" + rect.getWidth() + "\" height=\"" + rect.getHeight() + "\"");
1059 parentBuilder2.append(SELECTION_MASK_SECTION,"></rect>");
1061 parentBuilder2.append(SELECTION_MASK_SECTION,"\n</g>");
1064 } else if (!(node instanceof RouteGraphNode) && !(node instanceof LinkNode)){
1066 Element doc = renderSVGNode(svgGenerator, (IG2DNode)node);
1067 NodeList gList = doc.getElementsByTagName("g");
1068 if (gList.getLength() == 0)
1070 boolean hasContent = false;
1071 for (int i = 0; i < gList.getLength(); i++) {
1072 Node gNode = gList.item(i);
1073 if (gNode.hasChildNodes()) {
1080 String svg = printSVGDocument(doc);
1081 if (node instanceof SelectionShapeNode) {
1082 SingleElementNode parentSEN = (SingleElementNode)NodeUtil.getNearestParentOfType(node, SingleElementNode.class);
1083 if(parentSEN != null) {
1084 String key = getKey(parentSEN);
1085 RenderSVGContext parentBuilder2 = getParentBuilder(parentSEN);
1086 parentBuilder2.append(SELECTION_SECTION, "\n<g style=\"visibility:hidden\" class=\"selection\" id=\"" + key + "\">");
1087 parentBuilder2.append(SELECTION_SECTION, svg);
1088 parentBuilder2.append(SELECTION_SECTION, "\n</g>");
1090 parentBuilder2.append(SELECTION_MASK_SECTION, "\n<g class=\"selectionMask\" id=\"" + key /*+ "\" transform=\"" + matrixString + "\"*/+ "\">");
1091 Rectangle2D rect = node.getBounds();
1093 if(rect.getHeight() == rect.getHeight() && rect.getWidth() == rect.getWidth()) {
1094 parentBuilder2.append(SELECTION_MASK_SECTION,"<rect style=\"fill:#fff\" opacity=\"" + OPACITY + "\"");
1095 parentBuilder2.append(SELECTION_MASK_SECTION," x=\"" + rect.getX() + "\" y=\"" + rect.getY() + "\"");
1096 parentBuilder2.append(SELECTION_MASK_SECTION," width=\"" + rect.getWidth() + "\" height=\"" + rect.getHeight() + "\"");
1097 parentBuilder2.append(SELECTION_MASK_SECTION,"></rect>");
1099 parentBuilder2.append(SELECTION_MASK_SECTION,"\n</g>");
1102 parentBuilder.append(MAIN_SECTION, "<g class=\"" +node.getSimpleClassName() +"\">");
1103 parentBuilder.append(MAIN_SECTION, svg);
1104 parentBuilder.append(MAIN_SECTION, "\n</g>");
1106 } catch (Exception e) {
1107 // TODO: There are nodes that do not behave well when rendered to SVG. For backwards compatibility, we don't handle the exceptions.
1111 //enters.put(node, b.length());
1115 private boolean isSelection0(IG2DNode node) {
1117 if(node instanceof SelectionNode) {
1118 SelectionNode sn = (SelectionNode)node;
1119 return sn.getSelectionId() == 0;
1126 private RenderSVGContext getParentBuilder(IG2DNode node) {
1128 INode parentSEN = NodeUtil.getNearestParentOfType(node, SingleElementNode.class);
1129 if(parentSEN instanceof G2DSceneGraph) return result;
1131 RenderSVGContext parentBuilder = senBuilders.get(parentSEN);
1132 if(parentBuilder == null) return result;
1134 return parentBuilder;
1139 public void leave(IG2DNode node) {
1141 if( node instanceof SVGNode) {
1143 } else if (node instanceof ConnectionNode) {
1144 RenderSVGContext parentBuilder = getParentBuilder(node);
1145 SingleElementNode sen = (SingleElementNode)node;
1146 RenderSVGContext b = senBuilders.get(sen);
1147 String content = b.get(MAIN_SECTION);
1148 if(content.isEmpty()) {
1149 // Handling connection the same way as SingleElementNode would draw connection twice..
1150 // if(sen.getKey() != null) {
1152 // for(SelectionNode n : NodeUtil.collectNodes(node, SelectionNode.class)) {
1153 // n.setIgnore(true);
1156 // Element doc = renderSVGNode(svgGenerator, (IG2DNode)node);
1157 // String svg = printSVGDocument(doc);
1158 // parentBuilder.append(MAIN_SECTION, svg);
1161 parentBuilder.append(b);
1163 parentBuilder.append(SELECTION_MASK_SECTION, "\n</g>");
1164 parentBuilder.append(SELECTION_SECTION, "\n</g>");
1165 parentBuilder.append(MAIN_SECTION, "\n</g>");
1167 } else if (node instanceof G2DParentNode) {
1169 RenderSVGContext parentBuilder = getParentBuilder(node);
1171 if(node instanceof SingleElementNode) {
1172 SingleElementNode sen = (SingleElementNode)node;
1173 RenderSVGContext b = senBuilders.get(sen);
1174 String content = b.get(MAIN_SECTION);
1175 if(content.isEmpty()) {
1176 if(sen.getKey() != null) {
1178 for(SelectionNode n : NodeUtil.collectNodes(node, SelectionNode.class)) {
1182 Element doc = renderSVGNode(svgGenerator, (IG2DNode)node);
1183 String svg = printSVGDocument(doc);
1184 parentBuilder.append(MAIN_SECTION, svg);
1187 parentBuilder.append(b);
1191 AffineTransform at = node.getTransform();
1192 if(!at.isIdentity()) {
1193 parentBuilder.append(ALL_SECTIONS, "</g>");
1195 if(node instanceof SingleElementNode) {
1196 SingleElementNode sen = (SingleElementNode)node;
1197 //if(sen.getKey() != null) {
1198 parentBuilder.append(MAIN_SECTION, "</g>");