// Monoline/monospace vector font from the Apple 410 Color Plotter // (Also sold as the Yokogawa YEW PL-1000, c.1983-1988) // Extracted from the firmware ROM by Adam Mayer (@phooky) // See: https://github.com/phooky/Apple-410 (MIT License) // Ported to p5.js/json by Golan Levin, 12/2025 // // See: "Pulling Teeth From a Corpse: Extracting the // Vector Font From the Apple 410 Color Plotter" by Adam Mayer // https://www.nycresistor.com/2017/12/29/pulling-teeth-from-a-corpse-extracting-the-vector-font-from-the-apple-410-color-plotter/ function setup() { createCanvas(800, 300); pixelDensity(2); noLoop(); } function draw() { background('black'); stroke('white'); strokeJoin(ROUND); strokeWeight(1); noFill(); let tx = 75; let ty = 38; let tsz = 25; // size let tsp = 1.45; // spacing drawString("ABCDEFGHIJKLMNOPQRSTUVWXYZ", tx,ty+=50, tsz,tsp); drawString("abcdefghijklmnopqrstuvwxyz", tx,ty+=50, tsz,tsp); drawString("1234567890 Hello World", tx,ty+=50, tsz,tsp); drawString(".,;:'\"`?!$#@&^~|+-=/*_%<>()[]{}", tx,ty+=50, tsz*0.75,tsp); } // ----------------------------------------- // Extract glyph data function getGlyph(ch) { const code = "0x" + ch.charCodeAt(0).toString(16).padStart(2, "0"); return apple410font[code]; } // ----------------------------------------- // Render a single glyph // gx,gy: baseline position in pixels // size: desired cap height in pixels function drawGlyph(ch, gx, gy, size) { const glyph = getGlyph(ch); if (!glyph || !glyph.strokes) return; const sca = size / APPLE410_CAPH; for (let aStroke of glyph.strokes) { if (!aStroke.length) continue; beginShape(); for (let pt of aStroke) { const [px, py] = pt; const sx = gx + (px - APPLE410_L) * sca; const sy = gy + (py - APPLE410_YBASE) * sca; vertex(sx, sy); } endShape(); } } // ----------------------------------------- // Draw a string // str: text to draw // x,y: baseline of first character in pixels // size: cap height in pixels // spacing: extra spacing factor (1.0 = tight, 1.1 = slight gap) // lineHeight: multiplier for vertical distance between lines function drawString(str, x, y, size, spacing = 1.1, lineHeight = 1.3) { const sca = size / APPLE410_CAPH; const advancePx = APPLE410_W * sca * spacing; let cx = x; let cy = y; for (let i = 0; i < str.length; i++) { const ch = str[i]; if (ch === "\n") { cy += size * lineHeight; cx = x; continue; } drawGlyph(ch, cx, cy, size); cx += advancePx; } } // ----------------------------------------- // Global Apple-410 geometry constants (from the extracted data) const APPLE410_L = 25; // left margin in font units const APPLE410_R = 553; // typical right extent in font units const APPLE410_YBASE = 825; // baseline of most letters const APPLE410_YCAPTOP = 25; // top of caps const APPLE410_CAPH = APPLE410_YBASE - APPLE410_YCAPTOP; // ~800 const APPLE410_W = APPLE410_R - APPLE410_L; // ~528 const apple410font = { "0x20":{"char":" ","strokes":[]}, "0x21":{"char":"!","strokes":[[[25,25],[25,625]],[[25,825],[25,825]]]}, "0x22":{"char":"\"","strokes":[[[157,25],[157,225]],[[421,25],[421,225]]]}, "0x23":{"char":"#","strokes":[[[25,825],[289,25]],[[553,25],[289,825]],[[553,525],[25,525]],[[25,325],[553,325]]]}, "0x24":{"char":"$","strokes":[[[25,625],[157,725],[421,725],[553,625],[553,525],[421,425],[157,425],[25,325],[25,225],[157,125],[421,125],[553,225]],[[289,25],[289,825]]]}, "0x25":{"char":"%","strokes":[[[289,125],[157,25],[25,125],[157,225],[289,125]],[[553,25],[25,825]],[[289,725],[421,825],[553,725],[421,625],[289,725]]]}, "0x26":{"char":"&","strokes":[[[553,625],[421,825],[157,825],[25,725],[25,525],[421,225],[421,125],[289,25],[157,25],[25,125],[25,225],[553,825]]]}, "0x27":{"char":"'","strokes":[[[289,25],[157,225]]]}, "0x28":{"char":"(","strokes":[[[553,25],[421,125],[289,325],[289,525],[421,725],[553,825]]]}, "0x29":{"char":")","strokes":[[[25,25],[157,125],[289,325],[289,525],[157,725],[25,825]]]}, "0x2a":{"char":"*","strokes":[[[25,425],[553,425],[289,425],[421,225],[157,625],[289,425],[157,225],[421,625]]]}, "0x2b":{"char":"+","strokes":[[[25,425],[553,425],[289,425],[289,225],[289,625]]]}, "0x2c":{"char":",","strokes":[[[157,825],[25,825],[25,725],[157,725],[157,825],[25,925]]]}, "0x2d":{"char":"-","strokes":[[[25,425],[553,425]]]}, "0x2e":{"char":".","strokes":[[[25,825],[25,725],[157,725],[157,825],[25,825]]]}, "0x2f":{"char":"/","strokes":[[[25,825],[553,25]]]}, "0x30":{"char":"0","strokes":[[[157,825],[25,725],[25,125],[157,25],[421,25],[553,125],[553,725],[421,825],[157,825]]]}, "0x31":{"char":"1","strokes":[[[157,125],[289,25],[289,825],[157,825],[421,825]]]}, "0x32":{"char":"2","strokes":[[[25,125],[157,25],[421,25],[553,125],[553,325],[25,625],[25,825],[553,825]]]}, "0x33":{"char":"3","strokes":[[[25,125],[157,25],[421,25],[553,125],[553,325],[421,425],[157,425],[421,425],[553,525],[553,725],[421,825],[157,825],[25,725]]]}, "0x34":{"char":"4","strokes":[[[421,825],[421,25],[25,625],[553,625]]]}, "0x35":{"char":"5","strokes":[[[25,725],[157,825],[421,825],[553,725],[553,425],[421,325],[25,325],[25,25],[553,25]]]}, "0x36":{"char":"6","strokes":[[[25,425],[157,325],[421,325],[553,425],[553,725],[421,825],[157,825],[25,725],[25,125],[157,25],[421,25],[553,125]]]}, "0x37":{"char":"7","strokes":[[[25,125],[25,25],[553,25],[157,825]]]}, "0x38":{"char":"8","strokes":[[[157,425],[25,325],[25,125],[157,25],[421,25],[553,125],[553,325],[421,425],[157,425],[25,525],[25,725],[157,825],[421,825],[553,725],[553,525],[421,425]]]}, "0x39":{"char":"9","strokes":[[[25,725],[157,825],[421,825],[553,725],[553,125],[421,25],[157,25],[25,125],[25,425],[157,525],[421,525],[553,425]]]}, "0x3a":{"char":":","strokes":[[[25,525],[157,525],[157,425],[25,425],[25,525]],[[25,725],[25,825],[157,825],[157,725],[25,725]]]}, "0x3b":{"char":";","strokes":[[[25,525],[157,525],[157,425],[25,425],[25,525]],[[157,825],[25,825],[25,725],[157,725],[157,825],[25,925]]]}, "0x3c":{"char":"<","strokes":[[[553,225],[25,425],[553,625]]]}, "0x3d":{"char":"=","strokes":[[[25,325],[553,325]],[[25,525],[553,525]]]}, "0x3e":{"char":">","strokes":[[[25,225],[553,425],[25,625]]]}, "0x3f":{"char":"?","strokes":[[[25,125],[157,25],[421,25],[553,125],[553,325],[421,425],[289,425],[289,625]],[[289,825],[289,825]]]}, "0x40":{"char":"@","strokes":[[[421,525],[289,625],[157,625],[157,325],[289,225],[421,225],[421,625],[553,525],[553,125],[421,25],[157,25],[25,125],[25,725],[157,825],[421,825],[553,725]]]}, "0x41":{"char":"A","strokes":[[[25,825],[289,25],[421,425],[157,425],[421,425],[553,825]]]}, "0x42":{"char":"B","strokes":[[[25,825],[25,25],[421,25],[553,125],[553,325],[421,425],[25,425],[421,425],[553,525],[553,725],[421,825],[25,825]]]}, "0x43":{"char":"C","strokes":[[[553,125],[421,25],[157,25],[25,125],[25,725],[157,825],[421,825],[553,725]]]}, "0x44":{"char":"D","strokes":[[[25,825],[25,25],[421,25],[553,125],[553,725],[421,825],[25,825]]]}, "0x45":{"char":"E","strokes":[[[553,25],[25,25],[25,425],[421,425],[25,425],[25,825],[553,825]]]}, "0x46":{"char":"F","strokes":[[[25,825],[25,425],[421,425],[25,425],[25,25],[553,25]]]}, "0x47":{"char":"G","strokes":[[[421,525],[553,525],[553,725],[421,825],[157,825],[25,725],[25,125],[157,25],[421,25],[553,125]]]}, "0x48":{"char":"H","strokes":[[[25,825],[25,25],[25,425],[553,425],[553,25],[553,825]]]}, "0x49":{"char":"I","strokes":[[[157,825],[421,825],[289,825],[289,25],[157,25],[421,25]]]}, "0x4a":{"char":"J","strokes":[[[25,725],[157,825],[289,825],[421,725],[421,25],[289,25],[553,25]]]}, "0x4b":{"char":"K","strokes":[[[25,25],[25,825],[25,625],[553,25],[289,325],[553,825]]]}, "0x4c":{"char":"L","strokes":[[[25,25],[25,825],[553,825]]]}, "0x4d":{"char":"M","strokes":[[[25,825],[25,25],[289,625],[553,25],[553,825]]]}, "0x4e":{"char":"N","strokes":[[[25,825],[25,25],[553,825],[553,25]]]}, "0x4f":{"char":"O","strokes":[[[157,825],[25,725],[25,125],[157,25],[421,25],[553,125],[553,725],[421,825],[157,825]]]}, "0x50":{"char":"P","strokes":[[[25,825],[25,25],[421,25],[553,125],[553,325],[421,425],[25,425]]]}, "0x51":{"char":"Q","strokes":[[[157,825],[25,725],[25,125],[157,25],[421,25],[553,125],[553,725],[421,825],[157,825]],[[289,625],[553,825]]]}, "0x52":{"char":"R","strokes":[[[25,825],[25,25],[421,25],[553,125],[553,325],[421,425],[25,425],[289,425],[553,825]]]}, "0x53":{"char":"S","strokes":[[[25,725],[157,825],[421,825],[553,725],[553,525],[421,425],[157,425],[25,325],[25,125],[157,25],[421,25],[553,125]]]}, "0x54":{"char":"T","strokes":[[[25,25],[553,25],[289,25],[289,825]]]}, "0x55":{"char":"U","strokes":[[[25,25],[25,725],[157,825],[421,825],[553,725],[553,25]]]}, "0x56":{"char":"V","strokes":[[[25,25],[289,825],[553,25]]]}, "0x57":{"char":"W","strokes":[[[25,25],[157,825],[289,225],[421,825],[553,25]]]}, "0x58":{"char":"X","strokes":[[[25,825],[553,25],[289,425],[25,25],[553,825]]]}, "0x59":{"char":"Y","strokes":[[[25,25],[289,425],[553,25],[289,425],[289,825]]]}, "0x5a":{"char":"Z","strokes":[[[25,25],[553,25],[25,825],[553,825]]]}, "0x5b":{"char":"[","strokes":[[[553,25],[289,25],[289,825],[553,825]]]}, "0x5c":{"char":"\\","strokes":[[[25,25],[553,825]]]}, "0x5d":{"char":"]","strokes":[[[25,825],[289,825],[289,25],[25,25]]]}, "0x5e":{"char":"^","strokes":[[[157,125],[289,25],[421,125]]]}, "0x5f":{"char":"_","strokes":[[[25,925],[817,925]]]}, "0x60":{"char":"`","strokes":[[[289,25],[421,225]]]}, "0x61":{"char":"a","strokes":[[[553,725],[289,825],[157,825],[25,725],[25,425],[289,325],[553,325],[553,825]]]}, "0x62":{"char":"b","strokes":[[[25,25],[25,825],[421,825],[553,725],[553,425],[421,325],[25,325]]]}, "0x63":{"char":"c","strokes":[[[553,425],[421,325],[157,325],[25,425],[25,725],[157,825],[421,825],[553,725]]]}, "0x64":{"char":"d","strokes":[[[553,25],[553,825],[157,825],[25,725],[25,425],[157,325],[553,325]]]}, "0x65":{"char":"e","strokes":[[[25,625],[553,625],[553,425],[421,325],[157,325],[25,425],[25,725],[157,825],[553,825]]]}, "0x66":{"char":"f","strokes":[[[157,825],[157,425],[25,425],[421,425],[157,425],[157,125],[289,25],[421,25],[553,125]]]}, "0x67":{"char":"g","strokes":[[[25,1125],[289,1225],[421,1225],[553,1125],[553,325],[553,425],[289,325],[157,325],[25,425],[25,725],[157,825],[553,825]]]}, "0x68":{"char":"h","strokes":[[[25,825],[25,25],[25,325],[421,325],[553,425],[553,825]]]}, "0x69":{"char":"i","strokes":[[[289,125],[289,125]],[[157,325],[289,325],[289,825],[157,825],[421,825]]]}, "0x6a":{"char":"j","strokes":[[[25,1125],[157,1225],[421,1225],[553,1125],[553,325]],[[553,125],[553,125]]]}, "0x6b":{"char":"k","strokes":[[[25,25],[25,825],[25,525],[553,325],[289,425],[553,825]]]}, "0x6c":{"char":"l","strokes":[[[157,25],[289,25],[289,825],[157,825],[421,825]]]}, "0x6d":{"char":"m","strokes":[[[25,825],[25,325],[25,425],[289,325],[289,825],[289,425],[553,325],[553,825]]]}, "0x6e":{"char":"n","strokes":[[[25,825],[25,325],[25,425],[421,325],[553,425],[553,825]]]}, "0x6f":{"char":"o","strokes":[[[157,825],[25,725],[25,425],[157,325],[421,325],[553,425],[553,725],[421,825],[157,825]]]}, "0x70":{"char":"p","strokes":[[[25,1225],[25,325],[421,325],[553,425],[553,725],[421,825],[25,825]]]}, "0x71":{"char":"q","strokes":[[[553,1225],[553,325],[553,425],[289,325],[157,325],[25,425],[25,725],[157,825],[553,825]]]}, "0x72":{"char":"r","strokes":[[[25,825],[25,325],[25,425],[289,325],[421,325],[553,425]]]}, "0x73":{"char":"s","strokes":[[[25,725],[157,825],[421,825],[553,725],[553,625],[25,525],[25,425],[157,325],[421,325],[553,425]]]}, "0x74":{"char":"t","strokes":[[[25,325],[421,325],[157,325],[157,125],[157,725],[289,825],[421,825],[553,725]]]}, "0x75":{"char":"u","strokes":[[[25,325],[25,725],[157,825],[421,825],[553,725],[553,325]]]}, "0x76":{"char":"v","strokes":[[[25,325],[289,825],[553,325]]]}, "0x77":{"char":"w","strokes":[[[25,325],[157,825],[289,325],[421,825],[553,325]]]}, "0x78":{"char":"x","strokes":[[[25,825],[553,325]],[[25,325],[553,825]]]}, "0x79":{"char":"y","strokes":[[[25,1225],[289,825],[25,325],[289,825],[553,325]]]}, "0x7a":{"char":"z","strokes":[[[25,325],[553,325],[25,825],[553,825]]]}, "0x7b":{"char":"{","strokes":[[[553,25],[421,125],[421,325],[289,425],[421,525],[421,725],[553,825]]]}, "0x7c":{"char":"|","strokes":[[[25,25],[25,825]]]}, "0x7d":{"char":"}","strokes":[[[25,825],[157,725],[157,525],[289,425],[157,325],[157,125],[25,25]]]}, "0x7e":{"char":"~","strokes":[[[25,225],[157,125],[421,225],[553,125]]]} };