diff --git a/js/ui/mxgraph/src/js/util/mxVmlCanvas2D.js b/js/ui/mxgraph/src/js/util/mxVmlCanvas2D.js new file mode 100644 index 0000000..2b3d600 --- /dev/null +++ b/js/ui/mxgraph/src/js/util/mxVmlCanvas2D.js @@ -0,0 +1,1102 @@ +/** + * 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 . 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 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 and prefixes the given name with + * . + */ +mxVmlCanvas2D.prototype.createVmlElement = function(name) +{ + return this.createElement(mxClient.VML_PREFIX + ':' + name); +}; + +/** + * Function: addNode + * + * Adds the current node to the . + */ +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:' + state.fontBackgroundColor + ';'; + } + + if (state.fontBorderColor != null) + { + css += 'border:1px solid ' + 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); +};