547 lines
19 KiB
JavaScript
547 lines
19 KiB
JavaScript
|
var _util = require("../../core/util");
|
||
|
|
||
|
var retrieve2 = _util.retrieve2;
|
||
|
var retrieve3 = _util.retrieve3;
|
||
|
var each = _util.each;
|
||
|
var normalizeCssArray = _util.normalizeCssArray;
|
||
|
var isString = _util.isString;
|
||
|
var isObject = _util.isObject;
|
||
|
|
||
|
var textContain = require("../../contain/text");
|
||
|
|
||
|
var roundRectHelper = require("./roundRect");
|
||
|
|
||
|
var imageHelper = require("./image");
|
||
|
|
||
|
var fixShadow = require("./fixShadow");
|
||
|
|
||
|
var _constant = require("../constant");
|
||
|
|
||
|
var ContextCachedBy = _constant.ContextCachedBy;
|
||
|
var WILL_BE_RESTORED = _constant.WILL_BE_RESTORED;
|
||
|
var DEFAULT_FONT = textContain.DEFAULT_FONT; // TODO: Have not support 'start', 'end' yet.
|
||
|
|
||
|
var VALID_TEXT_ALIGN = {
|
||
|
left: 1,
|
||
|
right: 1,
|
||
|
center: 1
|
||
|
};
|
||
|
var VALID_TEXT_VERTICAL_ALIGN = {
|
||
|
top: 1,
|
||
|
bottom: 1,
|
||
|
middle: 1
|
||
|
}; // Different from `STYLE_COMMON_PROPS` of `graphic/Style`,
|
||
|
// the default value of shadowColor is `'transparent'`.
|
||
|
|
||
|
var SHADOW_STYLE_COMMON_PROPS = [['textShadowBlur', 'shadowBlur', 0], ['textShadowOffsetX', 'shadowOffsetX', 0], ['textShadowOffsetY', 'shadowOffsetY', 0], ['textShadowColor', 'shadowColor', 'transparent']];
|
||
|
var _tmpTextPositionResult = {};
|
||
|
var _tmpBoxPositionResult = {};
|
||
|
/**
|
||
|
* @param {module:zrender/graphic/Style} style
|
||
|
* @return {module:zrender/graphic/Style} The input style.
|
||
|
*/
|
||
|
|
||
|
function normalizeTextStyle(style) {
|
||
|
normalizeStyle(style);
|
||
|
each(style.rich, normalizeStyle);
|
||
|
return style;
|
||
|
}
|
||
|
|
||
|
function normalizeStyle(style) {
|
||
|
if (style) {
|
||
|
style.font = textContain.makeFont(style);
|
||
|
var textAlign = style.textAlign;
|
||
|
textAlign === 'middle' && (textAlign = 'center');
|
||
|
style.textAlign = textAlign == null || VALID_TEXT_ALIGN[textAlign] ? textAlign : 'left'; // Compatible with textBaseline.
|
||
|
|
||
|
var textVerticalAlign = style.textVerticalAlign || style.textBaseline;
|
||
|
textVerticalAlign === 'center' && (textVerticalAlign = 'middle');
|
||
|
style.textVerticalAlign = textVerticalAlign == null || VALID_TEXT_VERTICAL_ALIGN[textVerticalAlign] ? textVerticalAlign : 'top';
|
||
|
var textPadding = style.textPadding;
|
||
|
|
||
|
if (textPadding) {
|
||
|
style.textPadding = normalizeCssArray(style.textPadding);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
/**
|
||
|
* @param {CanvasRenderingContext2D} ctx
|
||
|
* @param {string} text
|
||
|
* @param {module:zrender/graphic/Style} style
|
||
|
* @param {Object|boolean} [rect] {x, y, width, height}
|
||
|
* If set false, rect text is not used.
|
||
|
* @param {Element|module:zrender/graphic/helper/constant.WILL_BE_RESTORED} [prevEl] For ctx prop cache.
|
||
|
*/
|
||
|
|
||
|
|
||
|
function renderText(hostEl, ctx, text, style, rect, prevEl) {
|
||
|
style.rich ? renderRichText(hostEl, ctx, text, style, rect, prevEl) : renderPlainText(hostEl, ctx, text, style, rect, prevEl);
|
||
|
} // Avoid setting to ctx according to prevEl if possible for
|
||
|
// performance in scenarios of large amount text.
|
||
|
|
||
|
|
||
|
function renderPlainText(hostEl, ctx, text, style, rect, prevEl) {
|
||
|
'use strict';
|
||
|
|
||
|
var needDrawBg = needDrawBackground(style);
|
||
|
var prevStyle;
|
||
|
var checkCache = false;
|
||
|
var cachedByMe = ctx.__attrCachedBy === ContextCachedBy.PLAIN_TEXT; // Only take and check cache for `Text` el, but not RectText.
|
||
|
|
||
|
if (prevEl !== WILL_BE_RESTORED) {
|
||
|
if (prevEl) {
|
||
|
prevStyle = prevEl.style;
|
||
|
checkCache = !needDrawBg && cachedByMe && prevStyle;
|
||
|
} // Prevent from using cache in `Style::bind`, because of the case:
|
||
|
// ctx property is modified by other properties than `Style::bind`
|
||
|
// used, and Style::bind is called next.
|
||
|
|
||
|
|
||
|
ctx.__attrCachedBy = needDrawBg ? ContextCachedBy.NONE : ContextCachedBy.PLAIN_TEXT;
|
||
|
} // Since this will be restored, prevent from using these props to check cache in the next
|
||
|
// entering of this method. But do not need to clear other cache like `Style::bind`.
|
||
|
else if (cachedByMe) {
|
||
|
ctx.__attrCachedBy = ContextCachedBy.NONE;
|
||
|
}
|
||
|
|
||
|
var styleFont = style.font || DEFAULT_FONT; // PENDING
|
||
|
// Only `Text` el set `font` and keep it (`RectText` will restore). So theoretically
|
||
|
// we can make font cache on ctx, which can cache for text el that are discontinuous.
|
||
|
// But layer save/restore needed to be considered.
|
||
|
// if (styleFont !== ctx.__fontCache) {
|
||
|
// ctx.font = styleFont;
|
||
|
// if (prevEl !== WILL_BE_RESTORED) {
|
||
|
// ctx.__fontCache = styleFont;
|
||
|
// }
|
||
|
// }
|
||
|
|
||
|
if (!checkCache || styleFont !== (prevStyle.font || DEFAULT_FONT)) {
|
||
|
ctx.font = styleFont;
|
||
|
} // Use the final font from context-2d, because the final
|
||
|
// font might not be the style.font when it is illegal.
|
||
|
// But get `ctx.font` might be time consuming.
|
||
|
|
||
|
|
||
|
var computedFont = hostEl.__computedFont;
|
||
|
|
||
|
if (hostEl.__styleFont !== styleFont) {
|
||
|
hostEl.__styleFont = styleFont;
|
||
|
computedFont = hostEl.__computedFont = ctx.font;
|
||
|
}
|
||
|
|
||
|
var textPadding = style.textPadding;
|
||
|
var textLineHeight = style.textLineHeight;
|
||
|
var contentBlock = hostEl.__textCotentBlock;
|
||
|
|
||
|
if (!contentBlock || hostEl.__dirtyText) {
|
||
|
contentBlock = hostEl.__textCotentBlock = textContain.parsePlainText(text, computedFont, textPadding, textLineHeight, style.truncate);
|
||
|
}
|
||
|
|
||
|
var outerHeight = contentBlock.outerHeight;
|
||
|
var textLines = contentBlock.lines;
|
||
|
var lineHeight = contentBlock.lineHeight;
|
||
|
var boxPos = getBoxPosition(_tmpBoxPositionResult, hostEl, style, rect);
|
||
|
var baseX = boxPos.baseX;
|
||
|
var baseY = boxPos.baseY;
|
||
|
var textAlign = boxPos.textAlign || 'left';
|
||
|
var textVerticalAlign = boxPos.textVerticalAlign; // Origin of textRotation should be the base point of text drawing.
|
||
|
|
||
|
applyTextRotation(ctx, style, rect, baseX, baseY);
|
||
|
var boxY = textContain.adjustTextY(baseY, outerHeight, textVerticalAlign);
|
||
|
var textX = baseX;
|
||
|
var textY = boxY;
|
||
|
|
||
|
if (needDrawBg || textPadding) {
|
||
|
// Consider performance, do not call getTextWidth util necessary.
|
||
|
var textWidth = textContain.getWidth(text, computedFont);
|
||
|
var outerWidth = textWidth;
|
||
|
textPadding && (outerWidth += textPadding[1] + textPadding[3]);
|
||
|
var boxX = textContain.adjustTextX(baseX, outerWidth, textAlign);
|
||
|
needDrawBg && drawBackground(hostEl, ctx, style, boxX, boxY, outerWidth, outerHeight);
|
||
|
|
||
|
if (textPadding) {
|
||
|
textX = getTextXForPadding(baseX, textAlign, textPadding);
|
||
|
textY += textPadding[0];
|
||
|
}
|
||
|
} // Always set textAlign and textBase line, because it is difficute to calculate
|
||
|
// textAlign from prevEl, and we dont sure whether textAlign will be reset if
|
||
|
// font set happened.
|
||
|
|
||
|
|
||
|
ctx.textAlign = textAlign; // Force baseline to be "middle". Otherwise, if using "top", the
|
||
|
// text will offset downward a little bit in font "Microsoft YaHei".
|
||
|
|
||
|
ctx.textBaseline = 'middle'; // Set text opacity
|
||
|
|
||
|
ctx.globalAlpha = style.opacity || 1; // Always set shadowBlur and shadowOffset to avoid leak from displayable.
|
||
|
|
||
|
for (var i = 0; i < SHADOW_STYLE_COMMON_PROPS.length; i++) {
|
||
|
var propItem = SHADOW_STYLE_COMMON_PROPS[i];
|
||
|
var styleProp = propItem[0];
|
||
|
var ctxProp = propItem[1];
|
||
|
var val = style[styleProp];
|
||
|
|
||
|
if (!checkCache || val !== prevStyle[styleProp]) {
|
||
|
ctx[ctxProp] = fixShadow(ctx, ctxProp, val || propItem[2]);
|
||
|
}
|
||
|
} // `textBaseline` is set as 'middle'.
|
||
|
|
||
|
|
||
|
textY += lineHeight / 2;
|
||
|
var textStrokeWidth = style.textStrokeWidth;
|
||
|
var textStrokeWidthPrev = checkCache ? prevStyle.textStrokeWidth : null;
|
||
|
var strokeWidthChanged = !checkCache || textStrokeWidth !== textStrokeWidthPrev;
|
||
|
var strokeChanged = !checkCache || strokeWidthChanged || style.textStroke !== prevStyle.textStroke;
|
||
|
var textStroke = getStroke(style.textStroke, textStrokeWidth);
|
||
|
var textFill = getFill(style.textFill);
|
||
|
|
||
|
if (textStroke) {
|
||
|
if (strokeWidthChanged) {
|
||
|
ctx.lineWidth = textStrokeWidth;
|
||
|
}
|
||
|
|
||
|
if (strokeChanged) {
|
||
|
ctx.strokeStyle = textStroke;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (textFill) {
|
||
|
if (!checkCache || style.textFill !== prevStyle.textFill) {
|
||
|
ctx.fillStyle = textFill;
|
||
|
}
|
||
|
} // Optimize simply, in most cases only one line exists.
|
||
|
|
||
|
|
||
|
if (textLines.length === 1) {
|
||
|
// Fill after stroke so the outline will not cover the main part.
|
||
|
textStroke && ctx.strokeText(textLines[0], textX, textY);
|
||
|
textFill && ctx.fillText(textLines[0], textX, textY);
|
||
|
} else {
|
||
|
for (var i = 0; i < textLines.length; i++) {
|
||
|
// Fill after stroke so the outline will not cover the main part.
|
||
|
textStroke && ctx.strokeText(textLines[i], textX, textY);
|
||
|
textFill && ctx.fillText(textLines[i], textX, textY);
|
||
|
textY += lineHeight;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function renderRichText(hostEl, ctx, text, style, rect, prevEl) {
|
||
|
// Do not do cache for rich text because of the complexity.
|
||
|
// But `RectText` this will be restored, do not need to clear other cache like `Style::bind`.
|
||
|
if (prevEl !== WILL_BE_RESTORED) {
|
||
|
ctx.__attrCachedBy = ContextCachedBy.NONE;
|
||
|
}
|
||
|
|
||
|
var contentBlock = hostEl.__textCotentBlock;
|
||
|
|
||
|
if (!contentBlock || hostEl.__dirtyText) {
|
||
|
contentBlock = hostEl.__textCotentBlock = textContain.parseRichText(text, style);
|
||
|
}
|
||
|
|
||
|
drawRichText(hostEl, ctx, contentBlock, style, rect);
|
||
|
}
|
||
|
|
||
|
function drawRichText(hostEl, ctx, contentBlock, style, rect) {
|
||
|
var contentWidth = contentBlock.width;
|
||
|
var outerWidth = contentBlock.outerWidth;
|
||
|
var outerHeight = contentBlock.outerHeight;
|
||
|
var textPadding = style.textPadding;
|
||
|
var boxPos = getBoxPosition(_tmpBoxPositionResult, hostEl, style, rect);
|
||
|
var baseX = boxPos.baseX;
|
||
|
var baseY = boxPos.baseY;
|
||
|
var textAlign = boxPos.textAlign;
|
||
|
var textVerticalAlign = boxPos.textVerticalAlign; // Origin of textRotation should be the base point of text drawing.
|
||
|
|
||
|
applyTextRotation(ctx, style, rect, baseX, baseY);
|
||
|
var boxX = textContain.adjustTextX(baseX, outerWidth, textAlign);
|
||
|
var boxY = textContain.adjustTextY(baseY, outerHeight, textVerticalAlign);
|
||
|
var xLeft = boxX;
|
||
|
var lineTop = boxY;
|
||
|
|
||
|
if (textPadding) {
|
||
|
xLeft += textPadding[3];
|
||
|
lineTop += textPadding[0];
|
||
|
}
|
||
|
|
||
|
var xRight = xLeft + contentWidth;
|
||
|
needDrawBackground(style) && drawBackground(hostEl, ctx, style, boxX, boxY, outerWidth, outerHeight);
|
||
|
|
||
|
for (var i = 0; i < contentBlock.lines.length; i++) {
|
||
|
var line = contentBlock.lines[i];
|
||
|
var tokens = line.tokens;
|
||
|
var tokenCount = tokens.length;
|
||
|
var lineHeight = line.lineHeight;
|
||
|
var usedWidth = line.width;
|
||
|
var leftIndex = 0;
|
||
|
var lineXLeft = xLeft;
|
||
|
var lineXRight = xRight;
|
||
|
var rightIndex = tokenCount - 1;
|
||
|
var token;
|
||
|
|
||
|
while (leftIndex < tokenCount && (token = tokens[leftIndex], !token.textAlign || token.textAlign === 'left')) {
|
||
|
placeToken(hostEl, ctx, token, style, lineHeight, lineTop, lineXLeft, 'left');
|
||
|
usedWidth -= token.width;
|
||
|
lineXLeft += token.width;
|
||
|
leftIndex++;
|
||
|
}
|
||
|
|
||
|
while (rightIndex >= 0 && (token = tokens[rightIndex], token.textAlign === 'right')) {
|
||
|
placeToken(hostEl, ctx, token, style, lineHeight, lineTop, lineXRight, 'right');
|
||
|
usedWidth -= token.width;
|
||
|
lineXRight -= token.width;
|
||
|
rightIndex--;
|
||
|
} // The other tokens are placed as textAlign 'center' if there is enough space.
|
||
|
|
||
|
|
||
|
lineXLeft += (contentWidth - (lineXLeft - xLeft) - (xRight - lineXRight) - usedWidth) / 2;
|
||
|
|
||
|
while (leftIndex <= rightIndex) {
|
||
|
token = tokens[leftIndex]; // Consider width specified by user, use 'center' rather than 'left'.
|
||
|
|
||
|
placeToken(hostEl, ctx, token, style, lineHeight, lineTop, lineXLeft + token.width / 2, 'center');
|
||
|
lineXLeft += token.width;
|
||
|
leftIndex++;
|
||
|
}
|
||
|
|
||
|
lineTop += lineHeight;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function applyTextRotation(ctx, style, rect, x, y) {
|
||
|
// textRotation only apply in RectText.
|
||
|
if (rect && style.textRotation) {
|
||
|
var origin = style.textOrigin;
|
||
|
|
||
|
if (origin === 'center') {
|
||
|
x = rect.width / 2 + rect.x;
|
||
|
y = rect.height / 2 + rect.y;
|
||
|
} else if (origin) {
|
||
|
x = origin[0] + rect.x;
|
||
|
y = origin[1] + rect.y;
|
||
|
}
|
||
|
|
||
|
ctx.translate(x, y); // Positive: anticlockwise
|
||
|
|
||
|
ctx.rotate(-style.textRotation);
|
||
|
ctx.translate(-x, -y);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function placeToken(hostEl, ctx, token, style, lineHeight, lineTop, x, textAlign) {
|
||
|
var tokenStyle = style.rich[token.styleName] || {};
|
||
|
tokenStyle.text = token.text; // 'ctx.textBaseline' is always set as 'middle', for sake of
|
||
|
// the bias of "Microsoft YaHei".
|
||
|
|
||
|
var textVerticalAlign = token.textVerticalAlign;
|
||
|
var y = lineTop + lineHeight / 2;
|
||
|
|
||
|
if (textVerticalAlign === 'top') {
|
||
|
y = lineTop + token.height / 2;
|
||
|
} else if (textVerticalAlign === 'bottom') {
|
||
|
y = lineTop + lineHeight - token.height / 2;
|
||
|
}
|
||
|
|
||
|
!token.isLineHolder && needDrawBackground(tokenStyle) && drawBackground(hostEl, ctx, tokenStyle, textAlign === 'right' ? x - token.width : textAlign === 'center' ? x - token.width / 2 : x, y - token.height / 2, token.width, token.height);
|
||
|
var textPadding = token.textPadding;
|
||
|
|
||
|
if (textPadding) {
|
||
|
x = getTextXForPadding(x, textAlign, textPadding);
|
||
|
y -= token.height / 2 - textPadding[2] - token.textHeight / 2;
|
||
|
}
|
||
|
|
||
|
setCtx(ctx, 'shadowBlur', retrieve3(tokenStyle.textShadowBlur, style.textShadowBlur, 0));
|
||
|
setCtx(ctx, 'shadowColor', tokenStyle.textShadowColor || style.textShadowColor || 'transparent');
|
||
|
setCtx(ctx, 'shadowOffsetX', retrieve3(tokenStyle.textShadowOffsetX, style.textShadowOffsetX, 0));
|
||
|
setCtx(ctx, 'shadowOffsetY', retrieve3(tokenStyle.textShadowOffsetY, style.textShadowOffsetY, 0));
|
||
|
setCtx(ctx, 'textAlign', textAlign); // Force baseline to be "middle". Otherwise, if using "top", the
|
||
|
// text will offset downward a little bit in font "Microsoft YaHei".
|
||
|
|
||
|
setCtx(ctx, 'textBaseline', 'middle');
|
||
|
setCtx(ctx, 'font', token.font || DEFAULT_FONT);
|
||
|
var textStroke = getStroke(tokenStyle.textStroke || style.textStroke, textStrokeWidth);
|
||
|
var textFill = getFill(tokenStyle.textFill || style.textFill);
|
||
|
var textStrokeWidth = retrieve2(tokenStyle.textStrokeWidth, style.textStrokeWidth); // Fill after stroke so the outline will not cover the main part.
|
||
|
|
||
|
if (textStroke) {
|
||
|
setCtx(ctx, 'lineWidth', textStrokeWidth);
|
||
|
setCtx(ctx, 'strokeStyle', textStroke);
|
||
|
ctx.strokeText(token.text, x, y);
|
||
|
}
|
||
|
|
||
|
if (textFill) {
|
||
|
setCtx(ctx, 'fillStyle', textFill);
|
||
|
ctx.fillText(token.text, x, y);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function needDrawBackground(style) {
|
||
|
return !!(style.textBackgroundColor || style.textBorderWidth && style.textBorderColor);
|
||
|
} // style: {textBackgroundColor, textBorderWidth, textBorderColor, textBorderRadius, text}
|
||
|
// shape: {x, y, width, height}
|
||
|
|
||
|
|
||
|
function drawBackground(hostEl, ctx, style, x, y, width, height) {
|
||
|
var textBackgroundColor = style.textBackgroundColor;
|
||
|
var textBorderWidth = style.textBorderWidth;
|
||
|
var textBorderColor = style.textBorderColor;
|
||
|
var isPlainBg = isString(textBackgroundColor);
|
||
|
setCtx(ctx, 'shadowBlur', style.textBoxShadowBlur || 0);
|
||
|
setCtx(ctx, 'shadowColor', style.textBoxShadowColor || 'transparent');
|
||
|
setCtx(ctx, 'shadowOffsetX', style.textBoxShadowOffsetX || 0);
|
||
|
setCtx(ctx, 'shadowOffsetY', style.textBoxShadowOffsetY || 0);
|
||
|
|
||
|
if (isPlainBg || textBorderWidth && textBorderColor) {
|
||
|
ctx.beginPath();
|
||
|
var textBorderRadius = style.textBorderRadius;
|
||
|
|
||
|
if (!textBorderRadius) {
|
||
|
ctx.rect(x, y, width, height);
|
||
|
} else {
|
||
|
roundRectHelper.buildPath(ctx, {
|
||
|
x: x,
|
||
|
y: y,
|
||
|
width: width,
|
||
|
height: height,
|
||
|
r: textBorderRadius
|
||
|
});
|
||
|
}
|
||
|
|
||
|
ctx.closePath();
|
||
|
}
|
||
|
|
||
|
if (isPlainBg) {
|
||
|
setCtx(ctx, 'fillStyle', textBackgroundColor);
|
||
|
|
||
|
if (style.fillOpacity != null) {
|
||
|
var originalGlobalAlpha = ctx.globalAlpha;
|
||
|
ctx.globalAlpha = style.fillOpacity * style.opacity;
|
||
|
ctx.fill();
|
||
|
ctx.globalAlpha = originalGlobalAlpha;
|
||
|
} else {
|
||
|
ctx.fill();
|
||
|
}
|
||
|
} else if (isObject(textBackgroundColor)) {
|
||
|
var image = textBackgroundColor.image;
|
||
|
image = imageHelper.createOrUpdateImage(image, null, hostEl, onBgImageLoaded, textBackgroundColor);
|
||
|
|
||
|
if (image && imageHelper.isImageReady(image)) {
|
||
|
ctx.drawImage(image, x, y, width, height);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (textBorderWidth && textBorderColor) {
|
||
|
setCtx(ctx, 'lineWidth', textBorderWidth);
|
||
|
setCtx(ctx, 'strokeStyle', textBorderColor);
|
||
|
|
||
|
if (style.strokeOpacity != null) {
|
||
|
var originalGlobalAlpha = ctx.globalAlpha;
|
||
|
ctx.globalAlpha = style.strokeOpacity * style.opacity;
|
||
|
ctx.stroke();
|
||
|
ctx.globalAlpha = originalGlobalAlpha;
|
||
|
} else {
|
||
|
ctx.stroke();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function onBgImageLoaded(image, textBackgroundColor) {
|
||
|
// Replace image, so that `contain/text.js#parseRichText`
|
||
|
// will get correct result in next tick.
|
||
|
textBackgroundColor.image = image;
|
||
|
}
|
||
|
|
||
|
function getBoxPosition(out, hostEl, style, rect) {
|
||
|
var baseX = style.x || 0;
|
||
|
var baseY = style.y || 0;
|
||
|
var textAlign = style.textAlign;
|
||
|
var textVerticalAlign = style.textVerticalAlign; // Text position represented by coord
|
||
|
|
||
|
if (rect) {
|
||
|
var textPosition = style.textPosition;
|
||
|
|
||
|
if (textPosition instanceof Array) {
|
||
|
// Percent
|
||
|
baseX = rect.x + parsePercent(textPosition[0], rect.width);
|
||
|
baseY = rect.y + parsePercent(textPosition[1], rect.height);
|
||
|
} else {
|
||
|
var res = hostEl && hostEl.calculateTextPosition ? hostEl.calculateTextPosition(_tmpTextPositionResult, style, rect) : textContain.calculateTextPosition(_tmpTextPositionResult, style, rect);
|
||
|
baseX = res.x;
|
||
|
baseY = res.y; // Default align and baseline when has textPosition
|
||
|
|
||
|
textAlign = textAlign || res.textAlign;
|
||
|
textVerticalAlign = textVerticalAlign || res.textVerticalAlign;
|
||
|
} // textOffset is only support in RectText, otherwise
|
||
|
// we have to adjust boundingRect for textOffset.
|
||
|
|
||
|
|
||
|
var textOffset = style.textOffset;
|
||
|
|
||
|
if (textOffset) {
|
||
|
baseX += textOffset[0];
|
||
|
baseY += textOffset[1];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
out = out || {};
|
||
|
out.baseX = baseX;
|
||
|
out.baseY = baseY;
|
||
|
out.textAlign = textAlign;
|
||
|
out.textVerticalAlign = textVerticalAlign;
|
||
|
return out;
|
||
|
}
|
||
|
|
||
|
function setCtx(ctx, prop, value) {
|
||
|
ctx[prop] = fixShadow(ctx, prop, value);
|
||
|
return ctx[prop];
|
||
|
}
|
||
|
/**
|
||
|
* @param {string} [stroke] If specified, do not check style.textStroke.
|
||
|
* @param {string} [lineWidth] If specified, do not check style.textStroke.
|
||
|
* @param {number} style
|
||
|
*/
|
||
|
|
||
|
|
||
|
function getStroke(stroke, lineWidth) {
|
||
|
return stroke == null || lineWidth <= 0 || stroke === 'transparent' || stroke === 'none' ? null // TODO pattern and gradient?
|
||
|
: stroke.image || stroke.colorStops ? '#000' : stroke;
|
||
|
}
|
||
|
|
||
|
function getFill(fill) {
|
||
|
return fill == null || fill === 'none' ? null // TODO pattern and gradient?
|
||
|
: fill.image || fill.colorStops ? '#000' : fill;
|
||
|
}
|
||
|
|
||
|
function parsePercent(value, maxValue) {
|
||
|
if (typeof value === 'string') {
|
||
|
if (value.lastIndexOf('%') >= 0) {
|
||
|
return parseFloat(value) / 100 * maxValue;
|
||
|
}
|
||
|
|
||
|
return parseFloat(value);
|
||
|
}
|
||
|
|
||
|
return value;
|
||
|
}
|
||
|
|
||
|
function getTextXForPadding(x, textAlign, textPadding) {
|
||
|
return textAlign === 'right' ? x - textPadding[1] : textAlign === 'center' ? x + textPadding[3] / 2 - textPadding[1] / 2 : x + textPadding[3];
|
||
|
}
|
||
|
/**
|
||
|
* @param {string} text
|
||
|
* @param {module:zrender/Style} style
|
||
|
* @return {boolean}
|
||
|
*/
|
||
|
|
||
|
|
||
|
function needDrawText(text, style) {
|
||
|
return text != null && (text || style.textBackgroundColor || style.textBorderWidth && style.textBorderColor || style.textPadding);
|
||
|
}
|
||
|
|
||
|
exports.normalizeTextStyle = normalizeTextStyle;
|
||
|
exports.renderText = renderText;
|
||
|
exports.getBoxPosition = getBoxPosition;
|
||
|
exports.getStroke = getStroke;
|
||
|
exports.getFill = getFill;
|
||
|
exports.parsePercent = parsePercent;
|
||
|
exports.needDrawText = needDrawText;
|