#!/usr/bin/env python3
"""Outline font glyphs to font-independent SVG assets (mark / lockup / favicon).

Setup:
    python3 -m venv .venv && ./.venv/bin/pip install fonttools brotli
    # download a font, e.g. a Google Fonts variable TTF, then:
    ./.venv/bin/python outline_glyphs.py

Edit the CONFIG block below for your brand, then run. Each glyph in a `colors`
list corresponds to one character in `text` (in order), so you control exactly
which letter/bracket/stroke gets which color.
"""
import os
import re
from fontTools.ttLib import TTFont
from fontTools.varLib.instancer import instantiateVariableFont
from fontTools.pens.svgPathPen import SVGPathPen
from fontTools.pens.boundsPen import BoundsPen
from fontTools.pens.transformPen import TransformPen

# ============================ CONFIG — edit this ============================
CONFIG = {
    "font": "Font.ttf",          # path to a .ttf/.otf/.woff2 (brotli needed for woff2)
    "weight": 700,               # instanced if the font is variable (fvar wght axis)
    "out_dir": "assets/brand",   # where the .svg files are written
    "tracking": 20,              # default letter tracking, in font units (1000/em)

    # Simple single-run marks: text + one color per character.
    "marks": [
        {"name": "mark",      "text": "<AB/>", "colors": ["#16233A", "#16233A", "#16233A", "#C65A3C", "#16233A"]},
        {"name": "mark-dark", "text": "<AB/>", "colors": ["#F4EFE6", "#F4EFE6", "#F4EFE6", "#E08A6A", "#F4EFE6"]},
        {"name": "mark-mono", "text": "<AB/>", "mono": True},  # all currentColor
    ],

    # Horizontal lockup: mark + divider + wordmark, each with its own colors.
    "lockups": [
        {
            "name": "lockup",
            "mark_text": "<AB/>", "mark_colors": ["#16233A", "#16233A", "#16233A", "#C65A3C", "#16233A"],
            "word_text": "AlphaBlue", "word_colors": ["#16233A"] * 5 + ["#2456C9"] * 4,
            "divider": "#ECE4D5",
        },
        {
            "name": "lockup-dark",
            "mark_text": "<AB/>", "mark_colors": ["#F4EFE6", "#F4EFE6", "#F4EFE6", "#E08A6A", "#F4EFE6"],
            "word_text": "AlphaBlue", "word_colors": ["#F4EFE6"] * 5 + ["#6E9BF0"] * 4,
            "divider": "#3E5872",
        },
    ],

    # Favicon tile: a compact 2-3 glyph fragment on a rounded tile (legible at 16px).
    "favicon": {
        "name": "favicon", "text": "A/B",
        "colors": ["#F4EFE6", "#E08A6A", "#F4EFE6"], "tile": "#16233A", "radius": 24,
    },
}
# ===========================================================================

f = TTFont(CONFIG["font"])
if "fvar" in f:
    instantiateVariableFont(f, {"wght": CONFIG["weight"]}, inplace=True)
gs = f.getGlyphSet()
cmap = f.getBestCmap()
UPM = f["head"].unitsPerEm
OUT = CONFIG["out_dir"]
os.makedirs(OUT, exist_ok=True)


def gname(ch):
    return cmap[ord(ch)]


def rnd(d, n=2):
    return re.sub(r"-?\d+\.?\d*", lambda m: f"{float(m.group()):.{n}f}".rstrip("0").rstrip("."), d)


def layout(text, em, tracking, x0=0.0, baseline=0.0):
    """Return per-glyph SVG path strings baked into px space (y-down)."""
    s = em / UPM
    penx = x0
    items = []
    for ch in text:
        gn = gname(ch)
        spen = SVGPathPen(gs)
        gs[gn].draw(TransformPen(spen, (s, 0, 0, -s, penx, baseline)))
        items.append(rnd(spen.getCommands()))
        penx += (gs[gn].width + tracking) * s
    return items, penx


def bbox(text, em, tracking, x0=0.0, baseline=0.0):
    s = em / UPM
    bp = BoundsPen(gs)
    penx = x0
    for ch in text:
        gn = gname(ch)
        gs[gn].draw(TransformPen(bp, (s, 0, 0, -s, penx, baseline)))
        penx += (gs[gn].width + tracking) * s
    return bp.bounds, penx


def by_color(paths, colors):
    """Merge same-color glyph paths into one <path> each, preserving first-seen order."""
    groups, order = {}, []
    for d, c in zip(paths, colors):
        if c not in groups:
            groups[c] = []
            order.append(c)
        groups[c].append(d)
    return [(c, " ".join(groups[c])) for c in order]


def svg(vb, body, title="Logo"):
    x, y, w, h = vb
    return (f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="{x:.2f} {y:.2f} {w:.2f} {h:.2f}" '
            f'role="img" aria-label="{title}" fill="none">\n{body}</svg>\n')


def write(name, text):
    with open(f"{OUT}/{name}.svg", "w") as fh:
        fh.write(text)
    print(f"  {name}.svg")


EM = 100
PAD = 12
tr = CONFIG["tracking"]

# ---- simple marks ----
for m in CONFIG["marks"]:
    items, _ = layout(m["text"], EM, tr)
    bb, _ = bbox(m["text"], EM, tr)
    vb = (bb[0] - PAD, bb[1] - PAD, (bb[2] - bb[0]) + 2 * PAD, (bb[3] - bb[1]) + 2 * PAD)
    if m.get("mono"):
        body = "".join(f'  <path fill="currentColor" d="{d}"/>\n' for d in items)
    else:
        body = "".join(f'  <path fill="{c}" d="{d}"/>\n' for c, d in by_color(items, m["colors"]))
    write(m["name"], svg(vb, body))

# ---- lockups ----
for lk in CONFIG["lockups"]:
    MEM, WEM, MTR, GAP = 64, 52, tr - 6, 26
    m_items, _ = layout(lk["mark_text"], MEM, MTR, 0, 0)
    m_bb, _ = bbox(lk["mark_text"], MEM, MTR)
    div_x = m_bb[2] + GAP
    word_x0 = div_x + GAP
    w_items, _ = layout(lk["word_text"], WEM, 0, word_x0, 0)
    w_bb, _ = bbox(lk["word_text"], WEM, 0, word_x0, 0)
    body = "".join(f'  <path fill="{c}" d="{d}"/>\n' for c, d in by_color(m_items, lk["mark_colors"]))
    body += (f'  <line x1="{div_x:.2f}" y1="{w_bb[1]:.2f}" x2="{div_x:.2f}" y2="0" '
             f'stroke="{lk["divider"]}" stroke-width="2.4" stroke-linecap="round"/>\n')
    body += "".join(f'  <path fill="{c}" d="{d}"/>\n' for c, d in by_color(w_items, lk["word_colors"]))
    xmin = min(m_bb[0], w_bb[0], div_x); xmax = max(m_bb[2], w_bb[2], div_x)
    ymin = min(m_bb[1], w_bb[1]); ymax = max(m_bb[3], w_bb[3])
    vb = (xmin - PAD, ymin - PAD, (xmax - xmin) + 2 * PAD, (ymax - ymin) + 2 * PAD)
    write(lk["name"], svg(vb, body))

# ---- favicon tile ----
fav = CONFIG.get("favicon")
if fav:
    TILE, INNER = 100, 62
    fem = 100
    fitems, _ = layout(fav["text"], fem, 6, 0, 0)
    fbb, _ = bbox(fav["text"], fem, 6)
    fw, fh = fbb[2] - fbb[0], fbb[3] - fbb[1]
    sc = INNER / max(fw, fh)
    tx = (TILE - fw * sc) / 2 - fbb[0] * sc
    ty = (TILE - fh * sc) / 2 - fbb[1] * sc
    glyphs = "".join(f'  <path fill="{c}" d="{d}"/>\n' for c, d in by_color(fitems, fav["colors"]))
    body = (f'  <rect x="0" y="0" width="100" height="100" rx="{fav.get("radius", 24)}" fill="{fav["tile"]}"/>\n'
            f'  <g transform="translate({tx:.2f} {ty:.2f}) scale({sc:.4f})">\n{glyphs}  </g>\n')
    write(fav["name"], svg((0, 0, 100, 100), body))

print(f"done -> {OUT}/")
