mh-two-thousand-and-two
2024-03-25 b8c93990f3fa5e50a8aca16bdc9c2758168aa0fd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/**
 * @file Manages SVG shadow elements.
 * @author Zhang Wenli
 */
 
import Definable from './Definable';
import Displayable from '../../graphic/Displayable';
import { Dictionary } from '../../core/types';
import { getIdURL, getShadowKey, hasShadow, normalizeColor } from '../../svg/helper';
import { createElement } from '../../svg/core';
 
type DisplayableExtended = Displayable & {
    _shadowDom: SVGElement
}
/**
 * Manages SVG shadow elements.
 *
 */
export default class ShadowManager extends Definable {
 
    private _shadowDomMap: Dictionary<SVGFilterElement> = {}
    private _shadowDomPool: SVGFilterElement[] = []
 
    constructor(zrId: number, svgRoot: SVGElement) {
        super(zrId, svgRoot, ['filter'], '__filter_in_use__', '_shadowDom');
    }
 
    /**
     * Add a new shadow tag in <defs>
     *
     * @param displayable  zrender displayable element
     * @return created DOM
     */
    private _getFromPool(): SVGFilterElement {
        let shadowDom = this._shadowDomPool.pop();    // Try to get one from trash.
        if (!shadowDom) {
            shadowDom = createElement('filter') as SVGFilterElement;
            shadowDom.setAttribute('id', 'zr' + this._zrId + '-shadow-' + this.nextId++);
            const domChild = createElement('feDropShadow');
            shadowDom.appendChild(domChild);
            this.addDom(shadowDom);
        }
 
        return shadowDom;
    }
 
 
    /**
     * Update shadow.
     */
    update(svgElement: SVGElement, displayable: Displayable) {
        const style = displayable.style;
        if (hasShadow(style)) {
            // Try getting shadow from cache.
            const shadowKey = getShadowKey(displayable);
            let shadowDom = (displayable as DisplayableExtended)._shadowDom = this._shadowDomMap[shadowKey];
            if (!shadowDom) {
                shadowDom = this._getFromPool();
                this._shadowDomMap[shadowKey] = shadowDom;
            }
            this.updateDom(svgElement, displayable, shadowDom);
        }
        else {
            // Remove shadow
            this.remove(svgElement, displayable);
        }
    }
 
 
    /**
     * Remove DOM and clear parent filter
     */
    remove(svgElement: SVGElement, displayable: Displayable) {
        if ((displayable as DisplayableExtended)._shadowDom != null) {
            (displayable as DisplayableExtended)._shadowDom = null;
            svgElement.removeAttribute('filter');
        }
    }
 
 
    /**
     * Update shadow dom
     *
     * @param displayable  zrender displayable element
     * @param shadowDom DOM to update
     */
    updateDom(svgElement: SVGElement, displayable: Displayable, shadowDom: SVGElement) {
        let domChild = shadowDom.children[0];
 
        const style = displayable.style;
        const globalScale = displayable.getGlobalScale();
        const scaleX = globalScale[0];
        const scaleY = globalScale[1];
        if (!scaleX || !scaleY) {
            return;
        }
 
        // TODO: textBoxShadowBlur is not supported yet
        const offsetX = style.shadowOffsetX || 0;
        const offsetY = style.shadowOffsetY || 0;
        const blur = style.shadowBlur;
        const normalizedColor = normalizeColor(style.shadowColor);
 
        domChild.setAttribute('dx', offsetX / scaleX + '');
        domChild.setAttribute('dy', offsetY / scaleY + '');
        domChild.setAttribute('flood-color', normalizedColor.color);
        domChild.setAttribute('flood-opacity', normalizedColor.opacity + '');
 
        // Divide by two here so that it looks the same as in canvas
        // See: https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-shadowblur
        const stdDx = blur / 2 / scaleX;
        const stdDy = blur / 2 / scaleY;
        const stdDeviation = stdDx + ' ' + stdDy;
        domChild.setAttribute('stdDeviation', stdDeviation);
 
        // Fix filter clipping problem
        shadowDom.setAttribute('x', '-100%');
        shadowDom.setAttribute('y', '-100%');
        shadowDom.setAttribute('width', '300%');
        shadowDom.setAttribute('height', '300%');
 
        // Store dom element in shadow, to avoid creating multiple
        // dom instances for the same shadow element
        (displayable as DisplayableExtended)._shadowDom = shadowDom;
 
        svgElement.setAttribute('filter', getIdURL(shadowDom.getAttribute('id')));
    }
 
    removeUnused() {
        const defs = this.getDefs(false);
        if (!defs) {
            // Nothing to remove
            return;
        }
        let shadowDomsPool = this._shadowDomPool;
 
        // let currentUsedShadow = 0;
        const shadowDomMap = this._shadowDomMap;
        for (let key in shadowDomMap) {
            if (shadowDomMap.hasOwnProperty(key)) {
                shadowDomsPool.push(shadowDomMap[key]);
            }
            // currentUsedShadow++;
        }
 
        // Reset the map.
        this._shadowDomMap = {};
    }
}