+ static class Generator extends SVGGraphics2D {
+
+ int elemLevel = 0;
+ String newElementId = null;
+ ArrayList<Element> elements = new ArrayList<Element>();
+
+ public static final String svgNS = "http://www.w3.org/2000/svg";
+
+ public Generator(SVGGeneratorContext ctx, boolean textAsShapes) {
+ super(ctx, textAsShapes);
+ }
+
+ public Generator(Document document) {
+ super(document);
+ // prevent batik comment in each g-element
+ getGeneratorContext().setComment(null);
+ }
+
+ @Override
+ public Element getRoot() {
+ Element root = super.getRoot();
+ for(Element e : elements) {
+ root.appendChild(e);
+ }
+ return root;
+ }
+
+ @Override
+ public void setRenderingHint(Key arg0, Object arg1) {
+ if(G2DRenderingHints.KEY_BEGIN_ELEMENT == arg0) {
+ elemLevel++;
+ }
+ if(G2DRenderingHints.KEY_ELEMENT_ID == arg0) {
+ if(arg1 != null)
+ newElementId = arg1.toString();
+ else
+ newElementId = UUID.randomUUID().toString();
+ }
+ if(G2DRenderingHints.KEY_END_ELEMENT == arg0) {
+ elemLevel--;
+ if(elemLevel == 0) {
+ Element group = getDOMFactory().createElement(SVG_G_TAG);
+ //Element group = getDOMFactory().createElementNS(SVG_NAMESPACE_URI, SVG_G_TAG);
+ group.setAttributeNS(null, "id", newElementId);
+ group.setAttributeNS(null, "class", arg1.toString());
+ getRoot(group);
+ elements.add(group);
+ }
+ }
+ super.setRenderingHint(arg0, arg1);
+ }
+
+ }
+
+ public static Element renderSVGNode(SVGGraphics2D svgGenerator, IG2DNode node) {
+
+ try {
+
+ svgGenerator.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ svgGenerator.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ svgGenerator.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+
+ node.render(svgGenerator);
+
+ } catch (Throwable t) {
+ LOGGER.error("Problems rendering scene graph to SVG", t);
+ }
+
+ return svgGenerator.getRoot();
+ }
+
+ public static String printSVGDocument(Element doce) {
+
+ StringBuilder result = new StringBuilder();
+
+ NodeList nl = doce.getChildNodes();
+
+ for(int i=0;i<nl.getLength();i++) {
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+
+ try {
+
+ TransformerFactory tf = TransformerFactory.newInstance();
+ Transformer transformer = tf.newTransformer();
+ transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+ transformer.setOutputProperty(OutputKeys.STANDALONE, "no");
+ transformer.setOutputProperty(OutputKeys.METHOD, "xml");
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+ transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+ transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
+
+ transformer.transform(new DOMSource(nl.item(i)),
+ new StreamResult(new OutputStreamWriter(os, "UTF-8")));
+
+ os.flush();
+ os.close();
+
+ } catch (Throwable t) {
+ LOGGER.error("Problems formatting SVGDocument to text.", t);
+ }
+
+ result.append(new String(os.toByteArray()));
+
+ }
+
+ return result.toString();
+
+ }
+
+ public static String renderSVG3(ICanvasContext ctx) {
+ return renderSVG3(ctx, -1, -1);
+ }
+
+ public static String renderSVG3(ICanvasContext ctx, double width, double height) {
+ return renderSVG0(width, height, ctx, p0 -> p0.stream().collect(Collectors.toMap(p1 -> p1, p2 -> p2)));
+ }
+
+ /**
+ * @param graph
+ * @param component
+ * @throws DatabaseException
+ */
+ private static Object[] createURIBasedL0Identifier(ReadGraph graph, Resource component) throws DatabaseException {
+ String uri = graph.getPossibleURI(component);
+ int hashCode = uri.hashCode();
+ Random random = new Random(hashCode);
+ long l1 = random.nextLong();
+ long l2 = random.nextLong();
+ return new Object[] { l1, l2 };
+ }
+
+ /**
+ * Default no-op mapper
+ */
+ private static final Function1<Set<?>, Map<?, ?>> mapper = p0 -> p0.stream().collect(Collectors.toMap(p1 -> p1, p2 -> p2));
+
+ public static String renderSVG(ICanvasContext ctx, double width, double height) {
+ return renderSVG0(width, height, ctx, mapper);
+ }
+
+ public static String renderSVG(ICanvasContext ctx) {
+ return renderSVG(ctx, -1, -1);
+ }
+
+ public static String renderSVGMapIdentifiers(ICanvasContext ctx) {
+ return renderSVGMapIdentifiers(ctx, -1, -1);
+ }
+
+ /**
+ * Renders ICanvasContext into SVG by mapping the SVG id's into URI based
+ * GUID's
+ *
+ * @param ctx
+ * @return
+ */
+ public static String renderSVGMapIdentifiers(ICanvasContext ctx, double width, double height) {
+ return renderSVG0(width, height, ctx, new Function1<Set<?>, Map<?, ?>>() {
+
+ @Override
+ public Map<?, ?> apply(Set<?> p0) {
+ try {
+ return Simantics.getSession().syncRequest(new UnaryRead<Set<?>, Map<?, ?>>(p0) {
+
+ @Override
+ public Map<?, ?> perform(ReadGraph graph) throws DatabaseException {
+ ModelingResources MOD = ModelingResources.getInstance(graph);
+ DiagramResource DIA = DiagramResource.getInstance(graph);
+ Layer0 L0 = Layer0.getInstance(graph);
+ return parameter.stream().collect(Collectors.toMap(p -> p, p -> {
+ try {
+ if (p instanceof Resource) {
+ Resource element = (Resource) p;
+ if (graph.isInstanceOf(element, DIA.Element)) {
+ Resource component = graph.getPossibleObject(element, MOD.ElementToComponent);
+ if (component != null) {
+ GUID identifier = graph.getPossibleRelatedValue(component, L0.identifier, GUID.BINDING);
+ if (identifier != null) {
+ return Arrays.toString(createURIBasedL0Identifier(graph, component));
+ } else {
+ LOGGER.error("Component {} does not have GUID identifier!", component);
+ }
+ } else if (graph.isInstanceOf(element, DIA.Connection) || graph.isInstanceOf(element, DIA.Terminal)) {
+ // Ok, lets create a hashcode for connections and terminals as they do not have identifiers
+ return graph.getURI(element).hashCode();
+ } else {
+ // Ok, lets create a hashcode or either return empty string in cases where no ID is required
+ if (graph.hasStatement(element, L0.HasName))
+ return graph.getURI(element).hashCode();
+ return "";
+ }
+ }
+ } else {
+ LOGGER.error("Parameter p {} is not resource but it is {}", p, p.getClass());
+ }
+ return p;
+ } catch (DatabaseException e) {
+ throw new RuntimeDatabaseException(e);
+ }
+ }));
+ }
+ });
+ } catch (DatabaseException e) {
+ LOGGER.error("Could not apply mappings", e);
+ throw new RuntimeDatabaseException(e);
+ }
+ }
+ });
+ }
+
+ static class RenderSVGContext {
+
+ Map<String,StringBuilder> documents = new HashMap<>();
+
+ public void append(String[] keys, String svgText) {
+ for(String key : keys) append(key, svgText);
+ }
+
+ public void append(String key, String svgText) {
+ StringBuilder builder = documents.get(key);
+ if(builder == null) {
+ builder = new StringBuilder();
+ documents.put(key, builder);
+ }
+ builder.append(svgText);
+ }
+
+ public void append(RenderSVGContext other) {
+ for(String key : other.documents.keySet()) {
+ append(key, other.get(key));
+ }
+ }
+
+ public String get(String key) {
+ StringBuilder builder = documents.get(key);
+ if(builder == null) return "";
+ else return builder.toString();
+ }
+
+ }
+
+ private static String renderSVG0(double width, double height, ICanvasContext ctx, Function1<Set<?>, Map<?, ?>> mappingFunction) {
+
+ // Get a DOMImplementation.
+ DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();
+
+ // Create an instance of org.w3c.dom.Document.
+ String svgNS = "http://www.w3.org/2000/svg";
+ Document document = domImpl.createDocument(svgNS, "svg", null);
+
+ // Create an instance of the SVG Generator.
+ SVGGraphics2D svgGenerator = new Generator(document);
+
+ RenderSVGContext result = new RenderSVGContext();
+
+ double[] matrix = new double[6];
+ try {
+
+ Selection selection = ctx.getAtMostOneItemOfClass(Selection.class);
+ if (selection != null) {
+ // This prevents workbench selection from being left over.
+ // Also prevents scene graph crap from being left on the screen.
+ IDiagram d = ctx.getDefaultHintContext().getHint(DiagramHints.KEY_DIAGRAM);
+ selection.setSelection(0, d.getElements());
+ }
+
+ G2DSceneGraph sg = ctx.getSceneGraph();
+ sg.performCleanup();
+ G2DParentNode root = (G2DParentNode) sg.getRootNode();
+
+ // rtree is the actual content of the diagram
+ RTreeNode rtree = NodeUtil.getNearestChildByClass(root, RTreeNode.class);
+ Rectangle2D rtreeBounds = NodeUtil.getLocalBounds(rtree);
+
+ // get the bounds of the content
+ Rectangle2D content = rtreeBounds;
+
+
+ if (content != null) {
+ // To account for dynamic padding of selection rectangles (5 units + stroke width)
+ int offset = 6;
+
+ double scale = width < 0 || height < 0 ? 1.0 : Math.min((width - 2*offset) / content.getWidth(), (height - 2*offset) / content.getHeight());
+
+ AffineTransform tr = new AffineTransform();
+ tr.translate(offset, offset);
+ tr.scale(scale, scale);
+ tr.translate(-content.getX(), -content.getY());
+ tr.getMatrix(matrix);
+ svgGenerator.setSVGCanvasSize(new Dimension((int)Math.ceil(scale * content.getWidth()) + 2*offset, (int)Math.ceil(scale * content.getHeight()) + 2*offset));
+ } else {
+ svgGenerator.setSVGCanvasSize(new Dimension(100, 100));
+ }
+ //svgGenerator.translate(offset, offset);
+ //svgGenerator.scale(scale, scale);
+ // translate svgGenerator to the x and y coordinates of current content
+ //svgGenerator.translate(-content.getX(), -content.getY());
+
+
+ //svgGenerator.setClip(content);
+
+ result.append(MAIN_SECTION, "<g class=\"symbols\">");
+ result.append(SELECTION_SECTION, "<g class=\"selections\">");
+ result.append(SELECTION_MASK_SECTION, "<g class=\"selectionMasks\">");
+
+ KeyVisitor keyVisitor = new KeyVisitor();
+ sg.accept(keyVisitor);
+
+ Set<Object> keys = keyVisitor.getKeys();
+
+ Map<?, ?> mappings = mappingFunction.apply(keys);
+
+ IG2DNodeVisitor visitor = new PrintingVisitor(svgGenerator, result, mappings);
+
+ sg.accept(visitor);
+
+ } catch (Throwable t) {
+ LOGGER.error("Problems rendering canvas context to SVG", t);
+ }
+
+
+ result.append(ALL_SECTIONS, "</g>");
+
+ StringBuilder res = new StringBuilder();
+ res.append("<svg width=\"100%\" height=\"100%\" stroke=\"black\">");
+ 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(")\">");
+ res.append(result.get(MAIN_SECTION));
+ res.append(result.get(SELECTION_SECTION));
+ res.append(result.get(SELECTION_MASK_SECTION));
+ res.append("</g>");
+ res.append("</svg>");
+
+// System.err.println(" == FINAL RESULT == ");
+// System.err.println(res.toString());
+
+ return res.toString();
+
+ }
+
+
+
+ private static class KeyVisitor implements IG2DNodeVisitor {