Source code of plot #032 back to plot
Download full working sketch as 032.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 Segment {
constructor() {
this.points = [];
}
}
class Glyph {
constructor(adv, paths) {
this.adv = adv;
this.paths = paths;
}
}
class SvgFont {
constructor(xmlStr) {
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;
}
}
write(size, text) {
const g = new paper.Group();
g.position = new Point(0, 0);
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 = new paper.Path(pathStr);
path.translate(new Point(x, 0));
g.addChild(path);
}
x += glyph.adv;
}
if (!this.noVerticalFlip) g.scale(1, -1, new Point(0, 0));
if (this.noVerticalFlip) g.translate(0, -this.capHeight);
g.scale(size / (this.ascent - this.descent), new Point(0, 0));
return g;
}
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;
}
export { SvgFont };