/** * $Id: mxSvgCanvas.java,v 1.1 2012/11/15 13:26:47 gaudenz Exp $ * Copyright (c) 2007, Gaudenz Alder */ package com.mxgraph.canvas; import java.awt.Font; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Hashtable; import java.util.List; import java.util.Map; import org.w3c.dom.Document; import org.w3c.dom.Element; import com.mxgraph.util.mxBase64; import com.mxgraph.util.mxConstants; import com.mxgraph.util.mxPoint; import com.mxgraph.util.mxRectangle; import com.mxgraph.util.mxUtils; import com.mxgraph.view.mxCellState; /** * An implementation of a canvas that uses SVG for painting. This canvas * ignores the STYLE_LABEL_BACKGROUNDCOLOR and * STYLE_LABEL_BORDERCOLOR styles due to limitations of SVG. */ public class mxSvgCanvas extends mxBasicCanvas { /** * Holds the HTML document that represents the canvas. */ protected Document document; /** * Used internally for looking up elements. Workaround for getElementById * not working. */ private Map<String, Element> gradients = new Hashtable<String, Element>(); /** * Used internally for looking up images. */ private Map<String, Element> images = new Hashtable<String, Element>(); /** * */ protected Element defs = null; /** * Specifies if images should be embedded as base64 encoded strings. * Default is false. */ protected boolean embedded = false; /** * Constructs a new SVG canvas for the specified dimension and scale. */ public mxSvgCanvas() { this(null); } /** * Constructs a new SVG canvas for the specified bounds, scale and * background color. */ public mxSvgCanvas(Document document) { setDocument(document); } /** * */ public void appendSvgElement(Element node) { if (document != null) { document.getDocumentElement().appendChild(node); } } /** * */ protected Element getDefsElement() { if (defs == null) { defs = document.createElement("defs"); Element svgNode = document.getDocumentElement(); if (svgNode.hasChildNodes()) { svgNode.insertBefore(defs, svgNode.getFirstChild()); } else { svgNode.appendChild(defs); } } return defs; } /** * */ public Element getGradientElement(String start, String end, String direction) { String id = getGradientId(start, end, direction); Element gradient = gradients.get(id); if (gradient == null) { gradient = createGradientElement(start, end, direction); gradient.setAttribute("id", "g" + (gradients.size() + 1)); getDefsElement().appendChild(gradient); gradients.put(id, gradient); } return gradient; } /** * */ public Element getGlassGradientElement() { String id = "mx-glass-gradient"; Element glassGradient = gradients.get(id); if (glassGradient == null) { glassGradient = document.createElement("linearGradient"); glassGradient.setAttribute("x1", "0%"); glassGradient.setAttribute("y1", "0%"); glassGradient.setAttribute("x2", "0%"); glassGradient.setAttribute("y2", "100%"); Element stop1 = document.createElement("stop"); stop1.setAttribute("offset", "0%"); stop1.setAttribute("style", "stop-color:#ffffff;stop-opacity:0.9"); glassGradient.appendChild(stop1); Element stop2 = document.createElement("stop"); stop2.setAttribute("offset", "100%"); stop2.setAttribute("style", "stop-color:#ffffff;stop-opacity:0.1"); glassGradient.appendChild(stop2); glassGradient.setAttribute("id", "g" + (gradients.size() + 1)); getDefsElement().appendChild(glassGradient); gradients.put(id, glassGradient); } return glassGradient; } /** * */ protected Element createGradientElement(String start, String end, String direction) { Element gradient = document.createElement("linearGradient"); gradient.setAttribute("x1", "0%"); gradient.setAttribute("y1", "0%"); gradient.setAttribute("x2", "0%"); gradient.setAttribute("y2", "0%"); if (direction == null || direction.equals(mxConstants.DIRECTION_SOUTH)) { gradient.setAttribute("y2", "100%"); } else if (direction.equals(mxConstants.DIRECTION_EAST)) { gradient.setAttribute("x2", "100%"); } else if (direction.equals(mxConstants.DIRECTION_NORTH)) { gradient.setAttribute("y1", "100%"); } else if (direction.equals(mxConstants.DIRECTION_WEST)) { gradient.setAttribute("x1", "100%"); } Element stop = document.createElement("stop"); stop.setAttribute("offset", "0%"); stop.setAttribute("style", "stop-color:" + start); gradient.appendChild(stop); stop = document.createElement("stop"); stop.setAttribute("offset", "100%"); stop.setAttribute("style", "stop-color:" + end); gradient.appendChild(stop); return gradient; } /** * */ public String getGradientId(String start, String end, String direction) { // Removes illegal characters from gradient ID if (start.startsWith("#")) { start = start.substring(1); } if (end.startsWith("#")) { end = end.substring(1); } // Workaround for gradient IDs not working in Safari 5 / Chrome 6 // if they contain uppercase characters start = start.toLowerCase(); end = end.toLowerCase(); String dir = null; if (direction == null || direction.equals(mxConstants.DIRECTION_SOUTH)) { dir = "south"; } else if (direction.equals(mxConstants.DIRECTION_EAST)) { dir = "east"; } else { String tmp = start; start = end; end = tmp; if (direction.equals(mxConstants.DIRECTION_NORTH)) { dir = "south"; } else if (direction.equals(mxConstants.DIRECTION_WEST)) { dir = "east"; } } return "mx-gradient-" + start + "-" + end + "-" + dir; } /** * Returns true if the given string ends with .png, .jpg or .gif. */ protected boolean isImageResource(String src) { return src != null && (src.toLowerCase().endsWith(".png") || src.toLowerCase().endsWith(".jpg") || src .toLowerCase().endsWith(".gif")); } /** * */ protected InputStream getResource(String src) { InputStream stream = null; try { stream = new BufferedInputStream(new URL(src).openStream()); } catch (Exception e1) { stream = getClass().getResourceAsStream(src); } return stream; } /** * @throws IOException * */ protected String createDataUrl(String src) throws IOException { String result = null; InputStream inputStream = isImageResource(src) ? getResource(src) : null; if (inputStream != null) { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1024); byte[] bytes = new byte[512]; // Read bytes from the input stream in bytes.length-sized chunks and write // them into the output stream int readBytes; while ((readBytes = inputStream.read(bytes)) > 0) { outputStream.write(bytes, 0, readBytes); } // Convert the contents of the output stream into a Data URL String format = "png"; int dot = src.lastIndexOf('.'); if (dot > 0 && dot < src.length()) { format = src.substring(dot + 1); } result = "data:image/" + format + ";base64," + mxBase64 .encodeToString(outputStream.toByteArray(), false); } return result; } /** * */ protected Element getEmbeddedImageElement(String src) { Element img = images.get(src); if (img == null) { img = document.createElement("svg"); img.setAttribute("width", "100%"); img.setAttribute("height", "100%"); Element inner = document.createElement("image"); inner.setAttribute("width", "100%"); inner.setAttribute("height", "100%"); // Store before transforming to DataURL images.put(src, img); if (!src.startsWith("data:image/")) { try { String tmp = createDataUrl(src); if (tmp != null) { src = tmp; } } catch (IOException e) { // ignore } } inner.setAttributeNS(mxConstants.NS_XLINK, "xlink:href", src); img.appendChild(inner); img.setAttribute("id", "i" + (images.size())); getDefsElement().appendChild(img); } return img; } /** * */ protected Element createImageElement(double x, double y, double w, double h, String src, boolean aspect, boolean flipH, boolean flipV, boolean embedded) { Element elem = null; if (embedded) { elem = document.createElement("use"); Element img = getEmbeddedImageElement(src); elem.setAttributeNS(mxConstants.NS_XLINK, "xlink:href", "#" + img.getAttribute("id")); } else { elem = document.createElement("image"); elem.setAttributeNS(mxConstants.NS_XLINK, "xlink:href", src); } elem.setAttribute("x", String.valueOf(x)); elem.setAttribute("y", String.valueOf(y)); elem.setAttribute("width", String.valueOf(w)); elem.setAttribute("height", String.valueOf(h)); // FIXME: SVG element must be used for reference to image with // aspect but for images with no aspect this does not work. if (aspect) { elem.setAttribute("preserveAspectRatio", "xMidYMid"); } else { elem.setAttribute("preserveAspectRatio", "none"); } double sx = 1; double sy = 1; double dx = 0; double dy = 0; if (flipH) { sx *= -1; dx = -w - 2 * x; } if (flipV) { sy *= -1; dy = -h - 2 * y; } String transform = ""; if (sx != 1 || sy != 1) { transform += "scale(" + sx + " " + sy + ") "; } if (dx != 0 || dy != 0) { transform += "translate(" + dx + " " + dy + ") "; } if (transform.length() > 0) { elem.setAttribute("transform", transform); } return elem; } /** * */ public void setDocument(Document document) { this.document = document; } /** * Returns a reference to the document that represents the canvas. * * @return Returns the document. */ public Document getDocument() { return document; } /** * */ public void setEmbedded(boolean value) { embedded = value; } /** * */ public boolean isEmbedded() { return embedded; } /* * (non-Javadoc) * @see com.mxgraph.canvas.mxICanvas#drawCell() */ public Object drawCell(mxCellState state) { Map<String, Object> style = state.getStyle(); Element elem = null; if (state.getAbsolutePointCount() > 1) { List<mxPoint> pts = state.getAbsolutePoints(); // Transpose all points by cloning into a new array pts = mxUtils.translatePoints(pts, translate.x, translate.y); // Draws the line elem = drawLine(pts, style); // Applies opacity float opacity = mxUtils.getFloat(style, mxConstants.STYLE_OPACITY, 100); if (opacity != 100) { String value = String.valueOf(opacity / 100); elem.setAttribute("fill-opacity", value); elem.setAttribute("stroke-opacity", value); } } else { int x = (int) state.getX() + translate.x; int y = (int) state.getY() + translate.y; int w = (int) state.getWidth(); int h = (int) state.getHeight(); if (!mxUtils.getString(style, mxConstants.STYLE_SHAPE, "").equals( mxConstants.SHAPE_SWIMLANE)) { elem = drawShape(x, y, w, h, style); } else { int start = (int) Math.round(mxUtils.getInt(style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE) * scale); // Removes some styles to draw the content area Map<String, Object> cloned = new Hashtable<String, Object>( style); cloned.remove(mxConstants.STYLE_FILLCOLOR); cloned.remove(mxConstants.STYLE_ROUNDED); if (mxUtils.isTrue(style, mxConstants.STYLE_HORIZONTAL, true)) { elem = drawShape(x, y, w, start, style); drawShape(x, y + start, w, h - start, cloned); } else { elem = drawShape(x, y, start, h, style); drawShape(x + start, y, w - start, h, cloned); } } } return elem; } /* * (non-Javadoc) * @see com.mxgraph.canvas.mxICanvas#drawLabel() */ public Object drawLabel(String label, mxCellState state, boolean html) { mxRectangle bounds = state.getLabelBounds(); if (drawLabels && bounds != null) { int x = (int) bounds.getX() + translate.x; int y = (int) bounds.getY() + translate.y; int w = (int) bounds.getWidth(); int h = (int) bounds.getHeight(); Map<String, Object> style = state.getStyle(); return drawText(label, x, y, w, h, style); } return null; } /** * Draws the shape specified with the STYLE_SHAPE key in the given style. * * @param x X-coordinate of the shape. * @param y Y-coordinate of the shape. * @param w Width of the shape. * @param h Height of the shape. * @param style Style of the the shape. */ public Element drawShape(int x, int y, int w, int h, Map<String, Object> style) { String fillColor = mxUtils.getString(style, mxConstants.STYLE_FILLCOLOR, "none"); String gradientColor = mxUtils.getString(style, mxConstants.STYLE_GRADIENTCOLOR, "none"); String strokeColor = mxUtils.getString(style, mxConstants.STYLE_STROKECOLOR, "none"); float strokeWidth = (float) (mxUtils.getFloat(style, mxConstants.STYLE_STROKEWIDTH, 1) * scale); float opacity = mxUtils.getFloat(style, mxConstants.STYLE_OPACITY, 100); // Draws the shape String shape = mxUtils.getString(style, mxConstants.STYLE_SHAPE, ""); Element elem = null; Element background = null; if (shape.equals(mxConstants.SHAPE_IMAGE)) { String img = getImageForStyle(style); if (img != null) { // Vertical and horizontal image flipping boolean flipH = mxUtils.isTrue(style, mxConstants.STYLE_IMAGE_FLIPH, false); boolean flipV = mxUtils.isTrue(style, mxConstants.STYLE_IMAGE_FLIPV, false); elem = createImageElement(x, y, w, h, img, PRESERVE_IMAGE_ASPECT, flipH, flipV, isEmbedded()); } } else if (shape.equals(mxConstants.SHAPE_LINE)) { String direction = mxUtils.getString(style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST); String d = null; if (direction.equals(mxConstants.DIRECTION_EAST) || direction.equals(mxConstants.DIRECTION_WEST)) { int mid = (y + h / 2); d = "M " + x + " " + mid + " L " + (x + w) + " " + mid; } else { int mid = (x + w / 2); d = "M " + mid + " " + y + " L " + mid + " " + (y + h); } elem = document.createElement("path"); elem.setAttribute("d", d + " Z"); } else if (shape.equals(mxConstants.SHAPE_ELLIPSE)) { elem = document.createElement("ellipse"); elem.setAttribute("cx", String.valueOf(x + w / 2)); elem.setAttribute("cy", String.valueOf(y + h / 2)); elem.setAttribute("rx", String.valueOf(w / 2)); elem.setAttribute("ry", String.valueOf(h / 2)); } else if (shape.equals(mxConstants.SHAPE_DOUBLE_ELLIPSE)) { elem = document.createElement("g"); background = document.createElement("ellipse"); background.setAttribute("cx", String.valueOf(x + w / 2)); background.setAttribute("cy", String.valueOf(y + h / 2)); background.setAttribute("rx", String.valueOf(w / 2)); background.setAttribute("ry", String.valueOf(h / 2)); elem.appendChild(background); int inset = (int) ((3 + strokeWidth) * scale); Element foreground = document.createElement("ellipse"); foreground.setAttribute("fill", "none"); foreground.setAttribute("stroke", strokeColor); foreground .setAttribute("stroke-width", String.valueOf(strokeWidth)); foreground.setAttribute("cx", String.valueOf(x + w / 2)); foreground.setAttribute("cy", String.valueOf(y + h / 2)); foreground.setAttribute("rx", String.valueOf(w / 2 - inset)); foreground.setAttribute("ry", String.valueOf(h / 2 - inset)); elem.appendChild(foreground); } else if (shape.equals(mxConstants.SHAPE_RHOMBUS)) { elem = document.createElement("path"); String d = "M " + (x + w / 2) + " " + y + " L " + (x + w) + " " + (y + h / 2) + " L " + (x + w / 2) + " " + (y + h) + " L " + x + " " + (y + h / 2); elem.setAttribute("d", d + " Z"); } else if (shape.equals(mxConstants.SHAPE_TRIANGLE)) { elem = document.createElement("path"); String direction = mxUtils.getString(style, mxConstants.STYLE_DIRECTION, ""); String d = null; if (direction.equals(mxConstants.DIRECTION_NORTH)) { d = "M " + x + " " + (y + h) + " L " + (x + w / 2) + " " + y + " L " + (x + w) + " " + (y + h); } else if (direction.equals(mxConstants.DIRECTION_SOUTH)) { d = "M " + x + " " + y + " L " + (x + w / 2) + " " + (y + h) + " L " + (x + w) + " " + y; } else if (direction.equals(mxConstants.DIRECTION_WEST)) { d = "M " + (x + w) + " " + y + " L " + x + " " + (y + h / 2) + " L " + (x + w) + " " + (y + h); } else // east { d = "M " + x + " " + y + " L " + (x + w) + " " + (y + h / 2) + " L " + x + " " + (y + h); } elem.setAttribute("d", d + " Z"); } else if (shape.equals(mxConstants.SHAPE_HEXAGON)) { elem = document.createElement("path"); String direction = mxUtils.getString(style, mxConstants.STYLE_DIRECTION, ""); String d = null; if (direction.equals(mxConstants.DIRECTION_NORTH) || direction.equals(mxConstants.DIRECTION_SOUTH)) { d = "M " + (x + 0.5 * w) + " " + y + " L " + (x + w) + " " + (y + 0.25 * h) + " L " + (x + w) + " " + (y + 0.75 * h) + " L " + (x + 0.5 * w) + " " + (y + h) + " L " + x + " " + (y + 0.75 * h) + " L " + x + " " + (y + 0.25 * h); } else { d = "M " + (x + 0.25 * w) + " " + y + " L " + (x + 0.75 * w) + " " + y + " L " + (x + w) + " " + (y + 0.5 * h) + " L " + (x + 0.75 * w) + " " + (y + h) + " L " + (x + 0.25 * w) + " " + (y + h) + " L " + x + " " + (y + 0.5 * h); } elem.setAttribute("d", d + " Z"); } else if (shape.equals(mxConstants.SHAPE_CLOUD)) { elem = document.createElement("path"); String d = "M " + (x + 0.25 * w) + " " + (y + 0.25 * h) + " C " + (x + 0.05 * w) + " " + (y + 0.25 * h) + " " + x + " " + (y + 0.5 * h) + " " + (x + 0.16 * w) + " " + (y + 0.55 * h) + " C " + x + " " + (y + 0.66 * h) + " " + (x + 0.18 * w) + " " + (y + 0.9 * h) + " " + (x + 0.31 * w) + " " + (y + 0.8 * h) + " C " + (x + 0.4 * w) + " " + (y + h) + " " + (x + 0.7 * w) + " " + (y + h) + " " + (x + 0.8 * w) + " " + (y + 0.8 * h) + " C " + (x + w) + " " + (y + 0.8 * h) + " " + (x + w) + " " + (y + 0.6 * h) + " " + (x + 0.875 * w) + " " + (y + 0.5 * h) + " C " + (x + w) + " " + (y + 0.3 * h) + " " + (x + 0.8 * w) + " " + (y + 0.1 * h) + " " + (x + 0.625 * w) + " " + (y + 0.2 * h) + " C " + (x + 0.5 * w) + " " + (y + 0.05 * h) + " " + (x + 0.3 * w) + " " + (y + 0.05 * h) + " " + (x + 0.25 * w) + " " + (y + 0.25 * h); elem.setAttribute("d", d + " Z"); } else if (shape.equals(mxConstants.SHAPE_ACTOR)) { elem = document.createElement("path"); double width3 = w / 3; String d = " M " + x + " " + (y + h) + " C " + x + " " + (y + 3 * h / 5) + " " + x + " " + (y + 2 * h / 5) + " " + (x + w / 2) + " " + (y + 2 * h / 5) + " C " + (x + w / 2 - width3) + " " + (y + 2 * h / 5) + " " + (x + w / 2 - width3) + " " + y + " " + (x + w / 2) + " " + y + " C " + (x + w / 2 + width3) + " " + y + " " + (x + w / 2 + width3) + " " + (y + 2 * h / 5) + " " + (x + w / 2) + " " + (y + 2 * h / 5) + " C " + (x + w) + " " + (y + 2 * h / 5) + " " + (x + w) + " " + (y + 3 * h / 5) + " " + (x + w) + " " + (y + h); elem.setAttribute("d", d + " Z"); } else if (shape.equals(mxConstants.SHAPE_CYLINDER)) { elem = document.createElement("g"); background = document.createElement("path"); double dy = Math.min(40, Math.floor(h / 5)); String d = " M " + x + " " + (y + dy) + " C " + x + " " + (y - dy / 3) + " " + (x + w) + " " + (y - dy / 3) + " " + (x + w) + " " + (y + dy) + " L " + (x + w) + " " + (y + h - dy) + " C " + (x + w) + " " + (y + h + dy / 3) + " " + x + " " + (y + h + dy / 3) + " " + x + " " + (y + h - dy); background.setAttribute("d", d + " Z"); elem.appendChild(background); Element foreground = document.createElement("path"); d = "M " + x + " " + (y + dy) + " C " + x + " " + (y + 2 * dy) + " " + (x + w) + " " + (y + 2 * dy) + " " + (x + w) + " " + (y + dy); foreground.setAttribute("d", d); foreground.setAttribute("fill", "none"); foreground.setAttribute("stroke", strokeColor); foreground .setAttribute("stroke-width", String.valueOf(strokeWidth)); elem.appendChild(foreground); } else { background = document.createElement("rect"); elem = background; elem.setAttribute("x", String.valueOf(x)); elem.setAttribute("y", String.valueOf(y)); elem.setAttribute("width", String.valueOf(w)); elem.setAttribute("height", String.valueOf(h)); if (mxUtils.isTrue(style, mxConstants.STYLE_ROUNDED, false)) { String r = String.valueOf(Math.min(w * mxConstants.RECTANGLE_ROUNDING_FACTOR, h * mxConstants.RECTANGLE_ROUNDING_FACTOR)); elem.setAttribute("rx", r); elem.setAttribute("ry", r); } // Paints the label image if (shape.equals(mxConstants.SHAPE_LABEL)) { String img = getImageForStyle(style); if (img != null) { String imgAlign = mxUtils.getString(style, mxConstants.STYLE_IMAGE_ALIGN, mxConstants.ALIGN_LEFT); String imgValign = mxUtils.getString(style, mxConstants.STYLE_IMAGE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE); int imgWidth = (int) (mxUtils.getInt(style, mxConstants.STYLE_IMAGE_WIDTH, mxConstants.DEFAULT_IMAGESIZE) * scale); int imgHeight = (int) (mxUtils.getInt(style, mxConstants.STYLE_IMAGE_HEIGHT, mxConstants.DEFAULT_IMAGESIZE) * scale); int spacing = (int) (mxUtils.getInt(style, mxConstants.STYLE_SPACING, 2) * scale); mxRectangle imageBounds = new mxRectangle(x, y, w, h); if (imgAlign.equals(mxConstants.ALIGN_CENTER)) { imageBounds.setX(imageBounds.getX() + (imageBounds.getWidth() - imgWidth) / 2); } else if (imgAlign.equals(mxConstants.ALIGN_RIGHT)) { imageBounds.setX(imageBounds.getX() + imageBounds.getWidth() - imgWidth - spacing - 2); } else // LEFT { imageBounds.setX(imageBounds.getX() + spacing + 4); } if (imgValign.equals(mxConstants.ALIGN_TOP)) { imageBounds.setY(imageBounds.getY() + spacing); } else if (imgValign.equals(mxConstants.ALIGN_BOTTOM)) { imageBounds .setY(imageBounds.getY() + imageBounds.getHeight() - imgHeight - spacing); } else // MIDDLE { imageBounds.setY(imageBounds.getY() + (imageBounds.getHeight() - imgHeight) / 2); } imageBounds.setWidth(imgWidth); imageBounds.setHeight(imgHeight); elem = document.createElement("g"); elem.appendChild(background); Element imageElement = createImageElement( imageBounds.getX(), imageBounds.getY(), imageBounds.getWidth(), imageBounds.getHeight(), img, false, false, false, isEmbedded()); if (opacity != 100) { String value = String.valueOf(opacity / 100); imageElement.setAttribute("opacity", value); } elem.appendChild(imageElement); } // Paints the glass effect if (mxUtils.isTrue(style, mxConstants.STYLE_GLASS, false)) { double size = 0.4; // TODO: Mask with rectangle or rounded rectangle of label // Creates glass overlay Element glassOverlay = document.createElement("path"); // LATER: Not sure what the behaviour is for mutiple SVG elements in page. // Probably its possible that this points to an element in another SVG // node which when removed will result in an undefined background. glassOverlay.setAttribute("fill", "url(#" + getGlassGradientElement().getAttribute("id") + ")"); String d = "m " + (x - strokeWidth) + "," + (y - strokeWidth) + " L " + (x - strokeWidth) + "," + (y + h * size) + " Q " + (x + w * 0.5) + "," + (y + h * 0.7) + " " + (x + w + strokeWidth) + "," + (y + h * size) + " L " + (x + w + strokeWidth) + "," + (y - strokeWidth) + " z"; glassOverlay.setAttribute("stroke-width", String.valueOf(strokeWidth / 2)); glassOverlay.setAttribute("d", d); elem.appendChild(glassOverlay); } } } double rotation = mxUtils.getDouble(style, mxConstants.STYLE_ROTATION); int cx = x + w / 2; int cy = y + h / 2; Element bg = background; if (bg == null) { bg = elem; } if (!bg.getNodeName().equalsIgnoreCase("use") && !bg.getNodeName().equalsIgnoreCase("image")) { if (!fillColor.equalsIgnoreCase("none") && !gradientColor.equalsIgnoreCase("none")) { String direction = mxUtils.getString(style, mxConstants.STYLE_GRADIENT_DIRECTION); Element gradient = getGradientElement(fillColor, gradientColor, direction); if (gradient != null) { bg.setAttribute("fill", "url(#" + gradient.getAttribute("id") + ")"); } } else { bg.setAttribute("fill", fillColor); } bg.setAttribute("stroke", strokeColor); bg.setAttribute("stroke-width", String.valueOf(strokeWidth)); // Adds the shadow element Element shadowElement = null; if (mxUtils.isTrue(style, mxConstants.STYLE_SHADOW, false) && !fillColor.equals("none")) { shadowElement = (Element) bg.cloneNode(true); shadowElement.setAttribute("transform", mxConstants.SVG_SHADOWTRANSFORM); shadowElement.setAttribute("fill", mxConstants.W3C_SHADOWCOLOR); shadowElement.setAttribute("stroke", mxConstants.W3C_SHADOWCOLOR); shadowElement.setAttribute("stroke-width", String.valueOf(strokeWidth)); if (rotation != 0) { shadowElement.setAttribute("transform", "rotate(" + rotation + "," + cx + "," + cy + ") " + mxConstants.SVG_SHADOWTRANSFORM); } if (opacity != 100) { String value = String.valueOf(opacity / 100); shadowElement.setAttribute("fill-opacity", value); shadowElement.setAttribute("stroke-opacity", value); } appendSvgElement(shadowElement); } } if (rotation != 0) { elem.setAttribute("transform", elem.getAttribute("transform") + " rotate(" + rotation + "," + cx + "," + cy + ")"); } if (opacity != 100) { String value = String.valueOf(opacity / 100); elem.setAttribute("fill-opacity", value); elem.setAttribute("stroke-opacity", value); } if (mxUtils.isTrue(style, mxConstants.STYLE_DASHED)) { String pattern = mxUtils.getString(style, mxConstants.STYLE_DASH_PATTERN, "3, 3"); elem.setAttribute("stroke-dasharray", pattern); } appendSvgElement(elem); return elem; } /** * Draws the given lines as segments between all points of the given list * of mxPoints. * * @param pts List of points that define the line. * @param style Style to be used for painting the line. */ public Element drawLine(List<mxPoint> pts, Map<String, Object> style) { Element group = document.createElement("g"); Element path = document.createElement("path"); boolean rounded = mxUtils.isTrue(style, mxConstants.STYLE_ROUNDED, false); String strokeColor = mxUtils.getString(style, mxConstants.STYLE_STROKECOLOR); float tmpStroke = (mxUtils.getFloat(style, mxConstants.STYLE_STROKEWIDTH, 1)); float strokeWidth = (float) (tmpStroke * scale); if (strokeColor != null && strokeWidth > 0) { // Draws the start marker Object marker = style.get(mxConstants.STYLE_STARTARROW); mxPoint pt = pts.get(1); mxPoint p0 = pts.get(0); mxPoint offset = null; if (marker != null) { float size = (mxUtils.getFloat(style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_MARKERSIZE)); offset = drawMarker(group, marker, pt, p0, size, tmpStroke, strokeColor); } else { double dx = pt.getX() - p0.getX(); double dy = pt.getY() - p0.getY(); double dist = Math.max(1, Math.sqrt(dx * dx + dy * dy)); double nx = dx * strokeWidth / dist; double ny = dy * strokeWidth / dist; offset = new mxPoint(nx / 2, ny / 2); } // Applies offset to the point if (offset != null) { p0 = (mxPoint) p0.clone(); p0.setX(p0.getX() + offset.getX()); p0.setY(p0.getY() + offset.getY()); offset = null; } // Draws the end marker marker = style.get(mxConstants.STYLE_ENDARROW); pt = pts.get(pts.size() - 2); mxPoint pe = pts.get(pts.size() - 1); if (marker != null) { float size = (mxUtils.getFloat(style, mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE)); offset = drawMarker(group, marker, pt, pe, size, tmpStroke, strokeColor); } else { double dx = pt.getX() - p0.getX(); double dy = pt.getY() - p0.getY(); double dist = Math.max(1, Math.sqrt(dx * dx + dy * dy)); double nx = dx * strokeWidth / dist; double ny = dy * strokeWidth / dist; offset = new mxPoint(nx / 2, ny / 2); } // Applies offset to the point if (offset != null) { pe = (mxPoint) pe.clone(); pe.setX(pe.getX() + offset.getX()); pe.setY(pe.getY() + offset.getY()); offset = null; } // Draws the line segments double arcSize = mxConstants.LINE_ARCSIZE * scale; pt = p0; String d = "M " + pt.getX() + " " + pt.getY(); for (int i = 1; i < pts.size() - 1; i++) { mxPoint tmp = pts.get(i); double dx = pt.getX() - tmp.getX(); double dy = pt.getY() - tmp.getY(); if ((rounded && i < pts.size() - 1) && (dx != 0 || dy != 0)) { // Draws a line from the last point to the current // point with a spacing of size off the current point // into direction of the last point double dist = Math.sqrt(dx * dx + dy * dy); double nx1 = dx * Math.min(arcSize, dist / 2) / dist; double ny1 = dy * Math.min(arcSize, dist / 2) / dist; double x1 = tmp.getX() + nx1; double y1 = tmp.getY() + ny1; d += " L " + x1 + " " + y1; // Draws a curve from the last point to the current // point with a spacing of size off the current point // into direction of the next point mxPoint next = pts.get(i + 1); dx = next.getX() - tmp.getX(); dy = next.getY() - tmp.getY(); dist = Math.max(1, Math.sqrt(dx * dx + dy * dy)); double nx2 = dx * Math.min(arcSize, dist / 2) / dist; double ny2 = dy * Math.min(arcSize, dist / 2) / dist; double x2 = tmp.getX() + nx2; double y2 = tmp.getY() + ny2; d += " Q " + tmp.getX() + " " + tmp.getY() + " " + x2 + " " + y2; tmp = new mxPoint(x2, y2); } else { d += " L " + tmp.getX() + " " + tmp.getY(); } pt = tmp; } d += " L " + pe.getX() + " " + pe.getY(); path.setAttribute("d", d); path.setAttribute("stroke", strokeColor); path.setAttribute("fill", "none"); path.setAttribute("stroke-width", String.valueOf(strokeWidth)); if (mxUtils.isTrue(style, mxConstants.STYLE_DASHED)) { String pattern = mxUtils.getString(style, mxConstants.STYLE_DASH_PATTERN, "3, 3"); path.setAttribute("stroke-dasharray", pattern); } group.appendChild(path); appendSvgElement(group); } return group; } /** * Draws the specified marker as a child path in the given parent. */ public mxPoint drawMarker(Element parent, Object type, mxPoint p0, mxPoint pe, float size, float strokeWidth, String color) { mxPoint offset = null; // Computes the norm and the inverse norm double dx = pe.getX() - p0.getX(); double dy = pe.getY() - p0.getY(); double dist = Math.max(1, Math.sqrt(dx * dx + dy * dy)); double absSize = size * scale; double nx = dx * absSize / dist; double ny = dy * absSize / dist; pe = (mxPoint) pe.clone(); pe.setX(pe.getX() - nx * strokeWidth / (2 * size)); pe.setY(pe.getY() - ny * strokeWidth / (2 * size)); nx *= 0.5 + strokeWidth / 2; ny *= 0.5 + strokeWidth / 2; Element path = document.createElement("path"); path.setAttribute("stroke-width", String.valueOf(strokeWidth * scale)); path.setAttribute("stroke", color); path.setAttribute("fill", color); String d = null; if (type.equals(mxConstants.ARROW_CLASSIC) || type.equals(mxConstants.ARROW_BLOCK)) { d = "M " + pe.getX() + " " + pe.getY() + " L " + (pe.getX() - nx - ny / 2) + " " + (pe.getY() - ny + nx / 2) + ((!type.equals(mxConstants.ARROW_CLASSIC)) ? "" : " L " + (pe.getX() - nx * 3 / 4) + " " + (pe.getY() - ny * 3 / 4)) + " L " + (pe.getX() + ny / 2 - nx) + " " + (pe.getY() - ny - nx / 2) + " z"; } else if (type.equals(mxConstants.ARROW_OPEN)) { nx *= 1.2; ny *= 1.2; d = "M " + (pe.getX() - nx - ny / 2) + " " + (pe.getY() - ny + nx / 2) + " L " + (pe.getX() - nx / 6) + " " + (pe.getY() - ny / 6) + " L " + (pe.getX() + ny / 2 - nx) + " " + (pe.getY() - ny - nx / 2) + " M " + pe.getX() + " " + pe.getY(); path.setAttribute("fill", "none"); } else if (type.equals(mxConstants.ARROW_OVAL)) { nx *= 1.2; ny *= 1.2; absSize *= 1.2; d = "M " + (pe.getX() - ny / 2) + " " + (pe.getY() + nx / 2) + " a " + (absSize / 2) + " " + (absSize / 2) + " 0 1,1 " + (nx / 8) + " " + (ny / 8) + " z"; } else if (type.equals(mxConstants.ARROW_DIAMOND)) { d = "M " + (pe.getX() + nx / 2) + " " + (pe.getY() + ny / 2) + " L " + (pe.getX() - ny / 2) + " " + (pe.getY() + nx / 2) + " L " + (pe.getX() - nx / 2) + " " + (pe.getY() - ny / 2) + " L " + (pe.getX() + ny / 2) + " " + (pe.getY() - nx / 2) + " z"; } if (d != null) { path.setAttribute("d", d); parent.appendChild(path); } return offset; } /** * Draws the specified text either using drawHtmlString or using drawString. * * @param text Text to be painted. * @param x X-coordinate of the text. * @param y Y-coordinate of the text. * @param w Width of the text. * @param h Height of the text. * @param style Style to be used for painting the text. */ public Object drawText(String text, int x, int y, int w, int h, Map<String, Object> style) { Element elem = null; String fontColor = mxUtils.getString(style, mxConstants.STYLE_FONTCOLOR, "black"); String fontFamily = mxUtils.getString(style, mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILIES); int fontSize = (int) (mxUtils.getInt(style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE) * scale); if (text != null && text.length() > 0) { float strokeWidth = (float) (mxUtils.getFloat(style, mxConstants.STYLE_STROKEWIDTH, 1) * scale); // Applies the opacity float opacity = mxUtils.getFloat(style, mxConstants.STYLE_TEXT_OPACITY, 100); // Draws the label background and border String bg = mxUtils.getString(style, mxConstants.STYLE_LABEL_BACKGROUNDCOLOR); String border = mxUtils.getString(style, mxConstants.STYLE_LABEL_BORDERCOLOR); String transform = null; if (!mxUtils.isTrue(style, mxConstants.STYLE_HORIZONTAL, true)) { double cx = x + w / 2; double cy = y + h / 2; transform = "rotate(270 " + cx + " " + cy + ")"; } if (bg != null || border != null) { Element background = document.createElement("rect"); background.setAttribute("x", String.valueOf(x)); background.setAttribute("y", String.valueOf(y)); background.setAttribute("width", String.valueOf(w)); background.setAttribute("height", String.valueOf(h)); if (bg != null) { background.setAttribute("fill", bg); } else { background.setAttribute("fill", "none"); } if (border != null) { background.setAttribute("stroke", border); } else { background.setAttribute("stroke", "none"); } background.setAttribute("stroke-width", String.valueOf(strokeWidth)); if (opacity != 100) { String value = String.valueOf(opacity / 100); background.setAttribute("fill-opacity", value); background.setAttribute("stroke-opacity", value); } if (transform != null) { background.setAttribute("transform", transform); } appendSvgElement(background); } elem = document.createElement("text"); int fontStyle = mxUtils.getInt(style, mxConstants.STYLE_FONTSTYLE); String weight = ((fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) ? "bold" : "normal"; elem.setAttribute("font-weight", weight); String uline = ((fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) ? "underline" : "none"; elem.setAttribute("font-decoration", uline); if ((fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) { elem.setAttribute("font-style", "italic"); } elem.setAttribute("font-size", String.valueOf(fontSize)); elem.setAttribute("font-family", fontFamily); elem.setAttribute("fill", fontColor); if (opacity != 100) { String value = String.valueOf(opacity / 100); elem.setAttribute("fill-opacity", value); elem.setAttribute("stroke-opacity", value); } int swingFontStyle = ((fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) ? Font.BOLD : Font.PLAIN; swingFontStyle += ((fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) ? Font.ITALIC : Font.PLAIN; String[] lines = text.split("\n"); y += fontSize + (h - lines.length * (fontSize + mxConstants.LINESPACING)) / 2 - 2; String align = mxUtils.getString(style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER); String anchor = "start"; if (align.equals(mxConstants.ALIGN_RIGHT)) { anchor = "end"; x += w - mxConstants.LABEL_INSET * scale; } else if (align.equals(mxConstants.ALIGN_CENTER)) { anchor = "middle"; x += w / 2; } else { x += mxConstants.LABEL_INSET * scale; } elem.setAttribute("text-anchor", anchor); for (int i = 0; i < lines.length; i++) { Element tspan = document.createElement("tspan"); tspan.setAttribute("x", String.valueOf(x)); tspan.setAttribute("y", String.valueOf(y)); tspan.appendChild(document.createTextNode(lines[i])); elem.appendChild(tspan); y += fontSize + mxConstants.LINESPACING; } if (transform != null) { elem.setAttribute("transform", transform); } appendSvgElement(elem); } return elem; } }