Source code of plot #060 back to plot
Download full working sketch as 060.tar.gz.
Unzip, then start a local web server and load the page in a browser.
Unless otherwise noted, code published here is © Gábor L Ugray, shared under the Creative Commons
BY-NC-SA license (Attribution, Non-Commercial, Share-Alike). Files in lib/thirdparty
, and additional
libraries in the downloadable archive, are shared under their respective open-source licenses, attributed
to their authors.
class Glyph {
constructor(adv, paths) {
this.adv = adv;
this.paths = paths;
}
}
/**
* Returned as a rendered text. Contains an SVG group, and the width of the rendered text.
*/
export class TextResult {
/**
* @param {SVGGElement} g - An SVG group element.
* @param {number} width - Width of the rendered text.
*/
constructor(g, width) {
this.g = g;
this.width = width;
}
}
export class SvgFont {
constructor(xmlStr) {
this.strokeWidthPx = 2;
const doc = new DOMParser().parseFromString(xmlStr, "text/xml");
const elmFace = doc.getElementsByTagName("font-face")[0];
this.ascent = Number(elmFace.getAttribute("ascent"));
this.descent = Number(elmFace.getAttribute("descent"));
this.capHeight = Number(elmFace.getAttribute("cap-height"));
this.noVerticalFlip = elmFace.getAttribute("no-vertical-flip") == "true";
this.glyphs = {};
const elmGlyphs = doc.getElementsByTagName("glyph");
for (let i = 0; i < elmGlyphs.length; ++i) {
const elmGlyph = elmGlyphs[i];
const c = elmGlyph.getAttribute("unicode");
const adv = Number(elmGlyph.getAttribute("horiz-adv-x"));
let path = elmGlyph.getAttribute("d");
let paths = [];
if (path) {
let pathParts = path.split("M");
for (let i = 0; i < pathParts.length; ++i) {
let pathPart = pathParts[i].trim();
if (pathPart == "") continue;
paths.push("M" + pathPart);
}
}
const glyph = new Glyph(adv, paths);
this.glyphs[c] = glyph;
}
}
/**
* Renders text to an SVG group element.
* @param {number} size Font size.
* @param {string} text The text to write.
* @returns {TextResult} Rendered result.
*/
write(size, text) {
let scaleVal = size / (this.ascent - this.descent);
const g = document.createElementNS("http://www.w3.org/2000/svg", "g");
let x = 0;
for (const c of text) {
const glyph = this.glyphs.hasOwnProperty(c) ? this.glyphs[c] : this.glyphs["*"];
for (const pathStr of glyph.paths) {
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("d", pathStr);
path.setAttribute("stroke", "black");
path.setAttribute("stroke-width", `${this.strokeWidthPx/scaleVal}px`);
path.setAttribute("fill", "none");
path.setAttribute("transform", `translate(${x}, ${this.descent})`);
g.appendChild(path);
}
x += glyph.adv;
}
let scaleX = scaleVal;
let scaleY = this.noVerticalFlip ? scaleVal : -scaleVal;
g.setAttribute("transform", `translate(0, ${this.descent*scaleVal}) scale(${scaleX}, ${scaleY})`);
const outerG = document.createElementNS("http://www.w3.org/2000/svg", "g");
outerG.appendChild(g);
return new TextResult(outerG, x * scaleVal);
}
// writeOnArc(size, baselineRadius, text, bottom, segLen = 2) {
//
// let distort = (pt, adv, angleAdv) => {
// let rad = baselineRadius + pt.y;
// let angle = angleAdv * pt.x / adv;
// return new Point(0, rad).rotate(-angle).add(0, -baselineRadius);
// };
//
// const origin = new Point(0, 0);
// const gText = new paper.Group();
// const scale = size / (this.ascent - this.descent);
// let angle = bottom ? 180 : 0;
// let spoke = new Point(0, -baselineRadius);
// if (!bottom) spoke.y -= this.capHeight * scale;
// let vflip = this.noVerticalFlip && !bottom || !this.noVerticalFlip && bottom;
// for (const c of text) {
// const glyph = this.glyphs.hasOwnProperty(c) ? this.glyphs[c] : this.glyphs["*"];
// let angleAdv = (glyph.adv * scale / baselineRadius) * 180 / Math.PI;
// if (!bottom) angleAdv = -angleAdv;
// let gGlyph = new paper.Group();
// for (const pathStr of glyph.paths) {
// const path = new paper.Path(pathStr);
// if (vflip) path.scale(1, -1, origin);
// else path.translate(0, -this.capHeight);
// path.scale(scale, origin);
// let pts = [];
// for (let i = 0; i < path.segments.length - 1; ++i) {
// let seg1 = path.segments[i];
// let seg2 = path.segments[i+1];
// pts.push(...getLinePts(seg1.point, seg2.point, segLen));
// if (i < path.segments.length - 2) pts.pop();
// }
// if (path.closed) pts.push(path.segments[0].point);
// for (let i = 0; i < pts.length; ++i) pts[i] = distort(pts[i], glyph.adv * scale, angleAdv);
// gGlyph.addChild(new Path({segments: pts}));
// }
// gGlyph.rotate(angle - 180, origin);
// gGlyph.translate(spoke.rotate(angle, origin));
// gText.addChild(gGlyph);
// angle -= angleAdv;
// }
// if (bottom) return [gText, 180 - angle];
// else return [gText, angle];
// }
}
// function getLinePts(pt1, pt2, segLen) {
// const lineVect = pt2.subtract(pt1);
// const lineLength = lineVect.length;
// const nSegs = Math.max(2, Math.round(lineLength / segLen));
// const segVect = lineVect.divide(nSegs);
// const pts = [];
// for (let i = 0; i <= nSegs; ++i) {
// pts.push(pt1.add(segVect.multiply(i)));
// }
// return pts;
// }