fabric
Version:
Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.
92 lines (78 loc) • 3.53 kB
text/typescript
import { svgNS } from './constants';
import { getMultipleNodes } from './getMultipleNodes';
import { applyViewboxTransform } from './applyViewboxTransform';
import { parseStyleString } from './parseStyleString';
export function parseUseDirectives(doc: Document) {
const nodelist = getMultipleNodes(doc, ['use', 'svg:use']);
const skipAttributes = ['x', 'y', 'xlink:href', 'href', 'transform'];
for (const useElement of nodelist) {
const useAttributes: NamedNodeMap = useElement.attributes;
const useAttrMap: Record<string, string> = {};
for (const attr of useAttributes) {
attr.value && (useAttrMap[attr.name] = attr.value);
}
const xlink = (useAttrMap['xlink:href'] || useAttrMap.href || '').slice(1);
if (xlink === '') {
return;
}
const referencedElement = doc.getElementById(xlink);
if (referencedElement === null) {
// if we can't find the target of the xlink, consider this use tag bad, similar to no xlink
return;
}
let clonedOriginal = referencedElement.cloneNode(true) as Element;
const originalAttributes: NamedNodeMap = clonedOriginal.attributes;
const originalAttrMap: Record<string, string> = {};
for (const attr of originalAttributes) {
attr.value && (originalAttrMap[attr.name] = attr.value);
}
// Transform attribute needs to be merged in a particular way
const { x = 0, y = 0, transform = '' } = useAttrMap;
const currentTrans = `${transform} ${
originalAttrMap.transform || ''
} translate(${x}, ${y})`;
applyViewboxTransform(clonedOriginal);
if (/^svg$/i.test(clonedOriginal.nodeName)) {
// if is an SVG, create a group and apply all the attributes on top of it
const el3 = clonedOriginal.ownerDocument.createElementNS(svgNS, 'g');
Object.entries(originalAttrMap).forEach(([name, value]) =>
el3.setAttributeNS(svgNS, name, value),
);
el3.append(...clonedOriginal.childNodes);
clonedOriginal = el3;
}
for (const attr of useAttributes) {
if (!attr) {
continue;
}
const { name, value } = attr;
if (skipAttributes.includes(name)) {
continue;
}
if (name === 'style') {
// when use has a style, merge the two styles, with the ref being priority (not use)
// priority is by feature. an attribute for fill on the original element
// will overwrite the fill in style or attribute for tha use
const styleRecord: Record<string, any> = {};
parseStyleString(value!, styleRecord);
// cleanup styleRecord from attributes of original
Object.entries(originalAttrMap).forEach(([name, value]) => {
styleRecord[name] = value;
});
// now we can put in the style of the original that will overwrite the original attributes
parseStyleString(originalAttrMap.style || '', styleRecord);
const mergedStyles = Object.entries(styleRecord)
.map((entry) => entry.join(':'))
.join(';');
clonedOriginal.setAttribute(name, mergedStyles);
} else {
// set the attribute from use element only if the original does not have it already
!originalAttrMap[name] && clonedOriginal.setAttribute(name, value!);
}
}
clonedOriginal.setAttribute('transform', currentTrans);
clonedOriginal.setAttribute('instantiated_by_use', '1');
clonedOriginal.removeAttribute('id');
useElement.parentNode!.replaceChild(clonedOriginal, useElement);
}
}