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