mirror of
https://github.com/multipleof4/stain.otf.git
synced 2026-01-13 16:17:55 +00:00
185 lines
4.7 KiB
JavaScript
185 lines
4.7 KiB
JavaScript
import fs from "fs";
|
|
import path from "path";
|
|
import url from "url";
|
|
import opentype from "opentype.js";
|
|
|
|
const __dirname=url.fileURLToPath(new URL(".",import.meta.url));
|
|
const distDir=path.resolve(__dirname,"..","dist");
|
|
if(!fs.existsSync(distDir))fs.mkdirSync(distDir,{recursive:true});
|
|
|
|
const upm=1000;
|
|
const ascent=800;
|
|
const descent=-200;
|
|
const bw=80;
|
|
const gap=40;
|
|
|
|
const makePath=d=>new opentype.Path(d);
|
|
|
|
const glyph=({name,unicode,advanceWidth,commands})=>new opentype.Glyph({
|
|
name,
|
|
unicode,
|
|
advanceWidth,
|
|
path:makePath(commands)
|
|
});
|
|
|
|
// Simple Candara-inspired, clean humanist-ish shapes
|
|
const glyphs=[];
|
|
|
|
// space
|
|
glyphs.push(glyph({
|
|
name:"space",
|
|
unicode:32,
|
|
advanceWidth:300,
|
|
commands:[]
|
|
}));
|
|
|
|
// Helper for vertical stems
|
|
const vStem=(x,y1,y2,w)=>[
|
|
{type:"M",x:x,y:y1},
|
|
{type:"L",x:x+w,y:y1},
|
|
{type:"L",x:x+w,y:y2},
|
|
{type:"L",x:x,y:y2},
|
|
{type:"Z"}
|
|
];
|
|
|
|
// Helper for horizontal bar
|
|
const hBar=(x1,x2,y,w)=>[
|
|
{type:"M",x:x1,y:y},
|
|
{type:"L",x:x2,y:y},
|
|
{type:"L",x:x2,y:y+w},
|
|
{type:"L",x:x1,y:y+w},
|
|
{type:"Z"}
|
|
];
|
|
|
|
// Uppercase A
|
|
(()=>{
|
|
const aw=600;
|
|
const base=0,top=ascent;
|
|
const left=140,right=460;
|
|
const barY=450,barW=60;
|
|
const cmds=[
|
|
{type:"M",x:80,y:base},
|
|
{type:"L",x:200,y:top},
|
|
{type:"L",x:260,y:top},
|
|
{type:"L",x:480,y:base},
|
|
{type:"L",x:400,y:base},
|
|
{type:"L",x:250,y:620},
|
|
{type:"L",x:120,y:base},
|
|
{type:"Z"},
|
|
...hBar(left,right,barY,barW)
|
|
];
|
|
glyphs.push(glyph({name:"A",unicode:65,advanceWidth:aw,commands:cmds}));
|
|
})();
|
|
|
|
// Uppercase B
|
|
(()=>{
|
|
const aw=600;
|
|
const x0=90;
|
|
const mid=450;
|
|
const r=230;
|
|
const cmds=[
|
|
...vStem(x0,0,ascent,bw),
|
|
{type:"M",x:x0+bw,y:ascent},
|
|
{type:"Q",x:x0+bw+220,y:ascent,x1:x0+bw+220,y1:ascent-180},
|
|
{type:"Q",x:x0+bw+220,y:ascent-320,x1:x0+bw,y1:mid},
|
|
{type:"L",x:x0+bw,y:mid},
|
|
{type:"Z"},
|
|
{type:"M",x:x0+bw,y:mid},
|
|
{type:"Q",x:x0+bw+200,y:mid,x1:x0+bw+200,y1:mid-160},
|
|
{type:"Q",x:x0+bw+200,y:mid-310,x1:x0+bw,y1:0},
|
|
{type:"L",x:x0+bw,y:0},
|
|
{type:"Z"}
|
|
];
|
|
glyphs.push(glyph({name:"B",unicode:66,advanceWidth:aw,commands:cmds}));
|
|
})();
|
|
|
|
// Uppercase C
|
|
(()=>{
|
|
const aw=600;
|
|
const cx=340,cy=400,ro=320,ri=220;
|
|
const cmds=[
|
|
{type:"M",x:cx+ro,y:cy+10},
|
|
{type:"Q",x:cx+ro-140,y:cy+260,x1:cx,y1:cy+260},
|
|
{type:"Q",x:cx-ro+40,y:cy+260,x1:cx-ro+40,y1:cy},
|
|
{type:"Q",x:cx-ro+40,y:cy-260,x1:cx,y1:cy-260},
|
|
{type:"Q",x:cx+ro-80,y:cy-260,x1:cx+ro,y1:cy-40},
|
|
{type:"L",x:cx+ro-60,y:cy-40},
|
|
{type:"Q",x:cx+ro-120,y:cy-210,x1:cx,y1:cy-210},
|
|
{type:"Q",x:cx-ri,y:cy-210,x1:cx-ri,y1:cy},
|
|
{type:"Q",x:cx-ri,y:cy+210,x1:cx,y1:cy+210},
|
|
{type:"Q",x:cx+ro-120,y:cy+210,x1:cx+ro-40,y1:cy+40},
|
|
{type:"Z"}
|
|
];
|
|
glyphs.push(glyph({name:"C",unicode:67,advanceWidth:aw,commands:cmds}));
|
|
})();
|
|
|
|
// Lowercase a
|
|
(()=>{
|
|
const aw=520;
|
|
const base=0,x0=90;
|
|
const cmds=[
|
|
{type:"M",x:x0+80,y:base},
|
|
{type:"Q",x:x0,y:base+70,x1:x0,y1:base+150},
|
|
{type:"Q",x:x0,y:base+260,x1:x0+110,y1:base+260},
|
|
{type:"Q",x:x0+210,y:base+260,x1:x0+260,y1:base+210},
|
|
{type:"L",x:x0+260,y:base},
|
|
{type:"L",x:x0+200,y:base},
|
|
{type:"L",x:x0+200,y:base+40},
|
|
{type:"Q",x:x0+160,y:base,x1:x0+80,y1:base},
|
|
{type:"Z"}
|
|
];
|
|
glyphs.push(glyph({name:"a",unicode:97,advanceWidth:aw,commands:cmds}));
|
|
})();
|
|
|
|
// Lowercase b
|
|
(()=>{
|
|
const aw=520;
|
|
const x0=90;
|
|
const top=700;
|
|
const mid=350;
|
|
const cmds=[
|
|
...vStem(x0,0,top,bw),
|
|
{type:"M",x:x0+bw,y:mid},
|
|
{type:"Q",x:x0+bw+160,y:mid,x1:x0+bw+160,y1:mid-130},
|
|
{type:"Q",x:x0+bw+160,y:mid-260,x1:x0+bw,y1:mid-260},
|
|
{type:"Q",x:x0+40,y:mid-260,x1:x0,y1:mid-140},
|
|
{type:"Q",x:x0,y:mid-40,x1:x0+40,y1:mid},
|
|
{type:"Z"}
|
|
];
|
|
glyphs.push(glyph({name:"b",unicode:98,advanceWidth:aw,commands:cmds}));
|
|
})();
|
|
|
|
// Lowercase c
|
|
(()=>{
|
|
const aw=480;
|
|
const cx=260,cy=260;
|
|
const cmds=[
|
|
{type:"M",x:cx+160,y:cy+40},
|
|
{type:"Q",x:cx+40,y:cy+160,x1:cx-60,y1:cy+160},
|
|
{type:"Q",x:cx-150,y:cy+160,x1:cx-150,y1:cy},
|
|
{type:"Q",x:cx-150,y:cy-160,x1:cx-40,y1:cy-160},
|
|
{type:"Q",x:cx+40,y:cy-160,x1:cx+120,y1:cy-60},
|
|
{type:"L",x:cx+80,y:cy-40},
|
|
{type:"Q",x:cx+20,y:cy-110,x1:cx-40,y1:cy-110},
|
|
{type:"Q",x:cx-80,y:cy-110,x1:cx-80,y1:cy},
|
|
{type:"Q",x:cx-80,y:cy+110,x1:cx-20,y1:cy+110},
|
|
{type:"Q",x:cx+40,y:cy+110,x1:cx+130,y1:cy+10},
|
|
{type:"Z"}
|
|
];
|
|
glyphs.push(glyph({name:"c",unicode:99,advanceWidth:aw,commands:cmds}));
|
|
})();
|
|
|
|
const font=new opentype.Font({
|
|
familyName:"Stain",
|
|
styleName:"Regular",
|
|
unitsPerEm:upm,
|
|
ascender:ascent,
|
|
descender:descent,
|
|
glyphs
|
|
});
|
|
|
|
const ttf=font.toArrayBuffer();
|
|
const outPath=path.join(distDir,"Stain-Regular.ttf");
|
|
fs.writeFileSync(outPath,Buffer.from(ttf));
|
|
console.log("Built",outPath);
|