1103 lines
26 KiB
JavaScript
1103 lines
26 KiB
JavaScript
/**
|
|
* Copyright (c) 2006-2015, JGraph Ltd
|
|
* Copyright (c) 2006-2015, Gaudenz Alder
|
|
*/
|
|
/**
|
|
*
|
|
* Class: mxVmlCanvas2D
|
|
*
|
|
* Implements a canvas to be used for rendering VML. Here is an example of implementing a
|
|
* fallback for SVG images which are not supported in VML-based browsers.
|
|
*
|
|
* (code)
|
|
* var mxVmlCanvas2DImage = mxVmlCanvas2D.prototype.image;
|
|
* mxVmlCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)
|
|
* {
|
|
* if (src.substring(src.length - 4, src.length) == '.svg')
|
|
* {
|
|
* src = 'http://www.jgraph.com/images/mxgraph.gif';
|
|
* }
|
|
*
|
|
* mxVmlCanvas2DImage.apply(this, arguments);
|
|
* };
|
|
* (end)
|
|
*
|
|
* To disable anti-aliasing in the output, use the following code.
|
|
*
|
|
* (code)
|
|
* document.createStyleSheet().cssText = mxClient.VML_PREFIX + '\\:*{antialias:false;)}';
|
|
* (end)
|
|
*
|
|
* A description of the public API is available in <mxXmlCanvas2D>. Note that
|
|
* there is a known issue in VML where gradients are painted using the outer
|
|
* bounding box of rotated shapes, not the actual bounds of the shape. See
|
|
* also <text> for plain text label restrictions in shapes for VML.
|
|
*/
|
|
var mxVmlCanvas2D = function(root)
|
|
{
|
|
mxAbstractCanvas2D.call(this);
|
|
|
|
/**
|
|
* Variable: root
|
|
*
|
|
* Reference to the container for the SVG content.
|
|
*/
|
|
this.root = root;
|
|
};
|
|
|
|
/**
|
|
* Extends mxAbstractCanvas2D
|
|
*/
|
|
mxUtils.extend(mxVmlCanvas2D, mxAbstractCanvas2D);
|
|
|
|
/**
|
|
* Variable: path
|
|
*
|
|
* Holds the current DOM node.
|
|
*/
|
|
mxVmlCanvas2D.prototype.node = null;
|
|
|
|
/**
|
|
* Variable: textEnabled
|
|
*
|
|
* Specifies if text output should be enabledetB. Default is true.
|
|
*/
|
|
mxVmlCanvas2D.prototype.textEnabled = true;
|
|
|
|
/**
|
|
* Variable: moveOp
|
|
*
|
|
* Contains the string used for moving in paths. Default is 'm'.
|
|
*/
|
|
mxVmlCanvas2D.prototype.moveOp = 'm';
|
|
|
|
/**
|
|
* Variable: lineOp
|
|
*
|
|
* Contains the string used for moving in paths. Default is 'l'.
|
|
*/
|
|
mxVmlCanvas2D.prototype.lineOp = 'l';
|
|
|
|
/**
|
|
* Variable: curveOp
|
|
*
|
|
* Contains the string used for bezier curves. Default is 'c'.
|
|
*/
|
|
mxVmlCanvas2D.prototype.curveOp = 'c';
|
|
|
|
/**
|
|
* Variable: closeOp
|
|
*
|
|
* Holds the operator for closing curves. Default is 'x e'.
|
|
*/
|
|
mxVmlCanvas2D.prototype.closeOp = 'x';
|
|
|
|
/**
|
|
* Variable: rotatedHtmlBackground
|
|
*
|
|
* Background color for rotated HTML. Default is ''. This can be set to eg.
|
|
* white to improve rendering of rotated text in VML for IE9.
|
|
*/
|
|
mxVmlCanvas2D.prototype.rotatedHtmlBackground = '';
|
|
|
|
/**
|
|
* Variable: vmlScale
|
|
*
|
|
* Specifies the scale used to draw VML shapes.
|
|
*/
|
|
mxVmlCanvas2D.prototype.vmlScale = 1;
|
|
|
|
/**
|
|
* Function: createElement
|
|
*
|
|
* Creates the given element using the document.
|
|
*/
|
|
mxVmlCanvas2D.prototype.createElement = function(name)
|
|
{
|
|
return document.createElement(name);
|
|
};
|
|
|
|
/**
|
|
* Function: createVmlElement
|
|
*
|
|
* Creates a new element using <createElement> and prefixes the given name with
|
|
* <mxClient.VML_PREFIX>.
|
|
*/
|
|
mxVmlCanvas2D.prototype.createVmlElement = function(name)
|
|
{
|
|
return this.createElement(mxClient.VML_PREFIX + ':' + name);
|
|
};
|
|
|
|
/**
|
|
* Function: addNode
|
|
*
|
|
* Adds the current node to the <root>.
|
|
*/
|
|
mxVmlCanvas2D.prototype.addNode = function(filled, stroked)
|
|
{
|
|
var node = this.node;
|
|
var s = this.state;
|
|
|
|
if (node != null)
|
|
{
|
|
if (node.nodeName == 'shape')
|
|
{
|
|
// Checks if the path is not empty
|
|
if (this.path != null && this.path.length > 0)
|
|
{
|
|
node.path = this.path.join(' ') + ' e';
|
|
node.style.width = this.root.style.width;
|
|
node.style.height = this.root.style.height;
|
|
node.coordsize = parseInt(node.style.width) + ' ' + parseInt(node.style.height);
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
node.strokeweight = this.format(Math.max(1, s.strokeWidth * s.scale / this.vmlScale)) + 'px';
|
|
|
|
if (s.shadow)
|
|
{
|
|
this.root.appendChild(this.createShadow(node,
|
|
filled && s.fillColor != null,
|
|
stroked && s.strokeColor != null));
|
|
}
|
|
|
|
if (stroked && s.strokeColor != null)
|
|
{
|
|
node.stroked = 'true';
|
|
node.strokecolor = s.strokeColor;
|
|
}
|
|
else
|
|
{
|
|
node.stroked = 'false';
|
|
}
|
|
|
|
node.appendChild(this.createStroke());
|
|
|
|
if (filled && s.fillColor != null)
|
|
{
|
|
node.appendChild(this.createFill());
|
|
}
|
|
else if (this.pointerEvents && (node.nodeName != 'shape' ||
|
|
this.path[this.path.length - 1] == this.closeOp))
|
|
{
|
|
node.appendChild(this.createTransparentFill());
|
|
}
|
|
else
|
|
{
|
|
node.filled = 'false';
|
|
}
|
|
|
|
// LATER: Update existing DOM for performance
|
|
this.root.appendChild(node);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Function: createTransparentFill
|
|
*
|
|
* Creates a transparent fill.
|
|
*/
|
|
mxVmlCanvas2D.prototype.createTransparentFill = function()
|
|
{
|
|
var fill = this.createVmlElement('fill');
|
|
fill.src = mxClient.imageBasePath + '/transparent.gif';
|
|
fill.type = 'tile';
|
|
|
|
return fill;
|
|
};
|
|
|
|
/**
|
|
* Function: createFill
|
|
*
|
|
* Creates a fill for the current state.
|
|
*/
|
|
mxVmlCanvas2D.prototype.createFill = function()
|
|
{
|
|
var s = this.state;
|
|
|
|
// Gradients in foregrounds not supported because special gradients
|
|
// with bounds must be created for each element in graphics-canvases
|
|
var fill = this.createVmlElement('fill');
|
|
fill.color = s.fillColor;
|
|
|
|
if (s.gradientColor != null)
|
|
{
|
|
fill.type = 'gradient';
|
|
fill.method = 'none';
|
|
fill.color2 = s.gradientColor;
|
|
var angle = 180 - s.rotation;
|
|
|
|
if (s.gradientDirection == mxConstants.DIRECTION_WEST)
|
|
{
|
|
angle -= 90 + ((this.root.style.flip == 'x') ? 180 : 0);
|
|
}
|
|
else if (s.gradientDirection == mxConstants.DIRECTION_EAST)
|
|
{
|
|
angle += 90 + ((this.root.style.flip == 'x') ? 180 : 0);
|
|
}
|
|
else if (s.gradientDirection == mxConstants.DIRECTION_NORTH)
|
|
{
|
|
angle -= 180 + ((this.root.style.flip == 'y') ? -180 : 0);
|
|
}
|
|
else
|
|
{
|
|
angle += ((this.root.style.flip == 'y') ? -180 : 0);
|
|
}
|
|
|
|
if (this.root.style.flip == 'x' || this.root.style.flip == 'y')
|
|
{
|
|
angle *= -1;
|
|
}
|
|
|
|
// LATER: Fix outer bounding box for rotated shapes used in VML.
|
|
fill.angle = mxUtils.mod(angle, 360);
|
|
fill.opacity = (s.alpha * s.gradientFillAlpha * 100) + '%';
|
|
fill.setAttribute(mxClient.OFFICE_PREFIX + ':opacity2', (s.alpha * s.gradientAlpha * 100) + '%');
|
|
}
|
|
else if (s.alpha < 1 || s.fillAlpha < 1)
|
|
{
|
|
fill.opacity = (s.alpha * s.fillAlpha * 100) + '%';
|
|
}
|
|
|
|
return fill;
|
|
};
|
|
/**
|
|
* Function: createStroke
|
|
*
|
|
* Creates a fill for the current state.
|
|
*/
|
|
mxVmlCanvas2D.prototype.createStroke = function()
|
|
{
|
|
var s = this.state;
|
|
var stroke = this.createVmlElement('stroke');
|
|
stroke.endcap = s.lineCap || 'flat';
|
|
stroke.joinstyle = s.lineJoin || 'miter';
|
|
stroke.miterlimit = s.miterLimit || '10';
|
|
|
|
if (s.alpha < 1 || s.strokeAlpha < 1)
|
|
{
|
|
stroke.opacity = (s.alpha * s.strokeAlpha * 100) + '%';
|
|
}
|
|
|
|
if (s.dashed)
|
|
{
|
|
stroke.dashstyle = this.getVmlDashStyle();
|
|
}
|
|
|
|
return stroke;
|
|
};
|
|
|
|
/**
|
|
* Function: getVmlDashPattern
|
|
*
|
|
* Returns a VML dash pattern for the current dashPattern.
|
|
* See http://msdn.microsoft.com/en-us/library/bb264085(v=vs.85).aspx
|
|
*/
|
|
mxVmlCanvas2D.prototype.getVmlDashStyle = function()
|
|
{
|
|
var result = 'dash';
|
|
|
|
if (typeof(this.state.dashPattern) === 'string')
|
|
{
|
|
var tok = this.state.dashPattern.split(' ');
|
|
|
|
if (tok.length > 0 && tok[0] == 1)
|
|
{
|
|
result = '0 2';
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* Function: createShadow
|
|
*
|
|
* Creates a shadow for the given node.
|
|
*/
|
|
mxVmlCanvas2D.prototype.createShadow = function(node, filled, stroked)
|
|
{
|
|
var s = this.state;
|
|
var rad = -s.rotation * (Math.PI / 180);
|
|
var cos = Math.cos(rad);
|
|
var sin = Math.sin(rad);
|
|
|
|
var dx = s.shadowDx * s.scale;
|
|
var dy = s.shadowDy * s.scale;
|
|
|
|
if (this.root.style.flip == 'x')
|
|
{
|
|
dx *= -1;
|
|
}
|
|
else if (this.root.style.flip == 'y')
|
|
{
|
|
dy *= -1;
|
|
}
|
|
|
|
var shadow = node.cloneNode(true);
|
|
shadow.style.marginLeft = Math.round(dx * cos - dy * sin) + 'px';
|
|
shadow.style.marginTop = Math.round(dx * sin + dy * cos) + 'px';
|
|
|
|
// Workaround for wrong cloning in IE8 standards mode
|
|
if (document.documentMode == 8)
|
|
{
|
|
shadow.strokeweight = node.strokeweight;
|
|
|
|
if (node.nodeName == 'shape')
|
|
{
|
|
shadow.path = this.path.join(' ') + ' e';
|
|
shadow.style.width = this.root.style.width;
|
|
shadow.style.height = this.root.style.height;
|
|
shadow.coordsize = parseInt(node.style.width) + ' ' + parseInt(node.style.height);
|
|
}
|
|
}
|
|
|
|
if (stroked)
|
|
{
|
|
shadow.strokecolor = s.shadowColor;
|
|
shadow.appendChild(this.createShadowStroke());
|
|
}
|
|
else
|
|
{
|
|
shadow.stroked = 'false';
|
|
}
|
|
|
|
if (filled)
|
|
{
|
|
shadow.appendChild(this.createShadowFill());
|
|
}
|
|
else
|
|
{
|
|
shadow.filled = 'false';
|
|
}
|
|
|
|
return shadow;
|
|
};
|
|
|
|
/**
|
|
* Function: createShadowFill
|
|
*
|
|
* Creates the fill for the shadow.
|
|
*/
|
|
mxVmlCanvas2D.prototype.createShadowFill = function()
|
|
{
|
|
var fill = this.createVmlElement('fill');
|
|
fill.color = this.state.shadowColor;
|
|
fill.opacity = (this.state.alpha * this.state.shadowAlpha * 100) + '%';
|
|
|
|
return fill;
|
|
};
|
|
|
|
/**
|
|
* Function: createShadowStroke
|
|
*
|
|
* Creates the stroke for the shadow.
|
|
*/
|
|
mxVmlCanvas2D.prototype.createShadowStroke = function()
|
|
{
|
|
var stroke = this.createStroke();
|
|
stroke.opacity = (this.state.alpha * this.state.shadowAlpha * 100) + '%';
|
|
|
|
return stroke;
|
|
};
|
|
|
|
/**
|
|
* Function: rotate
|
|
*
|
|
* Sets the rotation of the canvas. Note that rotation cannot be concatenated.
|
|
*/
|
|
mxVmlCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy)
|
|
{
|
|
if (flipH && flipV)
|
|
{
|
|
theta += 180;
|
|
}
|
|
else if (flipH)
|
|
{
|
|
this.root.style.flip = 'x';
|
|
}
|
|
else if (flipV)
|
|
{
|
|
this.root.style.flip = 'y';
|
|
}
|
|
|
|
if (flipH ? !flipV : flipV)
|
|
{
|
|
theta *= -1;
|
|
}
|
|
|
|
this.root.style.rotation = theta;
|
|
this.state.rotation = this.state.rotation + theta;
|
|
this.state.rotationCx = cx;
|
|
this.state.rotationCy = cy;
|
|
};
|
|
|
|
/**
|
|
* Function: begin
|
|
*
|
|
* Extends superclass to create path.
|
|
*/
|
|
mxVmlCanvas2D.prototype.begin = function()
|
|
{
|
|
mxAbstractCanvas2D.prototype.begin.apply(this, arguments);
|
|
this.node = this.createVmlElement('shape');
|
|
this.node.style.position = 'absolute';
|
|
};
|
|
|
|
/**
|
|
* Function: quadTo
|
|
*
|
|
* Replaces quadratic curve with bezier curve in VML.
|
|
*/
|
|
mxVmlCanvas2D.prototype.quadTo = function(x1, y1, x2, y2)
|
|
{
|
|
var s = this.state;
|
|
|
|
var cpx0 = (this.lastX + s.dx) * s.scale;
|
|
var cpy0 = (this.lastY + s.dy) * s.scale;
|
|
var qpx1 = (x1 + s.dx) * s.scale;
|
|
var qpy1 = (y1 + s.dy) * s.scale;
|
|
var cpx3 = (x2 + s.dx) * s.scale;
|
|
var cpy3 = (y2 + s.dy) * s.scale;
|
|
|
|
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);
|
|
|
|
this.path.push('c ' + this.format(cpx1) + ' ' + this.format(cpy1) +
|
|
' ' + this.format(cpx2) + ' ' + this.format(cpy2) +
|
|
' ' + this.format(cpx3) + ' ' + this.format(cpy3));
|
|
this.lastX = (cpx3 / s.scale) - s.dx;
|
|
this.lastY = (cpy3 / s.scale) - s.dy;
|
|
|
|
};
|
|
|
|
/**
|
|
* Function: createRect
|
|
*
|
|
* Sets the glass gradient.
|
|
*/
|
|
mxVmlCanvas2D.prototype.createRect = function(nodeName, x, y, w, h)
|
|
{
|
|
var s = this.state;
|
|
var n = this.createVmlElement(nodeName);
|
|
n.style.position = 'absolute';
|
|
n.style.left = this.format((x + s.dx) * s.scale) + 'px';
|
|
n.style.top = this.format((y + s.dy) * s.scale) + 'px';
|
|
n.style.width = this.format(w * s.scale) + 'px';
|
|
n.style.height = this.format(h * s.scale) + 'px';
|
|
|
|
return n;
|
|
};
|
|
|
|
/**
|
|
* Function: rect
|
|
*
|
|
* Sets the current path to a rectangle.
|
|
*/
|
|
mxVmlCanvas2D.prototype.rect = function(x, y, w, h)
|
|
{
|
|
this.node = this.createRect('rect', x, y, w, h);
|
|
};
|
|
|
|
/**
|
|
* Function: roundrect
|
|
*
|
|
* Sets the current path to a rounded rectangle.
|
|
*/
|
|
mxVmlCanvas2D.prototype.roundrect = function(x, y, w, h, dx, dy)
|
|
{
|
|
this.node = this.createRect('roundrect', x, y, w, h);
|
|
// SetAttribute needed here for IE8
|
|
this.node.setAttribute('arcsize', Math.max(dx * 100 / w, dy * 100 / h) + '%');
|
|
};
|
|
|
|
/**
|
|
* Function: ellipse
|
|
*
|
|
* Sets the current path to an ellipse.
|
|
*/
|
|
mxVmlCanvas2D.prototype.ellipse = function(x, y, w, h)
|
|
{
|
|
this.node = this.createRect('oval', x, y, w, h);
|
|
};
|
|
|
|
/**
|
|
* Function: image
|
|
*
|
|
* Paints an image.
|
|
*/
|
|
mxVmlCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)
|
|
{
|
|
var node = null;
|
|
|
|
if (!aspect)
|
|
{
|
|
node = this.createRect('image', x, y, w, h);
|
|
node.src = src;
|
|
}
|
|
else
|
|
{
|
|
// Uses fill with aspect to avoid asynchronous update of size
|
|
node = this.createRect('rect', x, y, w, h);
|
|
node.stroked = 'false';
|
|
|
|
// Handles image aspect via fill
|
|
var fill = this.createVmlElement('fill');
|
|
fill.aspect = (aspect) ? 'atmost' : 'ignore';
|
|
fill.rotate = 'true';
|
|
fill.type = 'frame';
|
|
fill.src = src;
|
|
|
|
node.appendChild(fill);
|
|
}
|
|
|
|
if (flipH && flipV)
|
|
{
|
|
node.style.rotation = '180';
|
|
}
|
|
else if (flipH)
|
|
{
|
|
node.style.flip = 'x';
|
|
}
|
|
else if (flipV)
|
|
{
|
|
node.style.flip = 'y';
|
|
}
|
|
|
|
if (this.state.alpha < 1 || this.state.fillAlpha < 1)
|
|
{
|
|
// KNOWN: Borders around transparent images in IE<9. Using fill.opacity
|
|
// fixes this problem by adding a white background in all IE versions.
|
|
node.style.filter += 'alpha(opacity=' + (this.state.alpha * this.state.fillAlpha * 100) + ')';
|
|
}
|
|
|
|
this.root.appendChild(node);
|
|
};
|
|
|
|
/**
|
|
* Function: createText
|
|
*
|
|
* Creates the innermost element that contains the HTML text.
|
|
*/
|
|
mxVmlCanvas2D.prototype.createDiv = function(str, align, valign, overflow)
|
|
{
|
|
var div = this.createElement('div');
|
|
var state = this.state;
|
|
|
|
var css = '';
|
|
|
|
if (state.fontBackgroundColor != null)
|
|
{
|
|
css += 'background-color:' + mxUtils.htmlEntities(state.fontBackgroundColor) + ';';
|
|
}
|
|
|
|
if (state.fontBorderColor != null)
|
|
{
|
|
css += 'border:1px solid ' + mxUtils.htmlEntities(state.fontBorderColor) + ';';
|
|
}
|
|
|
|
if (mxUtils.isNode(str))
|
|
{
|
|
div.appendChild(str);
|
|
}
|
|
else
|
|
{
|
|
if (overflow != 'fill' && overflow != 'width')
|
|
{
|
|
var div2 = this.createElement('div');
|
|
div2.style.cssText = css;
|
|
div2.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
|
|
div2.style.zoom = '1';
|
|
div2.style.textDecoration = 'inherit';
|
|
div2.innerHTML = str;
|
|
div.appendChild(div2);
|
|
}
|
|
else
|
|
{
|
|
div.style.cssText = css;
|
|
div.innerHTML = str;
|
|
}
|
|
}
|
|
|
|
var style = div.style;
|
|
|
|
style.fontSize = (state.fontSize / this.vmlScale) + 'px';
|
|
style.fontFamily = state.fontFamily;
|
|
style.color = state.fontColor;
|
|
style.verticalAlign = 'top';
|
|
style.textAlign = align || 'left';
|
|
style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (state.fontSize * mxConstants.LINE_HEIGHT / this.vmlScale) + 'px' : mxConstants.LINE_HEIGHT;
|
|
|
|
if ((state.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
|
|
{
|
|
style.fontWeight = 'bold';
|
|
}
|
|
|
|
if ((state.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
|
|
{
|
|
style.fontStyle = 'italic';
|
|
}
|
|
|
|
if ((state.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
|
|
{
|
|
style.textDecoration = 'underline';
|
|
}
|
|
|
|
return div;
|
|
};
|
|
|
|
/**
|
|
* Function: text
|
|
*
|
|
* Paints the given text. Possible values for format are empty string for plain
|
|
* text and html for HTML markup. Clipping, text background and border are not
|
|
* supported for plain text in VML.
|
|
*/
|
|
mxVmlCanvas2D.prototype.text = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)
|
|
{
|
|
if (this.textEnabled && str != null)
|
|
{
|
|
var s = this.state;
|
|
|
|
if (format == 'html')
|
|
{
|
|
if (s.rotation != null)
|
|
{
|
|
var pt = this.rotatePoint(x, y, s.rotation, s.rotationCx, s.rotationCy);
|
|
|
|
x = pt.x;
|
|
y = pt.y;
|
|
}
|
|
|
|
if (document.documentMode == 8 && !mxClient.IS_EM)
|
|
{
|
|
x += s.dx;
|
|
y += s.dy;
|
|
|
|
// Workaround for rendering offsets
|
|
if (overflow != 'fill' && valign == mxConstants.ALIGN_TOP)
|
|
{
|
|
y -= 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
x *= s.scale;
|
|
y *= s.scale;
|
|
}
|
|
|
|
// Adds event transparency in IE8 standards without the transparent background
|
|
// filter which cannot be used due to bugs in the zoomed bounding box (too slow)
|
|
// FIXME: No event transparency if inside v:rect (ie part of shape)
|
|
// KNOWN: Offset wrong for rotated text with word that are longer than the wrapping
|
|
// width in IE8 because real width of text cannot be determined here.
|
|
// This should be fixed in mxText.updateBoundingBox by calling before this and
|
|
// passing the real width to this method if not clipped and wrapped.
|
|
var abs = (document.documentMode == 8 && !mxClient.IS_EM) ? this.createVmlElement('group') : this.createElement('div');
|
|
abs.style.position = 'absolute';
|
|
abs.style.display = 'inline';
|
|
abs.style.left = this.format(x) + 'px';
|
|
abs.style.top = this.format(y) + 'px';
|
|
abs.style.zoom = s.scale;
|
|
|
|
var box = this.createElement('div');
|
|
box.style.position = 'relative';
|
|
box.style.display = 'inline';
|
|
|
|
var margin = mxUtils.getAlignmentAsPoint(align, valign);
|
|
var dx = margin.x;
|
|
var dy = margin.y;
|
|
|
|
var div = this.createDiv(str, align, valign, overflow);
|
|
var inner = this.createElement('div');
|
|
|
|
if (dir != null)
|
|
{
|
|
div.setAttribute('dir', dir);
|
|
}
|
|
|
|
if (wrap && w > 0)
|
|
{
|
|
if (!clip)
|
|
{
|
|
div.style.width = Math.round(w) + 'px';
|
|
}
|
|
|
|
div.style.wordWrap = mxConstants.WORD_WRAP;
|
|
div.style.whiteSpace = 'normal';
|
|
|
|
// LATER: Check if other cases need to be handled
|
|
if (div.style.wordWrap == 'break-word')
|
|
{
|
|
var tmp = div;
|
|
|
|
if (tmp.firstChild != null && tmp.firstChild.nodeName == 'DIV')
|
|
{
|
|
tmp.firstChild.style.width = '100%';
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
div.style.whiteSpace = 'nowrap';
|
|
}
|
|
|
|
var rot = s.rotation + (rotation || 0);
|
|
|
|
if (this.rotateHtml && rot != 0)
|
|
{
|
|
inner.style.display = 'inline';
|
|
inner.style.zoom = '1';
|
|
inner.appendChild(div);
|
|
|
|
// Box not needed for rendering in IE8 standards
|
|
if (document.documentMode == 8 && !mxClient.IS_EM && this.root.nodeName != 'DIV')
|
|
{
|
|
box.appendChild(inner);
|
|
abs.appendChild(box);
|
|
}
|
|
else
|
|
{
|
|
abs.appendChild(inner);
|
|
}
|
|
}
|
|
else if (document.documentMode == 8 && !mxClient.IS_EM)
|
|
{
|
|
box.appendChild(div);
|
|
abs.appendChild(box);
|
|
}
|
|
else
|
|
{
|
|
div.style.display = 'inline';
|
|
abs.appendChild(div);
|
|
}
|
|
|
|
// Inserts the node into the DOM
|
|
if (this.root.nodeName != 'DIV')
|
|
{
|
|
// Rectangle to fix position in group
|
|
var rect = this.createVmlElement('rect');
|
|
rect.stroked = 'false';
|
|
rect.filled = 'false';
|
|
|
|
rect.appendChild(abs);
|
|
this.root.appendChild(rect);
|
|
}
|
|
else
|
|
{
|
|
this.root.appendChild(abs);
|
|
}
|
|
|
|
if (clip)
|
|
{
|
|
div.style.overflow = 'hidden';
|
|
div.style.width = Math.round(w) + 'px';
|
|
|
|
if (!mxClient.IS_QUIRKS)
|
|
{
|
|
div.style.maxHeight = Math.round(h) + 'px';
|
|
}
|
|
}
|
|
else if (overflow == 'fill')
|
|
{
|
|
// KNOWN: Affects horizontal alignment in quirks
|
|
// but fill should only be used with align=left
|
|
div.style.overflow = 'hidden';
|
|
div.style.width = (Math.max(0, w) + 1) + 'px';
|
|
div.style.height = (Math.max(0, h) + 1) + 'px';
|
|
}
|
|
else if (overflow == 'width')
|
|
{
|
|
// KNOWN: Affects horizontal alignment in quirks
|
|
// but fill should only be used with align=left
|
|
div.style.overflow = 'hidden';
|
|
div.style.width = (Math.max(0, w) + 1) + 'px';
|
|
div.style.maxHeight = (Math.max(0, h) + 1) + 'px';
|
|
}
|
|
|
|
if (this.rotateHtml && rot != 0)
|
|
{
|
|
var rad = rot * (Math.PI / 180);
|
|
|
|
// Precalculate cos and sin for the rotation
|
|
var real_cos = parseFloat(parseFloat(Math.cos(rad)).toFixed(8));
|
|
var real_sin = parseFloat(parseFloat(Math.sin(-rad)).toFixed(8));
|
|
|
|
rad %= 2 * Math.PI;
|
|
if (rad < 0) rad += 2 * Math.PI;
|
|
rad %= Math.PI;
|
|
if (rad > Math.PI / 2) rad = Math.PI - rad;
|
|
|
|
var cos = Math.cos(rad);
|
|
var sin = Math.sin(rad);
|
|
|
|
// Adds div to document to measure size
|
|
if (document.documentMode == 8 && !mxClient.IS_EM)
|
|
{
|
|
div.style.display = 'inline-block';
|
|
inner.style.display = 'inline-block';
|
|
box.style.display = 'inline-block';
|
|
}
|
|
|
|
div.style.visibility = 'hidden';
|
|
div.style.position = 'absolute';
|
|
document.body.appendChild(div);
|
|
|
|
var sizeDiv = div;
|
|
|
|
if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')
|
|
{
|
|
sizeDiv = sizeDiv.firstChild;
|
|
}
|
|
|
|
var tmp = sizeDiv.offsetWidth + 3;
|
|
var oh = sizeDiv.offsetHeight;
|
|
|
|
if (clip)
|
|
{
|
|
w = Math.min(w, tmp);
|
|
oh = Math.min(oh, h);
|
|
}
|
|
else
|
|
{
|
|
w = tmp;
|
|
}
|
|
|
|
// Handles words that are longer than the given wrapping width
|
|
if (wrap)
|
|
{
|
|
div.style.width = w + 'px';
|
|
}
|
|
|
|
// Simulates max-height in quirks
|
|
if (mxClient.IS_QUIRKS && (clip || overflow == 'width') && oh > h)
|
|
{
|
|
oh = h;
|
|
|
|
// Quirks does not support maxHeight
|
|
div.style.height = oh + 'px';
|
|
}
|
|
|
|
h = oh;
|
|
|
|
var top_fix = (h - h * cos + w * -sin) / 2 - real_sin * w * (dx + 0.5) + real_cos * h * (dy + 0.5);
|
|
var left_fix = (w - w * cos + h * -sin) / 2 + real_cos * w * (dx + 0.5) + real_sin * h * (dy + 0.5);
|
|
|
|
if (abs.nodeName == 'group' && this.root.nodeName == 'DIV')
|
|
{
|
|
// Workaround for bug where group gets moved away if left and top are non-zero in IE8 standards
|
|
var pos = this.createElement('div');
|
|
pos.style.display = 'inline-block';
|
|
pos.style.position = 'absolute';
|
|
pos.style.left = this.format(x + (left_fix - w / 2) * s.scale) + 'px';
|
|
pos.style.top = this.format(y + (top_fix - h / 2) * s.scale) + 'px';
|
|
|
|
abs.parentNode.appendChild(pos);
|
|
pos.appendChild(abs);
|
|
}
|
|
else
|
|
{
|
|
var sc = (document.documentMode == 8 && !mxClient.IS_EM) ? 1 : s.scale;
|
|
|
|
abs.style.left = this.format(x + (left_fix - w / 2) * sc) + 'px';
|
|
abs.style.top = this.format(y + (top_fix - h / 2) * sc) + 'px';
|
|
}
|
|
|
|
// KNOWN: Rotated text rendering quality is bad for IE9 quirks
|
|
inner.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11="+real_cos+", M12="+
|
|
real_sin+", M21="+(-real_sin)+", M22="+real_cos+", sizingMethod='auto expand')";
|
|
inner.style.backgroundColor = this.rotatedHtmlBackground;
|
|
|
|
if (this.state.alpha < 1)
|
|
{
|
|
inner.style.filter += 'alpha(opacity=' + (this.state.alpha * 100) + ')';
|
|
}
|
|
|
|
// Restore parent node for DIV
|
|
inner.appendChild(div);
|
|
div.style.position = '';
|
|
div.style.visibility = '';
|
|
}
|
|
else if (document.documentMode != 8 || mxClient.IS_EM)
|
|
{
|
|
div.style.verticalAlign = 'top';
|
|
|
|
if (this.state.alpha < 1)
|
|
{
|
|
abs.style.filter = 'alpha(opacity=' + (this.state.alpha * 100) + ')';
|
|
}
|
|
|
|
// Adds div to document to measure size
|
|
var divParent = div.parentNode;
|
|
div.style.visibility = 'hidden';
|
|
document.body.appendChild(div);
|
|
|
|
w = div.offsetWidth;
|
|
var oh = div.offsetHeight;
|
|
|
|
// Simulates max-height in quirks
|
|
if (mxClient.IS_QUIRKS && clip && oh > h)
|
|
{
|
|
oh = h;
|
|
|
|
// Quirks does not support maxHeight
|
|
div.style.height = oh + 'px';
|
|
}
|
|
|
|
h = oh;
|
|
|
|
div.style.visibility = '';
|
|
divParent.appendChild(div);
|
|
|
|
abs.style.left = this.format(x + w * dx * this.state.scale) + 'px';
|
|
abs.style.top = this.format(y + h * dy * this.state.scale) + 'px';
|
|
}
|
|
else
|
|
{
|
|
if (this.state.alpha < 1)
|
|
{
|
|
div.style.filter = 'alpha(opacity=' + (this.state.alpha * 100) + ')';
|
|
}
|
|
|
|
// Faster rendering in IE8 without offsetWidth/Height
|
|
box.style.left = (dx * 100) + '%';
|
|
box.style.top = (dy * 100) + '%';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this.plainText(x, y, w, h, mxUtils.htmlEntities(str, false), align, valign, wrap, format, overflow, clip, rotation, dir);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Function: plainText
|
|
*
|
|
* Paints the outline of the current path.
|
|
*/
|
|
mxVmlCanvas2D.prototype.plainText = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)
|
|
{
|
|
// TextDirection is ignored since this code is not used (format is always HTML in the text function)
|
|
var s = this.state;
|
|
x = (x + s.dx) * s.scale;
|
|
y = (y + s.dy) * s.scale;
|
|
|
|
var node = this.createVmlElement('shape');
|
|
node.style.width = '1px';
|
|
node.style.height = '1px';
|
|
node.stroked = 'false';
|
|
|
|
var fill = this.createVmlElement('fill');
|
|
fill.color = s.fontColor;
|
|
fill.opacity = (s.alpha * 100) + '%';
|
|
node.appendChild(fill);
|
|
|
|
var path = this.createVmlElement('path');
|
|
path.textpathok = 'true';
|
|
path.v = 'm ' + this.format(0) + ' ' + this.format(0) + ' l ' + this.format(1) + ' ' + this.format(0);
|
|
|
|
node.appendChild(path);
|
|
|
|
// KNOWN: Font family and text decoration ignored
|
|
var tp = this.createVmlElement('textpath');
|
|
tp.style.cssText = 'v-text-align:' + align;
|
|
tp.style.align = align;
|
|
tp.style.fontFamily = s.fontFamily;
|
|
tp.string = str;
|
|
tp.on = 'true';
|
|
|
|
// Scale via fontsize instead of node.style.zoom for correct offsets in IE8
|
|
var size = s.fontSize * s.scale / this.vmlScale;
|
|
tp.style.fontSize = size + 'px';
|
|
|
|
// Bold
|
|
if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
|
|
{
|
|
tp.style.fontWeight = 'bold';
|
|
}
|
|
|
|
// Italic
|
|
if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
|
|
{
|
|
tp.style.fontStyle = 'italic';
|
|
}
|
|
|
|
// Underline
|
|
if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
|
|
{
|
|
tp.style.textDecoration = 'underline';
|
|
}
|
|
|
|
var lines = str.split('\n');
|
|
var textHeight = size + (lines.length - 1) * size * mxConstants.LINE_HEIGHT;
|
|
var dx = 0;
|
|
var dy = 0;
|
|
|
|
if (valign == mxConstants.ALIGN_BOTTOM)
|
|
{
|
|
dy = - textHeight / 2;
|
|
}
|
|
else if (valign != mxConstants.ALIGN_MIDDLE) // top
|
|
{
|
|
dy = textHeight / 2;
|
|
}
|
|
|
|
if (rotation != null)
|
|
{
|
|
node.style.rotation = rotation;
|
|
var rad = rotation * (Math.PI / 180);
|
|
dx = Math.sin(rad) * dy;
|
|
dy = Math.cos(rad) * dy;
|
|
}
|
|
|
|
// FIXME: Clipping is relative to bounding box
|
|
/*if (clip)
|
|
{
|
|
node.style.clip = 'rect(0px ' + this.format(w) + 'px ' + this.format(h) + 'px 0px)';
|
|
}*/
|
|
|
|
node.appendChild(tp);
|
|
node.style.left = this.format(x - dx) + 'px';
|
|
node.style.top = this.format(y + dy) + 'px';
|
|
|
|
this.root.appendChild(node);
|
|
};
|
|
|
|
/**
|
|
* Function: stroke
|
|
*
|
|
* Paints the outline of the current path.
|
|
*/
|
|
mxVmlCanvas2D.prototype.stroke = function()
|
|
{
|
|
this.addNode(false, true);
|
|
};
|
|
|
|
/**
|
|
* Function: fill
|
|
*
|
|
* Fills the current path.
|
|
*/
|
|
mxVmlCanvas2D.prototype.fill = function()
|
|
{
|
|
this.addNode(true, false);
|
|
};
|
|
|
|
/**
|
|
* Function: fillAndStroke
|
|
*
|
|
* Fills and paints the outline of the current path.
|
|
*/
|
|
mxVmlCanvas2D.prototype.fillAndStroke = function()
|
|
{
|
|
this.addNode(true, true);
|
|
};
|