import { adjustTextY, getIdURL, getMatrixStr, getPathPrecision, getShadowKey, getSRTTransformString, hasShadow, isAroundZero, isGradient, isImagePattern, isLinearGradient, isPattern, isRadialGradient, normalizeColor, round4, TEXT_ALIGN_TO_ANCHOR } from './helper.js'; import Path from '../graphic/Path.js'; import ZRImage from '../graphic/Image.js'; import { getLineHeight } from '../contain/text.js'; import TSpan from '../graphic/TSpan.js'; import SVGPathRebuilder from './SVGPathRebuilder.js'; import mapStyleToAttrs from './mapStyleToAttrs.js'; import { createVNode, vNodeToString } from './core.js'; import { assert, clone, isFunction, isString, logError, map, retrieve2 } from '../core/util.js'; import { createOrUpdateImage } from '../graphic/helper/image.js'; import { createCSSAnimation } from './cssAnimation.js'; import { hasSeparateFont, parseFontSize } from '../graphic/Text.js'; import { DEFAULT_FONT, DEFAULT_FONT_FAMILY } from '../core/platform.js'; var round = Math.round; function isImageLike(val) { return val && isString(val.src); } function isCanvasLike(val) { return val && isFunction(val.toDataURL); } function setStyleAttrs(attrs, style, el, scope) { mapStyleToAttrs(function (key, val) { var isFillStroke = key === 'fill' || key === 'stroke'; if (isFillStroke && isGradient(val)) { setGradient(style, attrs, key, scope); } else if (isFillStroke && isPattern(val)) { setPattern(el, attrs, key, scope); } else { attrs[key] = val; } }, style, el, false); setShadow(el, attrs, scope); } function noRotateScale(m) { return isAroundZero(m[0] - 1) && isAroundZero(m[1]) && isAroundZero(m[2]) && isAroundZero(m[3] - 1); } function noTranslate(m) { return isAroundZero(m[4]) && isAroundZero(m[5]); } function setTransform(attrs, m, compress) { if (m && !(noTranslate(m) && noRotateScale(m))) { var mul = compress ? 10 : 1e4; attrs.transform = noRotateScale(m) ? "translate(" + round(m[4] * mul) / mul + " " + round(m[5] * mul) / mul + ")" : getMatrixStr(m); } } function convertPolyShape(shape, attrs, mul) { var points = shape.points; var strArr = []; for (var i = 0; i < points.length; i++) { strArr.push(round(points[i][0] * mul) / mul); strArr.push(round(points[i][1] * mul) / mul); } attrs.points = strArr.join(' '); } function validatePolyShape(shape) { return !shape.smooth; } function createAttrsConvert(desc) { var normalizedDesc = map(desc, function (item) { return (typeof item === 'string' ? [item, item] : item); }); return function (shape, attrs, mul) { for (var i = 0; i < normalizedDesc.length; i++) { var item = normalizedDesc[i]; var val = shape[item[0]]; if (val != null) { attrs[item[1]] = round(val * mul) / mul; } } }; } var builtinShapesDef = { circle: [createAttrsConvert(['cx', 'cy', 'r'])], polyline: [convertPolyShape, validatePolyShape], polygon: [convertPolyShape, validatePolyShape] }; function hasShapeAnimation(el) { var animators = el.animators; for (var i = 0; i < animators.length; i++) { if (animators[i].targetName === 'shape') { return true; } } return false; } export function brushSVGPath(el, scope) { var style = el.style; var shape = el.shape; var builtinShpDef = builtinShapesDef[el.type]; var attrs = {}; var needsAnimate = scope.animation; var svgElType = 'path'; var strokePercent = el.style.strokePercent; var precision = (scope.compress && getPathPrecision(el)) || 4; if (builtinShpDef && !scope.willUpdate && !(builtinShpDef[1] && !builtinShpDef[1](shape)) && !(needsAnimate && hasShapeAnimation(el)) && !(strokePercent < 1)) { svgElType = el.type; var mul = Math.pow(10, precision); builtinShpDef[0](shape, attrs, mul); } else { var needBuildPath = !el.path || el.shapeChanged(); if (!el.path) { el.createPathProxy(); } var path = el.path; if (needBuildPath) { path.beginPath(); el.buildPath(path, el.shape); el.pathUpdated(); } var pathVersion = path.getVersion(); var elExt = el; var svgPathBuilder = elExt.__svgPathBuilder; if (elExt.__svgPathVersion !== pathVersion || !svgPathBuilder || strokePercent !== elExt.__svgPathStrokePercent) { if (!svgPathBuilder) { svgPathBuilder = elExt.__svgPathBuilder = new SVGPathRebuilder(); } svgPathBuilder.reset(precision); path.rebuildPath(svgPathBuilder, strokePercent); svgPathBuilder.generateStr(); elExt.__svgPathVersion = pathVersion; elExt.__svgPathStrokePercent = strokePercent; } attrs.d = svgPathBuilder.getStr(); } setTransform(attrs, el.transform); setStyleAttrs(attrs, style, el, scope); scope.animation && createCSSAnimation(el, attrs, scope); return createVNode(svgElType, el.id + '', attrs); } export function brushSVGImage(el, scope) { var style = el.style; var image = style.image; if (image && !isString(image)) { if (isImageLike(image)) { image = image.src; } else if (isCanvasLike(image)) { image = image.toDataURL(); } } if (!image) { return; } var x = style.x || 0; var y = style.y || 0; var dw = style.width; var dh = style.height; var attrs = { href: image, width: dw, height: dh }; if (x) { attrs.x = x; } if (y) { attrs.y = y; } setTransform(attrs, el.transform); setStyleAttrs(attrs, style, el, scope); scope.animation && createCSSAnimation(el, attrs, scope); return createVNode('image', el.id + '', attrs); } ; export function brushSVGTSpan(el, scope) { var style = el.style; var text = style.text; text != null && (text += ''); if (!text || isNaN(style.x) || isNaN(style.y)) { return; } var font = style.font || DEFAULT_FONT; var x = style.x || 0; var y = adjustTextY(style.y || 0, getLineHeight(font), style.textBaseline); var textAlign = TEXT_ALIGN_TO_ANCHOR[style.textAlign] || style.textAlign; var attrs = { 'dominant-baseline': 'central', 'text-anchor': textAlign }; if (hasSeparateFont(style)) { var separatedFontStr = ''; var fontStyle = style.fontStyle; var fontSize = parseFontSize(style.fontSize); if (!parseFloat(fontSize)) { return; } var fontFamily = style.fontFamily || DEFAULT_FONT_FAMILY; var fontWeight = style.fontWeight; separatedFontStr += "font-size:" + fontSize + ";font-family:" + fontFamily + ";"; if (fontStyle && fontStyle !== 'normal') { separatedFontStr += "font-style:" + fontStyle + ";"; } if (fontWeight && fontWeight !== 'normal') { separatedFontStr += "font-weight:" + fontWeight + ";"; } attrs.style = separatedFontStr; } else { attrs.style = "font: " + font; } if (text.match(/\s/)) { attrs['xml:space'] = 'preserve'; } if (x) { attrs.x = x; } if (y) { attrs.y = y; } setTransform(attrs, el.transform); setStyleAttrs(attrs, style, el, scope); scope.animation && createCSSAnimation(el, attrs, scope); return createVNode('text', el.id + '', attrs, undefined, text); } export function brush(el, scope) { if (el instanceof Path) { return brushSVGPath(el, scope); } else if (el instanceof ZRImage) { return brushSVGImage(el, scope); } else if (el instanceof TSpan) { return brushSVGTSpan(el, scope); } } function setShadow(el, attrs, scope) { var style = el.style; if (hasShadow(style)) { var shadowKey = getShadowKey(el); var shadowCache = scope.shadowCache; var shadowId = shadowCache[shadowKey]; if (!shadowId) { var globalScale = el.getGlobalScale(); var scaleX = globalScale[0]; var scaleY = globalScale[1]; if (!scaleX || !scaleY) { return; } var offsetX = style.shadowOffsetX || 0; var offsetY = style.shadowOffsetY || 0; var blur_1 = style.shadowBlur; var _a = normalizeColor(style.shadowColor), opacity = _a.opacity, color = _a.color; var stdDx = blur_1 / 2 / scaleX; var stdDy = blur_1 / 2 / scaleY; var stdDeviation = stdDx + ' ' + stdDy; shadowId = scope.zrId + '-s' + scope.shadowIdx++; scope.defs[shadowId] = createVNode('filter', shadowId, { 'id': shadowId, 'x': '-100%', 'y': '-100%', 'width': '300%', 'height': '300%' }, [ createVNode('feDropShadow', '', { 'dx': offsetX / scaleX, 'dy': offsetY / scaleY, 'stdDeviation': stdDeviation, 'flood-color': color, 'flood-opacity': opacity }) ]); shadowCache[shadowKey] = shadowId; } attrs.filter = getIdURL(shadowId); } } export function setGradient(style, attrs, target, scope) { var val = style[target]; var gradientTag; var gradientAttrs = { 'gradientUnits': val.global ? 'userSpaceOnUse' : 'objectBoundingBox' }; if (isLinearGradient(val)) { gradientTag = 'linearGradient'; gradientAttrs.x1 = val.x; gradientAttrs.y1 = val.y; gradientAttrs.x2 = val.x2; gradientAttrs.y2 = val.y2; } else if (isRadialGradient(val)) { gradientTag = 'radialGradient'; gradientAttrs.cx = retrieve2(val.x, 0.5); gradientAttrs.cy = retrieve2(val.y, 0.5); gradientAttrs.r = retrieve2(val.r, 0.5); } else { if (process.env.NODE_ENV !== 'production') { logError('Illegal gradient type.'); } return; } var colors = val.colorStops; var colorStops = []; for (var i = 0, len = colors.length; i < len; ++i) { var offset = round4(colors[i].offset) * 100 + '%'; var stopColor = colors[i].color; var _a = normalizeColor(stopColor), color = _a.color, opacity = _a.opacity; var stopsAttrs = { 'offset': offset }; stopsAttrs['stop-color'] = color; if (opacity < 1) { stopsAttrs['stop-opacity'] = opacity; } colorStops.push(createVNode('stop', i + '', stopsAttrs)); } var gradientVNode = createVNode(gradientTag, '', gradientAttrs, colorStops); var gradientKey = vNodeToString(gradientVNode); var gradientCache = scope.gradientCache; var gradientId = gradientCache[gradientKey]; if (!gradientId) { gradientId = scope.zrId + '-g' + scope.gradientIdx++; gradientCache[gradientKey] = gradientId; gradientAttrs.id = gradientId; scope.defs[gradientId] = createVNode(gradientTag, gradientId, gradientAttrs, colorStops); } attrs[target] = getIdURL(gradientId); } export function setPattern(el, attrs, target, scope) { var val = el.style[target]; var boundingRect = el.getBoundingRect(); var patternAttrs = {}; var repeat = val.repeat; var noRepeat = repeat === 'no-repeat'; var repeatX = repeat === 'repeat-x'; var repeatY = repeat === 'repeat-y'; var child; if (isImagePattern(val)) { var imageWidth_1 = val.imageWidth; var imageHeight_1 = val.imageHeight; var imageSrc = void 0; var patternImage = val.image; if (isString(patternImage)) { imageSrc = patternImage; } else if (isImageLike(patternImage)) { imageSrc = patternImage.src; } else if (isCanvasLike(patternImage)) { imageSrc = patternImage.toDataURL(); } if (typeof Image === 'undefined') { var errMsg = 'Image width/height must been given explictly in svg-ssr renderer.'; assert(imageWidth_1, errMsg); assert(imageHeight_1, errMsg); } else if (imageWidth_1 == null || imageHeight_1 == null) { var setSizeToVNode_1 = function (vNode, img) { if (vNode) { var svgEl = vNode.elm; var width = imageWidth_1 || img.width; var height = imageHeight_1 || img.height; if (vNode.tag === 'pattern') { if (repeatX) { height = 1; width /= boundingRect.width; } else if (repeatY) { width = 1; height /= boundingRect.height; } } vNode.attrs.width = width; vNode.attrs.height = height; if (svgEl) { svgEl.setAttribute('width', width); svgEl.setAttribute('height', height); } } }; var createdImage = createOrUpdateImage(imageSrc, null, el, function (img) { noRepeat || setSizeToVNode_1(patternVNode, img); setSizeToVNode_1(child, img); }); if (createdImage && createdImage.width && createdImage.height) { imageWidth_1 = imageWidth_1 || createdImage.width; imageHeight_1 = imageHeight_1 || createdImage.height; } } child = createVNode('image', 'img', { href: imageSrc, width: imageWidth_1, height: imageHeight_1 }); patternAttrs.width = imageWidth_1; patternAttrs.height = imageHeight_1; } else if (val.svgElement) { child = clone(val.svgElement); patternAttrs.width = val.svgWidth; patternAttrs.height = val.svgHeight; } if (!child) { return; } var patternWidth; var patternHeight; if (noRepeat) { patternWidth = patternHeight = 1; } else if (repeatX) { patternHeight = 1; patternWidth = patternAttrs.width / boundingRect.width; } else if (repeatY) { patternWidth = 1; patternHeight = patternAttrs.height / boundingRect.height; } else { patternAttrs.patternUnits = 'userSpaceOnUse'; } if (patternWidth != null && !isNaN(patternWidth)) { patternAttrs.width = patternWidth; } if (patternHeight != null && !isNaN(patternHeight)) { patternAttrs.height = patternHeight; } var patternTransform = getSRTTransformString(val); patternTransform && (patternAttrs.patternTransform = patternTransform); var patternVNode = createVNode('pattern', '', patternAttrs, [child]); var patternKey = vNodeToString(patternVNode); var patternCache = scope.patternCache; var patternId = patternCache[patternKey]; if (!patternId) { patternId = scope.zrId + '-p' + scope.patternIdx++; patternCache[patternKey] = patternId; patternAttrs.id = patternId; patternVNode = scope.defs[patternId] = createVNode('pattern', patternId, patternAttrs, [child]); } attrs[target] = getIdURL(patternId); } export function setClipPath(clipPath, attrs, scope) { var clipPathCache = scope.clipPathCache, defs = scope.defs; var clipPathId = clipPathCache[clipPath.id]; if (!clipPathId) { clipPathId = scope.zrId + '-c' + scope.clipPathIdx++; var clipPathAttrs = { id: clipPathId }; clipPathCache[clipPath.id] = clipPathId; defs[clipPathId] = createVNode('clipPath', clipPathId, clipPathAttrs, [brushSVGPath(clipPath, scope)]); } attrs['clip-path'] = getIdURL(clipPathId); }