summaryrefslogtreecommitdiff
path: root/src/js/shape/mxStencil.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/js/shape/mxStencil.js')
-rw-r--r--src/js/shape/mxStencil.js1585
1 files changed, 1585 insertions, 0 deletions
diff --git a/src/js/shape/mxStencil.js b/src/js/shape/mxStencil.js
new file mode 100644
index 0000000..d0e1a63
--- /dev/null
+++ b/src/js/shape/mxStencil.js
@@ -0,0 +1,1585 @@
+/**
+ * $Id: mxStencil.js,v 1.91 2012-07-16 10:22:44 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxStencil
+ *
+ * Implements a generic shape which is based on a XML node as a description.
+ * The node contains a background and a foreground node, which contain the
+ * definition to render the respective part of the shape. Note that the
+ * fill, stroke or fillstroke of the background is be the first statement
+ * of the foreground. This is because the content of the background node
+ * maybe used to not only render the shape itself, but also its shadow and
+ * other elements which do not require a fill, stroke or fillstroke.
+ *
+ * The shape uses a coordinate system with a width of 100 and a height of
+ * 100 by default. This can be changed by setting the w and h attribute of
+ * the shape element. The aspect attribute can be set to "variable" (default)
+ * or "fixed". If fixed is used, then the aspect which is defined via the w
+ * and h attribute is kept constant while the shape is scaled.
+ *
+ * The possible contents of the background and foreground elements are rect,
+ * ellipse, roundrect, text, image, include-shape or paths. A path element
+ * contains move, line, curve, quad, arc and close elements. The rect, ellipse
+ * and roundrect elements may be thought of as special path elements. All these
+ * path elements must be followed by either fill, stroke or fillstroke (note
+ * that text, image and include-shape or not path elements).
+ *
+ * The background element can be empty or contain at most one path element. It
+ * should not contain a text, image or include-shape element. If the background
+ * element is empty, then no shadow or glass effect will be rendered. If the
+ * background element is non-empty, then the corresponding fill, stroke or
+ * fillstroke should be the first element in the subsequent foreground element.
+ *
+ * The format of the XML is "a simplified HTML 5 Canvas". Each command changes
+ * the "current" state, so eg. a linecap, linejoin will be used for all
+ * subsequent line drawing, unless a save/restore appears, which saves/restores
+ * a state in a stack.
+ *
+ * The connections section contains the fixed connection points for a stencil.
+ * The perimeter attribute of the constraint element should have a value of 0
+ * or 1 (default), where 1 (true) specifies that the given point should be
+ * projected into the perimeter of the given shape.
+ *
+ * The x- and y-coordinates are typically between 0 and 1 and define the
+ * location of the connection point relative to the width and height of the
+ * shape.
+ *
+ * The dashpattern directive sets the current dashpattern. The format for the
+ * pattern attribute is a space-separated sequence of numbers, eg. 5 5 5 5,
+ * that specifies the lengths of alternating dashes and spaces in dashed lines.
+ * The dashpattern should be used together with the dashed directive to
+ * enabled/disable the dashpattern. The default dashpattern is 3 3.
+ *
+ * The strokewidth attribute defines a fixed strokewidth for the shape. It
+ * can contain a numeric value or the keyword "inherit", which means that the
+ * strokeWidth from the cell's style will be used and muliplied with the shape's
+ * scale. If numeric values are used, those are multiplied with the minimum
+ * scale used to render the stencil inside the shape's bounds.
+ *
+ * Constructor: mxStencilShape
+ *
+ * Constructs a new generic shape by setting <desc> to the given XML node and
+ * invoking <parseDescription> and <parseConstraints>.
+ *
+ * Parameters:
+ *
+ * desc - XML node that contains the stencil description.
+ */
+function mxStencil(desc)
+{
+ this.desc = desc;
+ this.parseDescription();
+ this.parseConstraints();
+};
+
+/**
+ * Variable: desc
+ *
+ * Holds the XML node with the stencil description.
+ */
+mxStencil.prototype.desc = null;
+
+/**
+ * Variable: constraints
+ *
+ * Holds an array of <mxConnectionConstraints> as defined in the shape.
+ */
+mxStencil.prototype.constraints = null;
+
+/**
+ * Variable: aspect
+ *
+ * Holds the aspect of the shape. Default is 'auto'.
+ */
+mxStencil.prototype.aspect = null;
+
+/**
+ * Variable: w0
+ *
+ * Holds the width of the shape. Default is 100.
+ */
+mxStencil.prototype.w0 = null;
+
+/**
+ * Variable: h0
+ *
+ * Holds the height of the shape. Default is 100.
+ */
+mxStencil.prototype.h0 = null;
+
+/**
+ * Variable: bgNodes
+ *
+ * Holds the XML node with the stencil description.
+ */
+mxStencil.prototype.bgNode = null;
+
+/**
+ * Variable: fgNodes
+ *
+ * Holds the XML node with the stencil description.
+ */
+mxStencil.prototype.fgNode = null;
+
+/**
+ * Variable: strokewidth
+ *
+ * Holds the strokewidth direction from the description.
+ */
+mxStencil.prototype.strokewidth = null;
+
+/**
+ * Function: parseDescription
+ *
+ * Reads <w0>, <h0>, <aspect>, <bgNodes> and <fgNodes> from <desc>.
+ */
+mxStencil.prototype.parseDescription = function()
+{
+ // LATER: Preprocess nodes for faster painting
+ this.fgNode = this.desc.getElementsByTagName('foreground')[0];
+ this.bgNode = this.desc.getElementsByTagName('background')[0];
+ this.w0 = Number(this.desc.getAttribute('w') || 100);
+ this.h0 = Number(this.desc.getAttribute('h') || 100);
+
+ // Possible values for aspect are: variable and fixed where
+ // variable means fill the available space and fixed means
+ // use w0 and h0 to compute the aspect.
+ var aspect = this.desc.getAttribute('aspect');
+ this.aspect = (aspect != null) ? aspect : 'variable';
+
+ // Possible values for strokewidth are all numbers and "inherit"
+ // where the inherit means take the value from the style (ie. the
+ // user-defined stroke-width). Note that the strokewidth is scaled
+ // by the minimum scaling that is used to draw the shape (sx, sy).
+ var sw = this.desc.getAttribute('strokewidth');
+ this.strokewidth = (sw != null) ? sw : '1';
+};
+
+/**
+ * Function: parseConstraints
+ *
+ * Reads the constraints from <desc> into <constraints> using
+ * <parseConstraint>.
+ */
+mxStencil.prototype.parseConstraints = function()
+{
+ var conns = this.desc.getElementsByTagName('connections')[0];
+
+ if (conns != null)
+ {
+ var tmp = mxUtils.getChildNodes(conns);
+
+ if (tmp != null && tmp.length > 0)
+ {
+ this.constraints = [];
+
+ for (var i = 0; i < tmp.length; i++)
+ {
+ this.constraints.push(this.parseConstraint(tmp[i]));
+ }
+ }
+ }
+};
+
+/**
+ * Function: parseConstraint
+ *
+ * Parses the given XML node and returns its <mxConnectionConstraint>.
+ */
+mxStencil.prototype.parseConstraint = function(node)
+{
+ var x = Number(node.getAttribute('x'));
+ var y = Number(node.getAttribute('y'));
+ var perimeter = node.getAttribute('perimeter') == '1';
+
+ return new mxConnectionConstraint(new mxPoint(x, y), perimeter);
+};
+
+/**
+ * Function: evaluateAttribute
+ *
+ * Gets the attribute for the given name from the given node. If the attribute
+ * does not exist then the text content of the node is evaluated and if it is
+ * a function it is invoked with <state> as the only argument and the return
+ * value is used as the attribute value to be returned.
+ */
+mxStencil.prototype.evaluateAttribute = function(node, attribute, state)
+{
+ var result = node.getAttribute(attribute);
+
+ if (result == null)
+ {
+ var text = mxUtils.getTextContent(node);
+
+ if (text != null)
+ {
+ var funct = mxUtils.eval(text);
+
+ if (typeof(funct) == 'function')
+ {
+ result = funct(state);
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: renderDom
+ *
+ * Updates the SVG or VML shape.
+ */
+mxStencil.prototype.renderDom = function(shape, bounds, parentNode, state)
+{
+ var vml = shape.dialect != mxConstants.DIALECT_SVG;
+ var vmlScale = (document.documentMode == 8) ? 1 : shape.vmlScale;
+ var rotation = shape.rotation || 0;
+ var inverse = false;
+
+ // New styles for shape flipping the stencil
+ var flipH = shape.style[mxConstants.STYLE_STENCIL_FLIPH];
+ var flipV = shape.style[mxConstants.STYLE_STENCIL_FLIPV];
+
+ if (flipH ? !flipV : flipV)
+ {
+ rotation *= -1;
+ }
+
+ // Default direction is east (ignored if rotation exists)
+ if (shape.direction != null)
+ {
+ if (shape.direction == 'north')
+ {
+ rotation += 270;
+ }
+ else if (shape.direction == 'west')
+ {
+ rotation += 180;
+ }
+ else if (shape.direction == 'south')
+ {
+ rotation += 90;
+ }
+
+ inverse = (shape.direction == 'north' || shape.direction == 'south');
+ }
+
+ if (flipH && flipV)
+ {
+ rotation += 180;
+ flipH = false;
+ flipV = false;
+ }
+
+ // SVG transform should be applied on all child shapes
+ var svgTransform = '';
+
+ // Implements direction style and vertical/horizontal flip
+ // via container transformation.
+ if (vml)
+ {
+ if (flipH)
+ {
+ parentNode.style.flip = 'x';
+ }
+ else if (flipV)
+ {
+ parentNode.style.flip = 'y';
+ }
+
+ if (rotation != 0)
+ {
+ parentNode.style.rotation = rotation;
+ }
+ }
+ else
+ {
+ if (flipH || flipV)
+ {
+ var sx = 1;
+ var sy = 1;
+ var dx = 0;
+ var dy = 0;
+
+ if (flipH)
+ {
+ sx = -1;
+ dx = -bounds.width - 2 * bounds.x;
+ }
+
+ if (flipV)
+ {
+ sy = -1;
+ dy = -bounds.height - 2 * bounds.y;
+ }
+
+ svgTransform = 'scale(' + sx + ' ' + sy + ') translate(' + dx + ' ' + dy + ')';
+ }
+
+ // Adds rotation as a separate transform
+ if (rotation != 0)
+ {
+ var cx = bounds.getCenterX();
+ var cy = bounds.getCenterY();
+ svgTransform += ' rotate(' + rotation + ' ' + cx + ' ' + cy + ')';
+ }
+ }
+
+ var background = (state == null);
+
+ if (this.bgNode != null || this.fgNode != null)
+ {
+ var x0 = (vml && state == null) ? 0 : bounds.x;
+ var y0 = (vml && state == null) ? 0 : bounds.y;
+ var sx = bounds.width / this.w0;
+ var sy = bounds.height / this.h0;
+
+ // Stores current location inside path
+ this.lastMoveX = 0;
+ this.lastMoveY = 0;
+
+ if (inverse)
+ {
+ sy = bounds.width / this.h0;
+ sx = bounds.height / this.w0;
+
+ var delta = (bounds.width - bounds.height) / 2;
+
+ x0 += delta;
+ y0 -= delta;
+ }
+
+ if (this.aspect == 'fixed')
+ {
+ sy = Math.min(sx, sy);
+ sx = sy;
+
+ // Centers the shape inside the available space
+ if (inverse)
+ {
+ x0 += (bounds.height - this.w0 * sx) / 2;
+ y0 += (bounds.width - this.h0 * sy) / 2;
+ }
+ else
+ {
+ x0 += (bounds.width - this.w0 * sx) / 2;
+ y0 += (bounds.height - this.h0 * sy) / 2;
+ }
+ }
+
+ // Workaround to improve VML rendering precision.
+ if (vml)
+ {
+ sx *= vmlScale;
+ sy *= vmlScale;
+ x0 *= vmlScale;
+ y0 *= vmlScale;
+ }
+
+ var minScale = Math.min(sx, sy);
+
+ // Stack of states for save/restore ops
+ var stack = [];
+
+ var currentState = (state != null) ? state :
+ {
+ fillColorAssigned: false,
+ fill: shape.fill,
+ stroke: shape.stroke,
+ strokeWidth: (this.strokewidth == 'inherit') ?
+ Number(shape.strokewidth) * shape.scale :
+ Number(this.strokewidth) * minScale / ((vml) ? vmlScale : 1),
+ dashed: shape.isDashed,
+ dashpattern: [3, 3],
+ alpha: shape.opacity,
+ linejoin: 'miter',
+ fontColor: '#000000',
+ fontSize: mxConstants.DEFAULT_FONTSIZE,
+ fontFamily: mxConstants.DEFAULT_FONTFAMILY,
+ fontStyle: 0
+ };
+
+ var currentPath = null;
+ var currentPoints = null;
+
+ var configurePath = function(path, state)
+ {
+ var sw = Math.max(1, state.strokeWidth);
+
+ if (vml)
+ {
+ path.strokeweight = Math.round(sw) + 'px';
+
+ if (state.fill != null)
+ {
+ // Gradient in foregrounds not supported because special gradients
+ // with bounds must be created for each element in graphics-canvases
+ var gradient = (!state.fillColorAssigned) ? shape.gradient : null;
+ var fill = document.createElement('v:fill');
+ shape.updateVmlFill(fill, state.fill, gradient, shape.gradientDirection, state.alpha);
+ path.appendChild(fill);
+ }
+ else
+ {
+ path.filled = 'false';
+ }
+
+ if (state.stroke != null)
+ {
+ path.stroked = 'true';
+ path.strokecolor = state.stroke;
+ }
+ else
+ {
+ path.stroked = 'false';
+ }
+
+ path.style.position = 'absolute';
+ }
+ else
+ {
+ path.setAttribute('stroke-width', sw);
+
+ if (state.fill != null && state.fillColorAssigned)
+ {
+ path.setAttribute('fill', state.fill);
+ }
+
+ if (state.stroke != null)
+ {
+ path.setAttribute('stroke', state.stroke);
+ }
+ }
+ };
+
+ var addToPath = function(s)
+ {
+ if (currentPath != null && currentPoints != null)
+ {
+ currentPoints.push(s);
+ }
+ };
+
+ var round = function(value)
+ {
+ return (vml) ? Math.round(value) : value;
+ };
+
+ // Will be moved to a hook later for example to set text values
+ var renderNode = function(node)
+ {
+ var name = node.nodeName;
+
+ var fillOp = name == 'fill';
+ var strokeOp = name == 'stroke';
+ var fillStrokeOp = name == 'fillstroke';
+
+ if (name == 'save')
+ {
+ stack.push(currentState);
+ currentState = mxUtils.clone(currentState);
+ }
+ else if (name == 'restore')
+ {
+ currentState = stack.pop();
+ }
+ else if (name == 'path')
+ {
+ currentPoints = [];
+
+ if (vml)
+ {
+ currentPath = document.createElement('v:shape');
+ configurePath.call(this, currentPath, currentState);
+ var w = Math.round(bounds.width) * vmlScale;
+ var h = Math.round(bounds.height) * vmlScale;
+ currentPath.style.width = w + 'px';
+ currentPath.style.height = h + 'px';
+ currentPath.coordsize = w + ',' + h;
+ }
+ else
+ {
+ currentPath = document.createElementNS(mxConstants.NS_SVG, 'path');
+ configurePath.call(this, currentPath, currentState);
+
+ if (svgTransform.length > 0)
+ {
+ currentPath.setAttribute('transform', svgTransform);
+ }
+
+ if (node.getAttribute('crisp') == '1')
+ {
+ currentPath.setAttribute('shape-rendering', 'crispEdges');
+ }
+ }
+
+ // Renders the elements inside the given path
+ var childNode = node.firstChild;
+
+ while (childNode != null)
+ {
+ if (childNode.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ renderNode.call(this, childNode);
+ }
+
+ childNode = childNode.nextSibling;
+ }
+
+ // Ends the current path
+ if (vml)
+ {
+ addToPath('e');
+ currentPath.path = currentPoints.join('');
+ }
+ else
+ {
+ currentPath.setAttribute('d', currentPoints.join(''));
+ }
+ }
+ else if (name == 'move')
+ {
+ var op = (vml) ? 'm' : 'M';
+ this.lastMoveX = round(x0 + Number(node.getAttribute('x')) * sx);
+ this.lastMoveY = round(y0 + Number(node.getAttribute('y')) * sy);
+ addToPath(op + ' ' + this.lastMoveX + ' ' + this.lastMoveY);
+ }
+ else if (name == 'line')
+ {
+ var op = (vml) ? 'l' : 'L';
+ this.lastMoveX = round(x0 + Number(node.getAttribute('x')) * sx);
+ this.lastMoveY = round(y0 + Number(node.getAttribute('y')) * sy);
+ addToPath(op + ' ' + this.lastMoveX + ' ' + this.lastMoveY);
+ }
+ else if (name == 'quad')
+ {
+ if (vml)
+ {
+ var cpx0 = this.lastMoveX;
+ var cpy0 = this.lastMoveY;
+ var qpx1 = x0 + Number(node.getAttribute('x1')) * sx;
+ var qpy1 = y0 + Number(node.getAttribute('y1')) * sy;
+ var cpx3 = x0 + Number(node.getAttribute('x2')) * sx;
+ var cpy3 = y0 + Number(node.getAttribute('y2')) * sy;
+
+ var cpx1 = cpx0 + 2/3 * (qpx1 - cpx0);
+ var cpy1 = cpy0 + 2/3 * (qpy1 - cpy0);
+
+ var cpx2 = cpx3 + 2/3 * (qpx1 - cpx3);
+ var cpy2 = cpy3 + 2/3 * (qpy1 - cpy3);
+
+ addToPath('c ' + Math.round(cpx1) + ' ' + Math.round(cpy1) + ' ' +
+ Math.round(cpx2) + ' ' + Math.round(cpy2) + ' ' +
+ Math.round(cpx3) + ' ' + Math.round(cpy3));
+
+ this.lastMoveX = cpx3;
+ this.lastMoveY = cpy3;
+ }
+ else
+ {
+ this.lastMoveX = x0 + Number(node.getAttribute('x2')) * sx;
+ this.lastMoveY = y0 + Number(node.getAttribute('y2')) * sy;
+
+ addToPath('Q ' + (x0 + Number(node.getAttribute('x1')) * sx) + ' ' +
+ (y0 + Number(node.getAttribute('y1')) * sy) + ' ' +
+ this.lastMoveX + ' ' + this.lastMoveY);
+ }
+ }
+ else if (name == 'curve')
+ {
+ var op = (vml) ? 'c' : 'C';
+ this.lastMoveX = round(x0 + Number(node.getAttribute('x3')) * sx);
+ this.lastMoveY = round(y0 + Number(node.getAttribute('y3')) * sy);
+
+ addToPath(op + ' ' + round(x0 + Number(node.getAttribute('x1')) * sx) + ' ' +
+ round(y0 + Number(node.getAttribute('y1')) * sy) + ' ' +
+ round(x0 + Number(node.getAttribute('x2')) * sx) + ' ' +
+ round(y0 + Number(node.getAttribute('y2')) * sy) + ' ' +
+ this.lastMoveX + ' ' + this.lastMoveY);
+ }
+ else if (name == 'close')
+ {
+ addToPath((vml) ? 'x' : 'Z');
+ }
+ else if (name == 'rect' || name == 'roundrect')
+ {
+ var rounded = name == 'roundrect';
+ var x = round(x0 + Number(node.getAttribute('x')) * sx);
+ var y = round(y0 + Number(node.getAttribute('y')) * sy);
+ var w = round(Number(node.getAttribute('w')) * sx);
+ var h = round(Number(node.getAttribute('h')) * sy);
+
+ var arcsize = node.getAttribute('arcsize');
+
+ if (arcsize == 0)
+ {
+ arcsize = mxConstants.RECTANGLE_ROUNDING_FACTOR * 100;
+ }
+
+ if (vml)
+ {
+ // LATER: Use HTML for non-rounded, gradientless rectangles
+ currentPath = document.createElement((rounded) ? 'v:roundrect' : 'v:rect');
+ currentPath.style.left = x + 'px';
+ currentPath.style.top = y + 'px';
+ currentPath.style.width = w + 'px';
+ currentPath.style.height = h + 'px';
+
+ if (rounded)
+ {
+ currentPath.setAttribute('arcsize', String(arcsize) + '%');
+ }
+ }
+ else
+ {
+ currentPath = document.createElementNS(mxConstants.NS_SVG, 'rect');
+ currentPath.setAttribute('x', x);
+ currentPath.setAttribute('y', y);
+ currentPath.setAttribute('width', w);
+ currentPath.setAttribute('height', h);
+
+ if (rounded)
+ {
+ var factor = Number(arcsize) / 100;
+ var r = Math.min(w * factor, h * factor);
+ currentPath.setAttribute('rx', r);
+ currentPath.setAttribute('ry', r);
+ }
+
+ if (svgTransform.length > 0)
+ {
+ currentPath.setAttribute('transform', svgTransform);
+ }
+
+ if (node.getAttribute('crisp') == '1')
+ {
+ currentPath.setAttribute('shape-rendering', 'crispEdges');
+ }
+ }
+
+ configurePath.call(this, currentPath, currentState);
+ }
+ else if (name == 'ellipse')
+ {
+ var x = round(x0 + Number(node.getAttribute('x')) * sx);
+ var y = round(y0 + Number(node.getAttribute('y')) * sy);
+ var w = round(Number(node.getAttribute('w')) * sx);
+ var h = round(Number(node.getAttribute('h')) * sy);
+
+ if (vml)
+ {
+ currentPath = document.createElement('v:arc');
+ currentPath.startangle = '0';
+ currentPath.endangle = '360';
+ currentPath.style.left = x + 'px';
+ currentPath.style.top = y + 'px';
+ currentPath.style.width = w + 'px';
+ currentPath.style.height = h + 'px';
+ }
+ else
+ {
+ currentPath = document.createElementNS(mxConstants.NS_SVG, 'ellipse');
+ currentPath.setAttribute('cx', x + w / 2);
+ currentPath.setAttribute('cy', y + h / 2);
+ currentPath.setAttribute('rx', w / 2);
+ currentPath.setAttribute('ry', h / 2);
+
+ if (svgTransform.length > 0)
+ {
+ currentPath.setAttribute('transform', svgTransform);
+ }
+ }
+
+ configurePath.call(this, currentPath, currentState);
+ }
+ else if (name == 'arc')
+ {
+ var r1 = Number(node.getAttribute('rx')) * sx;
+ var r2 = Number(node.getAttribute('ry')) * sy;
+ var angle = Number(node.getAttribute('x-axis-rotation'));
+ var largeArcFlag = Number(node.getAttribute('large-arc-flag'));
+ var sweepFlag = Number(node.getAttribute('sweep-flag'));
+ var x = x0 + Number(node.getAttribute('x')) * sx;
+ var y = y0 + Number(node.getAttribute('y')) * sy;
+
+ if (vml)
+ {
+ var curves = mxUtils.arcToCurves(this.lastMoveX, this.lastMoveY, r1, r2, angle, largeArcFlag, sweepFlag, x, y);
+
+ for (var i = 0; i < curves.length; i += 6)
+ {
+ addToPath('c' + ' ' + Math.round(curves[i]) + ' ' + Math.round(curves[i + 1]) + ' ' +
+ Math.round(curves[i + 2]) + ' ' + Math.round(curves[i + 3]) + ' ' +
+ Math.round(curves[i + 4]) + ' ' + Math.round(curves[i + 5]));
+
+ this.lastMoveX = curves[i + 4];
+ this.lastMoveY = curves[i + 5];
+ }
+ }
+ else
+ {
+ addToPath('A ' + r1 + ',' + r2 + ' ' + angle + ' ' + largeArcFlag + ',' + sweepFlag + ' ' + x + ',' + y);
+ this.lastMoveX = x0 + x;
+ this.lastMoveY = y0 + y;
+ }
+ }
+ else if (name == 'image')
+ {
+ var src = this.evaluateAttribute(node, 'src', shape.state);
+
+ if (src != null)
+ {
+ var x = round(x0 + Number(node.getAttribute('x')) * sx);
+ var y = round(y0 + Number(node.getAttribute('y')) * sy);
+ var w = round(Number(node.getAttribute('w')) * sx);
+ var h = round(Number(node.getAttribute('h')) * sy);
+
+ // TODO: _Not_ providing an aspect in the shapes format has the advantage
+ // of not needing a callback to adjust the image in VML. Since the shape
+ // developer can specify the aspect via width and height this should OK.
+ //var aspect = node.getAttribute('aspect') != '0';
+ var aspect = false;
+ var flipH = node.getAttribute('flipH') == '1';
+ var flipV = node.getAttribute('flipV') == '1';
+
+ if (vml)
+ {
+ currentPath = document.createElement('v:image');
+ currentPath.style.filter = 'alpha(opacity=' + currentState.alpha + ')';
+ currentPath.style.left = x + 'px';
+ currentPath.style.top = y + 'px';
+ currentPath.style.width = w + 'px';
+ currentPath.style.height = h + 'px';
+ currentPath.src = src;
+
+ if (flipH && flipV)
+ {
+ currentPath.style.rotation = '180';
+ }
+ else if (flipH)
+ {
+ currentPath.style.flip = 'x';
+ }
+ else if (flipV)
+ {
+ currentPath.style.flip = 'y';
+ }
+ }
+ else
+ {
+ currentPath = document.createElementNS(mxConstants.NS_SVG, 'image');
+ currentPath.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', src);
+ currentPath.setAttribute('opacity', currentState.alpha / 100);
+ currentPath.setAttribute('x', x);
+ currentPath.setAttribute('y', y);
+ currentPath.setAttribute('width', w);
+ currentPath.setAttribute('height', h);
+
+ if (!aspect)
+ {
+ currentPath.setAttribute('preserveAspectRatio', 'none');
+ }
+
+ if (flipH || flipV)
+ {
+ var scx = 1;
+ var scy = 1;
+ var dx = 0;
+ var dy = 0;
+
+ if (flipH)
+ {
+ scx = -1;
+ dx = -w - 2 * x;
+ }
+
+ if (flipV)
+ {
+ scy = -1;
+ dy = -h - 2 * y;
+ }
+
+ currentPath.setAttribute('transform', svgTransform + 'scale(' + scx + ' ' + scy + ')' +
+ ' translate('+dx+' '+dy+') ');
+ }
+ else
+ {
+ currentPath.setAttribute('transform', svgTransform);
+ }
+ }
+
+ parentNode.appendChild(currentPath);
+ }
+ }
+ else if (name == 'include-shape')
+ {
+ var stencil = mxStencilRegistry.getStencil(node.getAttribute('name'));
+
+ if (stencil != null)
+ {
+ var x = x0 + Number(node.getAttribute('x')) * sx;
+ var y = y0 + Number(node.getAttribute('y')) * sy;
+ var w = Number(node.getAttribute('w')) * sx;
+ var h = Number(node.getAttribute('h')) * sy;
+
+ stencil.renderDom(shape, new mxRectangle(x, y, w, h), parentNode, currentState);
+ }
+ }
+ // Additional labels are currently disabled. Needs fixing of VML
+ // text positon, SVG text rotation and ignored baseline in FF
+ else if (name == 'text')
+ {
+ var str = this.evaluateAttribute(node, 'str', shape.state);
+
+ if (str != null)
+ {
+ var x = round(x0 + Number(node.getAttribute('x')) * sx);
+ var y = round(y0 + Number(node.getAttribute('y')) * sy);
+ var align = node.getAttribute('align') || 'left';
+ var valign = node.getAttribute('valign') || 'top';
+
+ if (vml)
+ {
+ // Renders a single line of text with full rotation support
+ currentPath = document.createElement('v:shape');
+ currentPath.style.position = 'absolute';
+ currentPath.style.width = '1px';
+ currentPath.style.height = '1px';
+ currentPath.style.left = x + 'px';
+ currentPath.style.top = y + 'px';
+
+ var fill = document.createElement('v:fill');
+ fill.color = currentState.fontColor;
+ fill.on = 'true';
+ currentPath.appendChild(fill);
+
+ var stroke = document.createElement('v:stroke');
+ stroke.on = 'false';
+ currentPath.appendChild(stroke);
+
+ var path = document.createElement('v:path');
+ path.textpathok = 'true';
+ path.v = 'm ' + x + ' ' + y + ' l ' + (x + 1) + ' ' + y;
+
+ currentPath.appendChild(path);
+
+ var tp = document.createElement('v:textpath');
+ tp.style.cssText = 'v-text-align:' + align;
+ tp.style.fontSize = Math.round(currentState.fontSize / vmlScale) + 'px';
+
+ // FIXME: Font-family seems to be ignored for textpath
+ tp.style.fontFamily = currentState.fontFamily;
+ tp.string = str;
+ tp.on = 'true';
+
+ // Bold
+ if ((currentState.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+ {
+ tp.style.fontWeight = 'bold';
+ }
+
+ // Italic
+ if ((currentState.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+ {
+ tp.style.fontStyle = 'italic';
+ }
+
+ // FIXME: Text decoration not supported in textpath
+ if ((currentState.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+ {
+ tp.style.textDecoration = 'underline';
+ }
+
+ // LATER: Find vertical center for div via CSS if possible
+ if (valign == 'top')
+ {
+ currentPath.style.top = (y + currentState.fontSize / 2) + 'px';
+ }
+ else if (valign == 'bottom')
+ {
+ currentPath.style.top = (y - currentState.fontSize / 3) + 'px';
+ }
+
+ currentPath.appendChild(tp);
+ }
+ else
+ {
+ currentPath = document.createElementNS(mxConstants.NS_SVG, 'text');
+ currentPath.setAttribute('fill', currentState.fontColor);
+ currentPath.setAttribute('font-family', currentState.fontFamily);
+ currentPath.setAttribute('font-size', currentState.fontSize);
+ currentPath.setAttribute('stroke', 'none');
+ currentPath.setAttribute('x', x);
+ currentPath.appendChild(document.createTextNode(str));
+
+ // Bold
+ if ((currentState.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+ {
+ currentPath.setAttribute('font-weight', 'bold');
+ }
+
+ // Italic
+ if ((currentState.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+ {
+ currentPath.setAttribute('font-style', 'italic');
+ }
+
+ // Underline
+ if ((currentState.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+ {
+ currentPath.setAttribute('text-decoration', uline);
+ }
+
+ // Horizontal alignment
+ if (align == 'left')
+ {
+ align = 'start';
+ }
+ else if (align == 'center')
+ {
+ align = 'middle';
+ }
+ else if (align == 'right')
+ {
+ align = 'end';
+ }
+
+ currentPath.setAttribute('text-anchor', align);
+
+ // Vertical alignment
+ // Uses dy because FF ignores alignment-baseline
+ if (valign == 'top')
+ {
+ currentPath.setAttribute('y', y + currentState.fontSize / 5);
+ currentPath.setAttribute('dy', '1ex');
+ }
+ else if (valign == 'middle')
+ {
+ currentPath.setAttribute('y', y + currentState.fontSize / 8);
+ currentPath.setAttribute('dy', '0.5ex');
+ }
+ else
+ {
+ currentPath.setAttribute('y', y);
+ }
+
+ if (svgTransform.length > 0)
+ {
+ currentPath.setAttribute('transform', svgTransform);
+ }
+ }
+
+ parentNode.appendChild(currentPath);
+ }
+ }
+ else if (fillOp || strokeOp || fillStrokeOp)
+ {
+ if (currentPath != null)
+ {
+ var pattern = null;
+
+ if (currentState.dashed)
+ {
+ var f = (vml) ? minScale : Number(currentPath.getAttribute('stroke-width'));
+ var pat = [];
+
+ for (var i = 0; i < currentState.dashpattern.length; i++)
+ {
+ pat.push(Math.max(1, Math.round(Number(currentState.dashpattern[i]) * f)));
+ }
+
+ pattern = pat.join(' ');
+ }
+
+ if (strokeOp || fillStrokeOp)
+ {
+ if (vml)
+ {
+ var stroke = document.createElement('v:stroke');
+ stroke.endcap = currentState.linecap || 'flat';
+ stroke.joinstyle = currentState.linejoin || 'miter';
+ stroke.miterlimit = currentState.miterlimit || '10';
+ currentPath.appendChild(stroke);
+
+ // TODO: Dashpattern support in VML is limited, we should
+ // map this to VML or allow for a separate VML dashstyle.
+ if (pattern != null)
+ {
+ stroke.dashstyle = pattern;
+ }
+ }
+ else
+ {
+ if (currentState.linejoin != null)
+ {
+ currentPath.setAttribute('stroke-linejoin', currentState.linejoin);
+ }
+
+ if (currentState.linecap != null)
+ {
+ // flat is called butt in SVG
+ var value = currentState.linecap;
+
+ if (value == 'flat')
+ {
+ value = 'butt';
+ }
+
+ currentPath.setAttribute('stroke-linecap', value);
+ }
+
+ if (currentState.miterlimit != null)
+ {
+ currentPath.setAttribute('stroke-miterlimit', currentState.miterlimit);
+ }
+
+ // Handles dash pattern
+ if (pattern != null)
+ {
+ currentPath.setAttribute('stroke-dasharray', pattern);
+ }
+ }
+ }
+
+ // Adds the shadow
+ if (background && shape.isShadow)
+ {
+ var dx = mxConstants.SHADOW_OFFSET_X * shape.scale;
+ var dy = mxConstants.SHADOW_OFFSET_Y * shape.scale;
+
+ // Adds the shadow
+ if (vml)
+ {
+ var shadow = document.createElement('v:shadow');
+ shadow.setAttribute('on', 'true');
+ shadow.setAttribute('color', mxConstants.SHADOWCOLOR);
+ shadow.setAttribute('offset', Math.round(dx) + 'px,' + Math.round(dy) + 'px');
+ shadow.setAttribute('opacity', (mxConstants.SHADOW_OPACITY * 100) + '%');
+
+ var stroke = document.createElement('v:stroke');
+ stroke.endcap = currentState.linecap || 'flat';
+ stroke.joinstyle = currentState.linejoin || 'miter';
+ stroke.miterlimit = currentState.miterlimit || '10';
+
+ if (pattern != null)
+ {
+ stroke.dashstyle = pattern;
+ }
+
+ shadow.appendChild(stroke);
+ currentPath.appendChild(shadow);
+ }
+ else
+ {
+ var shadow = currentPath.cloneNode(true);
+ shadow.setAttribute('stroke', mxConstants.SHADOWCOLOR);
+
+ if (currentState.fill != null && (fillOp || fillStrokeOp))
+ {
+ shadow.setAttribute('fill', mxConstants.SHADOWCOLOR);
+ }
+ else
+ {
+ shadow.setAttribute('fill', 'none');
+ }
+
+ shadow.setAttribute('transform', 'translate(' + dx + ' ' + dy + ') ' +
+ (shadow.getAttribute('transform') || ''));
+ shadow.setAttribute('opacity', mxConstants.SHADOW_OPACITY);
+ parentNode.appendChild(shadow);
+ }
+ }
+
+ if (fillOp)
+ {
+ if (vml)
+ {
+ currentPath.stroked = 'false';
+ }
+ else
+ {
+ currentPath.setAttribute('stroke', 'none');
+ }
+ }
+ else if (strokeOp)
+ {
+ if (vml)
+ {
+ currentPath.filled = 'false';
+ }
+ else
+ {
+ currentPath.setAttribute('fill', 'none');
+ }
+ }
+
+ parentNode.appendChild(currentPath);
+ }
+
+ // Background was painted
+ if (background)
+ {
+ background = false;
+ }
+ }
+ else if (name == 'linecap')
+ {
+ currentState.linecap = node.getAttribute('cap');
+ }
+ else if (name == 'linejoin')
+ {
+ currentState.linejoin = node.getAttribute('join');
+ }
+ else if (name == 'miterlimit')
+ {
+ currentState.miterlimit = node.getAttribute('limit');
+ }
+ else if (name == 'dashed')
+ {
+ currentState.dashed = node.getAttribute('dashed') == '1';
+ }
+ else if (name == 'dashpattern')
+ {
+ var value = node.getAttribute('pattern');
+
+ if (value != null && value.length > 0)
+ {
+ currentState.dashpattern = value.split(' ');
+ }
+ }
+ else if (name == 'strokewidth')
+ {
+ currentState.strokeWidth = node.getAttribute('width') * minScale;
+
+ if (vml)
+ {
+ currentState.strokeWidth /= vmlScale;
+ }
+ }
+ else if (name == 'strokecolor')
+ {
+ currentState.stroke = node.getAttribute('color');
+ }
+ else if (name == 'fillcolor')
+ {
+ currentState.fill = node.getAttribute('color');
+ currentState.fillColorAssigned = true;
+ }
+ else if (name == 'alpha')
+ {
+ currentState.alpha = Number(node.getAttribute('alpha'));
+ }
+ else if (name == 'fontcolor')
+ {
+ currentState.fontColor = node.getAttribute('color');
+ }
+ else if (name == 'fontsize')
+ {
+ currentState.fontSize = Number(node.getAttribute('size')) * minScale;
+ }
+ else if (name == 'fontfamily')
+ {
+ currentState.fontFamily = node.getAttribute('family');
+ }
+ else if (name == 'fontstyle')
+ {
+ currentState.fontStyle = Number(node.getAttribute('style'));
+ }
+ };
+
+ // Adds a transparent rectangle in the background for hit-detection in SVG
+ if (!vml)
+ {
+ var rect = document.createElementNS(mxConstants.NS_SVG, 'rect');
+ rect.setAttribute('x', bounds.x);
+ rect.setAttribute('y', bounds.y);
+ rect.setAttribute('width', bounds.width);
+ rect.setAttribute('height', bounds.height);
+ rect.setAttribute('fill', 'none');
+ rect.setAttribute('stroke', 'none');
+ parentNode.appendChild(rect);
+ }
+
+ // Background switches to false after fill/stroke of the background
+ if (this.bgNode != null)
+ {
+ var tmp = this.bgNode.firstChild;
+
+ while (tmp != null)
+ {
+ if (tmp.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ renderNode.call(this, tmp);
+ }
+
+ tmp = tmp.nextSibling;
+ }
+ }
+ else
+ {
+ background = false;
+ }
+
+ if (this.fgNode != null)
+ {
+ var tmp = this.fgNode.firstChild;
+
+ while (tmp != null)
+ {
+ if (tmp.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ renderNode.call(this, tmp);
+ }
+
+ tmp = tmp.nextSibling;
+ }
+ }
+ }
+};
+
+/**
+ * Function: drawShape
+ *
+ * Draws this stencil inside the given bounds.
+ */
+mxStencil.prototype.drawShape = function(canvas, state, bounds, background)
+{
+ // TODO: Unify with renderDom, check performance of pluggable shape,
+ // internal structure (array of special structs?), relative and absolute
+ // coordinates (eg. note shape, process vs star, actor etc.), text rendering
+ // and non-proportional scaling, how to implement pluggable edge shapes
+ // (start, segment, end blocks), pluggable markers, how to implement
+ // swimlanes (title area) with this API, add icon, horizontal/vertical
+ // label, indicator for all shapes, rotation
+ var node = (background) ? this.bgNode : this.fgNode;
+
+ if (node != null)
+ {
+ var direction = mxUtils.getValue(state.style, mxConstants.STYLE_DIRECTION, null);
+ var aspect = this.computeAspect(state, bounds, direction);
+ var minScale = Math.min(aspect.width, aspect.height);
+ var sw = (this.strokewidth == 'inherit') ?
+ Number(mxUtils.getNumber(state.style, mxConstants.STYLE_STROKEWIDTH, 1)) * state.view.scale :
+ Number(this.strokewidth) * minScale;
+ this.lastMoveX = 0;
+ this.lastMoveY = 0;
+ canvas.setStrokeWidth(sw);
+
+ var tmp = node.firstChild;
+
+ while (tmp != null)
+ {
+ if (tmp.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ this.drawNode(canvas, state, tmp, aspect);
+ }
+
+ tmp = tmp.nextSibling;
+ }
+
+ return true;
+ }
+
+ return false;
+};
+
+/**
+ * Function: computeAspect
+ *
+ * Returns a rectangle that contains the offset in x and y and the horizontal
+ * and vertical scale in width and height used to draw this shape inside the
+ * given <mxRectangle>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shape should be drawn.
+ * bounds - <mxRectangle> that should contain the stencil.
+ * direction - Optional direction of the shape to be darwn.
+ */
+mxStencil.prototype.computeAspect = function(state, bounds, direction)
+{
+ var x0 = bounds.x;
+ var y0 = bounds.y;
+ var sx = bounds.width / this.w0;
+ var sy = bounds.height / this.h0;
+
+ var inverse = (direction == 'north' || direction == 'south');
+
+ if (inverse)
+ {
+ sy = bounds.width / this.h0;
+ sx = bounds.height / this.w0;
+
+ var delta = (bounds.width - bounds.height) / 2;
+
+ x0 += delta;
+ y0 -= delta;
+ }
+
+ if (this.aspect == 'fixed')
+ {
+ sy = Math.min(sx, sy);
+ sx = sy;
+
+ // Centers the shape inside the available space
+ if (inverse)
+ {
+ x0 += (bounds.height - this.w0 * sx) / 2;
+ y0 += (bounds.width - this.h0 * sy) / 2;
+ }
+ else
+ {
+ x0 += (bounds.width - this.w0 * sx) / 2;
+ y0 += (bounds.height - this.h0 * sy) / 2;
+ }
+ }
+
+ return new mxRectangle(x0, y0, sx, sy);
+};
+
+/**
+ * Function: drawNode
+ *
+ * Draws this stencil inside the given bounds.
+ */
+mxStencil.prototype.drawNode = function(canvas, state, node, aspect)
+{
+ var name = node.nodeName;
+ var x0 = aspect.x;
+ var y0 = aspect.y;
+ var sx = aspect.width;
+ var sy = aspect.height;
+ var minScale = Math.min(sx, sy);
+
+ // LATER: Move to lookup table
+ if (name == 'save')
+ {
+ canvas.save();
+ }
+ else if (name == 'restore')
+ {
+ canvas.restore();
+ }
+ else if (name == 'path')
+ {
+ canvas.begin();
+
+ // Renders the elements inside the given path
+ var childNode = node.firstChild;
+
+ while (childNode != null)
+ {
+ if (childNode.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ this.drawNode(canvas, state, childNode, aspect);
+ }
+
+ childNode = childNode.nextSibling;
+ }
+ }
+ else if (name == 'close')
+ {
+ canvas.close();
+ }
+ else if (name == 'move')
+ {
+ this.lastMoveX = x0 + Number(node.getAttribute('x')) * sx;
+ this.lastMoveY = y0 + Number(node.getAttribute('y')) * sy;
+ canvas.moveTo(this.lastMoveX, this.lastMoveY);
+ }
+ else if (name == 'line')
+ {
+ this.lastMoveX = x0 + Number(node.getAttribute('x')) * sx;
+ this.lastMoveY = y0 + Number(node.getAttribute('y')) * sy;
+ canvas.lineTo(this.lastMoveX, this.lastMoveY);
+ }
+ else if (name == 'quad')
+ {
+ this.lastMoveX = x0 + Number(node.getAttribute('x2')) * sx;
+ this.lastMoveY = y0 + Number(node.getAttribute('y2')) * sy;
+ canvas.quadTo(x0 + Number(node.getAttribute('x1')) * sx,
+ y0 + Number(node.getAttribute('y1')) * sy,
+ this.lastMoveX, this.lastMoveY);
+ }
+ else if (name == 'curve')
+ {
+ this.lastMoveX = x0 + Number(node.getAttribute('x3')) * sx;
+ this.lastMoveY = y0 + Number(node.getAttribute('y3')) * sy;
+ canvas.curveTo(x0 + Number(node.getAttribute('x1')) * sx,
+ y0 + Number(node.getAttribute('y1')) * sy,
+ x0 + Number(node.getAttribute('x2')) * sx,
+ y0 + Number(node.getAttribute('y2')) * sy,
+ this.lastMoveX, this.lastMoveY);
+ }
+ else if (name == 'arc')
+ {
+ // Arc from stencil is turned into curves in image output
+ var r1 = Number(node.getAttribute('rx')) * sx;
+ var r2 = Number(node.getAttribute('ry')) * sy;
+ var angle = Number(node.getAttribute('x-axis-rotation'));
+ var largeArcFlag = Number(node.getAttribute('large-arc-flag'));
+ var sweepFlag = Number(node.getAttribute('sweep-flag'));
+ var x = x0 + Number(node.getAttribute('x')) * sx;
+ var y = y0 + Number(node.getAttribute('y')) * sy;
+
+ var curves = mxUtils.arcToCurves(this.lastMoveX, this.lastMoveY, r1, r2, angle, largeArcFlag, sweepFlag, x, y);
+
+ for (var i = 0; i < curves.length; i += 6)
+ {
+ canvas.curveTo(curves[i], curves[i + 1], curves[i + 2],
+ curves[i + 3], curves[i + 4], curves[i + 5]);
+
+ this.lastMoveX = curves[i + 4];
+ this.lastMoveY = curves[i + 5];
+ }
+ }
+ else if (name == 'rect')
+ {
+ canvas.rect(x0 + Number(node.getAttribute('x')) * sx,
+ y0 + Number(node.getAttribute('y')) * sy,
+ Number(node.getAttribute('w')) * sx,
+ Number(node.getAttribute('h')) * sy);
+ }
+ else if (name == 'roundrect')
+ {
+ var arcsize = node.getAttribute('arcsize');
+
+ if (arcsize == 0)
+ {
+ arcsize = mxConstants.RECTANGLE_ROUNDING_FACTOR * 100;
+ }
+
+ var w = Number(node.getAttribute('w')) * sx;
+ var h = Number(node.getAttribute('h')) * sy;
+ var factor = Number(arcsize) / 100;
+ var r = Math.min(w * factor, h * factor);
+
+ canvas.roundrect(x0 + Number(node.getAttribute('x')) * sx,
+ y0 + Number(node.getAttribute('y')) * sy,
+ w, h, r, r);
+ }
+ else if (name == 'ellipse')
+ {
+ canvas.ellipse(x0 + Number(node.getAttribute('x')) * sx,
+ y0 + Number(node.getAttribute('y')) * sy,
+ Number(node.getAttribute('w')) * sx,
+ Number(node.getAttribute('h')) * sy);
+ }
+ else if (name == 'image')
+ {
+ var src = this.evaluateAttribute(node, 'src', state);
+
+ canvas.image(x0 + Number(node.getAttribute('x')) * sx,
+ y0 + Number(node.getAttribute('y')) * sy,
+ Number(node.getAttribute('w')) * sx,
+ Number(node.getAttribute('h')) * sy,
+ src, false, node.getAttribute('flipH') == '1',
+ node.getAttribute('flipV') == '1');
+ }
+ else if (name == 'text')
+ {
+ var str = this.evaluateAttribute(node, 'str', state);
+
+ canvas.text(x0 + Number(node.getAttribute('x')) * sx,
+ y0 + Number(node.getAttribute('y')) * sy,
+ 0, 0, str, node.getAttribute('align'),
+ node.getAttribute('valign'),
+ node.getAttribute('vertical'));
+ }
+ else if (name == 'include-shape')
+ {
+ var stencil = mxStencilRegistry.getStencil(node.getAttribute('name'));
+
+ if (stencil != null)
+ {
+ var x = x0 + Number(node.getAttribute('x')) * sx;
+ var y = y0 + Number(node.getAttribute('y')) * sy;
+ var w = Number(node.getAttribute('w')) * sx;
+ var h = Number(node.getAttribute('h')) * sy;
+
+ var tmp = new mxRectangle(x, y, w, h);
+ stencil.drawShape(canvas, state, tmp, true);
+ stencil.drawShape(canvas, state, tmp, false);
+ }
+ }
+ else if (name == 'fillstroke')
+ {
+ canvas.fillAndStroke();
+ }
+ else if (name == 'fill')
+ {
+ canvas.fill();
+ }
+ else if (name == 'stroke')
+ {
+ canvas.stroke();
+ }
+ else if (name == 'strokewidth')
+ {
+ canvas.setStrokeWidth(Number(node.getAttribute('width')) * minScale);
+ }
+ else if (name == 'dashed')
+ {
+ canvas.setDashed(node.getAttribute('dashed') == '1');
+ }
+ else if (name == 'dashpattern')
+ {
+ var value = node.getAttribute('pattern');
+
+ if (value != null)
+ {
+ var tmp = value.split(' ');
+ var pat = [];
+
+ for (var i = 0; i < tmp.length; i++)
+ {
+ if (tmp[i].length > 0)
+ {
+ pat.push(Number(tmp[i]) * minScale);
+ }
+ }
+
+ value = pat.join(' ');
+ canvas.setDashPattern(value);
+ }
+ }
+ else if (name == 'strokecolor')
+ {
+ canvas.setStrokeColor(node.getAttribute('color'));
+ }
+ else if (name == 'linecap')
+ {
+ canvas.setLineCap(node.getAttribute('cap'));
+ }
+ else if (name == 'linejoin')
+ {
+ canvas.setLineJoin(node.getAttribute('join'));
+ }
+ else if (name == 'miterlimit')
+ {
+ canvas.setMiterLimit(Number(node.getAttribute('limit')));
+ }
+ else if (name == 'fillcolor')
+ {
+ canvas.setFillColor(node.getAttribute('color'));
+ }
+ else if (name == 'fontcolor')
+ {
+ canvas.setFontColor(node.getAttribute('color'));
+ }
+ else if (name == 'fontstyle')
+ {
+ canvas.setFontStyle(node.getAttribute('style'));
+ }
+ else if (name == 'fontfamily')
+ {
+ canvas.setFontFamily(node.getAttribute('family'));
+ }
+ else if (name == 'fontsize')
+ {
+ canvas.setFontSize(Number(node.getAttribute('size')) * minScale);
+ }
+};