| /** |
| * Copyright (c) 2006-2015, JGraph Ltd |
| * Copyright (c) 2006-2015, Gaudenz Alder |
| */ |
| /** |
| * Class: mxText |
| * |
| * Extends <mxShape> to implement a text shape. To change vertical text from |
| * bottom to top to top to bottom, the following code can be used: |
| * |
| * (code) |
| * mxText.prototype.verticalTextRotation = 90; |
| * (end) |
| * |
| * Constructor: mxText |
| * |
| * Constructs a new text shape. |
| * |
| * Parameters: |
| * |
| * value - String that represents the text to be displayed. This is stored in |
| * <value>. |
| * bounds - <mxRectangle> that defines the bounds. This is stored in |
| * <mxShape.bounds>. |
| * align - Specifies the horizontal alignment. Default is ''. This is stored in |
| * <align>. |
| * valign - Specifies the vertical alignment. Default is ''. This is stored in |
| * <valign>. |
| * color - String that specifies the text color. Default is 'black'. This is |
| * stored in <color>. |
| * family - String that specifies the font family. Default is |
| * <mxConstants.DEFAULT_FONTFAMILY>. This is stored in <family>. |
| * size - Integer that specifies the font size. Default is |
| * <mxConstants.DEFAULT_FONTSIZE>. This is stored in <size>. |
| * fontStyle - Specifies the font style. Default is 0. This is stored in |
| * <fontStyle>. |
| * spacing - Integer that specifies the global spacing. Default is 2. This is |
| * stored in <spacing>. |
| * spacingTop - Integer that specifies the top spacing. Default is 0. The |
| * sum of the spacing and this is stored in <spacingTop>. |
| * spacingRight - Integer that specifies the right spacing. Default is 0. The |
| * sum of the spacing and this is stored in <spacingRight>. |
| * spacingBottom - Integer that specifies the bottom spacing. Default is 0.The |
| * sum of the spacing and this is stored in <spacingBottom>. |
| * spacingLeft - Integer that specifies the left spacing. Default is 0. The |
| * sum of the spacing and this is stored in <spacingLeft>. |
| * horizontal - Boolean that specifies if the label is horizontal. Default is |
| * true. This is stored in <horizontal>. |
| * background - String that specifies the background color. Default is null. |
| * This is stored in <background>. |
| * border - String that specifies the label border color. Default is null. |
| * This is stored in <border>. |
| * wrap - Specifies if word-wrapping should be enabled. Default is false. |
| * This is stored in <wrap>. |
| * clipped - Specifies if the label should be clipped. Default is false. |
| * This is stored in <clipped>. |
| * overflow - Value of the overflow style. Default is 'visible'. |
| */ |
| function mxText(value, bounds, align, valign, color, |
| family, size, fontStyle, spacing, spacingTop, spacingRight, |
| spacingBottom, spacingLeft, horizontal, background, border, |
| wrap, clipped, overflow, labelPadding, textDirection) |
| { |
| mxShape.call(this); |
| this.value = value; |
| this.bounds = bounds; |
| this.color = (color != null) ? color : 'black'; |
| this.align = (align != null) ? align : mxConstants.ALIGN_CENTER; |
| this.valign = (valign != null) ? valign : mxConstants.ALIGN_MIDDLE; |
| this.family = (family != null) ? family : mxConstants.DEFAULT_FONTFAMILY; |
| this.size = (size != null) ? size : mxConstants.DEFAULT_FONTSIZE; |
| this.fontStyle = (fontStyle != null) ? fontStyle : mxConstants.DEFAULT_FONTSTYLE; |
| this.spacing = parseInt(spacing || 2); |
| this.spacingTop = this.spacing + parseInt(spacingTop || 0); |
| this.spacingRight = this.spacing + parseInt(spacingRight || 0); |
| this.spacingBottom = this.spacing + parseInt(spacingBottom || 0); |
| this.spacingLeft = this.spacing + parseInt(spacingLeft || 0); |
| this.horizontal = (horizontal != null) ? horizontal : true; |
| this.background = background; |
| this.border = border; |
| this.wrap = (wrap != null) ? wrap : false; |
| this.clipped = (clipped != null) ? clipped : false; |
| this.overflow = (overflow != null) ? overflow : 'visible'; |
| this.labelPadding = (labelPadding != null) ? labelPadding : 0; |
| this.textDirection = textDirection; |
| this.rotation = 0; |
| this.updateMargin(); |
| }; |
| |
| /** |
| * Extends mxShape. |
| */ |
| mxUtils.extend(mxText, mxShape); |
| |
| /** |
| * Variable: baseSpacingTop |
| * |
| * Specifies the spacing to be added to the top spacing. Default is 0. Use the |
| * value 5 here to get the same label positions as in mxGraph 1.x. |
| */ |
| mxText.prototype.baseSpacingTop = 0; |
| |
| /** |
| * Variable: baseSpacingBottom |
| * |
| * Specifies the spacing to be added to the bottom spacing. Default is 0. Use the |
| * value 1 here to get the same label positions as in mxGraph 1.x. |
| */ |
| mxText.prototype.baseSpacingBottom = 0; |
| |
| /** |
| * Variable: baseSpacingLeft |
| * |
| * Specifies the spacing to be added to the left spacing. Default is 0. |
| */ |
| mxText.prototype.baseSpacingLeft = 0; |
| |
| /** |
| * Variable: baseSpacingRight |
| * |
| * Specifies the spacing to be added to the right spacing. Default is 0. |
| */ |
| mxText.prototype.baseSpacingRight = 0; |
| |
| /** |
| * Variable: replaceLinefeeds |
| * |
| * Specifies if linefeeds in HTML labels should be replaced with BR tags. |
| * Default is true. |
| */ |
| mxText.prototype.replaceLinefeeds = true; |
| |
| /** |
| * Variable: verticalTextRotation |
| * |
| * Rotation for vertical text. Default is -90 (bottom to top). |
| */ |
| mxText.prototype.verticalTextRotation = -90; |
| |
| /** |
| * Variable: ignoreClippedStringSize |
| * |
| * Specifies if the string size should be measured in <updateBoundingBox> if |
| * the label is clipped and the label position is center and middle. If this is |
| * true, then the bounding box will be set to <bounds>. Default is true. |
| * <ignoreStringSize> has precedence over this switch. |
| */ |
| mxText.prototype.ignoreClippedStringSize = true; |
| |
| /** |
| * Variable: ignoreStringSize |
| * |
| * Specifies if the actual string size should be measured. If disabled the |
| * boundingBox will not ignore the actual size of the string, otherwise |
| * <bounds> will be used instead. Default is false. |
| */ |
| mxText.prototype.ignoreStringSize = false; |
| |
| /** |
| * Variable: textWidthPadding |
| * |
| * Specifies the padding to be added to the text width for the bounding box. |
| * This is needed to make sure no clipping is applied to borders. Default is 4 |
| * for IE 8 standards mode and 3 for all others. |
| */ |
| mxText.prototype.textWidthPadding = (document.documentMode == 8 && !mxClient.IS_EM) ? 4 : 3; |
| |
| /** |
| * Variable: lastValue |
| * |
| * Contains the last rendered text value. Used for caching. |
| */ |
| mxText.prototype.lastValue = null; |
| |
| /** |
| * Variable: cacheEnabled |
| * |
| * Specifies if caching for HTML labels should be enabled. Default is true. |
| */ |
| mxText.prototype.cacheEnabled = true; |
| |
| /** |
| * Function: isParseVml |
| * |
| * Text shapes do not contain VML markup and do not need to be parsed. This |
| * method returns false to speed up rendering in IE8. |
| */ |
| mxText.prototype.isParseVml = function() |
| { |
| return false; |
| }; |
| |
| /** |
| * Function: isHtmlAllowed |
| * |
| * Returns true if HTML is allowed for this shape. This implementation returns |
| * true if the browser is not in IE8 standards mode. |
| */ |
| mxText.prototype.isHtmlAllowed = function() |
| { |
| return document.documentMode != 8 || mxClient.IS_EM; |
| }; |
| |
| /** |
| * Function: getSvgScreenOffset |
| * |
| * Disables offset in IE9 for crisper image output. |
| */ |
| mxText.prototype.getSvgScreenOffset = function() |
| { |
| return 0; |
| }; |
| |
| /** |
| * Function: checkBounds |
| * |
| * Returns true if the bounds are not null and all of its variables are numeric. |
| */ |
| mxText.prototype.checkBounds = function() |
| { |
| return (!isNaN(this.scale) && isFinite(this.scale) && this.scale > 0 && |
| this.bounds != null && !isNaN(this.bounds.x) && !isNaN(this.bounds.y) && |
| !isNaN(this.bounds.width) && !isNaN(this.bounds.height)); |
| }; |
| |
| /** |
| * Function: paint |
| * |
| * Generic rendering code. |
| */ |
| mxText.prototype.paint = function(c, update) |
| { |
| // Scale is passed-through to canvas |
| var s = this.scale; |
| var x = this.bounds.x / s; |
| var y = this.bounds.y / s; |
| var w = this.bounds.width / s; |
| var h = this.bounds.height / s; |
| |
| this.updateTransform(c, x, y, w, h); |
| this.configureCanvas(c, x, y, w, h); |
| |
| var unscaledWidth = (this.state != null) ? this.state.unscaledWidth : null; |
| |
| if (update) |
| { |
| if (this.node.firstChild != null && (unscaledWidth == null || |
| this.lastUnscaledWidth != unscaledWidth)) |
| { |
| c.invalidateCachedOffsetSize(this.node); |
| } |
| |
| c.updateText(x, y, w, h, this.align, this.valign, this.wrap, this.overflow, |
| this.clipped, this.getTextRotation(), this.node); |
| } |
| else |
| { |
| // Checks if text contains HTML markup |
| var realHtml = mxUtils.isNode(this.value) || this.dialect == mxConstants.DIALECT_STRICTHTML; |
| |
| // Always renders labels as HTML in VML |
| var fmt = (realHtml || c instanceof mxVmlCanvas2D) ? 'html' : ''; |
| var val = this.value; |
| |
| if (!realHtml && fmt == 'html') |
| { |
| val = mxUtils.htmlEntities(val, false); |
| } |
| |
| if (fmt == 'html' && !mxUtils.isNode(this.value)) |
| { |
| val = mxUtils.replaceTrailingNewlines(val, '<div><br></div>'); |
| } |
| |
| // Handles trailing newlines to make sure they are visible in rendering output |
| val = (!mxUtils.isNode(this.value) && this.replaceLinefeeds && fmt == 'html') ? |
| val.replace(/\n/g, '<br/>') : val; |
| |
| var dir = this.textDirection; |
| |
| if (dir == mxConstants.TEXT_DIRECTION_AUTO && !realHtml) |
| { |
| dir = this.getAutoDirection(); |
| } |
| |
| if (dir != mxConstants.TEXT_DIRECTION_LTR && dir != mxConstants.TEXT_DIRECTION_RTL) |
| { |
| dir = null; |
| } |
| |
| c.text(x, y, w, h, val, this.align, this.valign, this.wrap, fmt, this.overflow, |
| this.clipped, this.getTextRotation(), dir); |
| } |
| |
| // Needs to invalidate the cached offset widths if the geometry changes |
| this.lastUnscaledWidth = unscaledWidth; |
| }; |
| |
| /** |
| * Function: redraw |
| * |
| * Renders the text using the given DOM nodes. |
| */ |
| mxText.prototype.redraw = function() |
| { |
| if (this.visible && this.checkBounds() && this.cacheEnabled && this.lastValue == this.value && |
| (mxUtils.isNode(this.value) || this.dialect == mxConstants.DIALECT_STRICTHTML)) |
| { |
| if (this.node.nodeName == 'DIV' && (this.isHtmlAllowed() || !mxClient.IS_VML)) |
| { |
| this.updateSize(this.node, (this.state == null || this.state.view.textDiv == null)); |
| |
| if (mxClient.IS_IE && (document.documentMode == null || document.documentMode <= 8)) |
| { |
| this.updateHtmlFilter(); |
| } |
| else |
| { |
| this.updateHtmlTransform(); |
| } |
| |
| this.updateBoundingBox(); |
| } |
| else |
| { |
| var canvas = this.createCanvas(); |
| |
| if (canvas != null && canvas.updateText != null && |
| canvas.invalidateCachedOffsetSize != null) |
| { |
| this.paint(canvas, true); |
| this.destroyCanvas(canvas); |
| this.updateBoundingBox(); |
| } |
| else |
| { |
| // Fallback if canvas does not support updateText (VML) |
| mxShape.prototype.redraw.apply(this, arguments); |
| } |
| } |
| } |
| else |
| { |
| mxShape.prototype.redraw.apply(this, arguments); |
| |
| if (mxUtils.isNode(this.value) || this.dialect == mxConstants.DIALECT_STRICTHTML) |
| { |
| this.lastValue = this.value; |
| } |
| else |
| { |
| this.lastValue = null; |
| } |
| } |
| }; |
| |
| /** |
| * Function: resetStyles |
| * |
| * Resets all styles. |
| */ |
| mxText.prototype.resetStyles = function() |
| { |
| mxShape.prototype.resetStyles.apply(this, arguments); |
| |
| this.color = 'black'; |
| this.align = mxConstants.ALIGN_CENTER; |
| this.valign = mxConstants.ALIGN_MIDDLE; |
| this.family = mxConstants.DEFAULT_FONTFAMILY; |
| this.size = mxConstants.DEFAULT_FONTSIZE; |
| this.fontStyle = mxConstants.DEFAULT_FONTSTYLE; |
| this.spacing = 2; |
| this.spacingTop = 2; |
| this.spacingRight = 2; |
| this.spacingBottom = 2; |
| this.spacingLeft = 2; |
| this.horizontal = true; |
| delete this.background; |
| delete this.border; |
| this.textDirection = mxConstants.DEFAULT_TEXT_DIRECTION; |
| delete this.margin; |
| }; |
| |
| /** |
| * Function: apply |
| * |
| * Extends mxShape to update the text styles. |
| * |
| * Parameters: |
| * |
| * state - <mxCellState> of the corresponding cell. |
| */ |
| mxText.prototype.apply = function(state) |
| { |
| var old = this.spacing; |
| mxShape.prototype.apply.apply(this, arguments); |
| |
| if (this.style != null) |
| { |
| this.fontStyle = mxUtils.getValue(this.style, mxConstants.STYLE_FONTSTYLE, this.fontStyle); |
| this.family = mxUtils.getValue(this.style, mxConstants.STYLE_FONTFAMILY, this.family); |
| this.size = mxUtils.getValue(this.style, mxConstants.STYLE_FONTSIZE, this.size); |
| this.color = mxUtils.getValue(this.style, mxConstants.STYLE_FONTCOLOR, this.color); |
| this.align = mxUtils.getValue(this.style, mxConstants.STYLE_ALIGN, this.align); |
| this.valign = mxUtils.getValue(this.style, mxConstants.STYLE_VERTICAL_ALIGN, this.valign); |
| this.spacing = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING, this.spacing)); |
| this.spacingTop = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_TOP, this.spacingTop - old)) + this.spacing; |
| this.spacingRight = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_RIGHT, this.spacingRight - old)) + this.spacing; |
| this.spacingBottom = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_BOTTOM, this.spacingBottom - old)) + this.spacing; |
| this.spacingLeft = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_LEFT, this.spacingLeft - old)) + this.spacing; |
| this.horizontal = mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, this.horizontal); |
| this.background = mxUtils.getValue(this.style, mxConstants.STYLE_LABEL_BACKGROUNDCOLOR, this.background); |
| this.border = mxUtils.getValue(this.style, mxConstants.STYLE_LABEL_BORDERCOLOR, this.border); |
| this.textDirection = mxUtils.getValue(this.style, mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION); |
| this.opacity = mxUtils.getValue(this.style, mxConstants.STYLE_TEXT_OPACITY, 100); |
| this.updateMargin(); |
| } |
| |
| this.flipV = null; |
| this.flipH = null; |
| }; |
| |
| /** |
| * Function: getAutoDirection |
| * |
| * Used to determine the automatic text direction. Returns |
| * <mxConstants.TEXT_DIRECTION_LTR> or <mxConstants.TEXT_DIRECTION_RTL> |
| * depending on the contents of <value>. This is not invoked for HTML, wrapped |
| * content or if <value> is a DOM node. |
| */ |
| mxText.prototype.getAutoDirection = function() |
| { |
| // Looks for strong (directional) characters |
| var tmp = /[A-Za-z\u05d0-\u065f\u066a-\u06ef\u06fa-\u07ff\ufb1d-\ufdff\ufe70-\ufefc]/.exec(this.value); |
| |
| // Returns the direction defined by the character |
| return (tmp != null && tmp.length > 0 && tmp[0] > 'z') ? |
| mxConstants.TEXT_DIRECTION_RTL : mxConstants.TEXT_DIRECTION_LTR; |
| }; |
| |
| /** |
| * Function: updateBoundingBox |
| * |
| * Updates the <boundingBox> for this shape using the given node and position. |
| */ |
| mxText.prototype.updateBoundingBox = function() |
| { |
| var node = this.node; |
| this.boundingBox = this.bounds.clone(); |
| var rot = this.getTextRotation(); |
| |
| var h = (this.style != null) ? mxUtils.getValue(this.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER) : null; |
| var v = (this.style != null) ? mxUtils.getValue(this.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE) : null; |
| |
| if (!this.ignoreStringSize && node != null && this.overflow != 'fill' && (!this.clipped || |
| !this.ignoreClippedStringSize || h != mxConstants.ALIGN_CENTER || v != mxConstants.ALIGN_MIDDLE)) |
| { |
| var ow = null; |
| var oh = null; |
| |
| if (node.ownerSVGElement != null) |
| { |
| if (node.firstChild != null && node.firstChild.firstChild != null && |
| node.firstChild.firstChild.nodeName == 'foreignObject') |
| { |
| node = node.firstChild.firstChild; |
| ow = parseInt(node.getAttribute('width')) * this.scale; |
| oh = parseInt(node.getAttribute('height')) * this.scale; |
| } |
| else |
| { |
| try |
| { |
| var b = node.getBBox(); |
| |
| // Workaround for bounding box of empty string |
| if (typeof(this.value) == 'string' && mxUtils.trim(this.value) == 0) |
| { |
| this.boundingBox = null; |
| } |
| else if (b.width == 0 && b.height == 0) |
| { |
| this.boundingBox = null; |
| } |
| else |
| { |
| this.boundingBox = new mxRectangle(b.x, b.y, b.width, b.height); |
| } |
| |
| return; |
| } |
| catch (e) |
| { |
| // Ignores NS_ERROR_FAILURE in FF if container display is none. |
| } |
| } |
| } |
| else |
| { |
| var td = (this.state != null) ? this.state.view.textDiv : null; |
| |
| // Use cached offset size |
| if (this.offsetWidth != null && this.offsetHeight != null) |
| { |
| ow = this.offsetWidth * this.scale; |
| oh = this.offsetHeight * this.scale; |
| } |
| else |
| { |
| // Cannot get node size while container hidden so a |
| // shared temporary DIV is used for text measuring |
| if (td != null) |
| { |
| this.updateFont(td); |
| this.updateSize(td, false); |
| this.updateInnerHtml(td); |
| |
| node = td; |
| } |
| |
| var sizeDiv = node; |
| |
| if (document.documentMode == 8 && !mxClient.IS_EM) |
| { |
| var w = Math.round(this.bounds.width / this.scale); |
| |
| if (this.wrap && w > 0) |
| { |
| node.style.wordWrap = mxConstants.WORD_WRAP; |
| node.style.whiteSpace = 'normal'; |
| |
| if (node.style.wordWrap != 'break-word') |
| { |
| // Innermost DIV is used for measuring text |
| var divs = sizeDiv.getElementsByTagName('div'); |
| |
| if (divs.length > 0) |
| { |
| sizeDiv = divs[divs.length - 1]; |
| } |
| |
| ow = sizeDiv.offsetWidth + 2; |
| divs = this.node.getElementsByTagName('div'); |
| |
| if (this.clipped) |
| { |
| ow = Math.min(w, ow); |
| } |
| |
| // Second last DIV width must be updated in DOM tree |
| if (divs.length > 1) |
| { |
| divs[divs.length - 2].style.width = ow + 'px'; |
| } |
| } |
| } |
| else |
| { |
| node.style.whiteSpace = 'nowrap'; |
| } |
| } |
| else if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV') |
| { |
| sizeDiv = sizeDiv.firstChild; |
| } |
| |
| this.offsetWidth = sizeDiv.offsetWidth + this.textWidthPadding; |
| this.offsetHeight = sizeDiv.offsetHeight; |
| |
| ow = this.offsetWidth * this.scale; |
| oh = this.offsetHeight * this.scale; |
| } |
| } |
| |
| if (ow != null && oh != null) |
| { |
| this.boundingBox = new mxRectangle(this.bounds.x, |
| this.bounds.y, ow, oh); |
| } |
| } |
| |
| if (this.boundingBox != null) |
| { |
| if (rot != 0) |
| { |
| // Accounts for pre-rotated x and y |
| var bbox = mxUtils.getBoundingBox(new mxRectangle( |
| this.margin.x * this.boundingBox.width, |
| this.margin.y * this.boundingBox.height, |
| this.boundingBox.width, this.boundingBox.height), |
| rot, new mxPoint(0, 0)); |
| |
| this.unrotatedBoundingBox = mxRectangle.fromRectangle(this.boundingBox); |
| this.unrotatedBoundingBox.x += this.margin.x * this.unrotatedBoundingBox.width; |
| this.unrotatedBoundingBox.y += this.margin.y * this.unrotatedBoundingBox.height; |
| |
| this.boundingBox.x += bbox.x; |
| this.boundingBox.y += bbox.y; |
| this.boundingBox.width = bbox.width; |
| this.boundingBox.height = bbox.height; |
| } |
| else |
| { |
| this.boundingBox.x += this.margin.x * this.boundingBox.width; |
| this.boundingBox.y += this.margin.y * this.boundingBox.height; |
| this.unrotatedBoundingBox = null; |
| } |
| } |
| }; |
| |
| /** |
| * Function: getShapeRotation |
| * |
| * Returns 0 to avoid using rotation in the canvas via updateTransform. |
| */ |
| mxText.prototype.getShapeRotation = function() |
| { |
| return 0; |
| }; |
| |
| /** |
| * Function: getTextRotation |
| * |
| * Returns the rotation for the text label of the corresponding shape. |
| */ |
| mxText.prototype.getTextRotation = function() |
| { |
| return (this.state != null && this.state.shape != null) ? this.state.shape.getTextRotation() : 0; |
| }; |
| |
| /** |
| * Function: isPaintBoundsInverted |
| * |
| * Inverts the bounds if <mxShape.isBoundsInverted> returns true or if the |
| * horizontal style is false. |
| */ |
| mxText.prototype.isPaintBoundsInverted = function() |
| { |
| return !this.horizontal && this.state != null && this.state.view.graph.model.isVertex(this.state.cell); |
| }; |
| |
| /** |
| * Function: configureCanvas |
| * |
| * Sets the state of the canvas for drawing the shape. |
| */ |
| mxText.prototype.configureCanvas = function(c, x, y, w, h) |
| { |
| mxShape.prototype.configureCanvas.apply(this, arguments); |
| |
| c.setFontColor(this.color); |
| c.setFontBackgroundColor(this.background); |
| c.setFontBorderColor(this.border); |
| c.setFontFamily(this.family); |
| c.setFontSize(this.size); |
| c.setFontStyle(this.fontStyle); |
| }; |
| |
| /** |
| * Function: updateVmlContainer |
| * |
| * Sets the width and height of the container to 1px. |
| */ |
| mxText.prototype.updateVmlContainer = function() |
| { |
| this.node.style.left = Math.round(this.bounds.x) + 'px'; |
| this.node.style.top = Math.round(this.bounds.y) + 'px'; |
| this.node.style.width = '1px'; |
| this.node.style.height = '1px'; |
| this.node.style.overflow = 'visible'; |
| }; |
| |
| /** |
| * Function: redrawHtmlShape |
| * |
| * Updates the HTML node(s) to reflect the latest bounds and scale. |
| */ |
| mxText.prototype.redrawHtmlShape = function() |
| { |
| var style = this.node.style; |
| |
| // Resets CSS styles |
| style.whiteSpace = 'normal'; |
| style.overflow = ''; |
| style.width = ''; |
| style.height = ''; |
| |
| this.updateValue(); |
| this.updateFont(this.node); |
| this.updateSize(this.node, (this.state == null || this.state.view.textDiv == null)); |
| |
| this.offsetWidth = null; |
| this.offsetHeight = null; |
| |
| if (mxClient.IS_IE && (document.documentMode == null || document.documentMode <= 8)) |
| { |
| this.updateHtmlFilter(); |
| } |
| else |
| { |
| this.updateHtmlTransform(); |
| } |
| }; |
| |
| /** |
| * Function: updateHtmlTransform |
| * |
| * Returns the spacing as an <mxPoint>. |
| */ |
| mxText.prototype.updateHtmlTransform = function() |
| { |
| var theta = this.getTextRotation(); |
| var style = this.node.style; |
| var dx = this.margin.x; |
| var dy = this.margin.y; |
| |
| if (theta != 0) |
| { |
| mxUtils.setPrefixedStyle(style, 'transformOrigin', (-dx * 100) + '%' + ' ' + (-dy * 100) + '%'); |
| mxUtils.setPrefixedStyle(style, 'transform', 'translate(' + (dx * 100) + '%' + ',' + (dy * 100) + '%)' + |
| 'scale(' + this.scale + ') rotate(' + theta + 'deg)'); |
| } |
| else |
| { |
| mxUtils.setPrefixedStyle(style, 'transformOrigin', '0% 0%'); |
| mxUtils.setPrefixedStyle(style, 'transform', 'scale(' + this.scale + ')' + |
| 'translate(' + (dx * 100) + '%' + ',' + (dy * 100) + '%)'); |
| } |
| |
| style.left = Math.round(this.bounds.x - Math.ceil(dx * ((this.overflow != 'fill' && |
| this.overflow != 'width') ? 3 : 1))) + 'px'; |
| style.top = Math.round(this.bounds.y - dy * ((this.overflow != 'fill') ? 3 : 1)) + 'px'; |
| |
| if (this.opacity < 100) |
| { |
| style.opacity = this.opacity / 100; |
| } |
| else |
| { |
| style.opacity = ''; |
| } |
| }; |
| |
| /** |
| * Function: setInnerHtml |
| * |
| * Sets the inner HTML of the given element to the <value>. |
| */ |
| mxText.prototype.updateInnerHtml = function(elt) |
| { |
| if (mxUtils.isNode(this.value)) |
| { |
| elt.innerHTML = this.value.outerHTML; |
| } |
| else |
| { |
| var val = this.value; |
| |
| if (this.dialect != mxConstants.DIALECT_STRICTHTML) |
| { |
| // LATER: Can be cached in updateValue |
| val = mxUtils.htmlEntities(val, false); |
| } |
| |
| // Handles trailing newlines to make sure they are visible in rendering output |
| val = mxUtils.replaceTrailingNewlines(val, '<div> </div>'); |
| val = (this.replaceLinefeeds) ? val.replace(/\n/g, '<br/>') : val; |
| val = '<div style="display:inline-block;_display:inline;">' + val + '</div>'; |
| |
| elt.innerHTML = val; |
| } |
| }; |
| |
| /** |
| * Function: updateHtmlFilter |
| * |
| * Rotated text rendering quality is bad for IE9 quirks/IE8 standards |
| */ |
| mxText.prototype.updateHtmlFilter = function() |
| { |
| var style = this.node.style; |
| var dx = this.margin.x; |
| var dy = this.margin.y; |
| var s = this.scale; |
| |
| // Resets filter before getting offsetWidth |
| mxUtils.setOpacity(this.node, this.opacity); |
| |
| // Adds 1 to match table height in 1.x |
| var ow = 0; |
| var oh = 0; |
| var td = (this.state != null) ? this.state.view.textDiv : null; |
| var sizeDiv = this.node; |
| |
| // Fallback for hidden text rendering in IE quirks mode |
| if (td != null) |
| { |
| td.style.overflow = ''; |
| td.style.height = ''; |
| td.style.width = ''; |
| |
| this.updateFont(td); |
| this.updateSize(td, false); |
| this.updateInnerHtml(td); |
| |
| var w = Math.round(this.bounds.width / this.scale); |
| |
| if (this.wrap && w > 0) |
| { |
| td.style.whiteSpace = 'normal'; |
| td.style.wordWrap = mxConstants.WORD_WRAP; |
| ow = w; |
| |
| if (this.clipped) |
| { |
| ow = Math.min(ow, this.bounds.width); |
| } |
| |
| td.style.width = ow + 'px'; |
| } |
| else |
| { |
| td.style.whiteSpace = 'nowrap'; |
| } |
| |
| sizeDiv = td; |
| |
| if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV') |
| { |
| sizeDiv = sizeDiv.firstChild; |
| |
| if (this.wrap && td.style.wordWrap == 'break-word') |
| { |
| sizeDiv.style.width = '100%'; |
| } |
| } |
| |
| // Required to update the height of the text box after wrapping width is known |
| if (!this.clipped && this.wrap && w > 0) |
| { |
| ow = sizeDiv.offsetWidth + this.textWidthPadding; |
| td.style.width = ow + 'px'; |
| } |
| |
| oh = sizeDiv.offsetHeight + 2; |
| |
| if (mxClient.IS_QUIRKS && this.border != null && this.border != mxConstants.NONE) |
| { |
| oh += 3; |
| } |
| } |
| else if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV') |
| { |
| sizeDiv = sizeDiv.firstChild; |
| oh = sizeDiv.offsetHeight; |
| } |
| |
| ow = sizeDiv.offsetWidth + this.textWidthPadding; |
| |
| if (this.clipped) |
| { |
| oh = Math.min(oh, this.bounds.height); |
| } |
| |
| var w = this.bounds.width / s; |
| var h = this.bounds.height / s; |
| |
| // Handles special case for live preview with no wrapper DIV and no textDiv |
| if (this.overflow == 'fill') |
| { |
| oh = h; |
| ow = w; |
| } |
| else if (this.overflow == 'width') |
| { |
| oh = sizeDiv.scrollHeight; |
| ow = w; |
| } |
| |
| // Stores for later use |
| this.offsetWidth = ow; |
| this.offsetHeight = oh; |
| |
| // Simulates max-height CSS in quirks mode |
| if (mxClient.IS_QUIRKS && (this.clipped || (this.overflow == 'width' && h > 0))) |
| { |
| h = Math.min(h, oh); |
| style.height = Math.round(h) + 'px'; |
| } |
| else |
| { |
| h = oh; |
| } |
| |
| if (this.overflow != 'fill' && this.overflow != 'width') |
| { |
| if (this.clipped) |
| { |
| ow = Math.min(w, ow); |
| } |
| |
| w = ow; |
| |
| // Simulates max-width CSS in quirks mode |
| if ((mxClient.IS_QUIRKS && this.clipped) || this.wrap) |
| { |
| style.width = Math.round(w) + 'px'; |
| } |
| } |
| |
| h *= s; |
| w *= s; |
| |
| // Rotation case is handled via VML canvas |
| var rad = this.getTextRotation() * (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); |
| |
| var tx = w * -(dx + 0.5); |
| var ty = h * -(dy + 0.5); |
| |
| var top_fix = (h - h * cos + w * sin) / 2 + real_sin * tx - real_cos * ty; |
| var left_fix = (w - w * cos + h * sin) / 2 - real_cos * tx - real_sin * ty; |
| |
| if (rad != 0) |
| { |
| var f = 'progid:DXImageTransform.Microsoft.Matrix(M11=' + real_cos + ', M12='+ |
| real_sin + ', M21=' + (-real_sin) + ', M22=' + real_cos + ', sizingMethod=\'auto expand\')'; |
| |
| if (style.filter != null && style.filter.length > 0) |
| { |
| style.filter += ' ' + f; |
| } |
| else |
| { |
| style.filter = f; |
| } |
| } |
| |
| // Workaround for rendering offsets |
| var dy = 0; |
| |
| if (this.overflow != 'fill' && mxClient.IS_QUIRKS) |
| { |
| if (this.valign == mxConstants.ALIGN_TOP) |
| { |
| dy -= 1; |
| } |
| else if (this.valign == mxConstants.ALIGN_BOTTOM) |
| { |
| dy += 2; |
| } |
| else |
| { |
| dy += 1; |
| } |
| } |
| |
| style.zoom = s; |
| style.left = Math.round(this.bounds.x + left_fix - w / 2) + 'px'; |
| style.top = Math.round(this.bounds.y + top_fix - h / 2 + dy) + 'px'; |
| }; |
| |
| /** |
| * Function: updateValue |
| * |
| * Updates the HTML node(s) to reflect the latest bounds and scale. |
| */ |
| mxText.prototype.updateValue = function() |
| { |
| if (mxUtils.isNode(this.value)) |
| { |
| this.node.innerHTML = ''; |
| this.node.appendChild(this.value); |
| } |
| else |
| { |
| var val = this.value; |
| |
| if (this.dialect != mxConstants.DIALECT_STRICTHTML) |
| { |
| val = mxUtils.htmlEntities(val, false); |
| } |
| |
| // Handles trailing newlines to make sure they are visible in rendering output |
| val = mxUtils.replaceTrailingNewlines(val, '<div><br></div>'); |
| val = (this.replaceLinefeeds) ? val.replace(/\n/g, '<br/>') : val; |
| var bg = (this.background != null && this.background != mxConstants.NONE) ? this.background : null; |
| var bd = (this.border != null && this.border != mxConstants.NONE) ? this.border : null; |
| |
| if (this.overflow == 'fill' || this.overflow == 'width') |
| { |
| if (bg != null) |
| { |
| this.node.style.backgroundColor = bg; |
| } |
| |
| if (bd != null) |
| { |
| this.node.style.border = '1px solid ' + bd; |
| } |
| } |
| else |
| { |
| var css = ''; |
| |
| if (bg != null) |
| { |
| css += 'background-color:' + bg + ';'; |
| } |
| |
| if (bd != null) |
| { |
| css += 'border:1px solid ' + bd + ';'; |
| } |
| |
| // Wrapper DIV for background, zoom needed for inline in quirks |
| // and to measure wrapped font sizes in all browsers |
| // FIXME: Background size in quirks mode for wrapped text |
| var lh = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (this.size * mxConstants.LINE_HEIGHT) + 'px' : |
| mxConstants.LINE_HEIGHT; |
| val = '<div style="zoom:1;' + css + 'display:inline-block;_display:inline;text-decoration:inherit;' + |
| 'padding-bottom:1px;padding-right:1px;line-height:' + lh + '">' + val + '</div>'; |
| } |
| |
| this.node.innerHTML = val; |
| |
| // Sets text direction |
| var divs = this.node.getElementsByTagName('div'); |
| |
| if (divs.length > 0) |
| { |
| var dir = this.textDirection; |
| |
| if (dir == mxConstants.TEXT_DIRECTION_AUTO && this.dialect != mxConstants.DIALECT_STRICTHTML) |
| { |
| dir = this.getAutoDirection(); |
| } |
| |
| if (dir == mxConstants.TEXT_DIRECTION_LTR || dir == mxConstants.TEXT_DIRECTION_RTL) |
| { |
| divs[divs.length - 1].setAttribute('dir', dir); |
| } |
| else |
| { |
| divs[divs.length - 1].removeAttribute('dir'); |
| } |
| } |
| } |
| }; |
| |
| /** |
| * Function: updateFont |
| * |
| * Updates the HTML node(s) to reflect the latest bounds and scale. |
| */ |
| mxText.prototype.updateFont = function(node) |
| { |
| var style = node.style; |
| |
| style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (this.size * mxConstants.LINE_HEIGHT) + 'px' : mxConstants.LINE_HEIGHT; |
| style.fontSize = this.size + 'px'; |
| style.fontFamily = this.family; |
| style.verticalAlign = 'top'; |
| style.color = this.color; |
| |
| if ((this.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) |
| { |
| style.fontWeight = 'bold'; |
| } |
| else |
| { |
| style.fontWeight = ''; |
| } |
| |
| if ((this.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) |
| { |
| style.fontStyle = 'italic'; |
| } |
| else |
| { |
| style.fontStyle = ''; |
| } |
| |
| if ((this.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) |
| { |
| style.textDecoration = 'underline'; |
| } |
| else |
| { |
| style.textDecoration = ''; |
| } |
| |
| if (this.align == mxConstants.ALIGN_CENTER) |
| { |
| style.textAlign = 'center'; |
| } |
| else if (this.align == mxConstants.ALIGN_RIGHT) |
| { |
| style.textAlign = 'right'; |
| } |
| else |
| { |
| style.textAlign = 'left'; |
| } |
| }; |
| |
| /** |
| * Function: updateSize |
| * |
| * Updates the HTML node(s) to reflect the latest bounds and scale. |
| */ |
| mxText.prototype.updateSize = function(node, enableWrap) |
| { |
| var w = Math.max(0, Math.round(this.bounds.width / this.scale)); |
| var h = Math.max(0, Math.round(this.bounds.height / this.scale)); |
| var style = node.style; |
| |
| // NOTE: Do not use maxWidth here because wrapping will |
| // go wrong if the cell is outside of the viewable area |
| if (this.clipped) |
| { |
| style.overflow = 'hidden'; |
| |
| if (!mxClient.IS_QUIRKS) |
| { |
| style.maxHeight = h + 'px'; |
| style.maxWidth = w + 'px'; |
| } |
| else |
| { |
| style.width = w + 'px'; |
| } |
| } |
| else if (this.overflow == 'fill') |
| { |
| style.width = (w + 1) + 'px'; |
| style.height = (h + 1) + 'px'; |
| style.overflow = 'hidden'; |
| } |
| else if (this.overflow == 'width') |
| { |
| style.width = (w + 1) + 'px'; |
| style.maxHeight = (h + 1) + 'px'; |
| style.overflow = 'hidden'; |
| } |
| |
| if (this.wrap && w > 0) |
| { |
| style.wordWrap = mxConstants.WORD_WRAP; |
| style.whiteSpace = 'normal'; |
| style.width = w + 'px'; |
| |
| if (enableWrap && this.overflow != 'fill' && this.overflow != 'width') |
| { |
| var sizeDiv = node; |
| |
| if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV') |
| { |
| sizeDiv = sizeDiv.firstChild; |
| |
| if (node.style.wordWrap == 'break-word') |
| { |
| sizeDiv.style.width = '100%'; |
| } |
| } |
| |
| var tmp = sizeDiv.offsetWidth; |
| |
| // Workaround for text measuring in hidden containers |
| if (tmp == 0) |
| { |
| var prev = node.parentNode; |
| node.style.visibility = 'hidden'; |
| document.body.appendChild(node); |
| tmp = sizeDiv.offsetWidth; |
| node.style.visibility = ''; |
| prev.appendChild(node); |
| } |
| |
| tmp += 3; |
| |
| if (this.clipped) |
| { |
| tmp = Math.min(tmp, w); |
| } |
| |
| style.width = tmp + 'px'; |
| } |
| } |
| else |
| { |
| style.whiteSpace = 'nowrap'; |
| } |
| }; |
| |
| /** |
| * Function: getMargin |
| * |
| * Returns the spacing as an <mxPoint>. |
| */ |
| mxText.prototype.updateMargin = function() |
| { |
| this.margin = mxUtils.getAlignmentAsPoint(this.align, this.valign); |
| }; |
| |
| /** |
| * Function: getSpacing |
| * |
| * Returns the spacing as an <mxPoint>. |
| */ |
| mxText.prototype.getSpacing = function() |
| { |
| var dx = 0; |
| var dy = 0; |
| |
| if (this.align == mxConstants.ALIGN_CENTER) |
| { |
| dx = (this.spacingLeft - this.spacingRight) / 2; |
| } |
| else if (this.align == mxConstants.ALIGN_RIGHT) |
| { |
| dx = -this.spacingRight - this.baseSpacingRight; |
| } |
| else |
| { |
| dx = this.spacingLeft + this.baseSpacingLeft; |
| } |
| |
| if (this.valign == mxConstants.ALIGN_MIDDLE) |
| { |
| dy = (this.spacingTop - this.spacingBottom) / 2; |
| } |
| else if (this.valign == mxConstants.ALIGN_BOTTOM) |
| { |
| dy = -this.spacingBottom - this.baseSpacingBottom;; |
| } |
| else |
| { |
| dy = this.spacingTop + this.baseSpacingTop; |
| } |
| |
| return new mxPoint(dx, dy); |
| }; |