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.

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 };