| /** | 
|  * @file Manages SVG pattern elements. | 
|  * @author Zhang Wenli | 
|  */ | 
|   | 
| import Definable from './Definable'; | 
| import * as zrUtil from '../../core/util'; | 
| import Displayable from '../../graphic/Displayable'; | 
| import {PatternObject} from '../../graphic/Pattern'; | 
| import {createOrUpdateImage} from '../../graphic/helper/image'; | 
| import WeakMap from '../../core/WeakMap'; | 
| import { getIdURL, isPattern, isSVGPattern } from '../../svg/helper'; | 
| import { createElement } from '../../svg/core'; | 
|   | 
| const patternDomMap = new WeakMap<PatternObject, SVGElement>(); | 
|   | 
| /** | 
|  * Manages SVG pattern elements. | 
|  * | 
|  * @param   zrId    zrender instance id | 
|  * @param   svgRoot root of SVG document | 
|  */ | 
| export default class PatternManager extends Definable { | 
|   | 
|     constructor(zrId: number, svgRoot: SVGElement) { | 
|         super(zrId, svgRoot, ['pattern'], '__pattern_in_use__'); | 
|     } | 
|   | 
|   | 
|     /** | 
|      * Create new pattern DOM for fill or stroke if not exist, | 
|      * but will not update pattern 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') { | 
|                 const pattern = displayable.style[fillOrStroke] as PatternObject; | 
|                 if (isPattern(pattern)) { | 
|                     const defs = that.getDefs(true); | 
|   | 
|                     // Create dom in <defs> if not exists | 
|                     let dom = patternDomMap.get(pattern); | 
|                     if (dom) { | 
|                         // Pattern exists | 
|                         if (!defs.contains(dom)) { | 
|                             // __dom is no longer in defs, recreate | 
|                             that.addDom(dom); | 
|                         } | 
|                     } | 
|                     else { | 
|                         // New dom | 
|                         dom = that.add(pattern); | 
|                     } | 
|   | 
|                     that.markUsed(displayable); | 
|   | 
|                     svgElement.setAttribute(fillOrStroke, getIdURL(dom.getAttribute('id'))); | 
|                 } | 
|             }); | 
|         } | 
|     } | 
|   | 
|   | 
|     /** | 
|      * Add a new pattern tag in <defs> | 
|      * | 
|      * @param   pattern zr pattern instance | 
|      */ | 
|     add(pattern: PatternObject): SVGElement { | 
|         if (!isPattern(pattern)) { | 
|             return; | 
|         } | 
|   | 
|         let dom = createElement('pattern'); | 
|   | 
|         pattern.id = pattern.id == null ? this.nextId++ : pattern.id; | 
|         dom.setAttribute('id', 'zr' + this._zrId | 
|             + '-pattern-' + pattern.id); | 
|   | 
|         dom.setAttribute('patternUnits', 'userSpaceOnUse'); | 
|   | 
|         this.updateDom(pattern, dom); | 
|         this.addDom(dom); | 
|   | 
|         return dom; | 
|     } | 
|   | 
|   | 
|     /** | 
|      * Update pattern. | 
|      * | 
|      * @param pattern zr pattern instance or color string | 
|      */ | 
|     update(pattern: PatternObject | string) { | 
|         if (!isPattern(pattern)) { | 
|             return; | 
|         } | 
|   | 
|         const that = this; | 
|         this.doUpdate(pattern, function () { | 
|             const dom = patternDomMap.get(pattern); | 
|             that.updateDom(pattern, dom); | 
|         }); | 
|     } | 
|   | 
|   | 
|     /** | 
|      * Update pattern dom | 
|      * | 
|      * @param pattern zr pattern instance | 
|      * @param patternDom DOM to update | 
|      */ | 
|     updateDom(pattern: PatternObject, patternDom: SVGElement) { | 
|         if (isSVGPattern(pattern)) { | 
|             // New SVGPattern will not been supported in the legacy SVG renderer. | 
|             // svg-legacy will been removed soon. | 
|   | 
|             // const svgElement = pattern.svgElement; | 
|             // const isStringSVG = typeof svgElement === 'string'; | 
|             // if (isStringSVG || svgElement.parentNode !== patternDom) { | 
|             //     if (isStringSVG) { | 
|             //         patternDom.innerHTML = svgElement; | 
|             //     } | 
|             //     else { | 
|             //         patternDom.innerHTML = ''; | 
|             //         patternDom.appendChild(svgElement); | 
|             //     } | 
|   | 
|             //     patternDom.setAttribute('width', pattern.svgWidth as any); | 
|             //     patternDom.setAttribute('height', pattern.svgHeight as any); | 
|             // } | 
|         } | 
|         else { | 
|             let img: SVGElement; | 
|             const prevImage = patternDom.getElementsByTagName('image'); | 
|             if (prevImage.length) { | 
|                 if (pattern.image) { | 
|                     // Update | 
|                     img = prevImage[0]; | 
|                 } | 
|                 else { | 
|                     // Remove | 
|                     patternDom.removeChild(prevImage[0]); | 
|                     return; | 
|                 } | 
|             } | 
|             else if (pattern.image) { | 
|                 // Create | 
|                 img = createElement('image'); | 
|             } | 
|   | 
|             if (img) { | 
|                 let imageSrc; | 
|                 const patternImage = pattern.image; | 
|                 if (typeof patternImage === 'string') { | 
|                     imageSrc = patternImage; | 
|                 } | 
|                 else if (patternImage instanceof HTMLImageElement) { | 
|                     imageSrc = patternImage.src; | 
|                 } | 
|                 else if (patternImage instanceof HTMLCanvasElement) { | 
|                     imageSrc = patternImage.toDataURL(); | 
|                 } | 
|   | 
|                 if (imageSrc) { | 
|                     img.setAttribute('href', imageSrc); | 
|   | 
|                     // No need to re-render so dirty is empty | 
|                     const hostEl = { | 
|                         dirty: () => {} | 
|                     }; | 
|                     const updateSize = (img: HTMLImageElement) => { | 
|                         patternDom.setAttribute('width', img.width as any); | 
|                         patternDom.setAttribute('height', img.height as any); | 
|                     }; | 
|   | 
|                     const createdImage = createOrUpdateImage(imageSrc, img as any, hostEl, updateSize); | 
|                     if (createdImage && createdImage.width && createdImage.height) { | 
|                         // Loaded before | 
|                         updateSize(createdImage as HTMLImageElement); | 
|                     } | 
|   | 
|                     patternDom.appendChild(img); | 
|                 } | 
|             } | 
|         } | 
|   | 
|         const x = pattern.x || 0; | 
|         const y = pattern.y || 0; | 
|         const rotation = (pattern.rotation || 0) / Math.PI * 180; | 
|         const scaleX = pattern.scaleX || 1; | 
|         const scaleY = pattern.scaleY || 1; | 
|         const transform = `translate(${x}, ${y}) rotate(${rotation}) scale(${scaleX}, ${scaleY})`; | 
|         patternDom.setAttribute('patternTransform', transform); | 
|         patternDomMap.set(pattern, patternDom); | 
|     } | 
|   | 
|     /** | 
|      * Mark a single pattern to be used | 
|      * | 
|      * @param displayable displayable element | 
|      */ | 
|     markUsed(displayable: Displayable) { | 
|         if (displayable.style) { | 
|             if (isPattern(displayable.style.fill)) { | 
|                 super.markDomUsed(patternDomMap.get(displayable.style.fill)); | 
|             } | 
|             if (isPattern(displayable.style.stroke)) { | 
|                 super.markDomUsed(patternDomMap.get(displayable.style.stroke)); | 
|             } | 
|         } | 
|     } | 
|   | 
| } |