var Definable = require("./Definable"); var zrUtil = require("../../core/util"); var matrix = require("../../core/matrix"); /** * @file Manages SVG clipPath elements. * @author Zhang Wenli */ /** * 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 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 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 id = clipPath[dom].getAttribute('id'); clipPathEl = clipPath[dom]; // Use a dom that is already in if (!defs.contains(clipPathEl)) { // This happens when set old clipPath that has // been previously removed defs.appendChild(clipPathEl); } } else { // New 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 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); } }); } }; var _default = ClippathManager; module.exports = _default;