fabric
Version:
Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.
97 lines (89 loc) • 3.74 kB
text/typescript
import { applyViewboxTransform } from './applyViewboxTransform';
import { svgValidTagNamesRegEx } from './constants';
import { hasInvalidAncestor } from './hasInvalidAncestor';
import { parseUseDirectives } from './parseUseDirectives';
import type { SVGParsingOutput, TSvgReviverCallback } from './typedefs';
import type { LoadImageOptions } from '../util/misc/objectEnlive';
import { ElementsParser } from './elements_parser';
import { log, SignalAbortedError } from '../util/internals/console';
import { getTagName } from './getTagName';
const isValidSvgTag = (el: Element) =>
svgValidTagNamesRegEx.test(getTagName(el));
export const createEmptyResponse = (): SVGParsingOutput => ({
objects: [],
elements: [],
options: {},
allElements: [],
});
/**
* Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback
* @static
* @function
* @memberOf fabric
* @param {HTMLElement} doc SVG document to parse
* @param {TSvgParsedCallback} callback Invoked when the parsing is done, with null if parsing wasn't possible with the list of svg nodes.
* @param {TSvgReviverCallback} [reviver] Extra callback for further parsing of SVG elements, called after each fabric object has been created.
* Takes as input the original svg element and the generated `FabricObject` as arguments. Used to inspect extra properties not parsed by fabric,
* or extra custom manipulation
* @param {Object} [options] Object containing options for parsing
* @param {String} [options.crossOrigin] crossOrigin setting to use for external resources
* @param {AbortSignal} [options.signal] handle aborting, see https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal
* @return {SVGParsingOutput}
* {@link SVGParsingOutput} also receives `allElements` array as the last argument. This is the full list of svg nodes available in the document.
* You may want to use it if you are trying to regroup the objects as they were originally grouped in the SVG. ( This was the reason why it was added )
*/
export async function parseSVGDocument(
doc: Document,
reviver?: TSvgReviverCallback,
{ crossOrigin, signal }: LoadImageOptions = {},
): Promise<SVGParsingOutput> {
if (signal && signal.aborted) {
log('log', new SignalAbortedError('parseSVGDocument'));
// this is an unhappy path, we dont care about speed
return createEmptyResponse();
}
const documentElement = doc.documentElement;
parseUseDirectives(doc);
const descendants = Array.from(documentElement.getElementsByTagName('*')),
options = {
...applyViewboxTransform(documentElement),
crossOrigin,
signal,
};
const elements = descendants.filter((el) => {
applyViewboxTransform(el);
return isValidSvgTag(el) && !hasInvalidAncestor(el); // http://www.w3.org/TR/SVG/struct.html#DefsElement
});
if (!elements || (elements && !elements.length)) {
return {
...createEmptyResponse(),
options,
allElements: descendants,
};
}
const localClipPaths: Record<string, Element[]> = {};
descendants
.filter((el) => getTagName(el) === 'clipPath')
.forEach((el) => {
el.setAttribute('originalTransform', el.getAttribute('transform') || '');
const id = el.getAttribute('id')!;
localClipPaths[id] = Array.from(el.getElementsByTagName('*')).filter(
(el) => isValidSvgTag(el),
);
});
// Precedence of rules: style > class > attribute
const elementParser = new ElementsParser(
elements,
options,
reviver,
doc,
localClipPaths,
);
const instances = await elementParser.parse();
return {
objects: instances,
elements,
options,
allElements: descendants,
};
}