1
This commit is contained in:
661
ems-frontend/node_modules/zrender/src/svg/graphic.ts
generated
vendored
Normal file
661
ems-frontend/node_modules/zrender/src/svg/graphic.ts
generated
vendored
Normal file
@@ -0,0 +1,661 @@
|
||||
// TODO
|
||||
// 1. shadow
|
||||
// 2. Image: sx, sy, sw, sh
|
||||
|
||||
import {
|
||||
adjustTextY,
|
||||
getIdURL,
|
||||
getMatrixStr,
|
||||
getPathPrecision,
|
||||
getShadowKey,
|
||||
getSRTTransformString,
|
||||
hasShadow,
|
||||
isAroundZero,
|
||||
isGradient,
|
||||
isImagePattern,
|
||||
isLinearGradient,
|
||||
isPattern,
|
||||
isRadialGradient,
|
||||
normalizeColor,
|
||||
round4,
|
||||
TEXT_ALIGN_TO_ANCHOR
|
||||
} from './helper';
|
||||
import Path, { PathStyleProps } from '../graphic/Path';
|
||||
import ZRImage, { ImageStyleProps } from '../graphic/Image';
|
||||
import { getLineHeight } from '../contain/text';
|
||||
import TSpan, { TSpanStyleProps } from '../graphic/TSpan';
|
||||
import SVGPathRebuilder from './SVGPathRebuilder';
|
||||
import mapStyleToAttrs from './mapStyleToAttrs';
|
||||
import { SVGVNodeAttrs, createVNode, SVGVNode, vNodeToString, BrushScope, META_DATA_PREFIX } from './core';
|
||||
import { MatrixArray } from '../core/matrix';
|
||||
import Displayable from '../graphic/Displayable';
|
||||
import { assert, clone, isFunction, isString, logError, map, retrieve2 } from '../core/util';
|
||||
import Polyline from '../graphic/shape/Polyline';
|
||||
import Polygon from '../graphic/shape/Polygon';
|
||||
import { GradientObject } from '../graphic/Gradient';
|
||||
import { ImagePatternObject, SVGPatternObject } from '../graphic/Pattern';
|
||||
import { createOrUpdateImage } from '../graphic/helper/image';
|
||||
import { ImageLike } from '../core/types';
|
||||
import { createCSSAnimation } from './cssAnimation';
|
||||
import { hasSeparateFont, parseFontSize } from '../graphic/Text';
|
||||
import { DEFAULT_FONT, DEFAULT_FONT_FAMILY } from '../core/platform';
|
||||
import { createCSSEmphasis } from './cssEmphasis';
|
||||
import { getElementSSRData } from '../zrender';
|
||||
|
||||
const round = Math.round;
|
||||
|
||||
function isImageLike(val: any): val is HTMLImageElement {
|
||||
return val && isString(val.src);
|
||||
}
|
||||
function isCanvasLike(val: any): val is HTMLCanvasElement {
|
||||
return val && isFunction(val.toDataURL);
|
||||
}
|
||||
|
||||
|
||||
type AllStyleOption = PathStyleProps | TSpanStyleProps | ImageStyleProps;
|
||||
|
||||
function setStyleAttrs(attrs: SVGVNodeAttrs, style: AllStyleOption, el: Path | TSpan | ZRImage, scope: BrushScope) {
|
||||
mapStyleToAttrs((key, val) => {
|
||||
const 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;
|
||||
}
|
||||
if (isFillStroke && scope.ssr && val === 'none') {
|
||||
// When is none, it cannot be interacted when ssr
|
||||
// Setting `pointer-events` as `visible` to make it responding
|
||||
// See also https://www.w3.org/TR/SVG/interact.html#PointerEventsProperty
|
||||
attrs['pointer-events'] = 'visible';
|
||||
}
|
||||
}, style, el, false);
|
||||
|
||||
setShadow(el, attrs, scope);
|
||||
}
|
||||
|
||||
function setMetaData(attrs: SVGVNodeAttrs, el: Path | TSpan | ZRImage) {
|
||||
const metaData = getElementSSRData(el);
|
||||
if (metaData) {
|
||||
metaData.each((val, key) => {
|
||||
val != null && (attrs[(META_DATA_PREFIX + key).toLowerCase()] = val + '');
|
||||
});
|
||||
if (el.isSilent()) {
|
||||
attrs[META_DATA_PREFIX + 'silent'] = 'true';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function noRotateScale(m: MatrixArray) {
|
||||
return isAroundZero(m[0] - 1)
|
||||
&& isAroundZero(m[1])
|
||||
&& isAroundZero(m[2])
|
||||
&& isAroundZero(m[3] - 1);
|
||||
}
|
||||
|
||||
function noTranslate(m: MatrixArray) {
|
||||
return isAroundZero(m[4]) && isAroundZero(m[5]);
|
||||
}
|
||||
|
||||
function setTransform(attrs: SVGVNodeAttrs, m: MatrixArray, compress?: boolean) {
|
||||
if (m && !(noTranslate(m) && noRotateScale(m))) {
|
||||
const mul = compress ? 10 : 1e4;
|
||||
// Use translate possible to reduce the size a bit.
|
||||
attrs.transform = noRotateScale(m)
|
||||
? `translate(${round(m[4] * mul) / mul} ${round(m[5] * mul) / mul})` : getMatrixStr(m);
|
||||
}
|
||||
}
|
||||
|
||||
type ShapeMapDesc = (string | [string, string])[];
|
||||
type ConvertShapeToAttr = (shape: any, attrs: SVGVNodeAttrs, mul?: number) => void;
|
||||
type ShapeValidator = (shape: any) => boolean;
|
||||
|
||||
function convertPolyShape(shape: Polygon['shape'], attrs: SVGVNodeAttrs, mul: number) {
|
||||
const points = shape.points;
|
||||
const strArr = [];
|
||||
for (let 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: Polyline['shape']) {
|
||||
return !shape.smooth;
|
||||
}
|
||||
|
||||
function createAttrsConvert(desc: ShapeMapDesc): ConvertShapeToAttr {
|
||||
const normalizedDesc: [string, string][] = map(desc, (item) =>
|
||||
(typeof item === 'string' ? [item, item] : item)
|
||||
);
|
||||
|
||||
return function (shape, attrs, mul) {
|
||||
for (let i = 0; i < normalizedDesc.length; i++) {
|
||||
const item = normalizedDesc[i];
|
||||
const val = shape[item[0]];
|
||||
if (val != null) {
|
||||
attrs[item[1]] = round(val * mul) / mul;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const builtinShapesDef: Record<string, [ConvertShapeToAttr, ShapeValidator?]> = {
|
||||
circle: [createAttrsConvert(['cx', 'cy', 'r'])],
|
||||
polyline: [convertPolyShape, validatePolyShape],
|
||||
polygon: [convertPolyShape, validatePolyShape]
|
||||
// Ignore line because it will be larger.
|
||||
};
|
||||
|
||||
interface PathWithSVGBuildPath extends Path {
|
||||
__svgPathVersion: number
|
||||
__svgPathBuilder: SVGPathRebuilder
|
||||
__svgPathStrokePercent: number
|
||||
}
|
||||
|
||||
function hasShapeAnimation(el: Displayable) {
|
||||
const animators = el.animators;
|
||||
for (let i = 0; i < animators.length; i++) {
|
||||
if (animators[i].targetName === 'shape') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function brushSVGPath(el: Path, scope: BrushScope) {
|
||||
const style = el.style;
|
||||
const shape = el.shape;
|
||||
const builtinShpDef = builtinShapesDef[el.type];
|
||||
const attrs: SVGVNodeAttrs = {};
|
||||
const needsAnimate = scope.animation;
|
||||
let svgElType = 'path';
|
||||
const strokePercent = el.style.strokePercent;
|
||||
const precision = (scope.compress && getPathPrecision(el)) || 4;
|
||||
// Using SVG builtin shapes if possible
|
||||
if (builtinShpDef
|
||||
// Force to use path if it will update later.
|
||||
// To avoid some animation(like morph) fail
|
||||
&& !scope.willUpdate
|
||||
&& !(builtinShpDef[1] && !builtinShpDef[1](shape))
|
||||
// use `path` to simplify the animate element creation logic.
|
||||
&& !(needsAnimate && hasShapeAnimation(el))
|
||||
&& !(strokePercent < 1)
|
||||
) {
|
||||
svgElType = el.type;
|
||||
const mul = Math.pow(10, precision);
|
||||
builtinShpDef[0](shape, attrs, mul);
|
||||
}
|
||||
else {
|
||||
const needBuildPath = !el.path || el.shapeChanged();
|
||||
if (!el.path) {
|
||||
el.createPathProxy();
|
||||
}
|
||||
const path = el.path;
|
||||
|
||||
if (needBuildPath) {
|
||||
path.beginPath();
|
||||
el.buildPath(path, el.shape);
|
||||
el.pathUpdated();
|
||||
}
|
||||
const pathVersion = path.getVersion();
|
||||
const elExt = el as PathWithSVGBuildPath;
|
||||
|
||||
let 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);
|
||||
setMetaData(attrs, el);
|
||||
|
||||
scope.animation && createCSSAnimation(el, attrs, scope);
|
||||
scope.emphasis && createCSSEmphasis(el, attrs, scope);
|
||||
|
||||
return createVNode(svgElType, el.id + '', attrs);
|
||||
}
|
||||
|
||||
export function brushSVGImage(el: ZRImage, scope: BrushScope) {
|
||||
const style = el.style;
|
||||
let image = style.image;
|
||||
|
||||
if (image && !isString(image)) {
|
||||
if (isImageLike(image)) {
|
||||
image = image.src;
|
||||
}
|
||||
// heatmap layer in geo may be a canvas
|
||||
else if (isCanvasLike(image)) {
|
||||
image = image.toDataURL();
|
||||
}
|
||||
}
|
||||
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
|
||||
const x = style.x || 0;
|
||||
const y = style.y || 0;
|
||||
|
||||
const dw = style.width;
|
||||
const dh = style.height;
|
||||
|
||||
const attrs: SVGVNodeAttrs = {
|
||||
href: image as string,
|
||||
width: dw,
|
||||
height: dh
|
||||
};
|
||||
if (x) {
|
||||
attrs.x = x;
|
||||
}
|
||||
if (y) {
|
||||
attrs.y = y;
|
||||
}
|
||||
|
||||
setTransform(attrs, el.transform);
|
||||
setStyleAttrs(attrs, style, el, scope);
|
||||
setMetaData(attrs, el);
|
||||
|
||||
scope.animation && createCSSAnimation(el, attrs, scope);
|
||||
|
||||
return createVNode('image', el.id + '', attrs);
|
||||
};
|
||||
|
||||
export function brushSVGTSpan(el: TSpan, scope: BrushScope) {
|
||||
const style = el.style;
|
||||
|
||||
let text = style.text;
|
||||
// Convert to string
|
||||
text != null && (text += '');
|
||||
if (!text || isNaN(style.x) || isNaN(style.y)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// style.font has been normalized by `normalizeTextStyle`.
|
||||
const font = style.font || DEFAULT_FONT;
|
||||
|
||||
// Consider different font display differently in vertical align, we always
|
||||
// set verticalAlign as 'middle', and use 'y' to locate text vertically.
|
||||
const x = style.x || 0;
|
||||
const y = adjustTextY(style.y || 0, getLineHeight(font), style.textBaseline);
|
||||
const textAlign = TEXT_ALIGN_TO_ANCHOR[style.textAlign as keyof typeof TEXT_ALIGN_TO_ANCHOR]
|
||||
|| style.textAlign;
|
||||
|
||||
const attrs: SVGVNodeAttrs = {
|
||||
'dominant-baseline': 'central',
|
||||
'text-anchor': textAlign
|
||||
};
|
||||
|
||||
if (hasSeparateFont(style)) {
|
||||
// Set separate font attributes if possible. Or some platform like PowerPoint may not support it.
|
||||
let separatedFontStr = '';
|
||||
const fontStyle = style.fontStyle;
|
||||
const fontSize = parseFontSize(style.fontSize);
|
||||
if (!parseFloat(fontSize)) { // is 0px
|
||||
return;
|
||||
}
|
||||
|
||||
const fontFamily = style.fontFamily || DEFAULT_FONT_FAMILY;
|
||||
const fontWeight = style.fontWeight;
|
||||
separatedFontStr += `font-size:${fontSize};font-family:${fontFamily};`;
|
||||
|
||||
// TODO reduce the attribute to set. But should it inherit from the container element?
|
||||
if (fontStyle && fontStyle !== 'normal') {
|
||||
separatedFontStr += `font-style:${fontStyle};`;
|
||||
}
|
||||
if (fontWeight && fontWeight !== 'normal') {
|
||||
separatedFontStr += `font-weight:${fontWeight};`;
|
||||
}
|
||||
attrs.style = separatedFontStr;
|
||||
}
|
||||
else {
|
||||
// Use set font manually
|
||||
attrs.style = `font: ${font}`;
|
||||
}
|
||||
|
||||
|
||||
if (text.match(/\s/)) {
|
||||
// only enabled when have space in text.
|
||||
attrs['xml:space'] = 'preserve';
|
||||
}
|
||||
if (x) {
|
||||
attrs.x = x;
|
||||
}
|
||||
if (y) {
|
||||
attrs.y = y;
|
||||
}
|
||||
setTransform(attrs, el.transform);
|
||||
setStyleAttrs(attrs, style, el, scope);
|
||||
setMetaData(attrs, el);
|
||||
|
||||
scope.animation && createCSSAnimation(el, attrs, scope);
|
||||
|
||||
return createVNode('text', el.id + '', attrs, undefined, text);
|
||||
}
|
||||
|
||||
export function brush(el: Displayable, scope: BrushScope): SVGVNode {
|
||||
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: Displayable,
|
||||
attrs: SVGVNodeAttrs,
|
||||
scope: BrushScope
|
||||
) {
|
||||
const style = el.style;
|
||||
if (hasShadow(style)) {
|
||||
const shadowKey = getShadowKey(el);
|
||||
const shadowCache = scope.shadowCache;
|
||||
let shadowId = shadowCache[shadowKey];
|
||||
if (!shadowId) {
|
||||
const globalScale = el.getGlobalScale();
|
||||
const scaleX = globalScale[0];
|
||||
const scaleY = globalScale[1];
|
||||
if (!scaleX || !scaleY) {
|
||||
return;
|
||||
}
|
||||
|
||||
const offsetX = style.shadowOffsetX || 0;
|
||||
const offsetY = style.shadowOffsetY || 0;
|
||||
const blur = style.shadowBlur;
|
||||
const {opacity, color} = normalizeColor(style.shadowColor);
|
||||
const stdDx = blur / 2 / scaleX;
|
||||
const stdDy = blur / 2 / scaleY;
|
||||
const stdDeviation = stdDx + ' ' + stdDy;
|
||||
// Use a simple prefix to reduce the size
|
||||
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: PathStyleProps,
|
||||
attrs: SVGVNodeAttrs,
|
||||
target: 'fill' | 'stroke',
|
||||
scope: BrushScope
|
||||
) {
|
||||
const val = style[target] as GradientObject;
|
||||
let gradientTag;
|
||||
let gradientAttrs: SVGVNodeAttrs = {
|
||||
'gradientUnits': val.global
|
||||
? 'userSpaceOnUse' // x1, x2, y1, y2 in range of 0 to canvas width or height
|
||||
: 'objectBoundingBox' // x1, x2, y1, y2 in range of 0 to 1]
|
||||
};
|
||||
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;
|
||||
}
|
||||
|
||||
const colors = val.colorStops;
|
||||
|
||||
const colorStops = [];
|
||||
for (let i = 0, len = colors.length; i < len; ++i) {
|
||||
const offset = round4(colors[i].offset) * 100 + '%';
|
||||
|
||||
const stopColor = colors[i].color;
|
||||
// Fix Safari bug that stop-color not recognizing alpha #9014
|
||||
const {color, opacity} = normalizeColor(stopColor);
|
||||
|
||||
const stopsAttrs: SVGVNodeAttrs = {
|
||||
'offset': offset
|
||||
};
|
||||
// stop-color cannot be color, since:
|
||||
// The opacity value used for the gradient calculation is the
|
||||
// *product* of the value of stop-opacity and the opacity of the
|
||||
// value of stop-color.
|
||||
// See https://www.w3.org/TR/SVG2/pservers.html#StopOpacityProperty
|
||||
|
||||
stopsAttrs['stop-color'] = color;
|
||||
if (opacity < 1) {
|
||||
stopsAttrs['stop-opacity'] = opacity;
|
||||
}
|
||||
colorStops.push(
|
||||
createVNode('stop', i + '', stopsAttrs)
|
||||
);
|
||||
}
|
||||
|
||||
// Use the whole html as cache key.
|
||||
const gradientVNode = createVNode(gradientTag, '', gradientAttrs, colorStops);
|
||||
const gradientKey = vNodeToString(gradientVNode);
|
||||
const gradientCache = scope.gradientCache;
|
||||
let 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: Displayable,
|
||||
attrs: SVGVNodeAttrs,
|
||||
target: 'fill' | 'stroke',
|
||||
scope: BrushScope
|
||||
) {
|
||||
const val = el.style[target] as ImagePatternObject | SVGPatternObject;
|
||||
const boundingRect = el.getBoundingRect();
|
||||
const patternAttrs: SVGVNodeAttrs = {};
|
||||
const repeat = (val as ImagePatternObject).repeat;
|
||||
const noRepeat = repeat === 'no-repeat';
|
||||
const repeatX = repeat === 'repeat-x';
|
||||
const repeatY = repeat === 'repeat-y';
|
||||
let child: SVGVNode;
|
||||
if (isImagePattern(val)) {
|
||||
let imageWidth = val.imageWidth;
|
||||
let imageHeight = val.imageHeight;
|
||||
let imageSrc;
|
||||
const 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') {
|
||||
const errMsg = 'Image width/height must been given explictly in svg-ssr renderer.';
|
||||
assert(imageWidth, errMsg);
|
||||
assert(imageHeight, errMsg);
|
||||
}
|
||||
else if (imageWidth == null || imageHeight == null) {
|
||||
// TODO
|
||||
const setSizeToVNode = (vNode: SVGVNode, img: ImageLike) => {
|
||||
if (vNode) {
|
||||
const svgEl = vNode.elm as SVGElement;
|
||||
let width = imageWidth || img.width;
|
||||
let height = imageHeight || 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 as any);
|
||||
svgEl.setAttribute('height', height as any);
|
||||
}
|
||||
}
|
||||
};
|
||||
const createdImage = createOrUpdateImage(
|
||||
imageSrc, null, el, (img) => {
|
||||
noRepeat || setSizeToVNode(patternVNode, img);
|
||||
setSizeToVNode(child, img);
|
||||
}
|
||||
);
|
||||
if (createdImage && createdImage.width && createdImage.height) {
|
||||
// Loaded before
|
||||
imageWidth = imageWidth || createdImage.width;
|
||||
imageHeight = imageHeight || createdImage.height;
|
||||
}
|
||||
}
|
||||
|
||||
child = createVNode(
|
||||
'image',
|
||||
'img',
|
||||
{
|
||||
href: imageSrc,
|
||||
width: imageWidth,
|
||||
height: imageHeight
|
||||
}
|
||||
);
|
||||
patternAttrs.width = imageWidth;
|
||||
patternAttrs.height = imageHeight;
|
||||
}
|
||||
else if (val.svgElement) { // Only string supported in SSR.
|
||||
// TODO it's not so good to use textContent as innerHTML
|
||||
child = clone(val.svgElement);
|
||||
patternAttrs.width = val.svgWidth;
|
||||
patternAttrs.height = val.svgHeight;
|
||||
}
|
||||
if (!child) {
|
||||
return;
|
||||
}
|
||||
|
||||
let patternWidth;
|
||||
let patternHeight;
|
||||
if (noRepeat) {
|
||||
patternWidth = patternHeight = 1;
|
||||
}
|
||||
else if (repeatX) {
|
||||
patternHeight = 1;
|
||||
patternWidth = (patternAttrs.width as number) / boundingRect.width;
|
||||
}
|
||||
else if (repeatY) {
|
||||
patternWidth = 1;
|
||||
patternHeight = (patternAttrs.height as number) / boundingRect.height;
|
||||
}
|
||||
else {
|
||||
patternAttrs.patternUnits = 'userSpaceOnUse';
|
||||
}
|
||||
|
||||
if (patternWidth != null && !isNaN(patternWidth)) {
|
||||
patternAttrs.width = patternWidth;
|
||||
}
|
||||
if (patternHeight != null && !isNaN(patternHeight)) {
|
||||
patternAttrs.height = patternHeight;
|
||||
}
|
||||
|
||||
const patternTransform = getSRTTransformString(val);
|
||||
patternTransform && (patternAttrs.patternTransform = patternTransform);
|
||||
|
||||
// Use the whole html as cache key.
|
||||
let patternVNode = createVNode(
|
||||
'pattern',
|
||||
'',
|
||||
patternAttrs,
|
||||
[child]
|
||||
);
|
||||
const patternKey = vNodeToString(patternVNode);
|
||||
const patternCache = scope.patternCache;
|
||||
let 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: Path,
|
||||
attrs: SVGVNodeAttrs,
|
||||
scope: BrushScope
|
||||
) {
|
||||
const {clipPathCache, defs} = scope;
|
||||
let clipPathId = clipPathCache[clipPath.id];
|
||||
if (!clipPathId) {
|
||||
clipPathId = scope.zrId + '-c' + scope.clipPathIdx++;
|
||||
const clipPathAttrs: SVGVNodeAttrs = {
|
||||
id: clipPathId
|
||||
};
|
||||
|
||||
clipPathCache[clipPath.id] = clipPathId;
|
||||
defs[clipPathId] = createVNode(
|
||||
'clipPath', clipPathId, clipPathAttrs,
|
||||
[brushSVGPath(clipPath, scope)]
|
||||
);
|
||||
}
|
||||
attrs['clip-path'] = getIdURL(clipPathId);
|
||||
}
|
||||
Reference in New Issue
Block a user