import os
import subprocess
import sys
import tempfile
import fontforge
def main() -> int:
args = sys.argv[1:]
font_out = None
glyphs = []
i = 0
while i < len(args):
a = args[i]
if a == "--output":
font_out = args[i + 1]
i += 2
elif a == "--glyph":
spec = args[i + 1].split(":", 3)
if len(spec) < 2:
raise SystemExit(f"--glyph wants SVG_PATH:CODEPOINT[:NAME[:KEY=VAL]], got {args[i + 1]!r}")
svg_path = spec[0]
try:
codepoint = int(spec[1], 16)
except ValueError:
raise SystemExit(f"codepoint must be hex, got {spec[1]!r}")
name = spec[2] if len(spec) > 2 else f"u{codepoint:04X}"
extras = {}
extras_tail = spec[3] if len(spec) > 3 else ""
for part in extras_tail.split(":") if extras_tail else []:
if "=" in part:
k, v = part.split("=", 1)
extras[k] = v
glyphs.append((svg_path, codepoint, name, extras))
i += 2
elif a in ("-h", "--help"):
print(__doc__)
return 0
else:
raise SystemExit(f"unknown arg: {a}")
if not font_out:
raise SystemExit("--output is required")
if not glyphs:
raise SystemExit("at least one --glyph is required")
if os.path.exists(font_out):
print(f"loading existing font: {font_out}")
font = fontforge.open(font_out)
else:
font = fontforge.font()
font.em = 1000
font.familyname = "MnmlSymbols"
font.fontname = "MnmlSymbols-Regular"
font.fullname = "MnmlSymbols Regular"
font.weight = "Regular"
font.version = "1.0"
font.copyright = "mnml — layerable symbols font for branded integration icons"
new_names = []
seen_ids = set()
for lang, name_id, value in font.sfnt_names:
seen_ids.add(name_id)
if name_id == "Family":
value = "MnmlSymbols"
elif name_id == "SubFamily":
value = "Regular"
elif name_id == "Fullname":
value = "MnmlSymbols Regular"
elif name_id == "PostScriptName":
value = "MnmlSymbols-Regular"
elif name_id == "UniqueID":
value = "MnmlSymbols 1.0"
elif name_id == "Version":
value = "Version 1.0"
new_names.append((lang, name_id, value))
font.sfnt_names = tuple(new_names)
cell_w = 600 em = font.em
script_dir = os.path.dirname(os.path.realpath(__file__))
flatten_script = os.path.join(script_dir, "flatten_svg_evenodd.py")
for svg_path, codepoint, name, extras in glyphs:
print(f"adding U+{codepoint:04X} ({name}) ← {svg_path}")
needs_flatten = (
svg_path.lower().endswith(".svg")
and 'fill-rule="evenodd"' in open(svg_path).read()
)
if needs_flatten and os.path.exists(flatten_script):
tmp = tempfile.NamedTemporaryFile(suffix=".svg", delete=False, mode="w")
tmp.close()
try:
subprocess.run(
["python3", flatten_script, svg_path, tmp.name], check=True
)
import_from = tmp.name
print(f" ↳ pre-flattened evenodd → {tmp.name}")
except Exception as e:
print(f" ! flatten failed ({e}) — importing raw SVG")
import_from = svg_path
else:
import_from = svg_path
glyph = font.createChar(codepoint, name)
glyph.clear()
glyph.importOutlines(import_from, ())
bbox = glyph.boundingBox()
glyph_w = bbox[2] - bbox[0]
glyph_h = bbox[3] - bbox[1]
if glyph_w <= 0 or glyph_h <= 0:
print(f" ! empty glyph from {svg_path}, skipping")
continue
width_frac = float(extras.get("width", "1.25"))
height_frac = float(extras.get("height", "0.80"))
target_w = cell_w * width_frac
target_h = em * height_frac
scale = min(target_w / glyph_w, target_h / glyph_h)
glyph.transform((scale, 0.0, 0.0, scale, 0.0, 0.0))
bbox = glyph.boundingBox()
glyph_w = bbox[2] - bbox[0]
glyph_h = bbox[3] - bbox[1]
x_center_frac = float(extras.get("x_center", "0.5"))
auto_dx = -bbox[0] + (cell_w - glyph_w) / 2
nudge_dx = (x_center_frac - 0.5) * cell_w
dx = auto_dx + nudge_dx
center_frac = float(extras.get("center", "0.36"))
target_center = em * center_frac
dy = target_center - (bbox[1] + glyph_h / 2.0)
glyph.transform((1.0, 0.0, 0.0, 1.0, dx, dy))
glyph.width = cell_w
glyph.correctDirection()
glyph.simplify()
space = font.createChar(0x20, "space")
space.width = cell_w
print(f"writing {font_out}")
font.generate(font_out)
print(f"✓ built {font_out}")
return 0
if __name__ == "__main__":
sys.exit(main())