UNPKG

fabric

Version:

Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.

140 lines (135 loc) 5.77 kB
import { objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.mjs'; import { Gradient } from '../gradient/Gradient.mjs'; import { Group } from '../shapes/Group.mjs'; import { FabricImage } from '../shapes/Image.mjs'; import { classRegistry } from '../ClassRegistry.mjs'; import { invertTransform, multiplyTransformMatrices, qrDecompose } from '../util/misc/matrix.mjs'; import { removeTransformMatrixForSvgParsing } from '../util/transform_matrix_removal.mjs'; import { Point } from '../Point.mjs'; import { FILL, STROKE, CENTER } from '../constants.mjs'; import { getGradientDefs } from './getGradientDefs.mjs'; import { getCSSRules } from './getCSSRules.mjs'; import { getTagName } from './getTagName.mjs'; import { parseTransformAttribute } from './parseTransformAttribute.mjs'; const findTag = el => classRegistry.getSVGClass(getTagName(el).toLowerCase()); class ElementsParser { constructor(elements, options, reviver, doc, clipPaths) { this.elements = elements; this.options = options; this.reviver = reviver; this.regexUrl = /^url\(['"]?#([^'"]+)['"]?\)/g; this.doc = doc; this.clipPaths = clipPaths; this.gradientDefs = getGradientDefs(doc); this.cssRules = getCSSRules(doc); } parse() { return Promise.all(this.elements.map(element => this.createObject(element))); } async createObject(el) { const klass = findTag(el); if (klass) { const obj = await klass.fromElement(el, this.options, this.cssRules); this.resolveGradient(obj, el, FILL); this.resolveGradient(obj, el, STROKE); if (obj instanceof FabricImage && obj._originalElement) { removeTransformMatrixForSvgParsing(obj, obj.parsePreserveAspectRatioAttribute()); } else { removeTransformMatrixForSvgParsing(obj); } await this.resolveClipPath(obj, el); this.reviver && this.reviver(el, obj); return obj; } return null; } extractPropertyDefinition(obj, property, storage) { const value = obj[property], regex = this.regexUrl; if (!regex.test(value)) { return undefined; } // verify: can we remove the 'g' flag? and remove lastIndex changes? regex.lastIndex = 0; // we passed the regex test, so we know is not null; const id = regex.exec(value)[1]; regex.lastIndex = 0; // @todo fix this return storage[id]; } resolveGradient(obj, el, property) { const gradientDef = this.extractPropertyDefinition(obj, property, this.gradientDefs); if (gradientDef) { const opacityAttr = el.getAttribute(property + '-opacity'); const gradient = Gradient.fromElement(gradientDef, obj, _objectSpread2(_objectSpread2({}, this.options), {}, { opacity: opacityAttr })); obj.set(property, gradient); } } // TODO: resolveClipPath could be run once per clippath with minor work per object. // is a refactor that i m not sure is worth on this code async resolveClipPath(obj, usingElement, exactOwner) { const clipPathElements = this.extractPropertyDefinition(obj, 'clipPath', this.clipPaths); if (clipPathElements) { const objTransformInv = invertTransform(obj.calcTransformMatrix()); const clipPathTag = clipPathElements[0].parentElement; let clipPathOwner = usingElement; while (!exactOwner && clipPathOwner.parentElement && clipPathOwner.getAttribute('clip-path') !== obj.clipPath) { clipPathOwner = clipPathOwner.parentElement; } // move the clipPath tag as sibling to the real element that is using it clipPathOwner.parentElement.appendChild(clipPathTag); // this multiplication order could be opposite. // but i don't have an svg to test it // at the first SVG that has a transform on both places and is misplaced // try to invert this multiplication order const finalTransform = parseTransformAttribute("".concat(clipPathOwner.getAttribute('transform') || '', " ").concat(clipPathTag.getAttribute('originalTransform') || '')); clipPathTag.setAttribute('transform', "matrix(".concat(finalTransform.join(','), ")")); const container = await Promise.all(clipPathElements.map(clipPathElement => { return findTag(clipPathElement).fromElement(clipPathElement, this.options, this.cssRules).then(enlivedClippath => { removeTransformMatrixForSvgParsing(enlivedClippath); enlivedClippath.fillRule = enlivedClippath.clipRule; delete enlivedClippath.clipRule; return enlivedClippath; }); })); const clipPath = container.length === 1 ? container[0] : new Group(container); const gTransform = multiplyTransformMatrices(objTransformInv, clipPath.calcTransformMatrix()); if (clipPath.clipPath) { await this.resolveClipPath(clipPath, clipPathOwner, // this is tricky. // it tries to differentiate from when clipPaths are inherited by outside groups // or when are really clipPaths referencing other clipPaths clipPathTag.getAttribute('clip-path') ? clipPathOwner : undefined); } const { scaleX, scaleY, angle, skewX, translateX, translateY } = qrDecompose(gTransform); clipPath.set({ flipX: false, flipY: false }); clipPath.set({ scaleX, scaleY, angle, skewX, skewY: 0 }); clipPath.setPositionByOrigin(new Point(translateX, translateY), CENTER, CENTER); obj.clipPath = clipPath; } else { // if clip-path does not resolve to any element, delete the property. delete obj.clipPath; return; } } } export { ElementsParser }; //# sourceMappingURL=elements_parser.mjs.map