#!/usr/bin/env fontforge """ Build aifont.ttf: - Base: Meslo LGS NF Regular - Replace A-Z with arch-cut versions - Replace 0-9 with arch-cut versions - Replace a, i, o, s with custom ai-world designs - Keep everything else (other lowercase, symbols, nerd font icons) """ import fontforge import os BASE_FONT = "/tmp/MesloLGS NF Regular.ttf" UPPER_DIR = "/tmp/aifont_build/upper" DIGIT_DIR = "/tmp/aifont_build/digit" LOWER_DIR = "/tmp/aifont_build/lower" OUTPUT = "/tmp/aifont.ttf" font = fontforge.open(BASE_FONT) # Replace uppercase A-Z for c in range(ord('A'), ord('Z')+1): g = chr(c) svg = os.path.join(UPPER_DIR, g + ".svg") if os.path.exists(svg): glyph = font[c] w = glyph.width glyph.clear() glyph.importOutlines(svg) glyph.width = w glyph.correctDirection() print("Upper: " + g) # Replace digits 0-9 for c in range(ord('0'), ord('9')+1): g = chr(c) svg = os.path.join(DIGIT_DIR, g + ".svg") if os.path.exists(svg): glyph = font[c] w = glyph.width glyph.clear() glyph.importOutlines(svg) glyph.width = w glyph.correctDirection() print("Digit: " + g) # Replace lowercase a, i, o, s # These have different coordinate systems, so center them after import import psMat for g in ['a', 'i']: c = ord(g) svg = os.path.join(LOWER_DIR, g + ".svg") if os.path.exists(svg): glyph = font[c] target_w = glyph.width glyph.clear() glyph.importOutlines(svg) glyph.correctDirection() # Get bounding box and center horizontally bb = glyph.boundingBox() xmin, ymin, xmax, ymax = bb glyph_w = xmax - xmin glyph_h = ymax - ymin # Scale per glyph if g == 'i': target_h = 1500 elif g == 'a': target_h = 1400 else: target_h = 1200 if glyph_h > 0: scale = target_h / glyph_h glyph.transform(psMat.scale(scale)) bb = glyph.boundingBox() xmin, ymin, xmax, ymax = bb glyph_w = xmax - xmin # Center horizontally within the glyph width x_offset = (target_w - glyph_w) / 2.0 - xmin # Align baseline y_offset = -ymin glyph.transform(psMat.translate(x_offset, y_offset)) glyph.width = target_w # Thin down a, o, s (not i) - scale horizontally to 75% if g in ['a', 'o', 's']: bb = glyph.boundingBox() xmin, ymin, xmax, ymax = bb cx = (xmin + xmax) / 2.0 # Scale X to 75%, keep Y glyph.transform(psMat.compose( psMat.translate(-cx, 0), psMat.compose( psMat.scale(0.75, 1.0), psMat.translate(cx, 0) ) )) # Re-center bb = glyph.boundingBox() xmin, ymin, xmax, ymax = bb glyph_w = xmax - xmin x_offset = (target_w - glyph_w) / 2.0 - xmin glyph.transform(psMat.translate(x_offset, 0)) glyph.width = target_w print("Lower: " + g) # Re-import custom icons at E001-E003 (same approach as original aifont.py) # Build icon font separately, then merge ICON_DIR = "/tmp/aifont_build" scale = 200 # same as original icons_font = fontforge.font() icons_font.em = 1024 for cp, name, has_yshift in [(0xE001, "ai.svg", False), (0xE002, "syui.svg", False), (0xE003, "bluesky.svg", True)]: svg = os.path.join(ICON_DIR, name) if os.path.exists(svg): glyph = icons_font.createChar(cp, name.replace(".svg", "")) glyph.importOutlines(svg) if has_yshift: bb = glyph.boundingBox() yshift = -bb[1] glyph.transform((1, 0, 0, 1, 0, yshift)) s = scale / 100.0 glyph.transform((s, 0, 0, s, 0, 0)) glyph.width = 1024 print("Icon: " + name) icons_font.generate("/tmp/aifont_icons.ttf") icons_font.close() # Clear existing icon slots and merge for cp in [0xE001, 0xE002, 0xE003]: font.selection.select(cp) font.clear() font.selection.none() font.mergeFonts("/tmp/aifont_icons.ttf") # Set font metadata font.fontname = "aifont" font.familyname = "aifont" font.fullname = "aifont" font.appendSFNTName("English (US)", "Family", "aifont") font.appendSFNTName("English (US)", "SubFamily", "Regular") font.appendSFNTName("English (US)", "UniqueID", "aifont-regular") font.appendSFNTName("English (US)", "Fullname", "aifont") font.appendSFNTName("English (US)", "PostScriptName", "aifont") font.appendSFNTName("English (US)", "Preferred Family", "aifont") font.appendSFNTName("English (US)", "Preferred Styles", "Regular") font.generate(OUTPUT) print("\nDone: " + OUTPUT)