/** 
 | 
 * @file Manages SVG gradient elements. 
 | 
 * @author Zhang Wenli 
 | 
 */ 
 | 
  
 | 
import Definable from './Definable'; 
 | 
import * as zrUtil from '../../core/util'; 
 | 
import Displayable from '../../graphic/Displayable'; 
 | 
import { GradientObject } from '../../graphic/Gradient'; 
 | 
import { getIdURL, isGradient, isLinearGradient, isRadialGradient, normalizeColor, round4 } from '../../svg/helper'; 
 | 
import { createElement } from '../../svg/core'; 
 | 
  
 | 
  
 | 
type GradientObjectExtended = GradientObject & { 
 | 
    __dom: SVGElement 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Manages SVG gradient elements. 
 | 
 * 
 | 
 * @param   zrId    zrender instance id 
 | 
 * @param   svgRoot root of SVG document 
 | 
 */ 
 | 
export default class GradientManager extends Definable { 
 | 
  
 | 
    constructor(zrId: number, svgRoot: SVGElement) { 
 | 
        super(zrId, svgRoot, ['linearGradient', 'radialGradient'], '__gradient_in_use__'); 
 | 
    } 
 | 
  
 | 
  
 | 
    /** 
 | 
     * Create new gradient DOM for fill or stroke if not exist, 
 | 
     * but will not update gradient if exists. 
 | 
     * 
 | 
     * @param svgElement   SVG element to paint 
 | 
     * @param displayable  zrender displayable element 
 | 
     */ 
 | 
    addWithoutUpdate( 
 | 
        svgElement: SVGElement, 
 | 
        displayable: Displayable 
 | 
    ) { 
 | 
        if (displayable && displayable.style) { 
 | 
            const that = this; 
 | 
            zrUtil.each(['fill', 'stroke'], function (fillOrStroke: 'fill' | 'stroke') { 
 | 
                let value = displayable.style[fillOrStroke] as GradientObject; 
 | 
                if (isGradient(value)) { 
 | 
                    const gradient = value as GradientObjectExtended; 
 | 
                    const defs = that.getDefs(true); 
 | 
  
 | 
                    // Create dom in <defs> if not exists 
 | 
                    let dom; 
 | 
                    if (gradient.__dom) { 
 | 
                        // Gradient exists 
 | 
                        dom = gradient.__dom; 
 | 
                        if (!defs.contains(gradient.__dom)) { 
 | 
                            // __dom is no longer in defs, recreate 
 | 
                            that.addDom(dom); 
 | 
                        } 
 | 
                    } 
 | 
                    else { 
 | 
                        // New dom 
 | 
                        dom = that.add(gradient); 
 | 
                    } 
 | 
  
 | 
                    that.markUsed(displayable); 
 | 
  
 | 
                    svgElement.setAttribute(fillOrStroke, getIdURL(dom.getAttribute('id'))); 
 | 
                } 
 | 
            }); 
 | 
        } 
 | 
    } 
 | 
  
 | 
  
 | 
    /** 
 | 
     * Add a new gradient tag in <defs> 
 | 
     * 
 | 
     * @param   gradient zr gradient instance 
 | 
     */ 
 | 
    add(gradient: GradientObject): SVGElement { 
 | 
        let dom; 
 | 
        if (isLinearGradient(gradient)) { 
 | 
            dom = createElement('linearGradient'); 
 | 
        } 
 | 
        else if (isRadialGradient(gradient)) { 
 | 
            dom = createElement('radialGradient'); 
 | 
        } 
 | 
        else { 
 | 
            if (process.env.NODE_ENV !== 'production') { 
 | 
                zrUtil.logError('Illegal gradient type.'); 
 | 
            } 
 | 
            return null; 
 | 
        } 
 | 
  
 | 
        // Set dom id with gradient id, since each gradient instance 
 | 
        // will have no more than one dom element. 
 | 
        // id may exists before for those dirty elements, in which case 
 | 
        // id should remain the same, and other attributes should be 
 | 
        // updated. 
 | 
        gradient.id = gradient.id || this.nextId++; 
 | 
        dom.setAttribute('id', 'zr' + this._zrId 
 | 
            + '-gradient-' + gradient.id); 
 | 
  
 | 
        this.updateDom(gradient, dom); 
 | 
        this.addDom(dom); 
 | 
  
 | 
        return dom; 
 | 
    } 
 | 
  
 | 
  
 | 
    /** 
 | 
     * Update gradient. 
 | 
     * 
 | 
     * @param gradient zr gradient instance or color string 
 | 
     */ 
 | 
    update(gradient: GradientObject | string) { 
 | 
        if (!isGradient(gradient)) { 
 | 
            return; 
 | 
        } 
 | 
  
 | 
        const that = this; 
 | 
        this.doUpdate(gradient, function () { 
 | 
            const dom = (gradient as GradientObjectExtended).__dom; 
 | 
            if (!dom) { 
 | 
                return; 
 | 
            } 
 | 
  
 | 
            const tagName = dom.tagName; 
 | 
            const type = gradient.type; 
 | 
            if (type === 'linear' && tagName === 'linearGradient' 
 | 
                || type === 'radial' && tagName === 'radialGradient' 
 | 
            ) { 
 | 
                // Gradient type is not changed, update gradient 
 | 
                that.updateDom(gradient, (gradient as GradientObjectExtended).__dom); 
 | 
            } 
 | 
            else { 
 | 
                // Remove and re-create if type is changed 
 | 
                that.removeDom(gradient); 
 | 
                that.add(gradient); 
 | 
            } 
 | 
        }); 
 | 
    } 
 | 
  
 | 
  
 | 
    /** 
 | 
     * Update gradient dom 
 | 
     * 
 | 
     * @param gradient zr gradient instance 
 | 
     * @param dom DOM to update 
 | 
     */ 
 | 
    updateDom(gradient: GradientObject, dom: SVGElement) { 
 | 
        if (isLinearGradient(gradient)) { 
 | 
            dom.setAttribute('x1', gradient.x as any); 
 | 
            dom.setAttribute('y1', gradient.y as any); 
 | 
            dom.setAttribute('x2', gradient.x2 as any); 
 | 
            dom.setAttribute('y2', gradient.y2 as any); 
 | 
        } 
 | 
        else if (isRadialGradient(gradient)) { 
 | 
            dom.setAttribute('cx', gradient.x as any); 
 | 
            dom.setAttribute('cy', gradient.y as any); 
 | 
            dom.setAttribute('r', gradient.r as any); 
 | 
        } 
 | 
        else { 
 | 
            if (process.env.NODE_ENV !== 'production') { 
 | 
                zrUtil.logError('Illegal gradient type.'); 
 | 
            } 
 | 
            return; 
 | 
        } 
 | 
  
 | 
        dom.setAttribute('gradientUnits', 
 | 
            gradient.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 
 | 
        ); 
 | 
  
 | 
        // Remove color stops if exists 
 | 
        dom.innerHTML = ''; 
 | 
  
 | 
        // Add color stops 
 | 
        const colors = gradient.colorStops; 
 | 
        for (let i = 0, len = colors.length; i < len; ++i) { 
 | 
            const stop = createElement('stop'); 
 | 
            stop.setAttribute('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); 
 | 
            // 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 
 | 
            stop.setAttribute('stop-color', color); 
 | 
            if (opacity < 1) { 
 | 
                stop.setAttribute('stop-opacity', opacity as any); 
 | 
            } 
 | 
  
 | 
            dom.appendChild(stop); 
 | 
        } 
 | 
  
 | 
        // Store dom element in gradient, to avoid creating multiple 
 | 
        // dom instances for the same gradient element 
 | 
        (gradient as GradientObject as GradientObjectExtended).__dom = dom; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Mark a single gradient to be used 
 | 
     * 
 | 
     * @param displayable displayable element 
 | 
     */ 
 | 
    markUsed(displayable: Displayable) { 
 | 
        if (displayable.style) { 
 | 
            let gradient = displayable.style.fill as GradientObject as GradientObjectExtended; 
 | 
            if (gradient && gradient.__dom) { 
 | 
                super.markDomUsed(gradient.__dom); 
 | 
            } 
 | 
  
 | 
            gradient = displayable.style.stroke as GradientObject as GradientObjectExtended; 
 | 
            if (gradient && gradient.__dom) { 
 | 
                super.markDomUsed(gradient.__dom); 
 | 
            } 
 | 
        } 
 | 
    } 
 | 
  
 | 
  
 | 
} 
 |