js-draw
Version:
Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript.
686 lines (685 loc) • 30.6 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SVGLoaderLoadMethod = exports.svgLoaderAutoresizeClassName = exports.svgLoaderAttributeContainerID = exports.svgStyleAttributesDataKey = exports.svgAttributesDataKey = exports.defaultSVGViewRect = void 0;
const math_1 = require("@js-draw/math");
const BackgroundComponent_1 = __importStar(require("../components/BackgroundComponent"));
const ImageComponent_1 = __importDefault(require("../components/ImageComponent"));
const Stroke_1 = __importDefault(require("../components/Stroke"));
const SVGGlobalAttributesObject_1 = __importDefault(require("../components/SVGGlobalAttributesObject"));
const TextComponent_1 = __importStar(require("../components/TextComponent"));
const UnknownSVGObject_1 = __importDefault(require("../components/UnknownSVGObject"));
const RenderablePathSpec_1 = require("../rendering/RenderablePathSpec");
const SVGRenderer_1 = require("../rendering/renderers/SVGRenderer");
const determineFontSize_1 = __importDefault(require("./utils/determineFontSize"));
// Size of a loaded image if no size is specified.
exports.defaultSVGViewRect = new math_1.Rect2(0, 0, 500, 500);
// Key to retrieve unrecognised attributes from an AbstractComponent
exports.svgAttributesDataKey = 'svgAttrs';
// Like {@link svgAttributesDataKey}, but for styles
exports.svgStyleAttributesDataKey = 'svgStyleAttrs';
// Key that specifies the ID of an SVG element that contained a given node when the image
// was first loaded.
exports.svgLoaderAttributeContainerID = 'svgContainerID';
// If present in the exported SVG's class list, the image will be
// autoresized when components are added/removed.
exports.svgLoaderAutoresizeClassName = 'js-draw--autoresize';
// @internal
var SVGLoaderLoadMethod;
(function (SVGLoaderLoadMethod) {
SVGLoaderLoadMethod["IFrame"] = "iframe";
SVGLoaderLoadMethod["DOMParser"] = "domparser";
})(SVGLoaderLoadMethod || (exports.SVGLoaderLoadMethod = SVGLoaderLoadMethod = {}));
const supportedStrokeFillStyleAttrs = ['stroke', 'fill', 'stroke-width'];
// Handles loading images from SVG.
class SVGLoader {
constructor(source, onFinish, options) {
this.source = source;
this.onFinish = onFinish;
this.onAddComponent = null;
this.onProgress = null;
this.onDetermineExportRect = null;
this.processedCount = 0;
this.totalToProcess = 0;
this.containerGroupIDs = [];
this.encounteredIDs = [];
this.plugins = options.plugins ?? [];
this.storeUnknown = !(options.sanitize ?? false);
this.disableUnknownObjectWarnings = !!options.disableUnknownObjectWarnings;
}
// If [computedStyles] is given, it is preferred to directly accessing node's style object.
getStyle(node, computedStyles) {
let fill = math_1.Color4.transparent;
let stroke;
// If possible, use computedStyles (allows property inheritance).
// Chromium, however, sets .fill to a falsy, but not undefined value in some cases where
// styles are available. As such, use || instead of ??.
const fillAttribute = node.getAttribute('fill') ?? (computedStyles?.fill || node.style?.fill);
if (fillAttribute) {
try {
fill = math_1.Color4.fromString(fillAttribute);
}
catch {
console.error('Unknown fill color,', fillAttribute);
}
}
const strokeAttribute = node.getAttribute('stroke') ?? computedStyles?.stroke ?? node.style?.stroke ?? '';
const strokeWidthAttr = node.getAttribute('stroke-width') ??
computedStyles?.strokeWidth ??
node.style?.strokeWidth ??
'';
if (strokeAttribute && strokeWidthAttr) {
try {
let width = parseFloat(strokeWidthAttr ?? '1');
if (!isFinite(width)) {
width = 0;
}
const strokeColor = math_1.Color4.fromString(strokeAttribute);
if (strokeColor.a > 0) {
stroke = {
width,
color: strokeColor,
};
}
}
catch (e) {
console.error('Error parsing stroke data:', e);
}
}
const style = {
fill,
stroke,
};
return style;
}
strokeDataFromElem(node) {
const result = [];
const pathData = node.getAttribute('d') ?? '';
const style = this.getStyle(node);
// Break the path into chunks at each moveTo ('M') command:
const parts = pathData.split('M');
let isFirst = true;
for (const part of parts) {
// Skip effective no-ops -- moveTos without additional commands.
const isNoOpMoveTo = /^[0-9., \t\n]+$/.exec(part);
if (part !== '' && !isNoOpMoveTo) {
// We split the path by moveTo commands, so add the 'M' back in
// if it was present.
const current = !isFirst ? `M${part}` : part;
const path = math_1.Path.fromString(current);
const spec = (0, RenderablePathSpec_1.pathToRenderable)(path, style);
result.push(spec);
}
isFirst = false;
}
return result;
}
attachUnrecognisedAttrs(elem, node, supportedAttrs, supportedStyleAttrs) {
if (!this.storeUnknown) {
return;
}
for (const attr of node.getAttributeNames()) {
if (supportedAttrs.has(attr) || (attr === 'style' && supportedStyleAttrs)) {
continue;
}
elem.attachLoadSaveData(exports.svgAttributesDataKey, [
attr,
node.getAttribute(attr),
]);
}
if (supportedStyleAttrs && node.style) {
// Use a for loop instead of an iterator: js-dom seems to not
// support using node.style as an iterator.
for (let i = 0; i < node.style.length; i++) {
const attr = node.style[i];
if (attr === '' || !attr) {
continue;
}
if (supportedStyleAttrs.has(attr)) {
continue;
}
// TODO: Do we need special logic for !important properties?
elem.attachLoadSaveData(exports.svgStyleAttributesDataKey, {
key: attr,
value: node.style.getPropertyValue(attr),
priority: node.style.getPropertyPriority(attr),
});
}
}
}
// Adds a stroke with a single path
async addPath(node) {
let elem;
try {
const strokeData = this.strokeDataFromElem(node);
elem = new Stroke_1.default(strokeData);
this.attachUnrecognisedAttrs(elem, node, new Set([...supportedStrokeFillStyleAttrs, 'd']), new Set(supportedStrokeFillStyleAttrs));
}
catch (e) {
console.error('Invalid path in node', node, '\nError:', e, '\nAdding as an unknown object.');
if (this.storeUnknown) {
elem = new UnknownSVGObject_1.default(node);
}
else {
return;
}
}
await this.addComponent(elem);
}
async addBackground(node) {
// If a grid background,
if (node.classList.contains(BackgroundComponent_1.backgroundTypeToClassNameMap[BackgroundComponent_1.BackgroundType.Grid])) {
let foregroundStr;
let backgroundStr;
let gridStrokeWidthStr;
// If a group,
if (node.tagName.toLowerCase() === 'g') {
// We expect exactly two children. One of these is the solid
// background of the grid
if (node.children.length !== 2) {
await this.addUnknownNode(node);
return;
}
const background = node.children[0];
const grid = node.children[1];
backgroundStr = background.getAttribute('fill');
foregroundStr = grid.getAttribute('stroke');
gridStrokeWidthStr = grid.getAttribute('stroke-width');
}
else {
backgroundStr = node.getAttribute('fill');
foregroundStr = node.getAttribute('stroke');
gridStrokeWidthStr = node.getAttribute('stroke-width');
}
// Default to a transparent background.
backgroundStr ??= math_1.Color4.transparent.toHexString();
// A grid must have a foreground color specified.
if (!foregroundStr) {
await this.addUnknownNode(node);
return;
}
// Extract the grid size from the class name
let gridSize = undefined;
for (const className of node.classList) {
if (className.startsWith(BackgroundComponent_1.imageBackgroundGridSizeCSSPrefix)) {
const sizeStr = className.substring(BackgroundComponent_1.imageBackgroundGridSizeCSSPrefix.length);
gridSize = parseFloat(sizeStr.replace(/p/g, '.'));
}
}
let gridStrokeWidth = undefined;
if (gridStrokeWidthStr) {
gridStrokeWidth = parseFloat(gridStrokeWidthStr);
}
const backgroundColor = math_1.Color4.fromString(backgroundStr);
let foregroundColor = math_1.Color4.fromString(foregroundStr);
// Should the foreground color be determined automatically?
if (!node.classList.contains(BackgroundComponent_1.imageBackgroundNonAutomaticSecondaryColorCSSClassName)) {
foregroundColor = undefined;
}
const elem = BackgroundComponent_1.default.ofGrid(backgroundColor, gridSize, foregroundColor, gridStrokeWidth);
await this.addComponent(elem);
}
// Otherwise, if just a <path/>, it's a solid color background.
else if (node.tagName.toLowerCase() === 'path') {
const fill = math_1.Color4.fromString(node.getAttribute('fill') ?? node.style.fill ?? 'black');
const elem = new BackgroundComponent_1.default(BackgroundComponent_1.BackgroundType.SolidColor, fill);
await this.addComponent(elem);
}
else {
await this.addUnknownNode(node);
}
}
getComputedStyle(element) {
try {
// getComputedStyle may fail in jsdom when using a DOMParser.
return window.getComputedStyle(element);
}
catch (error) {
console.warn('Error computing style', error);
return undefined;
}
}
// If given, 'supportedAttrs' will have x, y, etc. attributes that were used in computing the transform added to it,
// to prevent storing duplicate transform information when saving the component.
getTransform(elem, supportedAttrs, computedStyles) {
// If possible, load the js-draw specific transform attribute
const highpTransformAttribute = 'data-highp-transform';
const rawTransformData = elem.getAttribute(highpTransformAttribute);
let transform;
if (rawTransformData) {
try {
transform = math_1.Mat33.fromCSSMatrix(rawTransformData);
supportedAttrs?.push(highpTransformAttribute);
}
catch (e) {
console.warn(`Unable to parse raw transform data, ${rawTransformData}. Falling back to CSS data. Error:`, e);
}
}
if (!transform) {
computedStyles ??= this.getComputedStyle(elem);
let transformProperty = computedStyles?.transform;
if (!transformProperty || transformProperty === 'none') {
transformProperty = elem.style?.transform || 'none';
}
// Prefer the actual .style.transform
// to the computed stylesheet -- in some browsers, the computedStyles version
// can have lower precision.
try {
transform = math_1.Mat33.fromCSSMatrix(elem.style.transform);
}
catch (_e) {
console.warn('matrix parse error', _e);
transform = math_1.Mat33.fromCSSMatrix(transformProperty);
}
const elemX = elem.getAttribute('x');
const elemY = elem.getAttribute('y');
if (elemX || elemY) {
const x = parseFloat(elemX ?? '0');
const y = parseFloat(elemY ?? '0');
if (!isNaN(x) && !isNaN(y)) {
supportedAttrs?.push('x', 'y');
transform = transform.rightMul(math_1.Mat33.translation(math_1.Vec2.of(x, y)));
}
}
}
return transform;
}
makeText(elem) {
const contentList = [];
for (const child of elem.childNodes) {
if (child.nodeType === Node.TEXT_NODE) {
contentList.push(child.nodeValue ?? '');
}
else if (child.nodeType === Node.ELEMENT_NODE) {
const subElem = child;
if (subElem.tagName.toLowerCase() === 'tspan') {
// FIXME: tspan's (x, y) components are absolute, not relative to the parent.
contentList.push(this.makeText(subElem));
}
else {
throw new Error(`Unrecognized text child element: ${subElem}`);
}
}
else {
throw new Error(`Unrecognized text child node: ${child}.`);
}
}
// If no content, the content is an empty string.
if (contentList.length === 0) {
contentList.push('');
}
// Compute styles.
const computedStyles = this.getComputedStyle(elem);
const supportedStyleAttrs = new Set([
'fontFamily',
'transform',
...supportedStrokeFillStyleAttrs,
]);
const style = {
size: (0, determineFontSize_1.default)(elem, computedStyles, supportedStyleAttrs),
fontFamily: computedStyles?.fontFamily || elem.style?.fontFamily || 'sans-serif',
fontWeight: computedStyles?.fontWeight || elem.style?.fontWeight || undefined,
fontStyle: computedStyles?.fontStyle || elem.style?.fontStyle || undefined,
renderingStyle: this.getStyle(elem, computedStyles),
};
const supportedAttrs = [];
let transform = this.getTransform(elem, supportedAttrs, computedStyles);
let transformMode = TextComponent_1.TextTransformMode.ABSOLUTE_XY;
const elemDX = elem.getAttribute('dx');
if (elemDX) {
transformMode = TextComponent_1.TextTransformMode.RELATIVE_X_ABSOLUTE_Y;
transform = transform.rightMul(math_1.Mat33.translation(math_1.Vec2.of(parseFloat(elemDX), 0)));
supportedAttrs.push('dx');
}
const elemDY = elem.getAttribute('dy');
if (elemDY) {
if (transformMode === TextComponent_1.TextTransformMode.RELATIVE_X_ABSOLUTE_Y) {
transformMode = TextComponent_1.TextTransformMode.RELATIVE_XY;
}
else {
transformMode = TextComponent_1.TextTransformMode.RELATIVE_Y_ABSOLUTE_X;
}
transform = transform.rightMul(math_1.Mat33.translation(math_1.Vec2.of(0, parseFloat(elemDY))));
supportedAttrs.push('dy');
}
const result = new TextComponent_1.default(contentList, transform, style, transformMode);
this.attachUnrecognisedAttrs(result, elem, new Set(supportedAttrs), new Set(supportedStyleAttrs));
return result;
}
async addText(elem) {
try {
const textElem = this.makeText(elem);
await this.addComponent(textElem);
}
catch (e) {
console.error('Invalid text object in node', elem, '. Continuing.... Error:', e);
this.addUnknownNode(elem);
}
}
async addImage(elem) {
const image = new Image();
image.src = elem.getAttribute('xlink:href') ?? elem.href.baseVal;
image.setAttribute('alt', elem.getAttribute('aria-label') ?? '');
try {
const supportedAttrs = [];
const transform = this.getTransform(elem, supportedAttrs);
const imageElem = await ImageComponent_1.default.fromImage(image, transform);
this.attachUnrecognisedAttrs(imageElem, elem, new Set(supportedAttrs), new Set(['transform']));
await this.addComponent(imageElem);
}
catch (e) {
console.error('Error loading image:', e, '. Element: ', elem, '. Continuing...');
await this.addUnknownNode(elem);
}
}
async addUnknownNode(node) {
if (this.storeUnknown) {
const component = new UnknownSVGObject_1.default(node);
await this.addComponent(component);
}
}
async startGroup(node) {
node = node.cloneNode(false);
// Select a unique ID based on the node's ID property (if it exists).
// Use `||` and not `??` so that empty string IDs are also replaced.
let id = node.id || `id-${this.encounteredIDs.length}`;
// Make id unique.
let idSuffixCounter = 0;
let suffix = '';
while (this.encounteredIDs.includes(id + suffix)) {
idSuffixCounter++;
suffix = '--' + idSuffixCounter;
}
id += suffix;
// Remove all children from the node -- children will be handled separately
// (not removing children here could cause duplicates in the result, when rendered).
node.replaceChildren();
node.id = id;
const component = new UnknownSVGObject_1.default(node);
this.addComponent(component);
// Add to IDs after -- we don't want the <g> element to be marked
// as its own container.
this.containerGroupIDs.push(node.id);
this.encounteredIDs.push(node.id);
}
// Ends the most recent group started by .startGroup
async endGroup() {
this.containerGroupIDs.pop();
}
async addComponent(component) {
// Attach the stack of container IDs
if (this.containerGroupIDs.length > 0) {
component.attachLoadSaveData(exports.svgLoaderAttributeContainerID, [...this.containerGroupIDs]);
}
await this.onAddComponent?.(component);
}
updateViewBox(node) {
const viewBoxAttr = node.getAttribute('viewBox');
if (this.rootViewBox || !viewBoxAttr) {
return;
}
const components = viewBoxAttr.split(/[ \t\n,]+/);
const x = parseFloat(components[0]);
const y = parseFloat(components[1]);
const width = parseFloat(components[2]);
const height = parseFloat(components[3]);
if (isNaN(x) || isNaN(y) || isNaN(width) || isNaN(height)) {
console.warn(`node ${node} has an unparsable viewbox. Viewbox: ${viewBoxAttr}. Match: ${components}.`);
return;
}
const autoresize = node.classList.contains(exports.svgLoaderAutoresizeClassName);
this.rootViewBox = new math_1.Rect2(x, y, width, height);
this.onDetermineExportRect?.(this.rootViewBox, { autoresize });
}
async updateSVGAttrs(node) {
if (this.storeUnknown) {
await this.onAddComponent?.(new SVGGlobalAttributesObject_1.default(this.getSourceAttrs(node)));
}
}
async visit(node) {
this.totalToProcess += node.childElementCount;
let visitChildren = true;
const visitPlugin = async () => {
for (const plugin of this.plugins) {
const processed = await plugin.visit(node, {
addComponent: (component) => {
return this.onAddComponent?.(component);
},
});
if (processed) {
visitChildren = false;
return true;
}
}
return false;
};
const visitBuiltIn = async () => {
switch (node.tagName.toLowerCase()) {
case 'g':
if (node.classList.contains(BackgroundComponent_1.imageBackgroundCSSClassName)) {
await this.addBackground(node);
visitChildren = false;
}
else {
await this.startGroup(node);
}
// Otherwise, continue -- visit the node's children.
break;
case 'path':
if (node.classList.contains(BackgroundComponent_1.imageBackgroundCSSClassName)) {
await this.addBackground(node);
}
else {
await this.addPath(node);
}
break;
case 'text':
await this.addText(node);
visitChildren = false;
break;
case 'image':
await this.addImage(node);
// Images should not have children.
visitChildren = false;
break;
case 'svg':
this.updateViewBox(node);
this.updateSVGAttrs(node);
break;
case 'style':
// Keeping unnecessary style sheets can cause the browser to keep all
// SVG elements *referenced* by the style sheet in some browsers.
//
// Only keep the style sheet if it won't be discarded on save.
if (node.getAttribute('id') !== SVGRenderer_1.renderedStylesheetId) {
await this.addUnknownNode(node);
}
break;
default:
if (!this.disableUnknownObjectWarnings) {
console.warn('Unknown SVG element,', node, node.tagName);
if (!(node instanceof SVGElement)) {
console.warn('Element', node, 'is not an SVGElement!', this.storeUnknown ? 'Continuing anyway.' : 'Skipping.');
}
}
await this.addUnknownNode(node);
return;
}
};
if (await visitPlugin()) {
visitChildren = false;
}
else {
await visitBuiltIn();
}
if (visitChildren) {
for (const child of node.children) {
await this.visit(child);
}
if (node.tagName.toLowerCase() === 'g') {
await this.endGroup();
}
}
this.processedCount++;
await this.onProgress?.(this.processedCount, this.totalToProcess);
}
// Get SVG element attributes (e.g. xlink=...)
getSourceAttrs(node) {
return node.getAttributeNames().map((attr) => {
return [attr, node.getAttribute(attr)];
});
}
async start(onAddComponent, onProgress, onDetermineExportRect = null) {
this.onAddComponent = onAddComponent;
this.onProgress = onProgress;
this.onDetermineExportRect = onDetermineExportRect;
// Estimate the number of tags to process.
this.totalToProcess = this.source.childElementCount;
this.processedCount = 0;
this.rootViewBox = null;
await this.visit(this.source);
const viewBox = this.rootViewBox;
if (!viewBox) {
this.onDetermineExportRect?.(exports.defaultSVGViewRect);
}
this.onFinish?.();
this.onFinish = null;
}
/**
* Create an `SVGLoader` from the content of an SVG image. SVGs are loaded within a sandboxed
* iframe with `sandbox="allow-same-origin"`
* [thereby disabling JavaScript](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox).
*
* @see {@link Editor.loadFrom}
* @param text - Textual representation of the SVG (e.g. `<svg viewbox='...'>...</svg>`).
* @param options - if `true` or `false`, treated as the `sanitize` option -- don't store unknown attributes.
*/
static fromString(text, options = false) {
const domParserLoad = typeof options !== 'boolean' && options?.loadMethod === SVGLoaderLoadMethod.DOMParser;
const { svgElem, cleanUp } = (() => {
// If the user requested an iframe load (the default) try to load with an iframe.
// There are some cases (e.g. in a sandboxed iframe) where this doesn't work.
// TODO(v2): Use domParserLoad by default.
if (!domParserLoad) {
try {
const sandbox = document.createElement('iframe');
sandbox.src = 'about:blank';
// allow-same-origin is necessary for how we interact with the sandbox. As such,
// DO NOT ENABLE ALLOW-SCRIPTS.
sandbox.setAttribute('sandbox', 'allow-same-origin');
sandbox.setAttribute('csp', "default-src 'about:blank'");
sandbox.style.display = 'none';
// Required to access the frame's DOM. See https://stackoverflow.com/a/17777943/17055750
document.body.appendChild(sandbox);
if (!sandbox.hasAttribute('sandbox')) {
sandbox.remove();
throw new Error('SVG loading iframe is not sandboxed.');
}
const sandboxDoc = sandbox.contentWindow?.document ?? sandbox.contentDocument;
if (sandboxDoc == null)
throw new Error('Unable to open a sandboxed iframe!');
sandboxDoc.open();
sandboxDoc.write(`
<!DOCTYPE html>
<html>
<head>
<title>SVG Loading Sandbox</title>
<meta name='viewport' conent='width=device-width,initial-scale=1.0'/>
<meta charset='utf-8'/>
</head>
<body style='font-size: 12px;'>
<script>
console.error('JavaScript should not be able to run here!');
throw new Error(
'The SVG sandbox is broken! Please double-check the sandboxing setting.'
);
</script>
</body>
</html>
`);
sandboxDoc.close();
const svgElem = sandboxDoc.createElementNS('http://www.w3.org/2000/svg', 'svg');
// eslint-disable-next-line no-unsanitized/property -- setting innerHTML in a sandboxed document.
svgElem.innerHTML = text;
sandboxDoc.body.appendChild(svgElem);
const cleanUp = () => {
svgElem.remove();
sandbox.remove();
sandbox.src = '';
};
return { svgElem, cleanUp };
}
catch (error) {
console.warn('Failed loading SVG via a sandboxed iframe. Some styles may not be loaded correctly. Error: ', error);
}
}
// Fall back to creating a DOMParser
const parser = new DOMParser();
const doc = parser.parseFromString(`<svg xmlns="http://www.w3.org/2000/svg">${text}</svg>`, 'text/html');
const svgElem = doc.querySelector('svg');
// Handle error messages reported while parsing. See
// https://developer.mozilla.org/en-US/docs/Web/Guide/Parsing_and_serializing_XML
const errorReportNode = doc.querySelector('parsererror');
if (errorReportNode) {
throw new Error('Parse error: ' + errorReportNode.textContent);
}
const cleanUp = () => { };
return { svgElem, cleanUp };
})();
// Handle options
let sanitize;
let disableUnknownObjectWarnings;
let plugins;
if (typeof options === 'boolean') {
sanitize = options;
disableUnknownObjectWarnings = false;
plugins = [];
}
else {
sanitize = options.sanitize ?? false;
disableUnknownObjectWarnings = options.disableUnknownObjectWarnings ?? false;
plugins = options.plugins;
}
return new SVGLoader(svgElem, cleanUp, {
sanitize,
disableUnknownObjectWarnings,
plugins,
});
}
}
exports.default = SVGLoader;