first commit

This commit is contained in:
jefferyzhao
2025-07-31 17:44:12 +08:00
commit b9bdc8598b
42390 changed files with 4467935 additions and 0 deletions

View File

@ -0,0 +1,56 @@
var env = require("../../core/env");
// Fix weird bug in some version of IE11 (like 11.0.9600.178**),
// where exception "unexpected call to method or property access"
// might be thrown when calling ctx.fill or ctx.stroke after a path
// whose area size is zero is drawn and ctx.clip() is called and
// shadowBlur is set. See #4572, #3112, #5777.
// (e.g.,
// ctx.moveTo(10, 10);
// ctx.lineTo(20, 10);
// ctx.closePath();
// ctx.clip();
// ctx.shadowBlur = 10;
// ...
// ctx.fill();
// )
var shadowTemp = [['shadowBlur', 0], ['shadowColor', '#000'], ['shadowOffsetX', 0], ['shadowOffsetY', 0]];
function _default(orignalBrush) {
// version string can be: '11.0'
return env.browser.ie && env.browser.version >= 11 ? function () {
var clipPaths = this.__clipPaths;
var style = this.style;
var modified;
if (clipPaths) {
for (var i = 0; i < clipPaths.length; i++) {
var clipPath = clipPaths[i];
var shape = clipPath && clipPath.shape;
var type = clipPath && clipPath.type;
if (shape && (type === 'sector' && shape.startAngle === shape.endAngle || type === 'rect' && (!shape.width || !shape.height))) {
for (var j = 0; j < shadowTemp.length; j++) {
// It is save to put shadowTemp static, because shadowTemp
// will be all modified each item brush called.
shadowTemp[j][2] = style[shadowTemp[j][0]];
style[shadowTemp[j][0]] = shadowTemp[j][1];
}
modified = true;
break;
}
}
}
orignalBrush.apply(this, arguments);
if (modified) {
for (var j = 0; j < shadowTemp.length; j++) {
style[shadowTemp[j][0]] = shadowTemp[j][2];
}
}
} : orignalBrush;
}
module.exports = _default;

21
node_modules/zrender/lib/graphic/helper/fixShadow.js generated vendored Normal file
View File

@ -0,0 +1,21 @@
var SHADOW_PROPS = {
'shadowBlur': 1,
'shadowOffsetX': 1,
'shadowOffsetY': 1,
'textShadowBlur': 1,
'textShadowOffsetX': 1,
'textShadowOffsetY': 1,
'textBoxShadowBlur': 1,
'textBoxShadowOffsetX': 1,
'textBoxShadowOffsetY': 1
};
function _default(ctx, propName, value) {
if (SHADOW_PROPS.hasOwnProperty(propName)) {
return value *= ctx.dpr;
}
return value;
}
module.exports = _default;

88
node_modules/zrender/lib/graphic/helper/image.js generated vendored Normal file
View File

@ -0,0 +1,88 @@
var LRU = require("../../core/LRU");
var globalImageCache = new LRU(50);
/**
* @param {string|HTMLImageElement|HTMLCanvasElement|Canvas} newImageOrSrc
* @return {HTMLImageElement|HTMLCanvasElement|Canvas} image
*/
function findExistImage(newImageOrSrc) {
if (typeof newImageOrSrc === 'string') {
var cachedImgObj = globalImageCache.get(newImageOrSrc);
return cachedImgObj && cachedImgObj.image;
} else {
return newImageOrSrc;
}
}
/**
* Caution: User should cache loaded images, but not just count on LRU.
* Consider if required images more than LRU size, will dead loop occur?
*
* @param {string|HTMLImageElement|HTMLCanvasElement|Canvas} newImageOrSrc
* @param {HTMLImageElement|HTMLCanvasElement|Canvas} image Existent image.
* @param {module:zrender/Element} [hostEl] For calling `dirty`.
* @param {Function} [cb] params: (image, cbPayload)
* @param {Object} [cbPayload] Payload on cb calling.
* @return {HTMLImageElement|HTMLCanvasElement|Canvas} image
*/
function createOrUpdateImage(newImageOrSrc, image, hostEl, cb, cbPayload) {
if (!newImageOrSrc) {
return image;
} else if (typeof newImageOrSrc === 'string') {
// Image should not be loaded repeatly.
if (image && image.__zrImageSrc === newImageOrSrc || !hostEl) {
return image;
} // Only when there is no existent image or existent image src
// is different, this method is responsible for load.
var cachedImgObj = globalImageCache.get(newImageOrSrc);
var pendingWrap = {
hostEl: hostEl,
cb: cb,
cbPayload: cbPayload
};
if (cachedImgObj) {
image = cachedImgObj.image;
!isImageReady(image) && cachedImgObj.pending.push(pendingWrap);
} else {
image = new Image();
image.onload = image.onerror = imageOnLoad;
globalImageCache.put(newImageOrSrc, image.__cachedImgObj = {
image: image,
pending: [pendingWrap]
});
image.src = image.__zrImageSrc = newImageOrSrc;
}
return image;
} // newImageOrSrc is an HTMLImageElement or HTMLCanvasElement or Canvas
else {
return newImageOrSrc;
}
}
function imageOnLoad() {
var cachedImgObj = this.__cachedImgObj;
this.onload = this.onerror = this.__cachedImgObj = null;
for (var i = 0; i < cachedImgObj.pending.length; i++) {
var pendingWrap = cachedImgObj.pending[i];
var cb = pendingWrap.cb;
cb && cb(this, pendingWrap.cbPayload);
pendingWrap.hostEl.dirty();
}
cachedImgObj.pending.length = 0;
}
function isImageReady(image) {
return image && image.width && image.height;
}
exports.findExistImage = findExistImage;
exports.createOrUpdateImage = createOrUpdateImage;
exports.isImageReady = isImageReady;

37
node_modules/zrender/lib/graphic/helper/poly.js generated vendored Normal file
View File

@ -0,0 +1,37 @@
var smoothSpline = require("./smoothSpline");
var smoothBezier = require("./smoothBezier");
function buildPath(ctx, shape, closePath) {
var points = shape.points;
var smooth = shape.smooth;
if (points && points.length >= 2) {
if (smooth && smooth !== 'spline') {
var controlPoints = smoothBezier(points, smooth, closePath, shape.smoothConstraint);
ctx.moveTo(points[0][0], points[0][1]);
var len = points.length;
for (var i = 0; i < (closePath ? len : len - 1); i++) {
var cp1 = controlPoints[i * 2];
var cp2 = controlPoints[i * 2 + 1];
var p = points[(i + 1) % len];
ctx.bezierCurveTo(cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1]);
}
} else {
if (smooth === 'spline') {
points = smoothSpline(points, closePath);
}
ctx.moveTo(points[0][0], points[0][1]);
for (var i = 1, l = points.length; i < l; i++) {
ctx.lineTo(points[i][0], points[i][1]);
}
}
closePath && ctx.closePath();
}
}
exports.buildPath = buildPath;

90
node_modules/zrender/lib/graphic/helper/roundRect.js generated vendored Normal file
View File

@ -0,0 +1,90 @@
/**
* @param {Object} ctx
* @param {Object} shape
* @param {number} shape.x
* @param {number} shape.y
* @param {number} shape.width
* @param {number} shape.height
* @param {number} shape.r
*/
function buildPath(ctx, shape) {
var x = shape.x;
var y = shape.y;
var width = shape.width;
var height = shape.height;
var r = shape.r;
var r1;
var r2;
var r3;
var r4; // Convert width and height to positive for better borderRadius
if (width < 0) {
x = x + width;
width = -width;
}
if (height < 0) {
y = y + height;
height = -height;
}
if (typeof r === 'number') {
r1 = r2 = r3 = r4 = r;
} else if (r instanceof Array) {
if (r.length === 1) {
r1 = r2 = r3 = r4 = r[0];
} else if (r.length === 2) {
r1 = r3 = r[0];
r2 = r4 = r[1];
} else if (r.length === 3) {
r1 = r[0];
r2 = r4 = r[1];
r3 = r[2];
} else {
r1 = r[0];
r2 = r[1];
r3 = r[2];
r4 = r[3];
}
} else {
r1 = r2 = r3 = r4 = 0;
}
var total;
if (r1 + r2 > width) {
total = r1 + r2;
r1 *= width / total;
r2 *= width / total;
}
if (r3 + r4 > width) {
total = r3 + r4;
r3 *= width / total;
r4 *= width / total;
}
if (r2 + r3 > height) {
total = r2 + r3;
r2 *= height / total;
r3 *= height / total;
}
if (r1 + r4 > height) {
total = r1 + r4;
r1 *= height / total;
r4 *= height / total;
}
ctx.moveTo(x + r1, y);
ctx.lineTo(x + width - r2, y);
r2 !== 0 && ctx.arc(x + width - r2, y + r2, r2, -Math.PI / 2, 0);
ctx.lineTo(x + width, y + height - r3);
r3 !== 0 && ctx.arc(x + width - r3, y + height - r3, r3, 0, Math.PI / 2);
ctx.lineTo(x + r4, y + height);
r4 !== 0 && ctx.arc(x + r4, y + height - r4, r4, Math.PI / 2, Math.PI);
ctx.lineTo(x, y + r1);
r1 !== 0 && ctx.arc(x + r1, y + r1, r1, Math.PI, Math.PI * 1.5);
}
exports.buildPath = buildPath;

105
node_modules/zrender/lib/graphic/helper/smoothBezier.js generated vendored Normal file
View File

@ -0,0 +1,105 @@
var _vector = require("../../core/vector");
var v2Min = _vector.min;
var v2Max = _vector.max;
var v2Scale = _vector.scale;
var v2Distance = _vector.distance;
var v2Add = _vector.add;
var v2Clone = _vector.clone;
var v2Sub = _vector.sub;
/**
* 贝塞尔平滑曲线
* @module zrender/shape/util/smoothBezier
* @author pissang (https://www.github.com/pissang)
* Kener (@Kener-林峰, kener.linfeng@gmail.com)
* errorrik (errorrik@gmail.com)
*/
/**
* 贝塞尔平滑曲线
* @alias module:zrender/shape/util/smoothBezier
* @param {Array} points 线段顶点数组
* @param {number} smooth 平滑等级, 0-1
* @param {boolean} isLoop
* @param {Array} constraint 将计算出来的控制点约束在一个包围盒内
* 比如 [[0, 0], [100, 100]], 这个包围盒会与
* 整个折线的包围盒做一个并集用来约束控制点。
* @param {Array} 计算出来的控制点数组
*/
function _default(points, smooth, isLoop, constraint) {
var cps = [];
var v = [];
var v1 = [];
var v2 = [];
var prevPoint;
var nextPoint;
var min;
var max;
if (constraint) {
min = [Infinity, Infinity];
max = [-Infinity, -Infinity];
for (var i = 0, len = points.length; i < len; i++) {
v2Min(min, min, points[i]);
v2Max(max, max, points[i]);
} // 与指定的包围盒做并集
v2Min(min, min, constraint[0]);
v2Max(max, max, constraint[1]);
}
for (var i = 0, len = points.length; i < len; i++) {
var point = points[i];
if (isLoop) {
prevPoint = points[i ? i - 1 : len - 1];
nextPoint = points[(i + 1) % len];
} else {
if (i === 0 || i === len - 1) {
cps.push(v2Clone(points[i]));
continue;
} else {
prevPoint = points[i - 1];
nextPoint = points[i + 1];
}
}
v2Sub(v, nextPoint, prevPoint); // use degree to scale the handle length
v2Scale(v, v, smooth);
var d0 = v2Distance(point, prevPoint);
var d1 = v2Distance(point, nextPoint);
var sum = d0 + d1;
if (sum !== 0) {
d0 /= sum;
d1 /= sum;
}
v2Scale(v1, v, -d0);
v2Scale(v2, v, d1);
var cp0 = v2Add([], point, v1);
var cp1 = v2Add([], point, v2);
if (constraint) {
v2Max(cp0, cp0, min);
v2Min(cp0, cp0, max);
v2Max(cp1, cp1, min);
v2Min(cp1, cp1, max);
}
cps.push(cp0);
cps.push(cp1);
}
if (isLoop) {
cps.push(cps.shift());
}
return cps;
}
module.exports = _default;

View File

@ -0,0 +1,68 @@
var _vector = require("../../core/vector");
var v2Distance = _vector.distance;
/**
* Catmull-Rom spline 插值折线
* @module zrender/shape/util/smoothSpline
* @author pissang (https://www.github.com/pissang)
* Kener (@Kener-林峰, kener.linfeng@gmail.com)
* errorrik (errorrik@gmail.com)
*/
/**
* @inner
*/
function interpolate(p0, p1, p2, p3, t, t2, t3) {
var v0 = (p2 - p0) * 0.5;
var v1 = (p3 - p1) * 0.5;
return (2 * (p1 - p2) + v0 + v1) * t3 + (-3 * (p1 - p2) - 2 * v0 - v1) * t2 + v0 * t + p1;
}
/**
* @alias module:zrender/shape/util/smoothSpline
* @param {Array} points 线段顶点数组
* @param {boolean} isLoop
* @return {Array}
*/
function _default(points, isLoop) {
var len = points.length;
var ret = [];
var distance = 0;
for (var i = 1; i < len; i++) {
distance += v2Distance(points[i - 1], points[i]);
}
var segs = distance / 2;
segs = segs < len ? len : segs;
for (var i = 0; i < segs; i++) {
var pos = i / (segs - 1) * (isLoop ? len : len - 1);
var idx = Math.floor(pos);
var w = pos - idx;
var p0;
var p1 = points[idx % len];
var p2;
var p3;
if (!isLoop) {
p0 = points[idx === 0 ? idx : idx - 1];
p2 = points[idx > len - 2 ? len - 1 : idx + 1];
p3 = points[idx > len - 3 ? len - 1 : idx + 2];
} else {
p0 = points[(idx - 1 + len) % len];
p2 = points[(idx + 1) % len];
p3 = points[(idx + 2) % len];
}
var w2 = w * w;
var w3 = w * w2;
ret.push([interpolate(p0[0], p1[0], p2[0], p3[0], w, w2, w3), interpolate(p0[1], p1[1], p2[1], p3[1], w, w2, w3)]);
}
return ret;
}
module.exports = _default;

View File

@ -0,0 +1,113 @@
/**
* Sub-pixel optimize for canvas rendering, prevent from blur
* when rendering a thin vertical/horizontal line.
*/
var round = Math.round;
/**
* Sub pixel optimize line for canvas
*
* @param {Object} outputShape The modification will be performed on `outputShape`.
* `outputShape` and `inputShape` can be the same object.
* `outputShape` object can be used repeatly, because all of
* the `x1`, `x2`, `y1`, `y2` will be assigned in this method.
* @param {Object} [inputShape]
* @param {number} [inputShape.x1]
* @param {number} [inputShape.y1]
* @param {number} [inputShape.x2]
* @param {number} [inputShape.y2]
* @param {Object} [style]
* @param {number} [style.lineWidth] If `null`/`undefined`/`0`, do not optimize.
*/
function subPixelOptimizeLine(outputShape, inputShape, style) {
if (!inputShape) {
return;
}
var x1 = inputShape.x1;
var x2 = inputShape.x2;
var y1 = inputShape.y1;
var y2 = inputShape.y2;
outputShape.x1 = x1;
outputShape.x2 = x2;
outputShape.y1 = y1;
outputShape.y2 = y2;
var lineWidth = style && style.lineWidth;
if (!lineWidth) {
return;
}
if (round(x1 * 2) === round(x2 * 2)) {
outputShape.x1 = outputShape.x2 = subPixelOptimize(x1, lineWidth, true);
}
if (round(y1 * 2) === round(y2 * 2)) {
outputShape.y1 = outputShape.y2 = subPixelOptimize(y1, lineWidth, true);
}
}
/**
* Sub pixel optimize rect for canvas
*
* @param {Object} outputShape The modification will be performed on `outputShape`.
* `outputShape` and `inputShape` can be the same object.
* `outputShape` object can be used repeatly, because all of
* the `x`, `y`, `width`, `height` will be assigned in this method.
* @param {Object} [inputShape]
* @param {number} [inputShape.x]
* @param {number} [inputShape.y]
* @param {number} [inputShape.width]
* @param {number} [inputShape.height]
* @param {Object} [style]
* @param {number} [style.lineWidth] If `null`/`undefined`/`0`, do not optimize.
*/
function subPixelOptimizeRect(outputShape, inputShape, style) {
if (!inputShape) {
return;
}
var originX = inputShape.x;
var originY = inputShape.y;
var originWidth = inputShape.width;
var originHeight = inputShape.height;
outputShape.x = originX;
outputShape.y = originY;
outputShape.width = originWidth;
outputShape.height = originHeight;
var lineWidth = style && style.lineWidth;
if (!lineWidth) {
return;
}
outputShape.x = subPixelOptimize(originX, lineWidth, true);
outputShape.y = subPixelOptimize(originY, lineWidth, true);
outputShape.width = Math.max(subPixelOptimize(originX + originWidth, lineWidth, false) - outputShape.x, originWidth === 0 ? 0 : 1);
outputShape.height = Math.max(subPixelOptimize(originY + originHeight, lineWidth, false) - outputShape.y, originHeight === 0 ? 0 : 1);
}
/**
* Sub pixel optimize for canvas
*
* @param {number} position Coordinate, such as x, y
* @param {number} lineWidth If `null`/`undefined`/`0`, do not optimize.
* @param {boolean=} positiveOrNegative Default false (negative).
* @return {number} Optimized position.
*/
function subPixelOptimize(position, lineWidth, positiveOrNegative) {
if (!lineWidth) {
return position;
} // Assure that (position + lineWidth / 2) is near integer edge,
// otherwise line will be fuzzy in canvas.
var doubledPosition = round(position * 2);
return (doubledPosition + round(lineWidth)) % 2 === 0 ? doubledPosition / 2 : (doubledPosition + (positiveOrNegative ? 1 : -1)) / 2;
}
exports.subPixelOptimizeLine = subPixelOptimizeLine;
exports.subPixelOptimizeRect = subPixelOptimizeRect;
exports.subPixelOptimize = subPixelOptimize;

547
node_modules/zrender/lib/graphic/helper/text.js generated vendored Normal file
View File

@ -0,0 +1,547 @@
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;