fabric
Version:
Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.
1 lines • 9.89 kB
Source Map (JSON)
{"version":3,"file":"elements_parser.min.mjs","names":[],"sources":["../../../src/parser/elements_parser.ts"],"sourcesContent":["import { Gradient } from '../gradient/Gradient';\nimport { Group } from '../shapes/Group';\nimport { FabricImage } from '../shapes/Image';\nimport { classRegistry } from '../ClassRegistry';\nimport {\n invertTransform,\n multiplyTransformMatrices,\n qrDecompose,\n} from '../util/misc/matrix';\nimport { removeTransformMatrixForSvgParsing } from '../util/transform_matrix_removal';\nimport type { FabricObject } from '../shapes/Object/FabricObject';\nimport { Point } from '../Point';\nimport { CENTER, FILL, STROKE } from '../constants';\nimport { getGradientDefs } from './getGradientDefs';\nimport { getCSSRules } from './getCSSRules';\nimport type { LoadImageOptions } from '../util';\nimport type { CSSRules, TSvgReviverCallback } from './typedefs';\nimport type { ParsedViewboxTransform } from './applyViewboxTransform';\nimport type { SVGOptions } from '../gradient';\nimport { getTagName } from './getTagName';\nimport { parseTransformAttribute } from './parseTransformAttribute';\n\nconst findTag = (el: Element) =>\n classRegistry.getSVGClass(getTagName(el).toLowerCase());\n\ntype StorageType = {\n fill: SVGGradientElement;\n stroke: SVGGradientElement;\n clipPath: Element[];\n};\n\ntype NotParsedFabricObject = FabricObject & {\n fill: string;\n stroke: string;\n clipPath?: string;\n clipRule?: CanvasFillRule;\n};\n\nexport class ElementsParser {\n declare elements: Element[];\n declare options: LoadImageOptions & ParsedViewboxTransform;\n declare reviver?: TSvgReviverCallback;\n declare regexUrl: RegExp;\n declare doc: Document;\n declare clipPaths: Record<string, Element[]>;\n declare gradientDefs: Record<string, SVGGradientElement>;\n declare cssRules: CSSRules;\n\n constructor(\n elements: Element[],\n options: LoadImageOptions & ParsedViewboxTransform,\n reviver: TSvgReviverCallback | undefined,\n doc: Document,\n clipPaths: Record<string, Element[]>,\n ) {\n this.elements = elements;\n this.options = options;\n this.reviver = reviver;\n this.regexUrl = /^url\\(['\"]?#([^'\"]+)['\"]?\\)/g;\n this.doc = doc;\n this.clipPaths = clipPaths;\n this.gradientDefs = getGradientDefs(doc);\n this.cssRules = getCSSRules(doc);\n }\n\n parse(): Promise<Array<FabricObject | null>> {\n return Promise.all(\n this.elements.map((element) => this.createObject(element)),\n );\n }\n\n async createObject(el: Element): Promise<FabricObject | null> {\n const klass = findTag(el);\n if (klass) {\n const obj: NotParsedFabricObject = await klass.fromElement(\n el,\n this.options,\n this.cssRules,\n );\n this.resolveGradient(obj, el, FILL);\n this.resolveGradient(obj, el, STROKE);\n if (obj instanceof FabricImage && obj._originalElement) {\n removeTransformMatrixForSvgParsing(\n obj,\n obj.parsePreserveAspectRatioAttribute(),\n );\n } else {\n removeTransformMatrixForSvgParsing(obj);\n }\n await this.resolveClipPath(obj, el);\n this.reviver && this.reviver(el, obj);\n return obj;\n }\n return null;\n }\n\n extractPropertyDefinition(\n obj: NotParsedFabricObject,\n property: 'fill' | 'stroke' | 'clipPath',\n storage: Record<string, StorageType[typeof property]>,\n ): StorageType[typeof property] | undefined {\n const value = obj[property]!,\n regex = this.regexUrl;\n if (!regex.test(value)) {\n return undefined;\n }\n // verify: can we remove the 'g' flag? and remove lastIndex changes?\n regex.lastIndex = 0;\n // we passed the regex test, so we know is not null;\n const id = regex.exec(value)![1];\n regex.lastIndex = 0;\n // @todo fix this\n return storage[id];\n }\n\n resolveGradient(\n obj: NotParsedFabricObject,\n el: Element,\n property: 'fill' | 'stroke',\n ) {\n const gradientDef = this.extractPropertyDefinition(\n obj,\n property,\n this.gradientDefs,\n ) as SVGGradientElement;\n if (gradientDef) {\n const opacityAttr = el.getAttribute(property + '-opacity');\n const gradient = Gradient.fromElement(gradientDef, obj, {\n ...this.options,\n opacity: opacityAttr,\n } as SVGOptions);\n obj.set(property, gradient);\n }\n }\n\n // TODO: resolveClipPath could be run once per clippath with minor work per object.\n // is a refactor that i m not sure is worth on this code\n async resolveClipPath(\n obj: NotParsedFabricObject,\n usingElement: Element,\n exactOwner?: Element,\n ) {\n const clipPathElements = this.extractPropertyDefinition(\n obj,\n 'clipPath',\n this.clipPaths,\n ) as Element[];\n if (clipPathElements) {\n const objTransformInv = invertTransform(obj.calcTransformMatrix());\n const clipPathTag = clipPathElements[0].parentElement!;\n let clipPathOwner = usingElement;\n while (\n !exactOwner &&\n clipPathOwner.parentElement &&\n clipPathOwner.getAttribute('clip-path') !== obj.clipPath\n ) {\n clipPathOwner = clipPathOwner.parentElement;\n }\n // move the clipPath tag as sibling to the real element that is using it\n clipPathOwner.parentElement!.appendChild(clipPathTag);\n\n // this multiplication order could be opposite.\n // but i don't have an svg to test it\n // at the first SVG that has a transform on both places and is misplaced\n // try to invert this multiplication order\n const finalTransform = parseTransformAttribute(\n `${clipPathOwner.getAttribute('transform') || ''} ${\n clipPathTag.getAttribute('originalTransform') || ''\n }`,\n );\n\n clipPathTag.setAttribute(\n 'transform',\n `matrix(${finalTransform.join(',')})`,\n );\n\n const container = await Promise.all(\n clipPathElements.map((clipPathElement) => {\n return findTag(clipPathElement)\n .fromElement(clipPathElement, this.options, this.cssRules)\n .then((enlivedClippath: NotParsedFabricObject) => {\n removeTransformMatrixForSvgParsing(enlivedClippath);\n enlivedClippath.fillRule = enlivedClippath.clipRule!;\n delete enlivedClippath.clipRule;\n return enlivedClippath;\n });\n }),\n );\n const clipPath =\n container.length === 1 ? container[0] : new Group(container);\n const gTransform = multiplyTransformMatrices(\n objTransformInv,\n clipPath.calcTransformMatrix(),\n );\n if (clipPath.clipPath) {\n await this.resolveClipPath(\n clipPath,\n clipPathOwner,\n // this is tricky.\n // it tries to differentiate from when clipPaths are inherited by outside groups\n // or when are really clipPaths referencing other clipPaths\n clipPathTag.getAttribute('clip-path') ? clipPathOwner : undefined,\n );\n }\n const { scaleX, scaleY, angle, skewX, translateX, translateY } =\n qrDecompose(gTransform);\n clipPath.set({\n flipX: false,\n flipY: false,\n });\n clipPath.set({\n scaleX,\n scaleY,\n angle,\n skewX,\n skewY: 0,\n });\n clipPath.setPositionByOrigin(\n new Point(translateX, translateY),\n CENTER,\n CENTER,\n );\n obj.clipPath = clipPath;\n } else {\n // if clip-path does not resolve to any element, delete the property.\n delete obj.clipPath;\n return;\n }\n }\n}\n"],"mappings":"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"}