// 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); 
 | 
} 
 |