mirror of
https://github.com/multipleof4/stain.otf.git
synced 2026-01-13 16:17:55 +00:00
Feat: Build readable Stain Regular font
This commit is contained in:
@@ -2,152 +2,74 @@ import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import opentype from "opentype.js";
|
||||
import { buildGlyph, METRICS } from "../src/glyphs.js";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const distDir = path.join(__dirname, "..", "dist");
|
||||
if (!fs.existsSync(distDir)) fs.mkdirSync(distDir, { recursive: true });
|
||||
|
||||
const upem = 1000;
|
||||
const ascent = 800;
|
||||
const descent = -200;
|
||||
const defaultWidth = 600;
|
||||
const FAMILY = "Stain";
|
||||
const STYLE = "Regular";
|
||||
|
||||
// AI-style stub: deterministic parametric glyph generator
|
||||
// This is where future AI (you call me) can map characters -> vector instructions.
|
||||
function genGlyphPathForChar(ch) {
|
||||
const charset = [
|
||||
" ",
|
||||
"A","B","C","D","E","F","G","H","I","J","K","L","M",
|
||||
"N","O","P","Q","R","S","T","U","V","W","X","Y","Z",
|
||||
"0","1","2","3","4","5","6","7","8","9",
|
||||
".",",",":",";","!","?","-","–","—","@"
|
||||
];
|
||||
|
||||
function buildNotdef() {
|
||||
const w = 600;
|
||||
const p = new opentype.Path();
|
||||
const code = ch.charCodeAt(0);
|
||||
|
||||
const seed = (code * 73) % 997;
|
||||
const thickness = 40 + (seed % 60);
|
||||
const inset = 80 + (seed % 120);
|
||||
const cx = defaultWidth / 2;
|
||||
|
||||
// Vertical stem
|
||||
p.moveTo(cx - thickness / 2, ascent);
|
||||
p.lineTo(cx + thickness / 2, ascent);
|
||||
p.lineTo(cx + thickness / 2, descent);
|
||||
p.lineTo(cx - thickness / 2, descent);
|
||||
const m = 60;
|
||||
p.moveTo(m, METRICS.descender);
|
||||
p.lineTo(w - m, METRICS.descender);
|
||||
p.lineTo(w - m, METRICS.ascender);
|
||||
p.lineTo(m, METRICS.ascender);
|
||||
p.close();
|
||||
|
||||
// Top stain-like slab
|
||||
const slabW = 260 + (seed % 140);
|
||||
const slabH = 80 + (seed % 50);
|
||||
p.moveTo(cx - slabW / 2, ascent);
|
||||
p.lineTo(cx + slabW / 2, ascent);
|
||||
p.lineTo(cx + slabW / 2, ascent - slabH);
|
||||
p.quadraticCurveTo(
|
||||
cx,
|
||||
ascent - slabH - (seed % 40),
|
||||
cx - slabW / 2,
|
||||
ascent - slabH
|
||||
);
|
||||
p.moveTo(m + 60, METRICS.descender + 60);
|
||||
p.lineTo(w - m - 60, METRICS.descender + 60);
|
||||
p.lineTo(w - m - 60, METRICS.ascender - 60);
|
||||
p.lineTo(m + 60, METRICS.ascender - 60);
|
||||
p.close();
|
||||
|
||||
// Bottom irregular notch
|
||||
const notchW = 120 + (seed % 80);
|
||||
const notchY = descent + 40 + (seed % 60);
|
||||
p.moveTo(cx - notchW / 2, notchY);
|
||||
p.lineTo(cx + notchW / 2, notchY);
|
||||
p.lineTo(cx + notchW / 2, descent);
|
||||
p.quadraticCurveTo(
|
||||
cx,
|
||||
descent - (seed % 50),
|
||||
cx - notchW / 2,
|
||||
descent
|
||||
);
|
||||
p.close();
|
||||
|
||||
// Inner void
|
||||
const inner = inset;
|
||||
if (inner + thickness * 1.5 < defaultWidth / 2) {
|
||||
const iw = defaultWidth - inner * 2;
|
||||
const ih = ascent - inner - 200;
|
||||
if (iw > 80 && ih > 80) {
|
||||
p.moveTo(inner, ih);
|
||||
p.lineTo(inner + iw, ih);
|
||||
p.lineTo(inner + iw, inner + 80);
|
||||
p.lineTo(inner, inner + 80);
|
||||
p.close();
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
return new opentype.Glyph({
|
||||
name: ".notdef",
|
||||
unicode: 0,
|
||||
advanceWidth: w,
|
||||
path: p
|
||||
});
|
||||
}
|
||||
|
||||
function createGlyph(ch, advanceWidth = defaultWidth) {
|
||||
const unicode = ch.charCodeAt(0);
|
||||
const pathObj = genGlyphPathForChar(ch);
|
||||
function createGlyph(ch) {
|
||||
const unicode = ch.codePointAt(0);
|
||||
const { path, width } = buildGlyph(ch);
|
||||
return new opentype.Glyph({
|
||||
name: ch === " " ? "space" : `uni${unicode.toString(16).toUpperCase()}`,
|
||||
unicode,
|
||||
advanceWidth,
|
||||
path: pathObj
|
||||
advanceWidth: width,
|
||||
path
|
||||
});
|
||||
}
|
||||
|
||||
function buildFont() {
|
||||
const fontName = "StainFont Basic";
|
||||
const chars = [
|
||||
" ",
|
||||
"A","B","C","D","E","F","G","H","I","J","K","L","M",
|
||||
"N","O","P","Q","R","S","T","U","V","W","X","Y","Z",
|
||||
"0","1","2","3","4","5","6","7","8","9",
|
||||
".","!", "?", "@", "#", "-", "_", ":", ";", ","
|
||||
];
|
||||
|
||||
const glyphs = [];
|
||||
|
||||
glyphs.push(new opentype.Glyph({
|
||||
name: ".notdef",
|
||||
unicode: 0,
|
||||
advanceWidth: defaultWidth,
|
||||
path: (() => {
|
||||
const p = new opentype.Path();
|
||||
p.moveTo(80, descent);
|
||||
p.lineTo(defaultWidth - 80, descent);
|
||||
p.lineTo(defaultWidth - 80, ascent);
|
||||
p.lineTo(80, ascent);
|
||||
p.close();
|
||||
p.moveTo(140, descent + 60);
|
||||
p.lineTo(defaultWidth - 140, descent + 60);
|
||||
p.lineTo(defaultWidth - 140, ascent - 60);
|
||||
p.lineTo(140, ascent - 60);
|
||||
p.close();
|
||||
return p;
|
||||
})()
|
||||
}));
|
||||
|
||||
chars.forEach(ch => {
|
||||
if (ch === " ") {
|
||||
glyphs.push(new opentype.Glyph({
|
||||
name: "space",
|
||||
unicode: 32,
|
||||
advanceWidth: defaultWidth * 0.5,
|
||||
path: new opentype.Path()
|
||||
}));
|
||||
} else {
|
||||
glyphs.push(createGlyph(ch));
|
||||
}
|
||||
});
|
||||
const glyphs = [buildNotdef()];
|
||||
charset.forEach(ch => glyphs.push(createGlyph(ch)));
|
||||
|
||||
const font = new opentype.Font({
|
||||
familyName: fontName,
|
||||
styleName: "Regular",
|
||||
unitsPerEm: upem,
|
||||
ascender: ascent,
|
||||
descender: descent,
|
||||
familyName: FAMILY,
|
||||
styleName: STYLE,
|
||||
unitsPerEm: METRICS.unitsPerEm,
|
||||
ascender: METRICS.ascender,
|
||||
descender: METRICS.descender,
|
||||
glyphs
|
||||
});
|
||||
|
||||
const ttfBuffer = Buffer.from(font.toArrayBuffer());
|
||||
|
||||
const outTTF = path.join(distDir, "StainFont-Basic.ttf");
|
||||
fs.writeFileSync(outTTF, ttfBuffer);
|
||||
|
||||
console.log(`Built: ${outTTF}`);
|
||||
const ttf = Buffer.from(font.toArrayBuffer());
|
||||
const out = path.join(distDir, "Stain-Regular.ttf");
|
||||
fs.writeFileSync(out, ttf);
|
||||
console.log(`Built: ${out}`);
|
||||
}
|
||||
|
||||
buildFont();
|
||||
|
||||
Reference in New Issue
Block a user