blob: a57499880f58d63af6265532b56d12a6ee281871 [file] [log] [blame]
/**
* 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>&nbsp;</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);
};