| /** |
| * @file Manages SVG clipPath elements. |
| * @author Zhang Wenli |
| */ |
| import Definable from './Definable'; |
| import * as zrUtil from '../../core/util'; |
| import * as matrix from '../../core/matrix'; |
| /** |
| * Manages SVG clipPath elements. |
| * |
| * @class |
| * @extends Definable |
| * @param {number} zrId zrender instance id |
| * @param {SVGElement} svgRoot root of SVG document |
| */ |
| |
| function ClippathManager(zrId, svgRoot) { |
| Definable.call(this, zrId, svgRoot, 'clipPath', '__clippath_in_use__'); |
| } |
| |
| zrUtil.inherits(ClippathManager, Definable); |
| /** |
| * Update clipPath. |
| * |
| * @param {Displayable} displayable displayable element |
| */ |
| |
| ClippathManager.prototype.update = function (displayable) { |
| var svgEl = this.getSvgElement(displayable); |
| |
| if (svgEl) { |
| this.updateDom(svgEl, displayable.__clipPaths, false); |
| } |
| |
| var textEl = this.getTextSvgElement(displayable); |
| |
| if (textEl) { |
| // Make another clipPath for text, since it's transform |
| // matrix is not the same with svgElement |
| this.updateDom(textEl, displayable.__clipPaths, true); |
| } |
| |
| this.markUsed(displayable); |
| }; |
| /** |
| * Create an SVGElement of displayable and create a <clipPath> of its |
| * clipPath |
| * |
| * @param {Displayable} parentEl parent element |
| * @param {ClipPath[]} clipPaths clipPaths of parent element |
| * @param {boolean} isText if parent element is Text |
| */ |
| |
| |
| ClippathManager.prototype.updateDom = function (parentEl, clipPaths, isText) { |
| if (clipPaths && clipPaths.length > 0) { |
| // Has clipPath, create <clipPath> with the first clipPath |
| var defs = this.getDefs(true); |
| var clipPath = clipPaths[0]; |
| var clipPathEl; |
| var id; |
| var dom = isText ? '_textDom' : '_dom'; |
| |
| if (clipPath[dom]) { |
| // Use a dom that is already in <defs> |
| id = clipPath[dom].getAttribute('id'); |
| clipPathEl = clipPath[dom]; // Use a dom that is already in <defs> |
| |
| if (!defs.contains(clipPathEl)) { |
| // This happens when set old clipPath that has |
| // been previously removed |
| defs.appendChild(clipPathEl); |
| } |
| } else { |
| // New <clipPath> |
| id = 'zr' + this._zrId + '-clip-' + this.nextId; |
| ++this.nextId; |
| clipPathEl = this.createElement('clipPath'); |
| clipPathEl.setAttribute('id', id); |
| defs.appendChild(clipPathEl); |
| clipPath[dom] = clipPathEl; |
| } // Build path and add to <clipPath> |
| |
| |
| var svgProxy = this.getSvgProxy(clipPath); |
| |
| if (clipPath.transform && clipPath.parent.invTransform && !isText) { |
| /** |
| * If a clipPath has a parent with transform, the transform |
| * of parent should not be considered when setting transform |
| * of clipPath. So we need to transform back from parent's |
| * transform, which is done by multiplying parent's inverse |
| * transform. |
| */ |
| // Store old transform |
| var transform = Array.prototype.slice.call(clipPath.transform); // Transform back from parent, and brush path |
| |
| matrix.mul(clipPath.transform, clipPath.parent.invTransform, clipPath.transform); |
| svgProxy.brush(clipPath); // Set back transform of clipPath |
| |
| clipPath.transform = transform; |
| } else { |
| svgProxy.brush(clipPath); |
| } |
| |
| var pathEl = this.getSvgElement(clipPath); |
| clipPathEl.innerHTML = ''; |
| /** |
| * Use `cloneNode()` here to appendChild to multiple parents, |
| * which may happend when Text and other shapes are using the same |
| * clipPath. Since Text will create an extra clipPath DOM due to |
| * different transform rules. |
| */ |
| |
| clipPathEl.appendChild(pathEl.cloneNode()); |
| parentEl.setAttribute('clip-path', 'url(#' + id + ')'); |
| |
| if (clipPaths.length > 1) { |
| // Make the other clipPaths recursively |
| this.updateDom(clipPathEl, clipPaths.slice(1), isText); |
| } |
| } else { |
| // No clipPath |
| if (parentEl) { |
| parentEl.setAttribute('clip-path', 'none'); |
| } |
| } |
| }; |
| /** |
| * Mark a single clipPath to be used |
| * |
| * @param {Displayable} displayable displayable element |
| */ |
| |
| |
| ClippathManager.prototype.markUsed = function (displayable) { |
| var that = this; // displayable.__clipPaths can only be `null`/`undefined` or an non-empty array. |
| |
| if (displayable.__clipPaths) { |
| zrUtil.each(displayable.__clipPaths, function (clipPath) { |
| if (clipPath._dom) { |
| Definable.prototype.markUsed.call(that, clipPath._dom); |
| } |
| |
| if (clipPath._textDom) { |
| Definable.prototype.markUsed.call(that, clipPath._textDom); |
| } |
| }); |
| } |
| }; |
| |
| export default ClippathManager; |