| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547 | 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;
 |