/**
* $Id: mxSvgCanvas.java,v 1.66 2011-03-08 17:16:07 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;
}
}