package org.simantics.diagram.svg.export; import java.awt.Graphics2D; import java.awt.Point; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.io.StringWriter; import java.util.UUID; import org.apache.batik.dom.GenericDOMImplementation; import org.apache.batik.svggen.SVGGraphics2D; import org.simantics.g2d.canvas.ICanvasContext; import org.simantics.g2d.diagram.DiagramHints; import org.simantics.g2d.diagram.DiagramUtils; import org.simantics.g2d.diagram.IDiagram; import org.simantics.g2d.participant.TransformUtil; import org.simantics.scenegraph.g2d.G2DRenderingHints; import org.simantics.scenegraph.utils.QualityHints; import org.simantics.utils.page.MarginUtils; import org.simantics.utils.page.MarginUtils.Margin; import org.simantics.utils.page.MarginUtils.Margins; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * org.simantics.diagram.export.ImageBuilder with SVG support * * @author Marko Luukkainen * */ public class SVGBuilder { Double dpi; Point size; double margin; Margins margins; /** * * @param file File to write the image (optional) * @param dpi Dots Per Inch * @param size Image size in pixels * @param margin percentage of image width for margins. 0.05 is 5%. */ public SVGBuilder(Double dpi, Point size, double margin) { this.dpi = dpi; this.size = size; this.margin = Math.max(margin,0.0); this.margins = null; } /** * * @param file File to write the image (optional) * @param dpi Dots Per Inch * @param size Image size in pixels * @param margins Margins */ public SVGBuilder(Double dpi, Point size, Margins margins) { this.dpi = dpi; this.size = size; this.margin = 0.0; this.margins = margins; } public static SVGGraphics2D defaultSVGGenerator() { 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 SVGGraphics2D(document); svgGenerator.getGeneratorContext().setIDGenerator(new UniqueIDGenerator(UUID.randomUUID().toString())); return svgGenerator; } /** * @param canvasContext * the canvas context to paint * @param writeResults * true to actually write the resulting Image to the file. */ public String paint(ICanvasContext canvasContext) throws Exception { return paint(canvasContext, defaultSVGGenerator()); } /** * @param canvasContext * the canvas context to paint * svgGenerator * the svg generator to use * @param writeResults * true to actually write the resulting Image to the file. */ public String paint(ICanvasContext canvasContext, SVGGraphics2D svgGenerator) throws Exception { Graphics2D g2 = svgGenerator; IDiagram diagram = canvasContext.getDefaultHintContext().getHint(DiagramHints.KEY_DIAGRAM); Rectangle2D diagramRect = DiagramUtils.getContentRect(diagram); if (diagramRect == null) diagramRect = new Rectangle2D.Double(0, 0, 100, 100); // add margins to content. double off = Math.max(diagramRect.getWidth(), diagramRect.getHeight()) * margin*0.5; diagramRect = new Rectangle2D.Double(diagramRect.getX() - off, diagramRect.getY() - off, diagramRect.getWidth() + off * 2.0, diagramRect.getHeight() + off * 2.0); if (margins != null) { diagramRect = org.simantics.scenegraph.utils.GeometryUtils.expandRectangle(diagramRect, margins.top.diagramAbsolute, margins.bottom.diagramAbsolute, margins.left.diagramAbsolute, margins.right.diagramAbsolute ); } // Make sure the transformation is reset. AffineTransform tr = new AffineTransform(); Rectangle2D controlArea; if (dpi != null) { double mmToInch = 1.0 / 25.4; controlArea = new Rectangle2D.Double(0, 0, diagramRect.getWidth() * mmToInch * dpi, diagramRect.getHeight() * mmToInch * dpi); if (margins != null) { double w = controlArea.getWidth(); double h = controlArea.getHeight(); controlArea = org.simantics.scenegraph.utils.GeometryUtils.expandRectangle(controlArea, margins.top.controlAbsolute + margins.top.controlRelative * h, margins.bottom.controlAbsolute + margins.bottom.controlRelative * h, margins.left.controlAbsolute + margins.left.controlRelative * w, margins.right.controlAbsolute + margins.right.controlRelative * w ); } } else { controlArea = new Rectangle2D.Double(0, 0, size.getX(), size.getY()); if (margins != null) { double w = controlArea.getWidth(); double h = controlArea.getHeight(); controlArea = org.simantics.scenegraph.utils.GeometryUtils.expandRectangle(controlArea, -(margins.top.controlAbsolute + margins.top.controlRelative * h), -(margins.bottom.controlAbsolute + margins.bottom.controlRelative * h), -(margins.left.controlAbsolute + margins.left.controlRelative * w), -(margins.right.controlAbsolute + margins.right.controlRelative * w) ); } } canvasContext.getSingleItem(TransformUtil.class).fitArea(controlArea, diagramRect, MarginUtils.NO_MARGINS); float width = (float)controlArea.getWidth(); float height = (float)controlArea.getHeight(); QualityHints.HIGH_QUALITY_HINTS.setQuality(g2); g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); g2.setTransform(tr); g2.setClip(new Rectangle2D.Double(0, 0, controlArea.getWidth(), controlArea.getHeight())); g2.setRenderingHint(G2DRenderingHints.KEY_CONTROL_BOUNDS, new Rectangle2D.Double(0, 0, controlArea.getWidth(), controlArea.getHeight())); if (canvasContext.isLocked()) throw new IllegalStateException("cannot render image, canvas context is locked: " + canvasContext); canvasContext.getSceneGraph().render(g2); Element root = svgGenerator.getRoot(); root.setAttributeNS(null, "viewBox", "0 0 " + width + " " + height); root.setAttributeNS(null, "height", Float.toString(height)); root.setAttributeNS(null, "width", Float.toString(width)); // Finally, stream out SVG to the standard output using // UTF-8 encoding. boolean useCSS = false; // we want to use CSS style attributes StringWriter writer = new StringWriter(); //svgGenerator.stream(writer, useCSS); svgGenerator.stream(root,writer, useCSS, false); return writer.toString(); } }