#!/usr/bin/env python3
"""Real text shaping for the share cards, via HarfBuzz.

`typeset.py` lays glyphs out by advance width alone. That is correct for Latin
and it is what the rest of this family ships — but it is wrong for Thai, and the
failure is invisible until you look at a rendered card.

Thai combining marks have **zero advance width**, so a naive layout puts every
mark in a cluster at the same pen position. `ที่` is ท + ี + ่ : the vowel sits
above the consonant and the tone mark has to sit above *the vowel*. Without GPOS
mark-to-mark positioning both marks are drawn at the same height, the tone mark
disappears inside the vowel, and the word silently loses its tone.

This scales far past Thai: Arabic needs joining forms, Devanagari and Khmer
reorder, Lao and Burmese stack. Any of them rendered through advance-width layout
is broken in a way that looks like a font problem and is not.

Copy this in **beside** `typeset.py` rather than editing that file, and route
`build_og.py`'s `layout`/`advance` through it:

    from typeset import load, wrap, clusters, require_glyphs, svg
    import shape as shaper

    TTF = {id(display): DISPLAY_TTF, id(body_f): BODY_TTF, id(meta_f): META_TTF}

    def layout(font, s, em, tracking=0.0, x0=0.0, baseline=0.0):
        return shaper.shape(TTF[id(font)], font["gs"], s, em, x0, baseline, tracking)

    def advance(font, s, em, tracking=0.0):
        return shaper.advance(TTF[id(font)], s, em, tracking)

Needs `uharfbuzz` in the venv. Only `pnpm og:build` uses it, it runs locally, and
its output is committed — the deploy runner never touches it.
"""
from __future__ import annotations

import functools

import uharfbuzz as hb
from fontTools.pens.svgPathPen import SVGPathPen
from fontTools.pens.transformPen import TransformPen


@functools.lru_cache(maxsize=8)
def _hb_font(path: str):
    with open(path, "rb") as fh:
        blob = hb.Blob(fh.read())
    face = hb.Face(blob)
    font = hb.Font(face)
    return font, face.upem


def shape(path: str, glyphset, text: str, em: float, x0: float = 0.0,
          baseline: float = 0.0, tracking: float = 0.0):
    """Per-glyph SVG paths in px space (y-down), plus the end pen x.

    Signature-compatible with `typeset.layout` so `build_og.py` can swap one for
    the other, except that it needs the font *path* as well as the glyph set —
    HarfBuzz shapes from the binary, fontTools draws from the glyph set.
    """
    font, upem = _hb_font(path)
    scale = em / upem

    buf = hb.Buffer()
    buf.add_str(text)
    buf.guess_segment_properties()          # picks script + direction from the text
    hb.shape(font, buf)

    order = font.get_glyph_name  # gid -> the name fontTools' glyphset is keyed by
    items = []
    penx, peny = x0, baseline
    for info, pos in zip(buf.glyph_infos, buf.glyph_positions):
        name = order(info.codepoint)
        pen = SVGPathPen(glyphset)
        # x_offset / y_offset are what actually lift a tone mark clear of the
        # vowel underneath it. y is negated: font space is y-up, SVG is y-down.
        tx = penx + pos.x_offset * scale
        ty = peny - pos.y_offset * scale
        glyphset[name].draw(TransformPen(pen, (scale, 0, 0, -scale, tx, ty)))
        d = pen.getCommands()
        if d:
            items.append(d)
        penx += pos.x_advance * scale + tracking
        peny -= pos.y_advance * scale
    return items, penx


def advance(path: str, text: str, em: float, tracking: float = 0.0) -> float:
    """Shaped advance — the width to wrap against."""
    font, upem = _hb_font(path)
    buf = hb.Buffer()
    buf.add_str(text)
    buf.guess_segment_properties()
    hb.shape(font, buf)
    total = sum(p.x_advance for p in buf.glyph_positions) * (em / upem)
    return total + tracking * len(buf.glyph_infos)
