Created
June 6, 2021 04:11
-
-
Save jkhaui/1433ba84983904950762c44fb1fe4e61 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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