/******************************************************************************* * Copyright (c) 2007 VTT Technical Research Centre of Finland and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.diagram.participant; import java.awt.Font; import java.awt.RenderingHints; import java.awt.geom.Rectangle2D; import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.batik.dom.GenericDOMImplementation; import org.apache.batik.svggen.SVGGeneratorContext; import org.apache.batik.svggen.SVGGeneratorContext.GraphicContextDefaults; import org.apache.batik.svggen.SVGGraphics2D; import org.apache.batik.svggen.SVGGraphics2DIOException; import org.eclipse.swt.dnd.Clipboard; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.widgets.Display; import org.simantics.diagram.elements.TextNode; import org.simantics.g2d.canvas.ICanvasContext; import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency; import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant; import org.simantics.g2d.diagram.participant.Selection; import org.simantics.g2d.element.ElementHints; import org.simantics.g2d.element.ElementUtils; import org.simantics.g2d.element.IElement; import org.simantics.scenegraph.INode; import org.simantics.scenegraph.g2d.G2DNode; import org.simantics.scenegraph.g2d.G2DSceneGraph; import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler; import org.simantics.scenegraph.g2d.events.command.CommandEvent; import org.simantics.scenegraph.g2d.events.command.Commands; import org.simantics.scenegraph.g2d.nodes.LinkNode; import org.simantics.scenegraph.g2d.nodes.SelectionNode; import org.simantics.scenegraph.g2d.nodes.SingleElementNode; import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphNode; import org.simantics.scenegraph.utils.NodeMapper; import org.simantics.scenegraph.utils.NodeUtil; import org.simantics.utils.ui.ErrorLogger; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.w3c.dom.Element; public class CopyAsSVGParticipant extends AbstractDiagramParticipant { @Dependency protected Selection sel; @EventHandler(priority = 0) public boolean handleCommand(CommandEvent e) { if (e.command.equals(Commands.COPY_AS_SVG)) { Set ss = sel.getSelection(0); copyAsSVG(getContext(), ss); return true; } return false; } private static void copyAsSVG(ICanvasContext canvasContext, Set elements) { G2DSceneGraph sg = canvasContext.getSceneGraph(); NodeMapper clipboardNodeMapper = new NodeMapper(); List selectionRenderingDisabledNodes = new ArrayList(); SingleElementNode clipboardNode = sg.addNode("svg-clipboard-temp", SingleElementNode.class); try { for (IElement e : elements) { INode node = e.getHint(ElementHints.KEY_SG_NODE); if (node != null) { // Don't render selection. Selection rendering could be a global rendering hint that is adhered by nodes! for(RouteGraphNode n : NodeUtil.collectNodes(node, RouteGraphNode.class)) { n.setIgnoreSelection(true); selectionRenderingDisabledNodes.add(n); } for(SelectionNode n : NodeUtil.collectNodes(node, SelectionNode.class)) { n.setIgnore(true); selectionRenderingDisabledNodes.add(n); } for(TextNode n : NodeUtil.collectNodes(node, TextNode.class)) { n.setShowSelection(false); selectionRenderingDisabledNodes.add(n); } String nodeId = clipboardNodeMapper.add(node); LinkNode delegate = clipboardNode.addNode(ElementUtils.generateNodeId(e), LinkNode.class); delegate.setDelegateId( nodeId ); } } DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation(); String svgNS = "http://www.w3.org/2000/svg"; Document document = domImpl.createDocument(svgNS, "svg", null); GraphicContextDefaults gcDefaults = new GraphicContextDefaults(); SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(document); Map hintMap = new HashMap(); hintMap.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); hintMap.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); hintMap.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); hintMap.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); hintMap.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); gcDefaults.setRenderingHints(new RenderingHints(hintMap)); gcDefaults.setFont(Font.decode(null)); ctx.setGraphicContextDefaults(gcDefaults); SVGGraphics2D svgG2D = new SVGGraphics2D(ctx, false); StringWriter writer = new StringWriter(); // Track connection crossings manually since we will render only the clipboard node. sg.getNode(ConnectionCrossingsParticipant.CONNECTION_CROSSINGS_NODE_KEY).render(svgG2D); clipboardNode.render(svgG2D); Element root = svgG2D.getRoot(); Rectangle2D bounds = clipboardNode.getBoundsInLocal(true); if (bounds != null) { root.setAttributeNS(null, "viewBox", bounds.getMinX() + " " + bounds.getMinY() + " " + bounds.getWidth() + " " + bounds.getHeight()); root.setAttributeNS(null, "height", Double.toString(bounds.getHeight())); root.setAttributeNS(null, "width", Double.toString(bounds.getWidth())); } try { svgG2D.stream(root, writer, false, false); } catch (SVGGraphics2DIOException e1) { ErrorLogger.defaultLogError("Failed to copy the diagram selection as SVG." , e1); } byte[] svgContent = writer.toString().getBytes(StandardCharsets.UTF_8); Display.getDefault().asyncExec(new Runnable() { @Override public void run() { Clipboard cb = new Clipboard(Display.getCurrent()); cb.setContents(new byte[][] {svgContent}, new Transfer[] { SVGTransfer.getInstance() } ); } }); } finally { clipboardNode.removeNodes(); clipboardNodeMapper.clear(); clipboardNode.remove(); // Restore the selection rendering state for changed nodes. for (G2DNode n : selectionRenderingDisabledNodes) { if (n instanceof RouteGraphNode) { ((RouteGraphNode) n).setIgnoreSelection(false); } else if (n instanceof SelectionNode) { ((SelectionNode)n).setIgnore(false); } else if (n instanceof TextNode) { ((TextNode)n).setShowSelection(true); } } } } }