868 lines
25 KiB
JavaScript
868 lines
25 KiB
JavaScript
/**
|
|
* Copyright (c) 2006-2015, JGraph Ltd
|
|
* Copyright (c) 2006-2015, Gaudenz Alder
|
|
*/
|
|
/**
|
|
* Class: mxStencil
|
|
*
|
|
* Implements a generic shape which is based on a XML node as a description.
|
|
*
|
|
* shape:
|
|
*
|
|
* The outer element is *shape*, that has attributes:
|
|
*
|
|
* - "name", string, required. The stencil name that uniquely identifies the shape.
|
|
* - "w" and "h" are optional decimal view bounds. This defines your co-ordinate
|
|
* system for the graphics operations in the shape. The default is 100,100.
|
|
* - "aspect", optional string. Either "variable", the default, or "fixed". Fixed
|
|
* means always render the shape with the aspect ratio defined by the ratio w/h.
|
|
* Variable causes the ratio to match that of the geometry of the current vertex.
|
|
* - "strokewidth", optional string. Either an integer or the string "inherit".
|
|
* "inherit" indicates that the strokeWidth of the cell is only changed on scaling,
|
|
* not on resizing. Default is "1".
|
|
* If numeric values are used, the strokeWidth of the cell is changed on both
|
|
* scaling and resizing and the value defines the multiple that is applied to
|
|
* the width.
|
|
*
|
|
* connections:
|
|
*
|
|
* If you want to define specific fixed connection points on the shape use the
|
|
* *connections* element. Each *constraint* element within connections defines
|
|
* a fixed connection point on the shape. Constraints have attributes:
|
|
*
|
|
* - "perimeter", required. 1 or 0. 0 sets the connection point where specified
|
|
* by x,y. 1 Causes the position of the connection point to be extrapolated from
|
|
* the center of the shape, through x,y to the point of intersection with the
|
|
* perimeter of the shape.
|
|
* - "x" and "y" are the position of the fixed point relative to the bounds of
|
|
* the shape. They can be automatically adjusted if perimeter=1. So, (0,0) is top
|
|
* left, (0.5,0.5) the center, (1,0.5) the center of the right hand edge of the
|
|
* bounds, etc. Values may be less than 0 or greater than 1 to be positioned
|
|
* outside of the shape.
|
|
* - "name", optional string. A unique identifier for the port on the shape.
|
|
*
|
|
* background and foreground:
|
|
*
|
|
* The path of the graphics drawing is split into two elements, *foreground* and
|
|
* *background*. The split is to define which part any shadow applied to the shape
|
|
* is derived from (the background). This, generally, means the background is the
|
|
* line tracing of the outside of the shape, but not always.
|
|
*
|
|
* Any stroke, fill or fillstroke of a background must be the first element of the
|
|
* foreground element, they must not be used within *background*. If the background
|
|
* is empty, this is not required.
|
|
*
|
|
* Because the background cannot have any fill or stroke, it can contain only one
|
|
* *path*, *rect*, *roundrect* or *ellipse* element (or none). It can also not
|
|
* include *image*, *text* or *include-shape*.
|
|
*
|
|
* Note that the state, styling and drawing in mxGraph stencils is very close in
|
|
* design to that of HTML 5 canvas. Tutorials on this subject, if you're not
|
|
* familiar with the topic, will give a good high-level introduction to the
|
|
* concepts used.
|
|
*
|
|
* State:
|
|
*
|
|
* Rendering within the foreground and background elements has the concept of
|
|
* state. There are two types of operations other than state save/load, styling
|
|
* and drawing. The styling operations change the current state, so you can save
|
|
* the current state with <save/> and pull the last saved state from the state
|
|
* stack using <restore/>.
|
|
*
|
|
* Styling:
|
|
*
|
|
* The elements that change colors within the current state all take a hash
|
|
* prefixed hex color code ("#FFEA80").
|
|
*
|
|
* - *strokecolor*, this sets the color that drawing paths will be rendered in
|
|
* when a stroke or fillstroke command is issued.
|
|
* - *fillcolor*, this sets the color that the inside of closed paths will be
|
|
* rendered in when a fill or fillstroke command is issued.
|
|
* - *fontcolor*, this sets the color that fonts are rendered in when text is drawn.
|
|
*
|
|
* *alpha* defines the degree of transparency used between 1.0 for fully opaque
|
|
* and 0.0 for fully transparent.
|
|
*
|
|
* *fillalpha* defines the degree of fill transparency used between 1.0 for fully
|
|
* opaque and 0.0 for fully transparent.
|
|
*
|
|
* *strokealpha* defines the degree of stroke transparency used between 1.0 for
|
|
* fully opaque and 0.0 for fully transparent.
|
|
*
|
|
* *strokewidth* defines the integer thickness of drawing elements rendered by
|
|
* stroking. Use fixed="1" to apply the value as-is, without scaling.
|
|
*
|
|
* *dashed* is "1" for dashing enabled and "0" for disabled.
|
|
*
|
|
* When *dashed* is enabled the current dash pattern, defined by *dashpattern*,
|
|
* is used on strokes. dashpattern is a sequence of space separated "on, off"
|
|
* lengths that define what distance to paint the stroke for, then what distance
|
|
* to paint nothing for, repeat... The default is "3 3". You could define a more
|
|
* complex pattern with "5 3 2 6", for example. Generally, it makes sense to have
|
|
* an even number of elements in the dashpattern, but that's not required.
|
|
*
|
|
* *linejoin*, *linecap* and *miterlimit* are best explained by the Mozilla page
|
|
* on Canvas styling (about halfway down). The values are all the same except we
|
|
* use "flat" for linecap, instead of Canvas' "butt".
|
|
*
|
|
* For font styling there are.
|
|
*
|
|
* - *fontsize*, an integer,
|
|
* - *fontstyle*, an ORed bit pattern of bold (1), italic (2) and underline (4),
|
|
* i.e bold underline is "5".
|
|
* - *fontfamily*, is a string defining the typeface to be used.
|
|
*
|
|
* Drawing:
|
|
*
|
|
* Most drawing is contained within a *path* element. Again, the graphic
|
|
* primitives are very similar to that of HTML 5 canvas.
|
|
*
|
|
* - *move* to attributes required decimals (x,y).
|
|
* - *line* to attributes required decimals (x,y).
|
|
* - *quad* to required decimals (x2,y2) via control point required decimals
|
|
* (x1,y1).
|
|
* - *curve* to required decimals (x3,y3), via control points required decimals
|
|
* (x1,y1) and (x2,y2).
|
|
* - *arc*, this doesn't follow the HTML Canvas signatures, instead it's a copy
|
|
* of the SVG arc command. The SVG specification documentation gives the best
|
|
* description of its behaviors. The attributes are named identically, they are
|
|
* decimals and all required.
|
|
* - *close* ends the current subpath and causes an automatic straight line to
|
|
* be drawn from the current point to the initial point of the current subpath.
|
|
*
|
|
* Complex drawing:
|
|
*
|
|
* In addition to the graphics primitive operations there are non-primitive
|
|
* operations. These provide an easy method to draw some basic shapes.
|
|
*
|
|
* - *rect*, attributes "x", "y", "w", "h", all required decimals
|
|
* - *roundrect*, attributes "x", "y", "w", "h", all required decimals. Also
|
|
* "arcsize" an optional decimal attribute defining how large, the corner curves
|
|
* are.
|
|
* - *ellipse*, attributes "x", "y", "w", "h", all required decimals.
|
|
*
|
|
* Note that these 3 shapes and all paths must be followed by either a fill,
|
|
* stroke, or fillstroke.
|
|
*
|
|
* Text:
|
|
*
|
|
* *text* elements have the following attributes.
|
|
*
|
|
* - "str", the text string to display, required.
|
|
* - "x" and "y", the decimal location (x,y) of the text element, required.
|
|
* - "align", the horizontal alignment of the text element, either "left",
|
|
* "center" or "right". Optional, default is "left".
|
|
* - "valign", the vertical alignment of the text element, either "top", "middle"
|
|
* or "bottom". Optional, default is "top".
|
|
* - "localized", 0 or 1, if 1 then the "str" actually contains a key to use to
|
|
* fetch the value out of mxResources. Optional, default is
|
|
* <mxStencil.defaultLocalized>.
|
|
* - "vertical", 0 or 1, if 1 the label is rendered vertically (rotated by 90
|
|
* degrees). Optional, default is 0.
|
|
* - "rotation", angle in degrees (0 to 360). The angle to rotate the text by.
|
|
* Optional, default is 0.
|
|
* - "align-shape", 0 or 1, if 0 ignore the rotation of the shape when setting
|
|
* the text rotation. Optional, default is 1.
|
|
*
|
|
* If <allowEval> is true, then the text content of the this element can define
|
|
* a function which is invoked with the shape as the only argument and returns
|
|
* the value for the text element (ignored if the str attribute is not null).
|
|
*
|
|
* Images:
|
|
*
|
|
* *image* elements can either be external URLs, or data URIs, where supported
|
|
* (not in IE 7-). Attributes are:
|
|
*
|
|
* - "src", required string. Either a data URI or URL.
|
|
* - "x", "y", required decimals. The (x,y) position of the image.
|
|
* - "w", "h", required decimals. The width and height of the image.
|
|
* - "flipH" and "flipV", optional 0 or 1. Whether to flip the image along the
|
|
* horizontal/vertical axis. Default is 0 for both.
|
|
*
|
|
* If <allowEval> is true, then the text content of the this element can define
|
|
* a function which is invoked with the shape as the only argument and returns
|
|
* the value for the image source (ignored if the src attribute is not null).
|
|
*
|
|
* Sub-shapes:
|
|
*
|
|
* *include-shape* allow stencils to be rendered within the current stencil by
|
|
* referencing the sub-stencil by name. Attributes are:
|
|
*
|
|
* - "name", required string. The unique shape name of the stencil.
|
|
* - "x", "y", "w", "h", required decimals. The (x,y) position of the sub-shape
|
|
* and its width and height.
|
|
*
|
|
* Constructor: mxStencil
|
|
*
|
|
* 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();
|
|
};
|
|
|
|
/**
|
|
* Extends mxShape.
|
|
*/
|
|
mxUtils.extend(mxStencil, mxShape);
|
|
|
|
/**
|
|
* Variable: defaultLocalized
|
|
*
|
|
* Static global variable that specifies the default value for the localized
|
|
* attribute of the text element. Default is false.
|
|
*/
|
|
mxStencil.defaultLocalized = false;
|
|
|
|
/**
|
|
* Function: allowEval
|
|
*
|
|
* Static global switch that specifies if the use of eval is allowed for
|
|
* evaluating text content and images. Default is false. Set this to true
|
|
* if stencils can not contain user input.
|
|
*/
|
|
mxStencil.allowEval = false;
|
|
|
|
/**
|
|
* 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';
|
|
var name = node.getAttribute('name');
|
|
|
|
return new mxConnectionConstraint(new mxPoint(x, y), perimeter, name);
|
|
};
|
|
|
|
/**
|
|
* Function: evaluateTextAttribute
|
|
*
|
|
* Gets the given attribute as a text. The return value from <evaluateAttribute>
|
|
* is used as a key to <mxResources.get> if the localized attribute in the text
|
|
* node is 1 or if <defaultLocalized> is true.
|
|
*/
|
|
mxStencil.prototype.evaluateTextAttribute = function(node, attribute, shape)
|
|
{
|
|
var result = this.evaluateAttribute(node, attribute, shape);
|
|
var loc = node.getAttribute('localized');
|
|
|
|
if ((mxStencil.defaultLocalized && loc == null) || loc == '1')
|
|
{
|
|
result = mxResources.get(result);
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* 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 <shape> as the only argument and the return
|
|
* value is used as the attribute value to be returned.
|
|
*/
|
|
mxStencil.prototype.evaluateAttribute = function(node, attribute, shape)
|
|
{
|
|
var result = node.getAttribute(attribute);
|
|
|
|
if (result == null)
|
|
{
|
|
var text = mxUtils.getTextContent(node);
|
|
|
|
if (text != null && mxStencil.allowEval)
|
|
{
|
|
var funct = mxUtils.eval(text);
|
|
|
|
if (typeof(funct) == 'function')
|
|
{
|
|
result = funct(shape);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* Function: drawShape
|
|
*
|
|
* Draws this stencil inside the given bounds.
|
|
*/
|
|
mxStencil.prototype.drawShape = function(canvas, shape, x, y, w, h)
|
|
{
|
|
var stack = canvas.states.slice();
|
|
|
|
// TODO: 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 direction = mxUtils.getValue(shape.style, mxConstants.STYLE_DIRECTION, null);
|
|
var aspect = this.computeAspect(shape.style, x, y, w, h, direction);
|
|
var minScale = Math.min(aspect.width, aspect.height);
|
|
var sw = (this.strokewidth == 'inherit') ?
|
|
Number(mxUtils.getNumber(shape.style, mxConstants.STYLE_STROKEWIDTH, 1)) :
|
|
Number(this.strokewidth) * minScale;
|
|
canvas.setStrokeWidth(sw);
|
|
|
|
// Draws a transparent rectangle for catching events
|
|
if (shape.style != null && mxUtils.getValue(shape.style, mxConstants.STYLE_POINTER_EVENTS, '0') == '1')
|
|
{
|
|
canvas.setStrokeColor(mxConstants.NONE);
|
|
canvas.rect(x, y, w, h);
|
|
canvas.stroke();
|
|
canvas.setStrokeColor(shape.stroke);
|
|
}
|
|
|
|
this.drawChildren(canvas, shape, x, y, w, h, this.bgNode, aspect, false, true);
|
|
this.drawChildren(canvas, shape, x, y, w, h, this.fgNode, aspect, true,
|
|
!shape.outline || shape.style == null || mxUtils.getValue(
|
|
shape.style, mxConstants.STYLE_BACKGROUND_OUTLINE, 0) == 0);
|
|
|
|
// Restores stack for unequal count of save/restore calls
|
|
if (canvas.states.length != stack.length)
|
|
{
|
|
canvas.states = stack;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Function: drawChildren
|
|
*
|
|
* Draws this stencil inside the given bounds.
|
|
*/
|
|
mxStencil.prototype.drawChildren = function(canvas, shape, x, y, w, h, node, aspect, disableShadow, paint)
|
|
{
|
|
if (node != null && w > 0 && h > 0)
|
|
{
|
|
var tmp = node.firstChild;
|
|
|
|
while (tmp != null)
|
|
{
|
|
if (tmp.nodeType == mxConstants.NODETYPE_ELEMENT)
|
|
{
|
|
this.drawNode(canvas, shape, tmp, aspect, disableShadow, paint);
|
|
}
|
|
|
|
tmp = tmp.nextSibling;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 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:
|
|
*
|
|
* shape - <mxShape> to be drawn.
|
|
* bounds - <mxRectangle> that should contain the stencil.
|
|
* direction - Optional direction of the shape to be darwn.
|
|
*/
|
|
mxStencil.prototype.computeAspect = function(shape, x, y, w, h, direction)
|
|
{
|
|
var x0 = x;
|
|
var y0 = y;
|
|
var sx = w / this.w0;
|
|
var sy = h / this.h0;
|
|
|
|
var inverse = (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH);
|
|
|
|
if (inverse)
|
|
{
|
|
sy = w / this.h0;
|
|
sx = h / this.w0;
|
|
|
|
var delta = (w - h) / 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 += (h - this.w0 * sx) / 2;
|
|
y0 += (w - this.h0 * sy) / 2;
|
|
}
|
|
else
|
|
{
|
|
x0 += (w - this.w0 * sx) / 2;
|
|
y0 += (h - 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, shape, node, aspect, disableShadow, paint)
|
|
{
|
|
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);
|
|
|
|
if (name == 'save')
|
|
{
|
|
canvas.save();
|
|
}
|
|
else if (name == 'restore')
|
|
{
|
|
canvas.restore();
|
|
}
|
|
else if (paint)
|
|
{
|
|
if (name == 'path')
|
|
{
|
|
canvas.begin();
|
|
|
|
var parseRegularly = true;
|
|
|
|
if (node.getAttribute('rounded') == '1')
|
|
{
|
|
parseRegularly = false;
|
|
|
|
var arcSize = Number(node.getAttribute('arcSize'));
|
|
var pointCount = 0;
|
|
var segs = [];
|
|
|
|
// Renders the elements inside the given path
|
|
var childNode = node.firstChild;
|
|
|
|
while (childNode != null)
|
|
{
|
|
if (childNode.nodeType == mxConstants.NODETYPE_ELEMENT)
|
|
{
|
|
var childName = childNode.nodeName;
|
|
|
|
if (childName == 'move' || childName == 'line')
|
|
{
|
|
if (childName == 'move' || segs.length == 0)
|
|
{
|
|
segs.push([]);
|
|
}
|
|
|
|
segs[segs.length - 1].push(new mxPoint(x0 + Number(childNode.getAttribute('x')) * sx,
|
|
y0 + Number(childNode.getAttribute('y')) * sy));
|
|
pointCount++;
|
|
}
|
|
else
|
|
{
|
|
//We only support move and line for rounded corners
|
|
parseRegularly = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
childNode = childNode.nextSibling;
|
|
}
|
|
|
|
if (!parseRegularly && pointCount > 0)
|
|
{
|
|
for (var i = 0; i < segs.length; i++)
|
|
{
|
|
var close = false, ps = segs[i][0], pe = segs[i][segs[i].length - 1];
|
|
|
|
if (ps.x == pe.x && ps.y == pe.y)
|
|
{
|
|
segs[i].pop();
|
|
close = true;
|
|
}
|
|
|
|
this.addPoints(canvas, segs[i], true, arcSize, close);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
parseRegularly = true;
|
|
}
|
|
}
|
|
|
|
if (parseRegularly)
|
|
{
|
|
// Renders the elements inside the given path
|
|
var childNode = node.firstChild;
|
|
|
|
while (childNode != null)
|
|
{
|
|
if (childNode.nodeType == mxConstants.NODETYPE_ELEMENT)
|
|
{
|
|
this.drawNode(canvas, shape, childNode, aspect, disableShadow, paint);
|
|
}
|
|
|
|
childNode = childNode.nextSibling;
|
|
}
|
|
}
|
|
}
|
|
else if (name == 'close')
|
|
{
|
|
canvas.close();
|
|
}
|
|
else if (name == 'move')
|
|
{
|
|
canvas.moveTo(x0 + Number(node.getAttribute('x')) * sx, y0 + Number(node.getAttribute('y')) * sy);
|
|
}
|
|
else if (name == 'line')
|
|
{
|
|
canvas.lineTo(x0 + Number(node.getAttribute('x')) * sx, y0 + Number(node.getAttribute('y')) * sy);
|
|
}
|
|
else if (name == 'quad')
|
|
{
|
|
canvas.quadTo(x0 + Number(node.getAttribute('x1')) * sx,
|
|
y0 + Number(node.getAttribute('y1')) * sy,
|
|
x0 + Number(node.getAttribute('x2')) * sx,
|
|
y0 + Number(node.getAttribute('y2')) * sy);
|
|
}
|
|
else if (name == 'curve')
|
|
{
|
|
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,
|
|
x0 + Number(node.getAttribute('x3')) * sx,
|
|
y0 + Number(node.getAttribute('y3')) * sy);
|
|
}
|
|
else if (name == 'arc')
|
|
{
|
|
canvas.arcTo(Number(node.getAttribute('rx')) * sx,
|
|
Number(node.getAttribute('ry')) * sy,
|
|
Number(node.getAttribute('x-axis-rotation')),
|
|
Number(node.getAttribute('large-arc-flag')),
|
|
Number(node.getAttribute('sweep-flag')),
|
|
x0 + Number(node.getAttribute('x')) * sx,
|
|
y0 + Number(node.getAttribute('y')) * sy);
|
|
}
|
|
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 = Number(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')
|
|
{
|
|
if (!shape.outline)
|
|
{
|
|
var src = this.evaluateAttribute(node, 'src', shape);
|
|
|
|
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')
|
|
{
|
|
if (!shape.outline)
|
|
{
|
|
var str = this.evaluateTextAttribute(node, 'str', shape);
|
|
var rotation = node.getAttribute('vertical') == '1' ? -90 : 0;
|
|
|
|
if (node.getAttribute('align-shape') == '0')
|
|
{
|
|
var dr = shape.rotation;
|
|
|
|
// Depends on flipping
|
|
var flipH = mxUtils.getValue(shape.style, mxConstants.STYLE_FLIPH, 0) == 1;
|
|
var flipV = mxUtils.getValue(shape.style, mxConstants.STYLE_FLIPV, 0) == 1;
|
|
|
|
if (flipH && flipV)
|
|
{
|
|
rotation -= dr;
|
|
}
|
|
else if (flipH || flipV)
|
|
{
|
|
rotation += dr;
|
|
}
|
|
else
|
|
{
|
|
rotation -= dr;
|
|
}
|
|
}
|
|
|
|
rotation -= node.getAttribute('rotation');
|
|
|
|
canvas.text(x0 + Number(node.getAttribute('x')) * sx,
|
|
y0 + Number(node.getAttribute('y')) * sy,
|
|
0, 0, str, node.getAttribute('align') || 'left',
|
|
node.getAttribute('valign') || 'top', false, '',
|
|
null, false, rotation);
|
|
}
|
|
}
|
|
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.drawShape(canvas, shape, x, y, w, h);
|
|
}
|
|
}
|
|
else if (name == 'fillstroke')
|
|
{
|
|
canvas.fillAndStroke();
|
|
}
|
|
else if (name == 'fill')
|
|
{
|
|
canvas.fill();
|
|
}
|
|
else if (name == 'stroke')
|
|
{
|
|
canvas.stroke();
|
|
}
|
|
else if (name == 'strokewidth')
|
|
{
|
|
var s = (node.getAttribute('fixed') == '1') ? 1 : minScale;
|
|
canvas.setStrokeWidth(Number(node.getAttribute('width')) * s);
|
|
}
|
|
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 == 'alpha')
|
|
{
|
|
canvas.setAlpha(node.getAttribute('alpha'));
|
|
}
|
|
else if (name == 'fillalpha')
|
|
{
|
|
canvas.setAlpha(node.getAttribute('alpha'));
|
|
}
|
|
else if (name == 'strokealpha')
|
|
{
|
|
canvas.setAlpha(node.getAttribute('alpha'));
|
|
}
|
|
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);
|
|
}
|
|
|
|
if (disableShadow && (name == 'fillstroke' || name == 'fill' || name == 'stroke'))
|
|
{
|
|
disableShadow = false;
|
|
canvas.setShadow(false);
|
|
}
|
|
}
|
|
};
|