#!/usr/bin/env python3
"""font_viewer.py - standalone font viewer.

Recreated from Prompt's --export-font-info viewer: reads a font's cmap + name
table with fontTools and renders an HTML page of every Unicode-mapped glyph
(preview, U+ code, glyph name) plus font metadata. Improvement over the original:
the glyph previews render IN THE FONT ITSELF (the font is embedded as an
@font-face), so you see the real shapes - ideal for checking which codepoints a
font actually has (e.g. verifying a "never-tofu" merge has the star, the dingbats,
the emoji you use).

Usage:
  python font_viewer.py <font.ttf|woff2|otf> [more fonts ...]
  python font_viewer.py C:\\fonts\\*.ttf
  python font_viewer.py --no-open <font>     # write the HTML, don't launch a browser
  python font_viewer.py --find U+2605 <font>  # also report whether codepoint(s) exist

Requires: fonttools   (pip install fonttools)
"""
import sys, re, html, base64, webbrowser
from pathlib import Path
from fontTools.ttLib import TTFont

CSS = """
body{background:#0e1117;color:#e6edf3;font-family:-apple-system,'Segoe UI',sans-serif;margin:0;padding:24px}
h1{margin:0 0 4px;font-size:26px}
h2{margin:28px 0 10px;border-bottom:1px solid #30363d;padding-bottom:6px;font-size:18px}
.muted{color:#8b949e;font-weight:400;font-size:.82em}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:10px;margin:14px 0}
.card{background:#161b22;border:1px solid #30363d;border-radius:8px;padding:10px 12px}
.card .k{font-size:11px;color:#8b949e;text-transform:uppercase;letter-spacing:.04em}
.card .v{margin-top:2px;word-break:break-word}
.glyphs{display:grid;grid-template-columns:repeat(auto-fill,minmax(92px,1fr));gap:8px}
.g{background:#161b22;border:1px solid #30363d;border-radius:8px;padding:8px 6px;text-align:center}
.g .ch{font-size:42px;line-height:1;height:56px;display:flex;align-items:center;justify-content:center}
.g .u{font-family:Consolas,Menlo,monospace;font-size:11px;color:#79c0ff;margin-top:4px}
.g .nm{font-size:10px;color:#8b949e;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
"""


def _name_strings(font):
    out = {}
    if 'name' in font:
        for rec in font['name'].names:
            try:
                out.setdefault(rec.nameID, rec.toUnicode())
            except Exception:
                pass
    return out


def _name(nm, nid, default=""):
    return nm.get(nid) or default


def _data_uri(path):
    ext = path.suffix.lower()
    mt = {'.woff2': 'font/woff2', '.woff': 'font/woff', '.otf': 'font/otf'}.get(ext, 'font/ttf')
    return f"data:{mt};base64," + base64.b64encode(path.read_bytes()).decode('ascii')


def build_html(path):
    path = Path(path)
    font = TTFont(str(path), fontNumber=0, lazy=True)
    try:
        nm = _name_strings(font)
        family = _name(nm, 16, _name(nm, 1, path.stem))
        subfam = _name(nm, 17, _name(nm, 2, 'Regular'))
        full = _name(nm, 4, f"{family} {subfam}".strip())
        cmap = font.getBestCmap() or {}
        entries = sorted((int(cp), str(gn or '')) for cp, gn in cmap.items())

        cards = [
            ('Family', family), ('Subfamily', subfam), ('Full Name', full),
            ('Version', _name(nm, 5)), ('File', str(path)),
            ('Glyph Order Count', str(len(font.getGlyphOrder() or []))),
            ('Mapped Unicode Glyphs', str(len(entries))),
        ]
        if 'OS/2' in font:
            os2 = font['OS/2']
            cards += [('Weight Class', str(getattr(os2, 'usWeightClass', '') or '')),
                      ('Width Class', str(getattr(os2, 'usWidthClass', '') or ''))]
        if 'head' in font:
            cards.append(('Units Per Em', str(getattr(font['head'], 'unitsPerEm', '') or '')))
        extras = [(l, _name(nm, nid)) for l, nid in
                  (('Manufacturer', 8), ('Designer', 9), ('Description', 10),
                   ('Designer URL', 12), ('License', 13), ('License URL', 14))]

        face = "FV_" + (re.sub(r'[^A-Za-z0-9]', '', path.stem) or "Font")
        cells = []
        for cp, gn in entries:
            cells.append(
                f'<div class="g"><div class="ch" style="font-family:\'{face}\'">{html.escape(chr(cp))}</div>'
                f'<div class="u">U+{cp:04X}</div>'
                f'<div class="nm" title="{html.escape(gn)}">{html.escape(gn) or "&nbsp;"}</div></div>'
            )
        cards_html = ''.join(
            f'<div class="card"><div class="k">{html.escape(l)}</div><div class="v">{html.escape(str(v) or "-")}</div></div>'
            for l, v in cards)
        extras_html = ''.join(
            f'<div class="card"><div class="k">{html.escape(l)}</div><div class="v">{html.escape(str(v))}</div></div>'
            for l, v in extras if str(v).strip())

        body = (
            f'<h1>{html.escape(full or path.stem)}</h1>'
            f'<div class="grid">{cards_html}</div>'
            + (f'<div class="grid">{extras_html}</div>' if extras_html else '')
            + f'<h2>Supported Glyphs <span class="muted">({len(entries)})</span></h2>'
            + (f'<div class="glyphs">{"".join(cells)}</div>' if cells else '<p class="muted">No Unicode-mapped glyphs.</p>')
        )
        face_css = f"@font-face{{font-family:'{face}';src:url('{_data_uri(path)}');}}"
        return ("<!doctype html><html><head><meta charset='utf-8'>"
                f"<title>{html.escape(full or path.stem)} - glyphs</title>"
                f"<style>{face_css}{CSS}</style></head><body>{body}</body></html>")
    finally:
        try:
            font.close()
        except Exception:
            pass


def cmap_of(path):
    f = TTFont(str(path), fontNumber=0, lazy=True)
    try:
        return f.getBestCmap() or {}
    finally:
        f.close()


def parse_cp(s):
    s = s.strip().upper().lstrip('U+').lstrip('0X')
    return int(s, 16)


def main():
    argv = sys.argv[1:]
    if not argv or argv[0] in ('-h', '--help'):
        print(__doc__); return
    no_open = '--no-open' in argv
    find = []
    if '--find' in argv:
        i = argv.index('--find')
        find = [parse_cp(c) for c in argv[i + 1].split(',')]
        del argv[i:i + 2]
    fonts = [a for a in argv if not a.startswith('--')]
    for fp in fonts:
        p = Path(fp)
        if not p.exists():
            print(f"  skip (not found): {fp}"); continue
        cmap = cmap_of(p)
        out = p.with_name(p.stem + '_glyphs.html')
        out.write_text(build_html(p), encoding='utf-8')
        print(f"  {p.name}: {len(cmap)} glyphs -> {out.name}")
        for cp in find:
            print(f"     U+{cp:04X}: {'PRESENT' if cp in cmap else 'MISSING'}")
        if not no_open:
            webbrowser.open(out.as_uri())


if __name__ == '__main__':
    main()
