Skip to content

Instantly share code, notes, and snippets.

@jkhaui
Created June 6, 2021 04:11
Show Gist options
  • Save jkhaui/1433ba84983904950762c44fb1fe4e61 to your computer and use it in GitHub Desktop.
Save jkhaui/1433ba84983904950762c44fb1fe4e61 to your computer and use it in GitHub Desktop.
/*
* START SIGNATURE FUNCTIONALITY
* */
// Define global variables.
const FILL_ATTRIBUTE = "fill";
const SVG_NODE = "svg";
// Low-level nodes.
const PATH_NODE = "path";
const RECT_NODE = "rect";
const CIRCLE_NODE = "circle";
const TEXT_NODE = "#text";
// Wrapper nodes.
const GLOBAL_NODE = "g";
const MASK_NODE = "mask";
const DEFS_NODE = "defs";
const CLIPPATH_NODE = "clipPath";
const STROKE_ATTRIBUTE = "stroke";
// SVG attributes to be copied onto the virtual SVG.
const WIDTH_ATTRIBUTE = "width";
const HEIGHT_ATTRIBUTE = "height";
const VIEWBOX_ATTRIBUTE = "viewBox";
let FILL_COLOR = "#000";
let STROKE_COLOR = "#000";
export const renderVirtualSvg = async (
fileData,
fillColor = FILL_COLOR,
strokeColor = STROKE_COLOR,
) => {
// 1. Instantiate a new virtual DOM instance.
// This creates an empty document node in-memory, with an empty svg element
// as the single child node.
const parser = new DOMParser();
const vDom = parser.parseFromString(fileData, "image/svg+xml");
const oldSvg = vDom.children[0];
const newSvg =
document.createElementNS("http://www.w3.org/2000/svg", "svg");
const childrenCollection = vDom.all;
const mutateAttribute = (newSvg, node) => {
const hasFillAttribute =
node.hasAttribute(FILL_ATTRIBUTE) ||
window
.getComputedStyle(node)
.getPropertyValue(FILL_ATTRIBUTE) !== "none";
const hasStrokeAttribute =
node.hasAttribute(STROKE_ATTRIBUTE) ||
window
.getComputedStyle(node)
.getPropertyValue(STROKE_ATTRIBUTE) !== "none";
if (hasFillAttribute) {
node.removeAttribute(FILL_ATTRIBUTE);
node.setAttribute("style", `fill: ${fillColor}`);
}
// TODO: FInd a way to handle transparent strokes.
if (hasStrokeAttribute) {
node.removeAttribute(STROKE_ATTRIBUTE);
node.setAttribute("style", `stroke: ${strokeColor}`);
}
newSvg.appendChild(node);
};
const copySvgAttributes = (oldSvg, newSvg) => {
if (oldSvg.hasAttribute(STROKE_ATTRIBUTE)) {
const _stroke = oldSvg.getAttribute(STROKE_ATTRIBUTE);
if (_stroke) {
newSvg.setAttribute(STROKE_ATTRIBUTE, _stroke);
}
}
if (oldSvg.hasAttribute(FILL_ATTRIBUTE)) {
const _fill = oldSvg.getAttribute(FILL_ATTRIBUTE);
if (_fill) {
newSvg.setAttribute(FILL_ATTRIBUTE, _fill);
}
}
if (oldSvg.hasAttribute(WIDTH_ATTRIBUTE)) {
const _width = oldSvg.getAttribute(WIDTH_ATTRIBUTE);
if (_width) {
newSvg.setAttribute(WIDTH_ATTRIBUTE, _width);
}
}
if (oldSvg.hasAttribute(HEIGHT_ATTRIBUTE)) {
const _height = oldSvg.getAttribute(HEIGHT_ATTRIBUTE);
if (_height) {
newSvg.setAttribute(HEIGHT_ATTRIBUTE, _height);
}
}
if (oldSvg.hasAttribute(VIEWBOX_ATTRIBUTE)) {
const _viewBox = oldSvg.getAttribute(VIEWBOX_ATTRIBUTE);
if (_viewBox) {
newSvg.setAttribute(VIEWBOX_ATTRIBUTE, _viewBox);
}
}
};
const traverseChildren = (oldSvg, newSvg, node) => {
const nodeType = node.nodeName;
switch (nodeType) {
case SVG_NODE:
copySvgAttributes(oldSvg, newSvg, node);
break;
// TODO: do NOT apply styling to rect nodes for now... It appears to
// ruin the svg.
case RECT_NODE:
case TEXT_NODE:
break;
case CIRCLE_NODE:
case PATH_NODE:
mutateAttribute(newSvg, node);
break;
case DEFS_NODE:
case GLOBAL_NODE:
// Declare a variable with (what is assumed to be) the low-level SVG
// element.
const grandchildNodes = node.childNodes;
const grandchildrenCollection = grandchildNodes;
const grandchildrenLength = grandchildNodes.length;
// const grandchildNodeType = grandchildNodes.nodeName;
// const hasGreatGrandChildren =
// grandchildNodes.length > 0;
// const grandchildNodeCount = grandchildNodes.length;
let i = -1;
if (++i < grandchildrenLength) {
do {
if (grandchildrenCollection[i]) {
traverseChildren(oldSvg, newSvg, grandchildrenCollection[i]);
}
}
while (++i < grandchildrenCollection);
}
// First-child nodes, e.g. <g>, <defs>, etc.
// if (++i < grandchildNodeCount) do {
// console.log(grandchildrenCollection[i]);
// if (hasGreatGrandChildren) {
// // TODO: handle recursive looping through n layers of child
// nodes. // const greatGrandChild = ... //
// mutateAttribute(greatGrandChild); return; } } while (++i <
// grandchildNodeCount);
break;
case CLIPPATH_NODE:
case MASK_NODE:
default:
// An exception error message is passed back to the caller if any of
// the above nodes, or otherwise unknown node types, are detected.
return false;
}
};
// Cache the length prop to maximise performance.
const parentLength = childrenCollection.length;
let i = -1;
let throwError = false;
// 2. Iterate through, and extract, all the low-level SVG elements.
if (++i < parentLength) {
do {
if (childrenCollection[i]) {
const mutate = traverseChildren(
oldSvg,
newSvg,
childrenCollection[i],
);
if ((childrenCollection[i] || {}).nodeName ===
PATH_NODE && i > 0) {
i--;
}
if (mutate === false) {
throwError = true;
}
}
}
while (++i < parentLength);
}
if (throwError) {
return Error("Unidentified node type.");
}
vDom.removeChild(oldSvg);
vDom.appendChild(newSvg);
const serializer = new XMLSerializer();
const stringifiedSvg = serializer.serializeToString(vDom);
// Returns the cleaned SVG string as a promise, if successful.
return stringifiedSvg;
};
export const setSvgColor = (
svgString,
fillColor = FILL_COLOR,
strokeColor = STROKE_COLOR,
) => {
const svg = `${svgString}`;
const svgNode = new DOMParser().parseFromString(svg, "text/html");
const pathList = svgNode.getElementsByTagName("path");
for (let i = 0; i < pathList.length; i++) {
const hasFillAttribute =
pathList[i].hasAttribute(FILL_ATTRIBUTE) ||
window
.getComputedStyle(pathList[i])
.getPropertyValue(FILL_ATTRIBUTE) !== "none";
const hasStrokeAttribute =
pathList[i].hasAttribute(STROKE_ATTRIBUTE) ||
window
.getComputedStyle(pathList[i])
.getPropertyValue(STROKE_ATTRIBUTE) !== "none";
if (hasFillAttribute) {
pathList[i].removeAttribute(FILL_ATTRIBUTE);
pathList[i].setAttribute("style", `fill: ${fillColor}`);
}
if (hasStrokeAttribute) {
pathList[i].removeAttribute(STROKE_ATTRIBUTE);
pathList[i].setAttribute("style", `stroke: ${strokeColor}`);
}
}
const serializer = new XMLSerializer();
const stringifiedSvg =
serializer.serializeToString(svgNode.getElementsByTagName("svg")[0]);
return stringifiedSvg;
};
/*
* END SIGNATURE FUNCTIONALITY
* */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment