mirror of
https://github.com/multipleof4/stain.otf.git
synced 2026-01-13 16:17:55 +00:00
Delete glyphs.js
This commit is contained in:
955
src/glyphs.js
955
src/glyphs.js
@@ -1,955 +0,0 @@
|
||||
import opentype from "opentype.js";
|
||||
|
||||
const UPEM = 1000;
|
||||
const ASC = 760;
|
||||
const DSC = -240;
|
||||
const CAP = 700;
|
||||
const XH = 520;
|
||||
const BASE = 0;
|
||||
|
||||
const STROKE = 78;
|
||||
const ROUND = 26;
|
||||
|
||||
const WIDTH = {
|
||||
default: 560,
|
||||
narrow: 500,
|
||||
wide: 620,
|
||||
space: 250
|
||||
};
|
||||
|
||||
const CHARSET = {
|
||||
upper: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
lower: "abcdefghijklmnopqrstuvwxyz",
|
||||
digits: "0123456789"
|
||||
};
|
||||
|
||||
const COMMON_PUNCT = ".,:;!?-–—()'\"@#$%&*+/=_";
|
||||
|
||||
function r(v) {
|
||||
return Math.round(v);
|
||||
}
|
||||
|
||||
function hBar(y, x1, x2, s = STROKE) {
|
||||
const p = new opentype.Path();
|
||||
p.moveTo(r(x1), r(y));
|
||||
p.lineTo(r(x2), r(y));
|
||||
p.lineTo(r(x2), r(y - s));
|
||||
p.lineTo(r(x1), r(y - s));
|
||||
p.close();
|
||||
return p;
|
||||
}
|
||||
|
||||
function vBar(x, y1, y2, s = STROKE) {
|
||||
const p = new opentype.Path();
|
||||
p.moveTo(r(x), r(y1));
|
||||
p.lineTo(r(x + s), r(y1));
|
||||
p.lineTo(r(x + s), r(y2));
|
||||
p.lineTo(r(x), r(y2));
|
||||
p.close();
|
||||
return p;
|
||||
}
|
||||
|
||||
function roundRect(x, y, w, h, rr = ROUND) {
|
||||
const r0 = Math.max(0, Math.min(rr, w / 2, h / 2));
|
||||
const p = new opentype.Path();
|
||||
const x2 = x + w;
|
||||
const y2 = y + h;
|
||||
|
||||
p.moveTo(r(x + r0), r(y));
|
||||
p.lineTo(r(x2 - r0), r(y));
|
||||
p.quadraticCurveTo(r(x2), r(y), r(x2), r(y + r0));
|
||||
p.lineTo(r(x2), r(y2 - r0));
|
||||
p.quadraticCurveTo(r(x2), r(y2), r(x2 - r0), r(y2));
|
||||
p.lineTo(r(x + r0), r(y2));
|
||||
p.quadraticCurveTo(r(x), r(y2), r(x), r(y2 - r0));
|
||||
p.lineTo(r(x), r(y + r0));
|
||||
p.quadraticCurveTo(r(x), r(y), r(x + r0), r(y));
|
||||
p.close();
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
function mergePaths(...paths) {
|
||||
const p = new opentype.Path();
|
||||
for (const part of paths) {
|
||||
if (!part) continue;
|
||||
if (part.commands) {
|
||||
p.commands = p.commands.concat(part.commands);
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Uppercase glyphs: geometric, slightly rounded, Candara-ish */
|
||||
|
||||
const upperBuilders = {
|
||||
"A": () => {
|
||||
const aw = WIDTH.wide;
|
||||
const mid = aw / 2;
|
||||
const s = STROKE * 0.9;
|
||||
const p = new opentype.Path();
|
||||
p.moveTo(r(STROKE), BASE);
|
||||
p.lineTo(r(STROKE + s), BASE);
|
||||
p.lineTo(r(mid), CAP);
|
||||
p.lineTo(r(mid - s), CAP);
|
||||
p.close();
|
||||
p.moveTo(r(aw - STROKE), BASE);
|
||||
p.lineTo(r(aw - STROKE - s), BASE);
|
||||
p.lineTo(r(mid - s * 0.3), CAP);
|
||||
p.lineTo(r(mid + s * 0.3), CAP);
|
||||
p.close();
|
||||
p.commands = p.commands.concat(
|
||||
hBar(XH + 20, STROKE * 1.4, aw - STROKE * 1.4, STROKE * 0.78).commands
|
||||
);
|
||||
return { path: p, width: aw };
|
||||
},
|
||||
"B": () => {
|
||||
const aw = WIDTH.wide;
|
||||
const left = STROKE * 0.8;
|
||||
const stem = vBar(left, BASE, CAP, STROKE * 0.9);
|
||||
const top = roundRect(left, CAP - 310, aw - left * 1.8, 160, 46);
|
||||
const bot = roundRect(left, BASE + 70, aw - left * 1.8, 160, 46);
|
||||
return { path: mergePaths(stem, top, bot), width: aw };
|
||||
},
|
||||
"C": () => {
|
||||
const aw = WIDTH.wide;
|
||||
const outer = roundRect(STROKE, BASE + 40, aw - STROKE * 2, CAP - 80, 120);
|
||||
const inner = roundRect(STROKE + 70, BASE + 90, aw - STROKE * 2 - 140, CAP - 180, 90);
|
||||
const p = mergePaths(outer, inner);
|
||||
// knock right side open by overlaying a vertical gap
|
||||
return { path: p, width: aw };
|
||||
},
|
||||
"D": () => {
|
||||
const aw = WIDTH.wide;
|
||||
const left = STROKE * 0.85;
|
||||
const stem = vBar(left, BASE, CAP, STROKE * 0.9);
|
||||
const bowl = roundRect(left, BASE + 40, aw - left * 1.5, CAP - 80, 90);
|
||||
return { path: mergePaths(stem, bowl), width: aw };
|
||||
},
|
||||
"E": () => {
|
||||
const aw = WIDTH.default;
|
||||
const x = STROKE * 0.85;
|
||||
return {
|
||||
path: mergePaths(
|
||||
vBar(x, BASE, CAP, STROKE * 0.9),
|
||||
hBar(CAP, x, aw - STROKE * 0.5),
|
||||
hBar(XH + 10, x, aw - STROKE * 0.9, STROKE * 0.75),
|
||||
hBar(BASE + 40, x, aw - STROKE * 0.5)
|
||||
),
|
||||
width: aw
|
||||
};
|
||||
},
|
||||
"F": () => {
|
||||
const aw = WIDTH.default;
|
||||
const x = STROKE * 0.85;
|
||||
return {
|
||||
path: mergePaths(
|
||||
vBar(x, BASE, CAP, STROKE * 0.9),
|
||||
hBar(CAP, x, aw - STROKE * 0.5),
|
||||
hBar(XH + 10, x, aw - STROKE * 0.9, STROKE * 0.75)
|
||||
),
|
||||
width: aw
|
||||
};
|
||||
},
|
||||
"G": () => {
|
||||
const aw = WIDTH.wide;
|
||||
const outer = roundRect(STROKE, BASE + 40, aw - STROKE * 2, CAP - 80, 110);
|
||||
const inner = roundRect(STROKE + 70, BASE + 90, aw - STROKE * 2 - 160, CAP - 180, 80);
|
||||
const cut = hBar(XH + 20, aw * 0.35, aw - STROKE * 0.8, STROKE * 0.8);
|
||||
const p = mergePaths(outer, inner, cut);
|
||||
return { path: p, width: aw };
|
||||
},
|
||||
"H": () => {
|
||||
const aw = WIDTH.wide;
|
||||
const l = STROKE * 0.85;
|
||||
const rgt = aw - STROKE * 1.5;
|
||||
return {
|
||||
path: mergePaths(
|
||||
vBar(l, BASE, CAP),
|
||||
vBar(rgt, BASE, CAP),
|
||||
hBar(XH + 20, l, rgt + STROKE)
|
||||
),
|
||||
width: aw
|
||||
};
|
||||
},
|
||||
"I": () => {
|
||||
const aw = WIDTH.narrow;
|
||||
const c = aw / 2 - STROKE / 2;
|
||||
return {
|
||||
path: mergePaths(
|
||||
hBar(CAP, STROKE * 0.4, aw - STROKE * 0.4, STROKE * 0.8),
|
||||
hBar(BASE + 40, STROKE * 0.4, aw - STROKE * 0.4, STROKE * 0.8),
|
||||
vBar(c, BASE + 40 + STROKE * 0.5, CAP - STROKE * 1.4, STROKE * 0.8)
|
||||
),
|
||||
width: aw
|
||||
};
|
||||
},
|
||||
"J": () => {
|
||||
const aw = WIDTH.default;
|
||||
const top = hBar(CAP, STROKE * 0.4, aw - STROKE * 0.4, STROKE * 0.8);
|
||||
const stem = vBar(aw - STROKE * 1.6, BASE + 110, CAP - STROKE, STROKE * 0.8);
|
||||
const bowl = roundRect(aw * 0.35, BASE, aw * 0.4, 120, 50);
|
||||
return { path: mergePaths(top, stem, bowl), width: aw };
|
||||
},
|
||||
"K": () => {
|
||||
const aw = WIDTH.wide;
|
||||
const l = STROKE * 0.85;
|
||||
const s = STROKE * 0.8;
|
||||
const p1 = vBar(l, BASE, CAP);
|
||||
const p2 = new opentype.Path();
|
||||
p2.moveTo(aw - STROKE, CAP);
|
||||
p2.lineTo(aw - STROKE - s, CAP);
|
||||
p2.lineTo(l + STROKE * 1.5, XH + 40);
|
||||
p2.lineTo(l + STROKE * 2.2, XH + 10);
|
||||
p2.close();
|
||||
const p3 = new opentype.Path();
|
||||
p3.moveTo(aw - STROKE, BASE);
|
||||
p3.lineTo(aw - STROKE - s, BASE + STROKE);
|
||||
p3.lineTo(l + STROKE * 1.5, XH - 40);
|
||||
p3.lineTo(l + STROKE * 2.2, XH - 10);
|
||||
p3.close();
|
||||
return { path: mergePaths(p1, p2, p3), width: aw };
|
||||
},
|
||||
"L": () => {
|
||||
const aw = WIDTH.default;
|
||||
const l = STROKE * 0.85;
|
||||
return {
|
||||
path: mergePaths(
|
||||
vBar(l, BASE, CAP),
|
||||
hBar(BASE + 40, l, aw - STROKE * 0.5)
|
||||
),
|
||||
width: aw
|
||||
};
|
||||
},
|
||||
"M": () => {
|
||||
const aw = WIDTH.wide + 40;
|
||||
const s = STROKE * 0.9;
|
||||
const left = vBar(STROKE, BASE, CAP, s);
|
||||
const right = vBar(aw - STROKE * 1.8, BASE, CAP, s);
|
||||
const mid = new opentype.Path();
|
||||
mid.moveTo(STROKE + s, CAP);
|
||||
mid.lineTo(aw / 2, CAP * 0.45);
|
||||
mid.lineTo(aw - STROKE * 1.8 - s, CAP);
|
||||
mid.lineTo(aw - STROKE * 1.8 - s * 1.4, CAP);
|
||||
mid.lineTo(aw / 2, CAP * 0.56);
|
||||
mid.lineTo(STROKE + s * 1.4, CAP);
|
||||
mid.close();
|
||||
return { path: mergePaths(left, right, mid), width: aw };
|
||||
},
|
||||
"N": () => {
|
||||
const aw = WIDTH.wide;
|
||||
const s = STROKE * 0.9;
|
||||
const left = vBar(STROKE, BASE, CAP, s);
|
||||
const right = vBar(aw - STROKE * 1.6, BASE, CAP, s);
|
||||
const diag = new opentype.Path();
|
||||
diag.moveTo(STROKE + s, CAP);
|
||||
diag.lineTo(STROKE + s * 1.7, CAP);
|
||||
diag.lineTo(aw - STROKE * 1.6 - s, BASE);
|
||||
diag.lineTo(aw - STROKE * 1.6 - s * 1.7, BASE);
|
||||
diag.close();
|
||||
return { path: mergePaths(left, right, diag), width: aw };
|
||||
},
|
||||
"O": () => {
|
||||
const aw = WIDTH.wide;
|
||||
const outer = roundRect(STROKE, BASE + 40, aw - STROKE * 2, CAP - 80, 110);
|
||||
const inner = roundRect(STROKE + 70, BASE + 90, aw - STROKE * 2 - 140, CAP - 180, 80);
|
||||
return { path: mergePaths(outer, inner), width: aw };
|
||||
},
|
||||
"P": () => {
|
||||
const aw = WIDTH.default;
|
||||
const l = STROKE * 0.85;
|
||||
const stem = vBar(l, BASE, CAP, STROKE * 0.9);
|
||||
const bowl = roundRect(l, CAP - 290, aw - l * 1.7, 210, 60);
|
||||
return { path: mergePaths(stem, bowl), width: aw };
|
||||
},
|
||||
"Q": () => {
|
||||
const base = upperBuilders["O"]();
|
||||
const aw = base.width;
|
||||
const tail = new opentype.Path();
|
||||
tail.moveTo(aw * 0.55, BASE + 140);
|
||||
tail.lineTo(aw * 0.82, BASE - 10);
|
||||
tail.lineTo(aw * 0.76, BASE + 70);
|
||||
tail.lineTo(aw * 0.50, BASE + 170);
|
||||
tail.close();
|
||||
return { path: mergePaths(base.path, tail), width: aw };
|
||||
},
|
||||
"R": () => {
|
||||
const aw = WIDTH.wide;
|
||||
const l = STROKE * 0.85;
|
||||
const stem = vBar(l, BASE, CAP, STROKE * 0.9);
|
||||
const bowl = roundRect(l, CAP - 290, aw - l * 1.9, 210, 50);
|
||||
const leg = new opentype.Path();
|
||||
leg.moveTo(aw - STROKE, BASE);
|
||||
leg.lineTo(aw - STROKE - STROKE * 0.9, BASE + STROKE);
|
||||
leg.lineTo(l + STROKE * 1.8, XH - 40);
|
||||
leg.lineTo(l + STROKE * 2.4, XH - 80);
|
||||
leg.close();
|
||||
return { path: mergePaths(stem, bowl, leg), width: aw };
|
||||
},
|
||||
"S": () => {
|
||||
const aw = WIDTH.default;
|
||||
const p = new opentype.Path();
|
||||
const top = CAP - 20;
|
||||
const mid = (CAP + BASE) / 2;
|
||||
const bot = BASE + 40;
|
||||
p.moveTo(aw - 40, top);
|
||||
p.quadraticCurveTo(aw * 0.2, CAP + 10, 60, mid + 90);
|
||||
p.quadraticCurveTo(20, mid + 10, aw * 0.3, mid - 40);
|
||||
p.quadraticCurveTo(aw - 20, mid - 90, aw - 40, bot - 10);
|
||||
p.quadraticCurveTo(aw * 0.5, bot - 70, 80, bot + 10);
|
||||
p.quadraticCurveTo(aw * 0.5, bot + 40, aw - 40, top - 40);
|
||||
p.close();
|
||||
return { path: p, width: aw };
|
||||
},
|
||||
"T": () => {
|
||||
const aw = WIDTH.wide;
|
||||
const c = aw / 2 - STROKE / 2;
|
||||
return {
|
||||
path: mergePaths(
|
||||
hBar(CAP, STROKE * 0.4, aw - STROKE * 0.4),
|
||||
vBar(c, BASE, CAP, STROKE * 0.9)
|
||||
),
|
||||
width: aw
|
||||
};
|
||||
},
|
||||
"U": () => {
|
||||
const aw = WIDTH.wide;
|
||||
const s = STROKE * 0.9;
|
||||
const left = vBar(STROKE, BASE + 140, CAP, s);
|
||||
const right = vBar(aw - STROKE * 2, BASE + 140, CAP, s);
|
||||
const bowl = roundRect(STROKE, BASE + 40, aw - STROKE * 3, 140, 80);
|
||||
return { path: mergePaths(left, right, bowl), width: aw };
|
||||
},
|
||||
"V": () => {
|
||||
const aw = WIDTH.wide;
|
||||
const s = STROKE * 0.9;
|
||||
const mid = aw / 2;
|
||||
const p1 = new opentype.Path();
|
||||
p1.moveTo(STROKE, CAP);
|
||||
p1.lineTo(STROKE + s, CAP);
|
||||
p1.lineTo(mid, BASE);
|
||||
p1.lineTo(mid - s, BASE);
|
||||
p1.close();
|
||||
const p2 = new opentype.Path();
|
||||
p2.moveTo(aw - STROKE, CAP);
|
||||
p2.lineTo(aw - STROKE - s, CAP);
|
||||
p2.lineTo(mid, BASE);
|
||||
p2.lineTo(mid + s, BASE);
|
||||
p2.close();
|
||||
return { path: mergePaths(p1, p2), width: aw };
|
||||
},
|
||||
"W": () => {
|
||||
const aw = WIDTH.wide + 80;
|
||||
const s = STROKE * 0.8;
|
||||
const x1 = STROKE;
|
||||
const x2 = aw * 0.34;
|
||||
const x3 = aw * 0.66;
|
||||
const x4 = aw - STROKE;
|
||||
const yTop = CAP;
|
||||
const yBot = BASE;
|
||||
const p = new opentype.Path();
|
||||
p.moveTo(x1, yTop);
|
||||
p.lineTo(x1 + s, yTop);
|
||||
p.lineTo(x2, yBot);
|
||||
p.lineTo(x2 - s, yBot);
|
||||
p.close();
|
||||
p.moveTo(x2, yBot);
|
||||
p.lineTo(x2 + s, yBot);
|
||||
p.lineTo(x3, yTop * 0.52);
|
||||
p.lineTo(x3 - s, yTop * 0.52);
|
||||
p.close();
|
||||
p.moveTo(x3, yTop * 0.52);
|
||||
p.lineTo(x3 + s, yTop * 0.52);
|
||||
p.lineTo(x4, yTop);
|
||||
p.lineTo(x4 - s, yTop);
|
||||
p.close();
|
||||
return { path: p, width: aw };
|
||||
},
|
||||
"X": () => {
|
||||
const aw = WIDTH.wide;
|
||||
const s = STROKE * 0.8;
|
||||
const p1 = new opentype.Path();
|
||||
p1.moveTo(STROKE, CAP);
|
||||
p1.lineTo(STROKE + s, CAP);
|
||||
p1.lineTo(aw - STROKE, BASE);
|
||||
p1.lineTo(aw - STROKE - s, BASE);
|
||||
p1.close();
|
||||
const p2 = new opentype.Path();
|
||||
p2.moveTo(aw - STROKE, CAP);
|
||||
p2.lineTo(aw - STROKE - s, CAP);
|
||||
p2.lineTo(STROKE, BASE);
|
||||
p2.lineTo(STROKE + s, BASE);
|
||||
p2.close();
|
||||
return { path: mergePaths(p1, p2), width: aw };
|
||||
},
|
||||
"Y": () => {
|
||||
const aw = WIDTH.wide;
|
||||
const s = STROKE * 0.85;
|
||||
const mid = aw / 2;
|
||||
const arms1 = new opentype.Path();
|
||||
arms1.moveTo(STROKE, CAP);
|
||||
arms1.lineTo(STROKE + s, CAP);
|
||||
arms1.lineTo(mid, XH);
|
||||
arms1.lineTo(mid - s, XH);
|
||||
arms1.close();
|
||||
const arms2 = new opentype.Path();
|
||||
arms2.moveTo(aw - STROKE, CAP);
|
||||
arms2.lineTo(aw - STROKE - s, CAP);
|
||||
arms2.lineTo(mid, XH);
|
||||
arms2.lineTo(mid + s, XH);
|
||||
arms2.close();
|
||||
const stem = vBar(mid - s / 2, BASE, XH, s * 0.9);
|
||||
return { path: mergePaths(arms1, arms2, stem), width: aw };
|
||||
},
|
||||
"Z": () => {
|
||||
const aw = WIDTH.wide;
|
||||
const top = hBar(CAP, STROKE * 0.4, aw - STROKE * 0.4);
|
||||
const bot = hBar(BASE + 40, STROKE * 0.6, aw - STROKE * 0.4);
|
||||
const diag = new opentype.Path();
|
||||
diag.moveTo(aw - STROKE * 0.9, CAP - STROKE);
|
||||
diag.lineTo(aw - STROKE * 1.5, CAP - STROKE);
|
||||
diag.lineTo(STROKE * 0.7, BASE + 40 + STROKE);
|
||||
diag.lineTo(STROKE * 1.3, BASE + 40 + STROKE);
|
||||
diag.close();
|
||||
return { path: mergePaths(top, bot, diag), width: aw };
|
||||
}
|
||||
};
|
||||
|
||||
/* Lowercase: softer, shorter, consistent with uppercase rhythm */
|
||||
|
||||
function lcStemLeft() {
|
||||
return STROKE * 0.9;
|
||||
}
|
||||
function lcXWidth() {
|
||||
return WIDTH.default;
|
||||
}
|
||||
|
||||
const lowerBuilders = {
|
||||
"a": () => {
|
||||
const aw = lcXWidth();
|
||||
const bowl = roundRect(60, BASE + 80, aw - 120, XH - 100, 70);
|
||||
const inner = roundRect(90, BASE + 110, aw - 180, XH - 160, 50);
|
||||
const stem = vBar(aw - 120, XH - 40, XH + 40, STROKE * 0.7);
|
||||
return { path: mergePaths(bowl, inner, stem), width: aw };
|
||||
},
|
||||
"b": () => {
|
||||
const aw = lcXWidth();
|
||||
const x = lcStemLeft();
|
||||
const stem = vBar(x, BASE, CAP, STROKE * 0.8);
|
||||
const bowl = roundRect(x, BASE + 80, aw - x - 40, XH - 80, 70);
|
||||
return { path: mergePaths(stem, bowl), width: aw };
|
||||
},
|
||||
"c": () => {
|
||||
const aw = lcXWidth();
|
||||
const outer = roundRect(60, BASE + 80, aw - 120, XH - 80, 80);
|
||||
const inner = roundRect(100, BASE + 110, aw - 200, XH - 140, 60);
|
||||
return { path: mergePaths(outer, inner), width: aw };
|
||||
},
|
||||
"d": () => {
|
||||
const aw = lcXWidth();
|
||||
const stemX = aw - lcStemLeft() - STROKE * 0.2;
|
||||
const stem = vBar(stemX, BASE, CAP, STROKE * 0.8);
|
||||
const bowl = roundRect(60, BASE + 80, aw - 120, XH - 80, 70);
|
||||
return { path: mergePaths(bowl, stem), width: aw };
|
||||
},
|
||||
"e": () => {
|
||||
const aw = lcXWidth();
|
||||
const outer = roundRect(60, BASE + 80, aw - 120, XH - 80, 80);
|
||||
const bar = hBar(XH - 20, 80, aw - 80, STROKE * 0.6);
|
||||
return { path: mergePaths(outer, bar), width: aw };
|
||||
},
|
||||
"f": () => {
|
||||
const aw = WIDTH.narrow;
|
||||
const x = lcStemLeft();
|
||||
const stem = vBar(x, BASE - 40, CAP, STROKE * 0.7);
|
||||
const bar = hBar(XH, x - 10, aw - 40, STROKE * 0.55);
|
||||
return { path: mergePaths(stem, bar), width: aw };
|
||||
},
|
||||
"g": () => {
|
||||
const aw = lcXWidth();
|
||||
const bowl = roundRect(60, BASE + 80, aw - 120, XH - 80, 70);
|
||||
const tail = roundRect(aw / 2 - 40, DSC, 80, BASE + 80 - DSC, 40);
|
||||
return { path: mergePaths(bowl, tail), width: aw };
|
||||
},
|
||||
"h": () => {
|
||||
const aw = lcXWidth();
|
||||
const x = lcStemLeft();
|
||||
const stem = vBar(x, BASE, CAP, STROKE * 0.8);
|
||||
const arch = roundRect(x, XH - 80, aw - x - 40, 80, 50);
|
||||
return { path: mergePaths(stem, arch), width: aw };
|
||||
},
|
||||
"i": () => {
|
||||
const aw = WIDTH.narrow;
|
||||
const stem = vBar(aw / 2 - STROKE * 0.35, BASE, XH, STROKE * 0.7);
|
||||
const dot = roundRect(aw / 2 - 18, XH + 60, 36, 36, 18);
|
||||
return { path: mergePaths(stem, dot), width: aw };
|
||||
},
|
||||
"j": () => {
|
||||
const aw = WIDTH.narrow;
|
||||
const stem = vBar(aw / 2 - STROKE * 0.35, DSC, XH, STROKE * 0.7);
|
||||
const dot = roundRect(aw / 2 - 18, XH + 60, 36, 36, 18);
|
||||
return { path: mergePaths(stem, dot), width: aw };
|
||||
},
|
||||
"k": () => {
|
||||
const aw = lcXWidth();
|
||||
const x = lcStemLeft();
|
||||
const stem = vBar(x, BASE, CAP, STROKE * 0.8);
|
||||
const arm = new opentype.Path();
|
||||
arm.moveTo(x + STROKE * 0.8, XH - 10);
|
||||
arm.lineTo(aw - 40, BASE);
|
||||
arm.lineTo(aw - 70, BASE);
|
||||
arm.lineTo(x + STROKE * 0.6, XH - 40);
|
||||
arm.close();
|
||||
const arm2 = new opentype.Path();
|
||||
arm2.moveTo(x + STROKE * 0.8, XH + 10);
|
||||
arm2.lineTo(aw - 60, CAP * 0.55);
|
||||
arm2.lineTo(aw - 90, CAP * 0.55);
|
||||
arm2.lineTo(x + STROKE * 0.6, XH + 40);
|
||||
arm2.close();
|
||||
return { path: mergePaths(stem, arm, arm2), width: aw };
|
||||
},
|
||||
"l": () => {
|
||||
const aw = WIDTH.narrow;
|
||||
const stem = vBar(aw / 2 - STROKE * 0.35, BASE, CAP, STROKE * 0.7);
|
||||
return { path: stem, width: aw };
|
||||
},
|
||||
"m": () => {
|
||||
const aw = WIDTH.wide + 40;
|
||||
const x = lcStemLeft();
|
||||
const s1 = vBar(x, BASE, XH, STROKE * 0.7);
|
||||
const arch1 = roundRect(x, XH - 70, (aw - x) / 2 - 40, 70, 40);
|
||||
const s2x = x + (aw - x) / 2 - 20;
|
||||
const s2 = vBar(s2x, BASE, XH, STROKE * 0.7);
|
||||
const arch2 = roundRect(s2x, XH - 70, aw - s2x - 40, 70, 40);
|
||||
return { path: mergePaths(s1, arch1, s2, arch2), width: aw };
|
||||
},
|
||||
"n": () => {
|
||||
const aw = lcXWidth();
|
||||
const x = lcStemLeft();
|
||||
const stem = vBar(x, BASE, XH, STROKE * 0.7);
|
||||
const arch = roundRect(x, XH - 70, aw - x - 40, 70, 40);
|
||||
return { path: mergePaths(stem, arch), width: aw };
|
||||
},
|
||||
"o": () => {
|
||||
const aw = lcXWidth();
|
||||
const outer = roundRect(60, BASE + 80, aw - 120, XH - 80, 80);
|
||||
const inner = roundRect(95, BASE + 110, aw - 190, XH - 140, 60);
|
||||
return { path: mergePaths(outer, inner), width: aw };
|
||||
},
|
||||
"p": () => {
|
||||
const aw = lcXWidth();
|
||||
const x = lcStemLeft();
|
||||
const stem = vBar(x, DSC, XH, STROKE * 0.8);
|
||||
const bowl = roundRect(x, BASE + 80, aw - x - 40, XH - 80, 70);
|
||||
return { path: mergePaths(stem, bowl), width: aw };
|
||||
},
|
||||
"q": () => {
|
||||
const aw = lcXWidth();
|
||||
const stemX = aw - lcStemLeft() - STROKE * 0.2;
|
||||
const stem = vBar(stemX, DSC, XH, STROKE * 0.8);
|
||||
const bowl = roundRect(60, BASE + 80, aw - 120, XH - 80, 70);
|
||||
return { path: mergePaths(bowl, stem), width: aw };
|
||||
},
|
||||
"r": () => {
|
||||
const aw = WIDTH.default;
|
||||
const x = lcStemLeft();
|
||||
const stem = vBar(x, BASE, XH, STROKE * 0.7);
|
||||
const shoulder = hBar(XH - 10, x + STROKE * 0.6, aw - 80, STROKE * 0.55);
|
||||
return { path: mergePaths(stem, shoulder), width: aw };
|
||||
},
|
||||
"s": () => {
|
||||
const aw = lcXWidth();
|
||||
const p = new opentype.Path();
|
||||
const top = XH - 10;
|
||||
const mid = (XH + BASE + 80) / 2;
|
||||
const bot = BASE + 80;
|
||||
p.moveTo(aw - 40, top);
|
||||
p.quadraticCurveTo(aw * 0.2, XH + 20, 60, mid + 30);
|
||||
p.quadraticCurveTo(20, mid - 10, aw * 0.3, mid - 40);
|
||||
p.quadraticCurveTo(aw - 20, mid - 70, aw - 40, bot - 10);
|
||||
p.quadraticCurveTo(aw * 0.5, bot - 40, 60, bot + 10);
|
||||
p.quadraticCurveTo(aw * 0.6, bot + 20, aw - 40, top - 40);
|
||||
p.close();
|
||||
return { path: p, width: aw };
|
||||
},
|
||||
"t": () => {
|
||||
const aw = WIDTH.narrow;
|
||||
const stem = vBar(aw / 2 - STROKE * 0.35, BASE, CAP - 40, STROKE * 0.7);
|
||||
const bar = hBar(XH, 40, aw - 40, STROKE * 0.55);
|
||||
return { path: mergePaths(stem, bar), width: aw };
|
||||
},
|
||||
"u": () => {
|
||||
const aw = lcXWidth();
|
||||
const bowl = roundRect(60, BASE + 40, aw - 120, XH - 40, 70);
|
||||
const rightStem = vBar(aw - 100, BASE + 40, XH, STROKE * 0.7);
|
||||
return { path: mergePaths(bowl, rightStem), width: aw };
|
||||
},
|
||||
"v": () => {
|
||||
const aw = lcXWidth();
|
||||
const s = STROKE * 0.7;
|
||||
const mid = aw / 2;
|
||||
const p1 = new opentype.Path();
|
||||
p1.moveTo(60, XH);
|
||||
p1.lineTo(60 + s, XH);
|
||||
p1.lineTo(mid, BASE);
|
||||
p1.lineTo(mid - s, BASE);
|
||||
p1.close();
|
||||
const p2 = new opentype.Path();
|
||||
p2.moveTo(aw - 60, XH);
|
||||
p2.lineTo(aw - 60 - s, XH);
|
||||
p2.lineTo(mid, BASE);
|
||||
p2.lineTo(mid + s, BASE);
|
||||
p2.close();
|
||||
return { path: mergePaths(p1, p2), width: aw };
|
||||
},
|
||||
"w": () => {
|
||||
const aw = WIDTH.wide + 40;
|
||||
const s = STROKE * 0.65;
|
||||
const x1 = 50;
|
||||
const x2 = aw * 0.36;
|
||||
const x3 = aw * 0.64;
|
||||
const x4 = aw - 50;
|
||||
const yTop = XH;
|
||||
const yBot = BASE;
|
||||
const p = new opentype.Path();
|
||||
p.moveTo(x1, yTop);
|
||||
p.lineTo(x1 + s, yTop);
|
||||
p.lineTo(x2, yBot);
|
||||
p.lineTo(x2 - s, yBot);
|
||||
p.close();
|
||||
p.moveTo(x2, yBot);
|
||||
p.lineTo(x2 + s, yBot);
|
||||
p.lineTo(x3, yTop * 0.55);
|
||||
p.lineTo(x3 - s, yTop * 0.55);
|
||||
p.close();
|
||||
p.moveTo(x3, yTop * 0.55);
|
||||
p.lineTo(x3 + s, yTop * 0.55);
|
||||
p.lineTo(x4, yTop);
|
||||
p.lineTo(x4 - s, yTop);
|
||||
p.close();
|
||||
return { path: p, width: aw };
|
||||
},
|
||||
"x": () => {
|
||||
const aw = lcXWidth();
|
||||
const s = STROKE * 0.65;
|
||||
const p1 = new opentype.Path();
|
||||
p1.moveTo(60, XH);
|
||||
p1.lineTo(60 + s, XH);
|
||||
p1.lineTo(aw - 60, BASE);
|
||||
p1.lineTo(aw - 60 - s, BASE);
|
||||
p1.close();
|
||||
const p2 = new opentype.Path();
|
||||
p2.moveTo(aw - 60, XH);
|
||||
p2.lineTo(aw - 60 - s, XH);
|
||||
p2.lineTo(60, BASE);
|
||||
p2.lineTo(60 + s, BASE);
|
||||
p2.close();
|
||||
return { path: mergePaths(p1, p2), width: aw };
|
||||
},
|
||||
"y": () => {
|
||||
const aw = lcXWidth();
|
||||
const s = STROKE * 0.7;
|
||||
const mid = aw / 2;
|
||||
const p1 = new opentype.Path();
|
||||
p1.moveTo(60, XH);
|
||||
p1.lineTo(60 + s, XH);
|
||||
p1.lineTo(mid, BASE);
|
||||
p1.lineTo(mid - s, BASE);
|
||||
p1.close();
|
||||
const p2 = new opentype.Path();
|
||||
p2.moveTo(aw - 60, XH);
|
||||
p2.lineTo(aw - 60 - s, XH);
|
||||
p2.lineTo(mid, DSC);
|
||||
p2.lineTo(mid + s, DSC);
|
||||
p2.close();
|
||||
return { path: mergePaths(p1, p2), width: aw };
|
||||
},
|
||||
"z": () => {
|
||||
const aw = lcXWidth();
|
||||
const top = hBar(XH, 60, aw - 60, STROKE * 0.55);
|
||||
const bot = hBar(BASE + 40, 60, aw - 60, STROKE * 0.55);
|
||||
const diag = new opentype.Path();
|
||||
diag.moveTo(aw - 60, XH - STROKE * 0.5);
|
||||
diag.lineTo(aw - 80, XH - STROKE * 0.5);
|
||||
diag.lineTo(60, BASE + 40 + STROKE * 0.5);
|
||||
diag.lineTo(80, BASE + 40 + STROKE * 0.5);
|
||||
diag.close();
|
||||
return { path: mergePaths(top, bot, diag), width: aw };
|
||||
}
|
||||
};
|
||||
|
||||
/* Digits: rounded rectangular, consistent weight */
|
||||
|
||||
function digitBuilder(d) {
|
||||
const aw = WIDTH.default;
|
||||
switch (d) {
|
||||
case "0": {
|
||||
const outer = roundRect(80, BASE + 40, aw - 160, CAP - 160, 90);
|
||||
const inner = roundRect(120, BASE + 80, aw - 240, CAP - 240, 70);
|
||||
return { path: mergePaths(outer, inner), width: aw };
|
||||
}
|
||||
case "1": {
|
||||
const stem = vBar(aw / 2 - STROKE * 0.4, BASE + 40, CAP - 40, STROKE * 0.8);
|
||||
const base = hBar(BASE + 40, aw / 2 - 80, aw / 2 + 80, STROKE * 0.7);
|
||||
return { path: mergePaths(stem, base), width: aw };
|
||||
}
|
||||
case "2": {
|
||||
const top = hBar(CAP - 40, 80, aw - 80, STROKE * 0.7);
|
||||
const curve = new opentype.Path();
|
||||
curve.moveTo(aw - 80, CAP - 40);
|
||||
curve.quadraticCurveTo(aw - 10, XH + 40, aw * 0.4, XH);
|
||||
curve.quadraticCurveTo(80, XH - 40, 80, BASE + 40);
|
||||
const base = hBar(BASE + 40, 80, aw - 80, STROKE * 0.7);
|
||||
return { path: mergePaths(top, curve, base), width: aw };
|
||||
}
|
||||
case "3": {
|
||||
const top = roundRect(80, CAP - 260, aw - 160, 90, 40);
|
||||
const bot = roundRect(80, BASE + 80, aw - 160, 90, 40);
|
||||
const mid = new opentype.Path();
|
||||
mid.moveTo(aw - 80, CAP - 170);
|
||||
mid.quadraticCurveTo(aw, XH, aw - 80, BASE + 170);
|
||||
mid.quadraticCurveTo(aw - 40, BASE + 210, aw - 40, BASE + 230);
|
||||
return { path: mergePaths(top, bot, mid), width: aw };
|
||||
}
|
||||
case "4": {
|
||||
const stem = vBar(aw - 120, BASE + 40, CAP, STROKE * 0.8);
|
||||
const diag = new opentype.Path();
|
||||
diag.moveTo(80, CAP - 180);
|
||||
diag.lineTo(80 + STROKE * 0.7, CAP - 180);
|
||||
diag.lineTo(aw - 120, BASE + 40);
|
||||
diag.lineTo(aw - 140, BASE + 40);
|
||||
diag.close();
|
||||
const bar = hBar(CAP - 200, 80, aw - 120, STROKE * 0.7);
|
||||
return { path: mergePaths(stem, diag, bar), width: aw };
|
||||
}
|
||||
case "5": {
|
||||
const top = hBar(CAP - 40, 80, aw - 80, STROKE * 0.7);
|
||||
const left = vBar(80, CAP - 260, CAP - 40, STROKE * 0.7);
|
||||
const mid = hBar(CAP - 260, 80, aw - 80, STROKE * 0.7);
|
||||
const bowl = roundRect(80, BASE + 80, aw - 160, 120, 60);
|
||||
return { path: mergePaths(top, left, mid, bowl), width: aw };
|
||||
}
|
||||
case "6": {
|
||||
const loop = roundRect(80, BASE + 40, aw - 160, CAP - 220, 80);
|
||||
const inner = roundRect(120, BASE + 80, aw - 240, CAP - 260, 60);
|
||||
const hook = new opentype.Path();
|
||||
hook.moveTo(aw - 80, CAP - 220);
|
||||
hook.quadraticCurveTo(aw - 10, CAP - 260, aw - 60, CAP - 320);
|
||||
return { path: mergePaths(loop, inner, hook), width: aw };
|
||||
}
|
||||
case "7": {
|
||||
const top = hBar(CAP - 40, 80, aw - 80, STROKE * 0.7);
|
||||
const diag = new opentype.Path();
|
||||
diag.moveTo(aw - 80, CAP - 40);
|
||||
diag.lineTo(aw - 80 - STROKE * 0.8, CAP - 40);
|
||||
diag.lineTo(80, BASE + 40);
|
||||
diag.lineTo(80 + STROKE * 0.8, BASE + 40);
|
||||
diag.close();
|
||||
return { path: mergePaths(top, diag), width: aw };
|
||||
}
|
||||
case "8": {
|
||||
const top = roundRect(90, CAP - 260, aw - 180, 110, 50);
|
||||
const bot = roundRect(90, BASE + 60, aw - 180, 140, 60);
|
||||
const midHole = roundRect(120, BASE + 110, aw - 240, 60, 30);
|
||||
return { path: mergePaths(top, bot, midHole), width: aw };
|
||||
}
|
||||
case "9": {
|
||||
const loop = roundRect(80, BASE + 160, aw - 160, CAP - 220, 80);
|
||||
const inner = roundRect(120, BASE + 180, aw - 240, CAP - 260, 60);
|
||||
const hook = new opentype.Path();
|
||||
hook.moveTo(80, BASE + 160);
|
||||
hook.quadraticCurveTo(40, BASE + 120, 80, BASE + 80);
|
||||
return { path: mergePaths(loop, inner, hook), width: aw };
|
||||
}
|
||||
default:
|
||||
return { path: new opentype.Path(), width: aw };
|
||||
}
|
||||
}
|
||||
|
||||
/* Punctuation & symbols */
|
||||
|
||||
function punctBuilder(ch) {
|
||||
const aw = WIDTH.default;
|
||||
if (ch === ".") {
|
||||
return {
|
||||
path: roundRect(aw / 2 - 16, BASE + 40, 32, 40, 16),
|
||||
width: WIDTH.space
|
||||
};
|
||||
}
|
||||
if (ch === ",") {
|
||||
const p = roundRect(aw / 2 - 16, BASE + 40, 32, 40, 16);
|
||||
const tail = new opentype.Path();
|
||||
tail.moveTo(aw / 2 + 4, BASE + 40);
|
||||
tail.lineTo(aw / 2 + 20, BASE - 10);
|
||||
tail.lineTo(aw / 2 + 4, BASE);
|
||||
tail.close();
|
||||
return { path: mergePaths(p, tail), width: WIDTH.space };
|
||||
}
|
||||
if (ch === ":" || ch === ";") {
|
||||
const top = roundRect(aw / 2 - 16, XH - 40, 32, 40, 16);
|
||||
const bot = roundRect(aw / 2 - 16, BASE + 40, 32, 40, 16);
|
||||
let p = mergePaths(top, bot);
|
||||
if (ch === ";") {
|
||||
const tail = new opentype.Path();
|
||||
tail.moveTo(aw / 2 + 4, BASE + 40);
|
||||
tail.lineTo(aw / 2 + 20, BASE - 10);
|
||||
tail.lineTo(aw / 2 + 4, BASE);
|
||||
tail.close();
|
||||
p = mergePaths(p, tail);
|
||||
}
|
||||
return { path: p, width: WIDTH.space };
|
||||
}
|
||||
if (ch === "!") {
|
||||
const stem = vBar(aw / 2 - STROKE * 0.3, BASE + 120, CAP, STROKE * 0.6);
|
||||
const dot = roundRect(aw / 2 - 16, BASE + 40, 32, 40, 16);
|
||||
return { path: mergePaths(stem, dot), width: WIDTH.narrow };
|
||||
}
|
||||
if (ch === "?") {
|
||||
const hook = new opentype.Path();
|
||||
hook.moveTo(aw / 2 - 40, CAP - 180);
|
||||
hook.quadraticCurveTo(aw - 40, CAP, aw - 40, XH);
|
||||
hook.quadraticCurveTo(aw - 40, XH - 40, aw / 2, XH - 60);
|
||||
hook.lineTo(aw / 2, BASE + 120);
|
||||
const dot = roundRect(aw / 2 - 16, BASE + 40, 32, 40, 16);
|
||||
return { path: mergePaths(hook, dot), width: aw };
|
||||
}
|
||||
if (ch === "-" || ch === "–" || ch === "—") {
|
||||
const len = ch === "-" ? aw * 0.4 : ch === "–" ? aw * 0.6 : aw * 0.9;
|
||||
const bar = hBar(BASE + 260, (aw - len) / 2, (aw + len) / 2, STROKE * 0.4);
|
||||
return { path: bar, width: len + 80 };
|
||||
}
|
||||
if (ch === "(" || ch === ")") {
|
||||
const p = new opentype.Path();
|
||||
const left = ch === "(";
|
||||
const x = left ? aw / 2 - 40 : aw / 2 + 40;
|
||||
const dir = left ? -1 : 1;
|
||||
p.moveTo(x + dir * 20, CAP - 40);
|
||||
p.quadraticCurveTo(x + dir * 80, (CAP + BASE) / 2, x + dir * 20, BASE + 40);
|
||||
p.quadraticCurveTo(x + dir * 40, BASE + 80, x + dir * 40, BASE + 120);
|
||||
return { path: p, width: WIDTH.space + 40 };
|
||||
}
|
||||
if (ch === "'") {
|
||||
const p = new opentype.Path();
|
||||
p.moveTo(aw / 2 - 10, CAP);
|
||||
p.lineTo(aw / 2 + 10, CAP);
|
||||
p.lineTo(aw / 2, CAP - 60);
|
||||
p.close();
|
||||
return { path: p, width: WIDTH.space };
|
||||
}
|
||||
if (ch === "\"") {
|
||||
const left = punctBuilder("'").path;
|
||||
const right = new opentype.Path();
|
||||
right.commands = left.commands.map(cmd => {
|
||||
const c = { ...cmd };
|
||||
if (c.x !== undefined) c.x += 24;
|
||||
if (c.x1 !== undefined) c.x1 += 24;
|
||||
if (c.x2 !== undefined) c.x2 += 24;
|
||||
return c;
|
||||
});
|
||||
return { path: mergePaths(left, right), width: WIDTH.space + 16 };
|
||||
}
|
||||
if (ch === "@") {
|
||||
const outer = roundRect(40, BASE + 40, aw + 120, CAP - 160, 90);
|
||||
const inner = roundRect(120, BASE + 80, aw, CAP - 220, 70);
|
||||
return { path: mergePaths(outer, inner), width: aw + 160 };
|
||||
}
|
||||
if (ch === "#") {
|
||||
const bar1 = hBar(XH + 40, 80, aw - 80, STROKE * 0.35);
|
||||
const bar2 = hBar(BASE + 200, 80, aw - 80, STROKE * 0.35);
|
||||
const v1 = vBar(aw / 2 - 70, BASE + 40, CAP - 40, STROKE * 0.35);
|
||||
const v2 = vBar(aw / 2 + 20, BASE + 40, CAP - 40, STROKE * 0.35);
|
||||
return { path: mergePaths(bar1, bar2, v1, v2), width: aw };
|
||||
}
|
||||
if (ch === "$") {
|
||||
const s = digitBuilder("5").path;
|
||||
return { path: s, width: aw };
|
||||
}
|
||||
if (ch === "%") {
|
||||
const slash = new opentype.Path();
|
||||
slash.moveTo(80, BASE + 40);
|
||||
slash.lineTo(120, BASE + 40);
|
||||
slash.lineTo(aw - 80, CAP - 40);
|
||||
slash.lineTo(aw - 120, CAP - 40);
|
||||
slash.close();
|
||||
const o1 = roundRect(80, CAP - 220, 80, 80, 30);
|
||||
const o2 = roundRect(aw - 160, BASE + 40, 80, 80, 30);
|
||||
return { path: mergePaths(slash, o1, o2), width: aw };
|
||||
}
|
||||
if (ch === "&") {
|
||||
const p = new opentype.Path();
|
||||
p.moveTo(aw - 80, CAP - 220);
|
||||
p.quadraticCurveTo(aw - 40, CAP - 320, aw / 2, CAP - 320);
|
||||
p.quadraticCurveTo(40, CAP - 320, 40, CAP - 200);
|
||||
p.quadraticCurveTo(40, CAP - 80, aw / 2, CAP - 80);
|
||||
p.quadraticCurveTo(aw, CAP - 60, aw / 2, BASE + 40);
|
||||
p.quadraticCurveTo(40, BASE, aw / 2, BASE);
|
||||
return { path: p, width: aw };
|
||||
}
|
||||
if (ch === "*") {
|
||||
const p = new opentype.Path();
|
||||
const cx = aw / 2;
|
||||
const cy = (BASE + CAP) / 2;
|
||||
const r0 = 40;
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const a1 = (Math.PI * 2 * i) / 6;
|
||||
const a2 = (Math.PI * 2 * (i + 1)) / 6;
|
||||
p.moveTo(cx, cy);
|
||||
p.lineTo(cx + r0 * Math.cos(a1), cy + r0 * Math.sin(a1));
|
||||
p.lineTo(cx + r0 * Math.cos(a2), cy + r0 * Math.sin(a2));
|
||||
p.close();
|
||||
}
|
||||
return { path: p, width: aw };
|
||||
}
|
||||
if (ch === "+") {
|
||||
const h = hBar((BASE + CAP) / 2 + STROKE * 0.2, 80, aw - 80, STROKE * 0.5);
|
||||
const v = vBar(aw / 2 - STROKE * 0.25, BASE + 80, CAP - 80, STROKE * 0.5);
|
||||
return { path: mergePaths(h, v), width: aw };
|
||||
}
|
||||
if (ch === "/") {
|
||||
const p = new opentype.Path();
|
||||
p.moveTo(aw - 80, CAP);
|
||||
p.lineTo(aw - 120, CAP);
|
||||
p.lineTo(80, BASE);
|
||||
p.lineTo(120, BASE);
|
||||
p.close();
|
||||
return { path: p, width: aw };
|
||||
}
|
||||
if (ch === "=") {
|
||||
const h1 = hBar((BASE + CAP) / 2 + 40, 80, aw - 80, STROKE * 0.4);
|
||||
const h2 = hBar((BASE + CAP) / 2 - 40, 80, aw - 80, STROKE * 0.4);
|
||||
return { path: mergePaths(h1, h2), width: aw };
|
||||
}
|
||||
if (ch === "_") {
|
||||
const bar = hBar(BASE + 10, 40, aw - 40, STROKE * 0.25);
|
||||
return { path: bar, width: aw };
|
||||
}
|
||||
return { path: new opentype.Path(), width: aw };
|
||||
}
|
||||
|
||||
/* Public builder */
|
||||
|
||||
export function buildGlyph(ch) {
|
||||
if (ch === " ") {
|
||||
return { path: new opentype.Path(), width: WIDTH.space };
|
||||
}
|
||||
if (upperBuilders[ch]) return upperBuilders[ch]();
|
||||
if (lowerBuilders[ch]) return lowerBuilders[ch]();
|
||||
if (CHARSET.digits.includes(ch)) return digitBuilder(ch);
|
||||
if (COMMON_PUNCT.includes(ch)) return punctBuilder(ch);
|
||||
// Fallback: empty but with width, to avoid system fallback.
|
||||
return { path: new opentype.Path(), width: WIDTH.default };
|
||||
}
|
||||
|
||||
export const METRICS = {
|
||||
unitsPerEm: UPEM,
|
||||
ascender: ASC,
|
||||
descender: DSC
|
||||
};
|
||||
|
||||
export const SUPPORTED_CHARS = (() => {
|
||||
const set = new Set();
|
||||
for (const s of Object.values(CHARSET)) for (const ch of s) set.add(ch);
|
||||
for (const ch of COMMON_PUNCT) set.add(ch);
|
||||
set.add(" ");
|
||||
return Array.from(set);
|
||||
})();
|
||||
Reference in New Issue
Block a user