UNPKG

facesjs

Version:

A JavaScript library for generating vector-based cartoon faces

132 lines (130 loc) 3.9 kB
"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;