facesjs
Version:
A JavaScript library for generating vector-based cartoon faces
132 lines (130 loc) • 3.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.faceToSvgString = void 0;
var _svgPathBbox = require("svg-path-bbox");
var _display = require("./display.js");
/**
* An instance of this object can pretend to be the global "document"
* variable used in a browser context so that the calls made by the display()
* function populate our structure instead of manipulating DOM nodes.
*/
class SvgDocument {
innerHTML = "";
container = {
appendChild(_node) {}
};
appendChild(node) {
this.root = node;
}
getElementById(_id) {
return this.container;
}
createElementNS(_namespace, tag) {
this.root = new SvgNode(tag, undefined);
return this.root;
}
toXml() {
return this.root.toXml();
}
}
class SvgNode {
attributes = {};
childNodes = [];
constructor(tag, xml) {
this.tag = tag;
this.xml = xml;
// We will need to insert attribute of the g node, so let's
// split the xml <g>...</g>
if (this.xml?.startsWith("<g>")) {
this.tag = "g";
this.childNodes.push(new SvgNode(undefined, this.xml.substring("<g>".length, this.xml.length - "</g>".length)));
// Let's calculate the bbox of this g group which is the smallest bbox
// that contains the bboxes of all paths, so that translation, rotation
// and scaling operations applied to this element get the correct values
// when invoking getBBox()
let pathStart = 0;
while (true) {
pathStart = this.xml.indexOf(' d="', pathStart);
if (pathStart === -1) break;
pathStart += ' d="'.length;
const pathEnd = this.xml.indexOf('"', pathStart);
const path = this.xml.substring(pathStart, pathEnd);
const bbox = (0, _svgPathBbox.svgPathBbox)(path);
if (this.minX === undefined || bbox[0] < this.minX) {
this.minX = bbox[0];
}
if (this.minY === undefined || bbox[1] < this.minY) {
this.minY = bbox[1];
}
if (this.maxX === undefined || bbox[2] > this.maxX) {
this.maxX = bbox[2];
}
if (this.maxY === undefined || bbox[3] > this.maxY) {
this.maxY = bbox[3];
}
pathStart = pathEnd + 1;
}
this.xml = undefined;
}
}
setAttribute(name, value) {
this.attributes[name] = value;
}
insertAdjacentHTML(_position, content) {
this.lastChild = new SvgNode(undefined, content);
this.childNodes.push(this.lastChild);
}
getBBox() {
return {
x: this.minX,
y: this.minY,
width: this.maxX - this.minX,
height: this.maxY - this.minY
};
}
getAttribute(name) {
return this.attributes[name];
}
toXml() {
let s = "";
if (this.tag) {
let openTag = `<${this.tag}`;
if (this.tag === "svg") {
openTag += ' xmlns="http://www.w3.org/2000/svg"';
}
for (const attributeName of Object.keys(this.attributes)) {
openTag += ` ${attributeName}="${this.attributes[attributeName]}"`;
}
openTag += ">";
s += openTag + "\n";
for (const child of this.childNodes) {
s += child.toXml() + "\n";
}
s += `</${this.tag}>`;
} else {
s += this.xml;
}
return s;
}
}
/**
* Renders the given face in a pseudo DOM element and then returns the
* SVG image as an XML string.
*/
const faceToSvgString = (face, overrides) => {
const svgDocument = new SvgDocument();
// Even though we will provide a pseudo HTML elment, display() accesses
// document.createElementNS() so we need to inject our own code there.
// Let's first save what's already there
const backup = global.document;
try {
global.document = svgDocument;
(0, _display.display)(svgDocument, face, overrides);
} finally {
global.document = backup;
}
return svgDocument.toXml();
};
exports.faceToSvgString = faceToSvgString;