From 831888c7524b6223a24bea5fb4b4350dd2be928f Mon Sep 17 00:00:00 2001 From: Antti Villberg Date: Mon, 27 Feb 2017 07:50:14 +0200 Subject: [PATCH] Support SVG generation from scenegraph. refs #7054 Change-Id: Id7543adbb65f938e4478c1a095579407dfe02c9a --- .../diagram/participant/ElementPainter.java | 5 +- .../META-INF/MANIFEST.MF | 3 +- .../scl/Simantics/Scenegraph.scl | 4 +- .../org/simantics/modeling/SCLScenegraph.java | 138 ++++++++++++++++++ .../simantics/scenegraph/g2d/G2DHints.java | 36 +++++ .../scenegraph/g2d/nodes/ConnectionNode.java | 10 ++ .../g2d/nodes/SingleElementNode.java | 25 ++++ .../scenegraph/g2d/nodes/UnboundedNode.java | 9 ++ 8 files changed, 227 insertions(+), 3 deletions(-) create mode 100644 bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/G2DHints.java diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementPainter.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementPainter.java index 1eb15f61c..674f1c457 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementPainter.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementPainter.java @@ -557,6 +557,7 @@ public class ElementPainter extends AbstractDiagramParticipant implements Compos ConnectionNode holder = e.getHint(sgKey); if (holder == null) { holder = parentNode.addNode(ElementUtils.generateNodeId(e), ConnectionNode.class); + holder.setKey(e.getHint(ElementHints.KEY_OBJECT)); holder.setTransferableProvider(new ElementTransferableProvider(getContext(), e)); e.setHint(sgKey, holder); holder.setZIndex(parentNode.getNodeCount() + 1); @@ -566,7 +567,9 @@ public class ElementPainter extends AbstractDiagramParticipant implements Compos SingleElementNode holder = e.getHint(sgKey); if (holder == null) { - holder = parentNode.addNode(ElementUtils.generateNodeId(e), SingleElementNode.class); + String id = ElementUtils.generateNodeId(e); + holder = parentNode.addNode(id, SingleElementNode.class); + holder.setKey(e.getHint(ElementHints.KEY_OBJECT)); holder.setTransferableProvider(new ElementTransferableProvider(getContext(), e)); e.setHint(sgKey, holder); holder.setZIndex(parentNode.getNodeCount() + 1); diff --git a/bundles/org.simantics.modeling/META-INF/MANIFEST.MF b/bundles/org.simantics.modeling/META-INF/MANIFEST.MF index 5245ed47e..7f81e56f2 100644 --- a/bundles/org.simantics.modeling/META-INF/MANIFEST.MF +++ b/bundles/org.simantics.modeling/META-INF/MANIFEST.MF @@ -37,7 +37,8 @@ Require-Bundle: org.simantics.simulation;bundle-version="1.0.0", org.simantics.scl.db;bundle-version="0.1.3", org.simantics.selectionview.ontology;bundle-version="1.2.0", org.simantics.scl.ui;bundle-version="0.5.0", - org.slf4j.api + org.slf4j.api, + org.apache.batik Export-Package: org.simantics.modeling, org.simantics.modeling.actions, org.simantics.modeling.adapters, diff --git a/bundles/org.simantics.modeling/scl/Simantics/Scenegraph.scl b/bundles/org.simantics.modeling/scl/Simantics/Scenegraph.scl index fa1d2193f..5e0e83075 100644 --- a/bundles/org.simantics.modeling/scl/Simantics/Scenegraph.scl +++ b/bundles/org.simantics.modeling/scl/Simantics/Scenegraph.scl @@ -51,12 +51,14 @@ importJava "org.simantics.modeling.SCLScenegraph" where editNodeText :: ICanvasContext -> String -> String -> String -> String copyPaste :: ICanvasContext -> ICanvasContext -> [Resource] -> Boolean + + renderSVG :: ICanvasContext -> String getSceneGraphProvider :: Diagram -> ICanvasSceneGraphProvider getSceneGraphProvider diagram = do diagramName = syncRead(\() -> getSafeName diagram) diagramRVI = "/" + diagramName - model = syncRead(\() -> getPossibleModel diagram) + model = syncRead(\() -> fromJust $ possibleIndexRoot diagram) composite = syncRead(\() -> compositeToDiagram' diagram) getICanvasSceneGraphProvider model composite diagramRVI diff --git a/bundles/org.simantics.modeling/src/org/simantics/modeling/SCLScenegraph.java b/bundles/org.simantics.modeling/src/org/simantics/modeling/SCLScenegraph.java index 33d7f0629..3fbfa2304 100644 --- a/bundles/org.simantics.modeling/src/org/simantics/modeling/SCLScenegraph.java +++ b/bundles/org.simantics.modeling/src/org/simantics/modeling/SCLScenegraph.java @@ -1,10 +1,21 @@ package org.simantics.modeling; +import java.awt.Dimension; +import java.awt.RenderingHints.Key; +import java.awt.geom.Rectangle2D; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; +import java.util.UUID; +import org.apache.batik.dom.GenericDOMImplementation; +import org.apache.batik.svggen.SVGGraphics2D; import org.simantics.db.Resource; import org.simantics.db.exception.DatabaseException; import org.simantics.diagram.elements.DiagramNodeUtil; @@ -19,6 +30,7 @@ import org.simantics.g2d.element.IElement; import org.simantics.g2d.scenegraph.ICanvasSceneGraphProvider; import org.simantics.g2d.utils.CanvasUtils; import org.simantics.scenegraph.ParentNode; +import org.simantics.scenegraph.g2d.G2DHints; import org.simantics.scenegraph.g2d.G2DParentNode; import org.simantics.scenegraph.g2d.G2DSceneGraph; import org.simantics.scenegraph.g2d.events.command.Commands; @@ -29,9 +41,13 @@ import org.simantics.scenegraph.g2d.nodes.DataNode; import org.simantics.scenegraph.g2d.nodes.DecorationSVGNode; import org.simantics.scenegraph.g2d.nodes.NavigationNode; import org.simantics.scenegraph.g2d.nodes.SingleElementNode; +import org.simantics.scenegraph.g2d.nodes.spatial.RTreeNode; import org.simantics.scenegraph.utils.NodeUtil; import org.simantics.trend.impl.ItemNode; import org.simantics.utils.threads.ThreadUtils; +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Document; +import org.w3c.dom.Element; public class SCLScenegraph { @@ -285,6 +301,128 @@ public class SCLScenegraph { } return true; } + + + + + static class Generator extends SVGGraphics2D { + + int elemLevel = 0; + String newElementId = null; + ArrayList elements = new ArrayList(); + + public static final String svgNS = "http://www.w3.org/2000/svg"; + + public Generator(Document document) { + super(document); + } + + @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(G2DHints.KEY_BEGIN_ELEMENT == arg0) { + elemLevel++; + } + if(G2DHints.KEY_ELEMENT_ID == arg0) { + if(arg1 != null) + newElementId = arg1.toString(); + else + newElementId = UUID.randomUUID().toString(); + } + if(G2DHints.KEY_END_ELEMENT == arg0) { + elemLevel--; + if(elemLevel == 0) { + 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 String renderSVG(ICanvasContext ctx) { + + // 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); + + try { + + G2DSceneGraph sg = ctx.getSceneGraph(); + 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); + + // nav is a node that has zooming functionalities + NavigationNode nav = NodeUtil.getNearestChildByClass(root, NavigationNode.class); + nav.setZoomEnabled(true); + + // fit view with the contents of rtreeBounds + nav.zoomTo(rtreeBounds); + + // get the bounds of the content + Rectangle2D content = NodeUtil.getLocalBounds(nav); + + // translate svgGenerator to the x and y coordinates of current content + svgGenerator.translate(-1 * content.getX(), -1 * content.getY()); + + Rectangle2D destination = new Rectangle2D.Double(0,0,1000,1000); + double sx = destination.getWidth() / content.getWidth(); + double sy = destination.getHeight() / content.getHeight(); + double scale = sx < sy ? sx : sy; + + svgGenerator.scale(scale, scale); + + // Set svgCanvasSize to the given size parameters + svgGenerator.setSVGCanvasSize(new Dimension((int)(scale * content.getWidth()), (int)(scale * content.getHeight()))); + svgGenerator.setClip(content); + + sg.render(svgGenerator); + + } finally { + } + + String result = ""; + + // Finally, stream out SVG to the standard output using + // UTF-8 encoding. + boolean useCSS = true; // we want to use CSS style attributes + try { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + Writer out = new OutputStreamWriter(os, "UTF-8"); + svgGenerator.stream(out, useCSS); + os.flush(); + os.close(); + result = new String(os.toByteArray());//Base64.encode(os.toByteArray()); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + return result; + + } } \ No newline at end of file diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/G2DHints.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/G2DHints.java new file mode 100644 index 000000000..e9603488c --- /dev/null +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/G2DHints.java @@ -0,0 +1,36 @@ +package org.simantics.scenegraph.g2d; + +import java.util.Map; + +public final class G2DHints { + + private G2DHints() { + } + + public static final Key KEY_BEGIN_ELEMENT = new G2DHints.Key(0); + public static final Key KEY_END_ELEMENT = new G2DHints.Key(1); + public static final Key KEY_ELEMENT_ID = new G2DHints.Key(2); + + public static class Key extends java.awt.RenderingHints.Key { + + public Key(int privateKey) { + super(privateKey); + } + + @Override + public boolean isCompatibleValue(Object val) { + switch (intKey()) { + case 0: + return val == null || val instanceof String + || val instanceof Map; + case 1: + return val == null || val instanceof Object; + case 2: + return val == null || val instanceof Object; + default: + throw new RuntimeException("Not possible!"); + } + } + } + +} \ No newline at end of file diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/ConnectionNode.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/ConnectionNode.java index a83ca5264..60c222f6d 100644 --- a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/ConnectionNode.java +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/ConnectionNode.java @@ -13,11 +13,13 @@ package org.simantics.scenegraph.g2d.nodes; import java.awt.Color; import java.awt.Composite; +import java.awt.Graphics2D; import java.awt.Stroke; import java.awt.geom.Point2D; import org.simantics.diagram.connection.RouteGraph; import org.simantics.scenegraph.INode; +import org.simantics.scenegraph.g2d.G2DHints; import org.simantics.scenegraph.g2d.IG2DNode; import org.simantics.scenegraph.g2d.events.MouseEvent; import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphNode; @@ -150,4 +152,12 @@ public class ConnectionNode extends SingleElementNode implements InitValueSuppor return false; } + public void beforeRender(Graphics2D g) { + g.setRenderingHint(G2DHints.KEY_BEGIN_ELEMENT, "connection"); + } + + public void afterRender(Graphics2D g) { + g.setRenderingHint(G2DHints.KEY_END_ELEMENT, "connection"); + } + } diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SingleElementNode.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SingleElementNode.java index 00ca6b866..9495a6440 100644 --- a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SingleElementNode.java +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SingleElementNode.java @@ -17,6 +17,8 @@ import java.awt.Graphics2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; +import org.simantics.scenegraph.ParentNode; +import org.simantics.scenegraph.g2d.G2DHints; import org.simantics.scenegraph.g2d.IG2DNode; import org.simantics.scenegraph.g2d.events.EventTypes; import org.simantics.scenegraph.g2d.events.MouseEvent; @@ -28,11 +30,20 @@ public class SingleElementNode extends TransformNode implements InitValueSupport private static final long serialVersionUID = -4982578347433716440L; + private Object key; private TransferableProvider transferableProvider; protected Composite composite; protected Boolean visible = Boolean.TRUE; protected Boolean hidden = Boolean.FALSE; + public void setKey(Object key) { + this.key = key; + } + + public Object getKey() { + return key; + } + public void setTransferableProvider(TransferableProvider transferableProvider) { if (transferableProvider != this.transferableProvider) { if (this.transferableProvider != null) @@ -71,11 +82,22 @@ public class SingleElementNode extends TransformNode implements InitValueSupport return hidden; } + public void beforeRender(Graphics2D g) { + g.setRenderingHint(G2DHints.KEY_BEGIN_ELEMENT, "definedElement"); + } + + public void afterRender(Graphics2D g) { + g.setRenderingHint(G2DHints.KEY_ELEMENT_ID, getKey()); + g.setRenderingHint(G2DHints.KEY_END_ELEMENT, "definedElement"); + } + @Override public void render(Graphics2D g) { if (!visible || hidden) return; + beforeRender(g); + Composite oldComposite = null; if(composite != null) { oldComposite = g.getComposite(); @@ -91,6 +113,9 @@ public class SingleElementNode extends TransformNode implements InitValueSupport if (oldComposite != null) g.setComposite(oldComposite); + + afterRender(g); + } @Override diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/UnboundedNode.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/UnboundedNode.java index 4cacc70f9..9b35b33a9 100644 --- a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/UnboundedNode.java +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/UnboundedNode.java @@ -11,6 +11,7 @@ *******************************************************************************/ package org.simantics.scenegraph.g2d.nodes; +import java.awt.Graphics2D; import java.awt.geom.Rectangle2D; public class UnboundedNode extends SingleElementNode { @@ -21,5 +22,13 @@ public class UnboundedNode extends SingleElementNode { public Rectangle2D getBoundsInLocal() { return null; } + + @Override + public void beforeRender(Graphics2D g) { + } + + @Override + public void afterRender(Graphics2D g) { + } } -- 2.47.1