X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.modeling%2Fsrc%2Forg%2Fsimantics%2Fmodeling%2FSCLScenegraph.java;h=82115ef0e0825e2c9b8b2c69793f6bffd015344b;hp=5f375095fb239db428f53a07040bc58e7b10b21d;hb=f48fa9bd04b1802047c1eba99ad73eb4234a46c2;hpb=39684a05baa1b599b8cc306cdd8a07ea6eb3cfdb 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 5f375095f..82115ef0e 100644 --- a/bundles/org.simantics.modeling/src/org/simantics/modeling/SCLScenegraph.java +++ b/bundles/org.simantics.modeling/src/org/simantics/modeling/SCLScenegraph.java @@ -1,6 +1,7 @@ package org.simantics.modeling; import java.awt.BasicStroke; +import java.awt.Color; import java.awt.Dimension; import java.awt.RenderingHints; import java.awt.RenderingHints.Key; @@ -18,6 +19,7 @@ import java.util.Map; import java.util.Random; import java.util.Set; import java.util.UUID; +import java.util.function.Function; import java.util.stream.Collectors; import javax.xml.transform.OutputKeys; @@ -33,17 +35,23 @@ import org.simantics.Simantics; import org.simantics.datatypes.literal.GUID; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; +import org.simantics.db.common.request.IndexRoot; +import org.simantics.db.common.request.ResourceRead; import org.simantics.db.common.request.UnaryRead; import org.simantics.db.exception.DatabaseException; import org.simantics.db.exception.RuntimeDatabaseException; +import org.simantics.db.layer0.variable.Variables; +import org.simantics.diagram.elements.DecorationSVGNode; import org.simantics.diagram.elements.DiagramNodeUtil; import org.simantics.diagram.elements.TextGridNode; import org.simantics.diagram.elements.TextNode; import org.simantics.diagram.stubs.DiagramResource; +import org.simantics.g2d.canvas.Hints; import org.simantics.g2d.canvas.ICanvasContext; import org.simantics.g2d.diagram.DiagramHints; import org.simantics.g2d.diagram.IDiagram; import org.simantics.g2d.diagram.handler.DataElementMap; +import org.simantics.g2d.diagram.participant.ElementPainter.SelectionShapeNode; import org.simantics.g2d.diagram.participant.Selection; import org.simantics.g2d.element.IElement; import org.simantics.g2d.scenegraph.ICanvasSceneGraphProvider; @@ -61,7 +69,7 @@ import org.simantics.scenegraph.g2d.nodes.BackgroundNode; import org.simantics.scenegraph.g2d.nodes.BoundsNode; import org.simantics.scenegraph.g2d.nodes.ConnectionNode; import org.simantics.scenegraph.g2d.nodes.DataNode; -import org.simantics.scenegraph.g2d.nodes.DecorationSVGNode; +import org.simantics.scenegraph.g2d.nodes.LinkNode; import org.simantics.scenegraph.g2d.nodes.NavigationNode; import org.simantics.scenegraph.g2d.nodes.SVGNode; import org.simantics.scenegraph.g2d.nodes.SelectionNode; @@ -70,6 +78,7 @@ import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphNode; import org.simantics.scenegraph.g2d.nodes.spatial.RTreeNode; import org.simantics.scenegraph.utils.NodeUtil; import org.simantics.scl.runtime.function.Function1; +import org.simantics.scl.runtime.tuple.Tuple2; import org.simantics.trend.impl.ItemNode; import org.simantics.utils.threads.ThreadUtils; import org.slf4j.Logger; @@ -77,6 +86,7 @@ import org.slf4j.LoggerFactory; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.w3c.dom.Node; import org.w3c.dom.NodeList; public class SCLScenegraph { @@ -86,19 +96,57 @@ public class SCLScenegraph { private static final String MAIN_SECTION = "main"; private static final String SELECTION_SECTION = "selection"; private static final String SELECTION_MASK_SECTION = "selectionMask"; - + private static final String[] ALL_SECTIONS = { MAIN_SECTION, SELECTION_SECTION, SELECTION_MASK_SECTION }; + // Changed from 0.001 to 0.0001 to prevent creation of huge BufferedImage's when + // generating PDF from SVG. If SVG contains any transparency then Batik uses + // bitmap-rendering which remarkably slows things down + // See org.apache.batik.gvt.AbstractGraphicsNode.paint(Graphics2D) where decisions are made + // if AlphaComposite should be painted + private static final String OPACITY = "0.0001"; + @Deprecated public static ICanvasSceneGraphProvider getICanvasSceneGraphProvider(Resource model, Resource diagram, String diagramRVI) throws DatabaseException, InterruptedException { ICanvasSceneGraphProvider provider = DiagramNodeUtil.loadSceneGraphProvider(model, diagram, diagramRVI); return provider; } + @Deprecated public static void disposeSceneGraphProvider(ICanvasSceneGraphProvider provider) { provider.dispose(); } - + + public static T doWithICanvasSceneGraphProvider(Resource diagram, Function1 func) throws DatabaseException { + return doWithICanvasSceneGraphProvider(diagram, (Function) provider -> func.apply(provider)); + } + + public static T doWithICanvasSceneGraphProvider(Resource diagram, Function func) throws DatabaseException { + Tuple2 result = Simantics.getSession().syncRequest(new ResourceRead(diagram) { + + @Override + public Tuple2 perform(ReadGraph graph) throws DatabaseException { + Resource indexRoot = graph.syncRequest(new IndexRoot(resource)); + String diagramRVI = Variables.getRVI(graph, resource); + return new Tuple2(indexRoot, diagramRVI); + } + }); + ICanvasSceneGraphProvider provider = DiagramNodeUtil.loadSceneGraphProvider((Resource) result.c0, diagram, (String) result.c1); + try { + return func.apply(provider); + } finally { + provider.dispose(); + } + } + + public static T doWithCanvasContext(Resource diagram, Function1 func) throws DatabaseException { + return doWithCanvasContext(diagram, (Function) canvasContext -> func.apply(canvasContext)); + } + + public static T doWithCanvasContext(Resource diagram, Function func) throws DatabaseException { + return doWithICanvasSceneGraphProvider(diagram, (Function) provider -> func.apply(provider.getCanvasContext())); + } + public static String getNodeTransform(ICanvasContext ctx, String name) { Set texts = NodeUtil.collectNodes(ctx.getSceneGraph(), TextNode.class); @@ -240,6 +288,23 @@ public class SCLScenegraph { } return "No nodes in scenegraph!"; } + + /** + * Returns background color of a canvasContext or null. + * @param ctx + * @return color in RGBA List format, or null. + */ + public static List getBackgroundColor(ICanvasContext ctx) { + Color color = ctx.getDefaultHintContext().getHint(Hints.KEY_BACKGROUND_COLOR); + if (color == null) + return null; + ArrayList rgba = new ArrayList<>(4); + rgba.add(color.getRed()); + rgba.add(color.getGreen()); + rgba.add(color.getBlue()); + rgba.add(color.getAlpha()); + return rgba; + } public static String sceneGraphTest (ICanvasContext ctx, String module, String value) { @@ -342,12 +407,14 @@ public class SCLScenegraph { public static final String svgNS = "http://www.w3.org/2000/svg"; - public Generator(SVGGeneratorContext ctx, boolean joku) { - super(ctx, joku); + 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 @@ -386,20 +453,7 @@ public class SCLScenegraph { } - public static Element renderSVGNode(IG2DNode node) { - - // 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); - - SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(document); - ctx.setComment(null); - - // Create an instance of the SVG Generator. - SVGGraphics2D svgGenerator = new Generator(ctx, false); + public static Element renderSVGNode(SVGGraphics2D svgGenerator, IG2DNode node) { try { @@ -456,7 +510,11 @@ public class SCLScenegraph { } public static String renderSVG3(ICanvasContext ctx) { - return renderSVG0(ctx, p0 -> p0.stream().collect(Collectors.toMap(p1 -> p1, p2 -> p2))); + 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))); } /** @@ -477,11 +535,19 @@ public class SCLScenegraph { * Default no-op mapper */ private static final Function1, 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 renderSVG0(ctx, mapper); + 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 @@ -489,8 +555,8 @@ public class SCLScenegraph { * @param ctx * @return */ - public static String renderSVGMapIdentifiers(ICanvasContext ctx) { - return renderSVG0(ctx, new Function1, Map>() { + public static String renderSVGMapIdentifiers(ICanvasContext ctx, double width, double height) { + return renderSVG0(width, height, ctx, new Function1, Map>() { @Override public Map apply(Set p0) { @@ -574,7 +640,7 @@ public class SCLScenegraph { } - private static String renderSVG0(ICanvasContext ctx, Function1, Map> mappingFunction) { + private static String renderSVG0(double width, double height, ICanvasContext ctx, Function1, Map> mappingFunction) { // Get a DOMImplementation. DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation(); @@ -588,6 +654,7 @@ public class SCLScenegraph { RenderSVGContext result = new RenderSVGContext(); + double[] matrix = new double[6]; try { Selection selection = ctx.getAtMostOneItemOfClass(Selection.class); @@ -599,49 +666,44 @@ public class SCLScenegraph { } 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); - // 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); - - svgGenerator.scale(3,3); - + 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(-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; - - // Set svgCanvasSize to the given size parameters - svgGenerator.setSVGCanvasSize(new Dimension((int)(scale * content.getWidth()), (int)(scale * content.getHeight()))); - svgGenerator.setClip(content); + //svgGenerator.translate(-content.getX(), -content.getY()); - double trX = -1 * content.getX(); - double trY = -1 * content.getY(); - result.append(MAIN_SECTION, ""); - - result.append(SELECTION_SECTION, ""); + //svgGenerator.setClip(content); + result.append(MAIN_SECTION, ""); + result.append(SELECTION_SECTION, ""); result.append(SELECTION_MASK_SECTION, ""); - + KeyVisitor keyVisitor = new KeyVisitor(); sg.accept(keyVisitor); @@ -649,7 +711,7 @@ public class SCLScenegraph { Map mappings = mappingFunction.apply(keys); - IG2DNodeVisitor visitor = new PrintingVisitor(result, mappings); + IG2DNodeVisitor visitor = new PrintingVisitor(svgGenerator, result, mappings); sg.accept(visitor); @@ -658,16 +720,16 @@ public class SCLScenegraph { } - result.append(SELECTION_SECTION, ""); - result.append(SELECTION_MASK_SECTION, ""); - result.append(MAIN_SECTION, ""); + result.append(ALL_SECTIONS, ""); StringBuilder res = new StringBuilder(); res.append(""); + res.append(""); res.append(result.get(MAIN_SECTION)); res.append(result.get(SELECTION_SECTION)); res.append(result.get(SELECTION_MASK_SECTION)); - res.append(result.get("")); + res.append(""); + res.append(""); // System.err.println(" == FINAL RESULT == "); // System.err.println(res.toString()); @@ -709,20 +771,26 @@ public class SCLScenegraph { HashMap senBuilders = new HashMap<>(); private RenderSVGContext result; + private SVGGraphics2D svgGenerator; private Map mappings; - public PrintingVisitor(RenderSVGContext result, Map mappings) { + public PrintingVisitor(SVGGraphics2D svgGenerator, RenderSVGContext result, Map mappings) { this.result = result; this.mappings = mappings; + this.svgGenerator = svgGenerator; } private String getKey(SingleElementNode node) { String key; - if (mappings.containsKey(node.getKey())) - key = mappings.get(node.getKey()).toString(); - else - key = node.getKey().toString(); + if(node.getKey() != null) { + if (mappings.containsKey(node.getKey())) + key = mappings.get(node.getKey()).toString(); + else + key = node.getKey().toString(); + } else { + key = Long.toString(node.getId()); + } return key; } @@ -741,9 +809,9 @@ public class SCLScenegraph { String key = getKey((ConnectionNode) node); parentBuilder.append(MAIN_SECTION, "\n"); parentBuilder.append(SELECTION_SECTION, "\n"); - parentBuilder.append(SELECTION_MASK_SECTION, "\n"); + parentBuilder.append(SELECTION_MASK_SECTION, "\n"); - Element doc = renderSVGNode((IG2DNode)node); + Element doc = renderSVGNode(svgGenerator, (IG2DNode)node); String svg = printSVGDocument(doc); parentBuilder.append(MAIN_SECTION, svg); @@ -751,7 +819,7 @@ public class SCLScenegraph { n.setIgnoreSelection(false); } - doc = renderSVGNode((IG2DNode)node); + doc = renderSVGNode(svgGenerator, (IG2DNode)node); svg = printSVGDocument(doc); parentBuilder.append(SELECTION_SECTION, svg); @@ -761,7 +829,7 @@ public class SCLScenegraph { n.setDynamicStroke(bs); } - doc = renderSVGNode((IG2DNode)node); + doc = renderSVGNode(svgGenerator, (IG2DNode)node); svg = printSVGDocument(doc); parentBuilder.append(SELECTION_MASK_SECTION, svg); @@ -769,44 +837,71 @@ public class SCLScenegraph { parentBuilder.append(SELECTION_SECTION, "\n"); parentBuilder.append(MAIN_SECTION, "\n"); - } else if (node instanceof SelectionNode) { + } else if (isSelection0(node)) { SelectionNode n = (SelectionNode)node; SingleElementNode parentSEN = (SingleElementNode)NodeUtil.getNearestParentOfType(node, SingleElementNode.class); - if(parentSEN != null && parentSEN.getKey() != null) { + if(parentSEN != null) { RenderSVGContext parentBuilder2 = getParentBuilder(parentSEN); String key = getKey(parentSEN); - Element doc = renderSVGNode((IG2DNode)node); + n.setIgnore(false); + Element doc = renderSVGNode(svgGenerator, (IG2DNode)node); + n.setIgnore(true); String svg = printSVGDocument(doc); parentBuilder2.append(SELECTION_SECTION, "\n"); parentBuilder2.append(SELECTION_SECTION, svg); parentBuilder2.append(SELECTION_SECTION, "\n"); - parentBuilder2.append(SELECTION_MASK_SECTION, "\n"); + parentBuilder2.append(SELECTION_MASK_SECTION, "\n"); Rectangle2D rect = n.getRect(); - parentBuilder2.append(SELECTION_MASK_SECTION,""); + // NaN + if(rect.getHeight() == rect.getHeight() && rect.getWidth() == rect.getWidth()) { + parentBuilder2.append(SELECTION_MASK_SECTION,""); + } parentBuilder2.append(SELECTION_MASK_SECTION,"\n"); + } + } else if (node instanceof SelectionNode) { + + Element doc = renderSVGNode(svgGenerator, (IG2DNode)node); + String svg = printSVGDocument(doc); + parentBuilder.append(MAIN_SECTION, ""); + parentBuilder.append(MAIN_SECTION, svg); + parentBuilder.append(MAIN_SECTION, "\n"); + } else if (node instanceof SVGNode) { SVGNode svg = (SVGNode)node; + AffineTransform at = svg.getTransform(); + if (!at.isIdentity()) { + if(at.getScaleX() == 1.0 && at.getScaleY() == 1.0 && at.getShearX() == 0.0 && at.getShearY() == 0.0) { + String m = "translate(" + at.getTranslateX() + " " + at.getTranslateY() + ")"; + parentBuilder.append(MAIN_SECTION, "\n"); + } else { + double[] ds = new double[6]; + at.getMatrix(ds); + String m = "matrix(" + ds[0] + " " + ds[1] + " " + ds[2] + " " + ds[3] + " " + ds[4] + " " + ds[5] + ")"; + parentBuilder.append(MAIN_SECTION, "\n"); + } + } parentBuilder.append(MAIN_SECTION, svg.getSVGText()); + if (!at.isIdentity()) { + parentBuilder.append(MAIN_SECTION, "\n"); + } } else if (node instanceof G2DParentNode) { AffineTransform at = node.getTransform(); if(node instanceof SingleElementNode) { SingleElementNode sen = (SingleElementNode)node; - if(sen.getKey() != null) { - String key = getKey(sen); - String typeClass = sen.getTypeClass(); - String clazz = "definedElement"; - if(typeClass != null && !typeClass.isEmpty()) - clazz = clazz + " " + typeClass; - - parentBuilder.append(MAIN_SECTION, "\n"); - } + String key = getKey(sen); + String typeClass = sen.getTypeClass(); + String clazz = "definedElement"; + if(typeClass != null && !typeClass.isEmpty()) + clazz = clazz + " " + typeClass; + + parentBuilder.append(MAIN_SECTION, "\n"); senBuilders.put(sen, new RenderSVGContext()); } if(!at.isIdentity()) { @@ -820,12 +915,101 @@ public class SCLScenegraph { parentBuilder.append(ALL_SECTIONS, "\n"); } } + } else if (node instanceof TextNode) { + TextNode text = (TextNode)node; + + SingleElementNode parentSEN = (SingleElementNode)NodeUtil.getNearestParentOfType(node, SingleElementNode.class); + if(parentSEN != null) { + + text.setShowSelection(false); + Element doc = renderSVGNode(svgGenerator, (IG2DNode)node); + String svg = printSVGDocument(doc); + parentBuilder.append(MAIN_SECTION, svg); + + RenderSVGContext parentBuilder2 = getParentBuilder(parentSEN); + + String key = getKey(parentSEN); + text.setShowSelection(true); + doc = renderSVGNode(svgGenerator, (IG2DNode)node); + svg = printSVGDocument(doc); + + parentBuilder2.append(SELECTION_SECTION, "\n"); + parentBuilder2.append(SELECTION_SECTION, svg); + parentBuilder2.append(SELECTION_SECTION, "\n"); + parentBuilder2.append(SELECTION_MASK_SECTION, "\n"); + Rectangle2D rect = text.getBounds(); + // NaN + if(rect.getHeight() == rect.getHeight() && rect.getWidth() == rect.getWidth()) { + parentBuilder2.append(SELECTION_MASK_SECTION,""); + } + parentBuilder2.append(SELECTION_MASK_SECTION,"\n"); + + } + } else if (!(node instanceof RouteGraphNode) && !(node instanceof LinkNode)){ + try { + Element doc = renderSVGNode(svgGenerator, (IG2DNode)node); + NodeList gList = doc.getElementsByTagName("g"); + if (gList.getLength() == 0) + return; + boolean hasContent = false; + for (int i = 0; i < gList.getLength(); i++) { + Node gNode = gList.item(i); + if (gNode.hasChildNodes()) { + hasContent = true; + break; + } + } + if (!hasContent) + return; + String svg = printSVGDocument(doc); + if (node instanceof SelectionShapeNode) { + SingleElementNode parentSEN = (SingleElementNode)NodeUtil.getNearestParentOfType(node, SingleElementNode.class); + if(parentSEN != null) { + String key = getKey(parentSEN); + RenderSVGContext parentBuilder2 = getParentBuilder(parentSEN); + parentBuilder2.append(SELECTION_SECTION, "\n"); + parentBuilder2.append(SELECTION_SECTION, svg); + parentBuilder2.append(SELECTION_SECTION, "\n"); + + parentBuilder2.append(SELECTION_MASK_SECTION, "\n"); + Rectangle2D rect = node.getBounds(); + // NaN + if(rect.getHeight() == rect.getHeight() && rect.getWidth() == rect.getWidth()) { + parentBuilder2.append(SELECTION_MASK_SECTION,""); + } + parentBuilder2.append(SELECTION_MASK_SECTION,"\n"); + } + } else { + parentBuilder.append(MAIN_SECTION, ""); + parentBuilder.append(MAIN_SECTION, svg); + parentBuilder.append(MAIN_SECTION, "\n"); + } + } catch (Exception e) { + // TODO: There are nodes that do not behave well when rendered to SVG. For backwards compatibility, we don't handle the exceptions. + } } //enters.put(node, b.length()); } + private boolean isSelection0(IG2DNode node) { + + if(node instanceof SelectionNode) { + SelectionNode sn = (SelectionNode)node; + return sn.getSelectionId() == 0; + } else { + return false; + } + + } + private RenderSVGContext getParentBuilder(IG2DNode node) { INode parentSEN = NodeUtil.getNearestParentOfType(node, SingleElementNode.class); @@ -858,7 +1042,7 @@ public class SCLScenegraph { n.setIgnore(true); } - Element doc = renderSVGNode((IG2DNode)node); + Element doc = renderSVGNode(svgGenerator, (IG2DNode)node); String svg = printSVGDocument(doc); parentBuilder.append(MAIN_SECTION, svg); } @@ -873,9 +1057,9 @@ public class SCLScenegraph { } if(node instanceof SingleElementNode) { SingleElementNode sen = (SingleElementNode)node; - if(sen.getKey() != null) { + //if(sen.getKey() != null) { parentBuilder.append(MAIN_SECTION, ""); - } + //} } } indent --;