"""Native variant/assembly and composite geometry, in dependency order.
"""

from dataclasses import dataclass, field
from fractions import Fraction

import large_variant_policy as L
import mapping as U
import psMat
from glyphs import uname


@dataclass(frozen=True)
class VariantLimits:
    condition: str
    # Source also uses these caps for selected fallback branches.
    max_height: int = 5580
    max_width: int = 12000
    connector_tolerance: int = 8


@dataclass
class Variants:
    vertical: dict
    horizontal: dict
    assemblies: dict
    connector_limits: dict
    brace_fallbacks: set = field(default_factory=set)


@dataclass(frozen=True)
class Accents:
    glyphs: set
    slots: tuple
    center: int


def compose_glyph(sources, uni, name, ref, offsets, adv):
    """Compose copies of a reference glyph at the given offsets."""
    out, uni_owner = sources.font, sources.uni_owner
    gn = uname(uni) if uni is not None else name
    if uni is not None and uni in uni_owner:
        return None
    out.createChar(uni if uni is not None else -1, gn)
    g = out[gn]
    for dx, dy in offsets:
        g.addReference(ref, psMat.translate(dx, dy))
    g.width = round(adv)
    if uni is not None:
        uni_owner[uni] = gn
    return gn


def build_variants(sources, limits, op_variants, geometry):
    LARGE_POLICY = limits.condition
    MAXVAR = limits.max_height
    MAXWIDTH = limits.max_width
    fonts = sources.faces
    import_glyph = sources.import_glyph
    imported = sources.imported
    out = sources.font
    piece = sources.piece
    recenter_on_axis = sources.recenter_on_axis
    uni_owner = sources.uni_owner

    def exa_chain(start):
        """Return the NEXTLARGER chain and its terminal VARCHAR recipe.
        """
        _, tfm, _ = fonts["exa"]
        seq, c, seen = [], start, set()
        while c is not None and c in tfm.chars and c not in seen:
            seen.add(c)
            seq.append(c)
            vc = tfm.chars[c]["varchar"]
            nxt = tfm.chars[c]["next"]
            if nxt is None:
                return seq, vc
            c = nxt
        return seq, None

    vert_variants = {}  # base gname -> [gnames]
    vert_assembly = {}  # base -> [(glyph, extender)], bottom to top
    # Cap generic connector estimates with local source
    # geometry where needed.
    connector_limits = {}

    def build_vdelim(basespec, exastart, base_uni=None, base_name=None):
        tag, slot = basespec
        base = import_glyph(tag, slot, name=base_name, unicode_=base_uni)
        if base is None or "exa" not in fonts:
            return
        _, tfm, _ = fonts["exa"]
        seq, vc = exa_chain(exastart)
        var = [base]
        for c in seq:
            # A TeX VARCHAR slot selects an assembly,
            # not a fixed-size variant.
            is_terminal_piece = bool(tfm.chars.get(c, {}).get("varchar")) or (
                vc is not None
                and c == seq[-1]
                and c in (vc.get("TOP"), vc.get("BOT"), vc.get("MID"))
                and c != vc.get("REP")
            )
            if is_terminal_piece and len(seq) > 1:
                # Exclude assembly parts from fixed-size variants.
                continue
            v = import_glyph("exa", c, name=f"{base}.v{len(var)}")
            if v:
                var.append(v)
        vert_variants[base] = var
        if vc and "REP" in vc:
            parts = []  # Bottom to top.
            if "BOT" in vc:
                parts.append((piece(vc["BOT"]), 0))
            parts.append((piece(vc["REP"]), 1))
            if "MID" in vc:
                parts.append((piece(vc["MID"]), 0))
                parts.append((piece(vc["REP"]), 1))
            if "TOP" in vc:
                parts.append((piece(vc["TOP"]), 0))
            parts = [(p, e) for (p, e) in parts if p]
            if parts and not any(e == 0 for (p, e) in parts):
                # Repeat-only recipes use the same glyph as fixed ends
                # and a central extender.
                rep = parts[0][0]
                parts = [(rep, 0), (rep, 1), (rep, 0)]
            if parts:
                vert_assembly[base] = parts

    for u, (bspec, ex0) in U.V_DELIMS.items():
        build_vdelim(bspec, ex0, base_uni=u)
    for nm, (bspec, ex0) in U.V_DELIMS_ALT.items():
        build_vdelim(bspec, ex0, base_name=nm)

    if LARGE_POLICY == "source" and "exa" in fonts:
        # Plain's moustaches are VARCHAR recipes. Their terminal glyphs
        # alone are incomplete; make independent, complete Unicode
        # roots from the raw ends.
        for cp, bottom_slot, top_slot in (
            (0x23B0, 140, 137),
            (0x23B1, 139, 138),
        ):
            bottom, top, rep = piece(bottom_slot), piece(top_slot), piece(141)
            root = uname(cp)
            out.createChar(cp, root)
            g = out[root]
            bb, tb = out[bottom].boundingBox(), out[top].boundingBox()
            # The lower source end's upper stem reach
            # supplies the join overlap.
            overlap = max(0, min(bb[3], tb[3] - tb[1]))
            g.addReference(bottom, psMat.translate(0, -bb[1]))
            g.addReference(
                top, psMat.translate(0, bb[3] - bb[1] - overlap - tb[1])
            )
            g.width = out[bottom].width
            g.unlinkRef()
            uni_owner[cp] = root
            vert_variants[root] = [root]
            vert_assembly[root] = [(bottom, 0), (rep, 1), (top, 0)]
        rep = piece(141)
        root = uname(0x23AA)
        out.createChar(0x23AA, root)
        out[root].addReference(rep)
        out[root].width = out[rep].width
        out[root].unlinkRef()
        uni_owner[0x23AA] = root
        vert_variants[root] = [root]
        vert_assembly[root] = [(rep, 0), (rep, 1), (rep, 0)]

        # The broken TFM NEXTLARGER paths for the eight morphed designs
        # are not followed. Metric references are declared independently
        # of
        # raw links; the adopted series is installed below.
        for slot in U.SOURCE_MORPHED_SLOTS:
            if import_glyph("exa", slot, name=f"exa.morphed{slot}") is None:
                raise RuntimeError("missing morphed source exa:%d" % slot)

    vert_variants.update(op_variants)

    # Extend variant ladders from the
    # package-selected extension families.
    EXT_TAGS = ("exe", "exf", "exg")

    def chain_slots(tfm, start):
        """Follow the TFM NEXTLARGER chain."""
        out, seen, s = [], set(), start
        while s is not None and s in tfm.chars and s not in seen:
            seen.add(s)
            out.append(s)
            s = tfm.chars[s].get("next")
        if s is not None:
            raise ValueError("broken NEXTLARGER chain: %s" % s)
        return out

    def chain_heads(tfm):
        """Return slots with no incoming NEXTLARGER link."""
        targets = {
            c["next"] for c in tfm.chars.values() if c.get("next") is not None
        }
        return {s for s in tfm.chars if s not in targets}

    def extend_chain(base_gname, slot, table, size_key, identity=None):
        """Append an extension chain only when its head matches the base
        source.
        """
        added = []
        for tag in EXT_TAGS:
            if tag not in fonts:
                continue
            _, tfm, _ = fonts[tag]
            if slot not in tfm.chars or slot not in chain_heads(tfm):
                continue
            for s in chain_slots(tfm, slot):
                g = import_glyph(tag, s, name=f"{base_gname}.{tag}{s}")
                if not g:
                    continue
                bb = out[g].boundingBox()
                # Compare height for vertical variants and advance
                # for horizontal variants.
                if size_key == "h":
                    extent, cap = bb[3] - bb[1], MAXVAR
                else:
                    extent, cap = out[g].width, MAXWIDTH
                if (
                    not L.exempt(LARGE_POLICY, identity, size_key)
                    and cap
                    and extent > cap
                ):
                    # Discard oversized variants and import records.
                    imported.pop((tag, s), None)
                    out.removeGlyph(out[g])
                    continue
                if size_key == "h":
                    recenter_on_axis(g)
                added.append(g)
        if added:
            table[base_gname] = table.get(base_gname, [base_gname]) + added
            # Sort variants by size; equal-size stages may remain.
            key = (
                (lambda gn: out[gn].width)
                if size_key == "w"
                else (
                    lambda gn: out[gn].boundingBox()[3]
                    - out[gn].boundingBox()[1]
                )
            )
            head, rest = table[base_gname][0], table[base_gname][1:]
            table[base_gname] = [head] + sorted(rest, key=key)
        return added

    # Only extend matching chain heads; brackets, floors,
    # and ceilings have none.
    for u, (bspec, ex0) in U.V_DELIMS.items():
        if u not in uni_owner:
            continue
        base = uni_owner[u]
        extend_chain(base, ex0, vert_variants, "h", identity=u)

    if LARGE_POLICY == "source":
        # Import and measure all declared paths through the same native
        # geometry machinery. The root copy has no public Unicode alias.
        for root, choice in sources.growth["choices"].items():
            base = uni_owner[choice["cp"]]
            out.createChar(-1, root)
            out[root].addReference(base)
            out[root].width = out[base].width
            out[root].unlinkRef()
            vals = [root]
            for tag, slot in choice["refs"]:
                g = import_glyph(tag, slot)
                recenter_on_axis(g)
                # Lua represents MATH ladders as mutable per-glyph next
                # links. Distinct continuations cannot share finite
                # GIDs.
                clone = f"{root}.stage{len(vals)}"
                out.createChar(-1, clone)
                out[clone].addReference(g)
                out[clone].width = out[g].width
                out[clone].italicCorrection = out[g].italicCorrection
                out[clone].unlinkRef()
                sources.variant_sources[clone] = (tag, slot)
                vals.append(clone)
            vert_variants[root] = vals
            vert_assembly[root] = list(vert_assembly[base])
        for cp in (123, 125):
            base = uni_owner[cp]
            vert_variants[base] = [base] + [
                imported[tuple(ref)]
                for ref in sources.growth["vertical"][str(cp)][1:]
            ]

    # Horizontal variants and brace assemblies.
    horiz_variants = {}
    for u, chain in U.H_CHAINS.items():
        gnames = []
        for tag, slot in chain:
            if tag not in fonts:
                continue
            g = import_glyph(
                tag,
                slot,
                unicode_=u if not gnames else None,
                name=None if not gnames else None,
            )
            if g:
                gnames.append(g)
        if len(gnames) >= 1:
            horiz_variants[gnames[0]] = gnames

    # Keep public bar accents fixed. Private variants do not
    # remap standard commands.
    _bar_base = uni_owner.get(0x0304)
    if _bar_base is not None:
        _bar_chain = list(horiz_variants.get(_bar_base, [_bar_base]))
        if len(_bar_chain) > 1:
            _widebar_cp = 0xE286
            _widebar_name = "wideoverbar.compat"
            if _widebar_name not in out:
                out.createChar(_widebar_cp, _widebar_name)
                _wg = out[_widebar_name]
                _wg.addReference(_bar_base)
                # The private finite bar keeps its advance and outline;
                # attachment is width/2.
                _wg.width = out[_bar_base].width
                _wg.unlinkRef()
            uni_owner[_widebar_cp] = _widebar_name
            horiz_variants[_widebar_name] = [_widebar_name] + _bar_chain[1:]
        horiz_variants.pop(_bar_base, None)

    # Extend only matching wide-accent chains. Equal slots can
    # have different semantics.
    _WIDE_ACCENT_EXTENSION = (0x0302, 0x0303, 0x030C)
    for u in _WIDE_ACCENT_EXTENSION:
        chain = U.H_CHAINS.get(u, ())
        if u not in uni_owner:
            continue
        exa_slots = [slot for (tag, slot) in chain if tag == "exa"]
        if exa_slots:
            extend_chain(
                uni_owner[u], exa_slots[0], horiz_variants, "w", identity=u
            )

    # Use the designated mt2exf overparen
    # continuation at its source scale.
    if 0x23DC in uni_owner and "exf" in fonts:
        base = uni_owner[0x23DC]
        extra = []
        for slot in U.SOURCE_OVERPAREN_CONTINUATION:
            g = import_glyph("exf", slot, name=f"{base}.exf{slot}")
            if not g:
                continue
            if (
                not L.exempt(LARGE_POLICY, 0x23DC, "w")
                and MAXWIDTH
                and out[g].width > MAXWIDTH
            ):
                imported.pop(("exf", slot), None)
                out.removeGlyph(out[g])
                continue
            extra.append(g)
        if extra:
            vals = horiz_variants.get(base, [base]) + extra
            # Deduplicate equal-width stages while preserving
            # the first source form.
            seen_w = set()
            dedup = []
            for gn in sorted(vals, key=lambda q: out[q].width):
                w = out[gn].width
                if w in seen_w:
                    continue
                seen_w.add(w)
                dedup.append(gn)
            horiz_variants[base] = dedup

        # Bridge the fixed overparen ladders with an assembly derived
        # from the local outline.

        def _make_overparen_parts():
            left = "overparen.left.mt2"
            ext = "overparen.ext.mt2"
            right = "overparen.right.mt2"

            try:
                tpl = geometry["overparen_template"]
                ops = tpl["path"]
            except Exception as exc:
                raise RuntimeError(
                    "local overparen source geometry is missing"
                ) from exc
            expected_ops = (
                "moveTo",
                "lineTo",
                "curveTo",
                "curveTo",
                "lineTo",
                "curveTo",
                "curveTo",
                "closePath",
            )
            if tuple(rec.get("op") for rec in ops) != expected_ops:
                raise RuntimeError(
                    "unexpected local MTPro2 overparen contour topology"
                )

            def ipoints(i):
                pts = []
                for x, y in ops[i]["points"]:
                    if round(x) != x or round(y) != y:
                        raise RuntimeError(
                            "non-integral local Type1 overparen coordinate"
                        )
                    pts.append((int(x), int(y)))
                return pts

            start_pt = ipoints(0)[0]
            left_inner = ipoints(1)[0]
            lower_left = ipoints(2)
            lower_right = ipoints(3)
            right_edge = ipoints(4)[0]
            upper_right = ipoints(5)
            upper_left = ipoints(6)
            mid_lower = lower_left[-1]
            mid_upper = upper_right[-1]
            if mid_lower[0] != mid_upper[0] or start_pt != upper_left[-1]:
                raise RuntimeError(
                    "local overparen source contour is not the expected "
                    "symmetric-cap topology"
                )
            span = right_edge[0] - start_pt[0]
            if span <= 0:
                raise RuntimeError("invalid local overparen source span")

            # Derive connector proportions from the local span.
            connector = round(span / 15)
            ext_width = round(span / 6)
            inner_x = mid_lower[0] + connector
            right_dx = connector - mid_lower[0]
            left_width = inner_x - start_pt[0]
            right_width = right_edge[0] + right_dx

            if left not in out:
                out.createChar(-1, left)
                g = out[left]
                pen = g.glyphPen()
                pen.moveTo(start_pt)
                pen.lineTo(left_inner)
                pen.curveTo(*lower_left)
                pen.lineTo((inner_x, mid_lower[1]))
                pen.lineTo((inner_x, mid_upper[1]))
                pen.lineTo(mid_upper)
                pen.curveTo(*upper_left)
                pen.closePath()
                pen = None
                g.width = left_width
            if ext not in out:
                out.createChar(-1, ext)
                g = out[ext]
                pen = g.glyphPen()
                pen.moveTo((0, mid_lower[1]))
                pen.lineTo((ext_width, mid_lower[1]))
                pen.lineTo((ext_width, mid_upper[1]))
                pen.lineTo((0, mid_upper[1]))
                pen.closePath()
                pen = None
                g.width = ext_width
            if right not in out:

                def tx(pt):
                    return (pt[0] + right_dx, pt[1])

                out.createChar(-1, right)
                g = out[right]
                pen = g.glyphPen()
                pen.moveTo((0, mid_lower[1]))
                pen.lineTo((connector, mid_lower[1]))
                pen.curveTo(*(tx(q) for q in lower_right))
                pen.lineTo(tx(right_edge))
                pen.curveTo(*(tx(q) for q in upper_right))
                pen.lineTo((0, mid_upper[1]))
                pen.closePath()
                pen = None
                g.width = right_width
            return left, ext, right

        _opl, _ope, _opr = _make_overparen_parts()
        vert_assembly[("H", base)] = [(_opl, 0), (_ope, 1), (_opr, 0)]

    # widearc switches to exe:208 and exf/exg:177 in the original
    # package. The old overparen continuation is retained;
    # equal sizes stay distinct.
    if LARGE_POLICY == "source" and 0x23DC in uni_owner:
        # Standalone \arc uses exa:195. Keep it outside the automatic
        # widearc construction. It remains a compatibility measurement
        # input and does not introduce a public selector.
        if import_glyph("exa", 195, name="exa.arc195") is None:
            raise RuntimeError("source exa:195 missing")
        _base = uni_owner[0x23DC]
        for _tag, _head in U.SOURCE_WIDEARC_CHAINS:
            for _slot in chain_slots(fonts[_tag][1], _head):
                _g = import_glyph(_tag, _slot, name=f"{_base}.{_tag}{_slot}")
                if _g not in horiz_variants[_base]:
                    horiz_variants[_base].append(_g)
    return Variants(
        vert_variants, horiz_variants, vert_assembly, connector_limits
    )


def build_accents_and_dots(sources):
    SOURCE_POLICY = sources.rules
    _axis_height = sources.axis_height
    _mu_to_units = sources.mu_to_units
    _pt_to_units = sources.pt_to_units

    def compose(*args):
        return compose_glyph(sources, *args)

    fonts = sources.faces
    imported = sources.imported
    out = sources.font
    uni_owner = sources.uni_owner
    _NORMALIZED_ACCENT_MACROS = (
        "grave",
        "acute",
        "check",
        "breve",
        "bar",
        "hat",
        "dot",
        "tilde",
        "ddot",
        "mathring",
        "vec",
        "dotup",
        "ddotup",
        "dddot",
        "ddddot",
        "dddotup",
        "ddddotup",
    )
    try:
        ACCENT_SLOTS = [
            SOURCE_POLICY.accents[name].slot
            for name in _NORMALIZED_ACCENT_MACROS
        ]
    except KeyError as exc:
        raise RuntimeError(
            "required local MTPro2 accent declaration missing: %s"
            % exc.args[0]
        )
    accent_glyphs = set()
    ACC_CENTER = -(
        _axis_height() + round((fonts["exa"][1].fontdimen[8] * out.em) / 4)
    )
    for slot in ACCENT_SLOTS:
        key = ("syt", slot)
        if key not in imported:
            continue
        g = out[imported[key]]
        bb = g.boundingBox()
        cx = (bb[0] + bb[2]) / 2
        g.transform(psMat.translate(ACC_CENTER - cx, 0))
        g.width = 0
        accent_glyphs.add(g.glyphname)

    if ("mit", 0x3A) in imported and ("syt", 0x01) in imported:
        # Read dot geometry from the local package; horizontal
        # spacing uses math units.
        P = out[imported[("mit", 0x3A)]].width
        Cw = out[imported[("syt", 0x01)]].width
        per = imported[("mit", 0x3A)]
        cdo = imported[("syt", 0x01)]
        tfm_symbols = fonts["syt"][1]
        ellipsis_gap = _mu_to_units(Fraction(3, 1))
        compose(
            0x2026,
            None,
            per,
            [(0, 0), (P + ellipsis_gap, 0), (2 * (P + ellipsis_gap), 0)],
            3 * P + 2 * ellipsis_gap,
        )
        compose(
            0x22EF,
            None,
            cdo,
            [(0, 0), (Cw + ellipsis_gap, 0), (2 * (Cw + ellipsis_gap), 0)],
            3 * Cw + 2 * ellipsis_gap,
        )

        vstep = _pt_to_units(
            SOURCE_POLICY.dot_macros.vdots_step_pt, tfm_symbols
        )
        compose(0x22EE, None, per, [(0, 0), (0, vstep), (0, 2 * vstep)], P)

        diagonal_gap = _mu_to_units(SOURCE_POLICY.dot_macros.ddots_inner_mu)
        raises = tuple(
            _pt_to_units(x, tfm_symbols)
            for x in SOURCE_POLICY.dot_macros.ddots_raises_pt
        )
        if len(raises) != 3:
            raise RuntimeError("local ddots policy must contain three raises")
        xs = [0, P + diagonal_gap, 2 * (P + diagonal_gap)]
        dadv = 3 * P + 2 * diagonal_gap
        compose(0x22F1, None, per, list(zip(xs, raises)), dadv)
        compose(0x22F0, None, per, list(zip(xs, reversed(raises))), dadv)

        # Center standalone dot glyphs on the local math axis.
        axis = _axis_height()
        for u in (0x22EE, 0x22F1, 0x22F0):
            if u not in uni_owner:
                continue
            g = out[uni_owner[u]]
            bb = g.boundingBox()
            g.transform(psMat.translate(0, axis - (bb[1] + bb[3]) / 2))
    return Accents(accent_glyphs, tuple(ACCENT_SLOTS), ACC_CENTER)


def build_arrows_and_braces(sources, limits, variants, accents):
    CONNECTOR_PROFILE_TOLERANCE = limits.connector_tolerance
    LARGE_POLICY = limits.condition
    accent_glyphs = accents.glyphs
    connector_limits = variants.connector_limits
    fonts = sources.faces
    horiz_variants = variants.horizontal
    import_glyph = sources.import_glyph
    imported = sources.imported
    out = sources.font
    piece = sources.piece
    uni_owner = sources.uni_owner
    vert_assembly = variants.assemblies
    ARROW_CONNECTOR = 200

    def _trim_hconnector(src, suffix, connect_left=False, connect_right=False):
        """Create an unencoded assembly-only copy with connector
        sidebearings removed.
        """
        bits = ("L" if connect_left else "") + ("R" if connect_right else "")
        nm = f"{src}.hconn{bits}.{suffix}"
        if nm in out:
            return nm
        out.createChar(-1, nm)
        g = out[nm]
        g.addReference(src)
        g.unlinkRef()
        bb = g.boundingBox()
        xmin, _, xmax, _ = bb
        oldw = out[src].width
        if connect_left:
            g.transform(psMat.translate(-xmin, 0))
            oldw -= xmin
            xmax -= xmin
            xmin = 0
        if connect_right:
            # The right connector ends at actual ink, not at
            # the source advance edge.
            g.width = round(xmax)
        else:
            g.width = round(oldw)
        if connect_left and connect_right:
            bb = g.boundingBox()
            if bb[0] != 0:
                g.transform(psMat.translate(-bb[0], 0))
                bb = g.boundingBox()
            g.width = max(1, round(bb[2]))
        return nm

    def _resolve_harrow_ref(ref):
        if ref == "Relbar":
            return imported.get(("syt", 0x48))
        if isinstance(ref, int):
            return uni_owner.get(ref)
        return None

    def _arrow_component(src, idx, count, extender, suffix):
        left = idx > 0
        right = idx < count - 1
        gn = _trim_hconnector(src, suffix, left, right)
        if extender:
            # Both edges are straight connector material after trimming.
            conn = min(ARROW_CONNECTOR, max(100, out[gn].width // 3))
            return gn, 1, conn, conn, out[gn].width
        conn = min(ARROW_CONNECTOR, max(100, out[gn].width // 3))
        return (
            gn,
            0,
            (conn if left else 0),
            (conn if right else 0),
            out[gn].width,
        )

    def _resolve_arrow_parts(part_spec, suffix):
        raw = []
        for kind, ref in part_spec:
            gn = _resolve_harrow_ref(ref)
            if gn is None:
                return None
            raw.append((gn, kind == "E"))
        return [
            _arrow_component(gn, i, len(raw), ext, suffix)
            for i, (gn, ext) in enumerate(raw)
        ]

    def harrow_assembly(base_uni, part_spec):
        if base_uni not in uni_owner:
            return
        tups = _resolve_arrow_parts(part_spec, f"U{base_uni:04X}")
        if tups:
            out[uni_owner[base_uni]].horizontalComponents = tuple(tups)

    # Arrow fills use fixed ends and an extender
    # from the MTPro2 outlines.
    _ARROW_SPECS = {
        0x2190: [("G", 0x2190), ("E", 0x2212), ("G", 0x2212)],
        0x2192: [("G", 0x2212), ("E", 0x2212), ("G", 0x2192)],
        0x2194: [("G", 0x2190), ("E", 0x2212), ("G", 0x2192)],
        0x21D0: [("G", 0x21D0), ("E", "Relbar"), ("G", "Relbar")],
        0x21D2: [("G", "Relbar"), ("E", "Relbar"), ("G", 0x21D2)],
        0x21D4: [("G", 0x21D0), ("E", "Relbar"), ("G", 0x21D2)],
    }
    for _u, _spec in _ARROW_SPECS.items():
        harrow_assembly(_u, _spec)

    # Mapsto uses dedicated short/long source glyphs, not an
    # arbitrary-width assembly.
    if 0x20D7 in uni_owner and 0x2192 in uni_owner and 0x2212 in uni_owner:
        vb = out[uni_owner[0x20D7]].boundingBox()
        ab = out[uni_owner[0x2192]].boundingBox()
        dy = round((vb[1] + vb[3]) / 2 - (ab[1] + ab[3]) / 2) + 25

        def accent_copy(src, suffix):
            nm = f"{src}.{suffix}"
            if nm in out:
                return nm
            out.createChar(-1, nm)
            g = out[nm]
            g.addReference(src, psMat.translate(0, dy))
            g.width = out[src].width
            g.unlinkRef()
            return nm

        if 0x20D6 not in uni_owner:
            src = uni_owner[0x20D7]
            nm = uname(0x20D6)
            out.createChar(0x20D6, nm)
            g = out[nm]
            g.addReference(src)
            g.unlinkRef()
            g.transform(psMat.scale(-1, 1))
            bb = g.boundingBox()
            g.transform(psMat.translate(vb[0] - bb[0], 0))
            g.width = 0
            uni_owner[0x20D6] = nm
            accent_glyphs.add(nm)

        shaft_src = uni_owner[0x2212]
        shaft = _trim_hconnector(shaft_src, "vector.ex", True, True)
        shaft_acc = accent_copy(shaft, "acc")
        shaft_conn = min(ARROW_CONNECTOR, max(100, out[shaft].width // 3))

        # Keep public U+20D7 fixed; private U+E287 carries the
        # accent-height assembly. Standard commands are not remapped
        # to the private adapter.
        _widevec_cp = 0xE287
        _widevec_name = "widevector.compat"
        if _widevec_name not in out:
            out.createChar(_widevec_cp, _widevec_name)
            _wg = out[_widevec_name]
            _wg.addReference(uni_owner[0x20D7])
            _wg.width = out[uni_owner[0x20D7]].width
            _wg.unlinkRef()
        uni_owner[_widevec_cp] = _widevec_name
        accent_glyphs.add(_widevec_name)

        # The right vector starts with its endpoint;
        # shaft_acc supplies extra width.
        rend = accent_copy(uni_owner[0x2192], "vector.end.acc")
        rconn = min(ARROW_CONNECTOR, max(100, out[rend].width // 3))
        out[_widevec_name].horizontalComponents = (
            (shaft_acc, 1, shaft_conn, shaft_conn, out[shaft_acc].width),
            (rend, 0, rconn, 0, out[rend].width),
        )

        # Left vector: same minimum width, mirrored assembly order.
        lend = accent_copy(uni_owner[0x2190], "vector.end.acc")
        # End the assembly connector at actual ink, retaining the
        # original outline and its non-connecting left sidebearing.
        out[lend].width = round(out[lend].boundingBox()[2])
        lconn = min(ARROW_CONNECTOR, max(100, out[lend].width // 3))
        out[uni_owner[0x20D6]].horizontalComponents = (
            (lend, 0, 0, lconn, out[lend].width),
            (shaft_acc, 1, shaft_conn, shaft_conn, out[shaft_acc].width),
        )

    def make_rule(name, y0, y1):
        out.createChar(-1, name)
        g = out[name]
        pen = g.glyphPen()
        w = 200
        pen.moveTo((0, y0))
        pen.lineTo((0, y1))
        pen.lineTo((w, y1))
        pen.lineTo((w, y0))
        pen.closePath()
        pen = None
        g.width = w
        return name

    if "exa" in fonts:
        _, exatfm, _ = fonts["exa"]
        bld, brd, blu, bru = (piece(130), piece(131), piece(132), piece(133))
        if all([bld, brd, blu, bru]):
            # Standard braces use mt2exa corners and rules, separate
            # from overcbrace designs. Read the rule band from the
            # local braceld TFM height.
            brace_rule_h = round(exatfm.chars[130]["ht"] * 1000)
            ext = make_rule("brace.hext", 0, brace_rule_h)

            def merge_hparts(left, right, name):
                if name in out:
                    return name
                out.createChar(-1, name)
                g = out[name]
                wl = out[left].width
                g.addReference(left)
                g.addReference(right, psMat.translate(wl, 0))
                g.width = wl + out[right].width
                g.unlinkRef()
                return name

            over_center = merge_hparts(bru, blu, "brace.over.center")
            under_center = merge_hparts(brd, bld, "brace.under.center")

            def shifted_copy(src, name, dy):
                if name in out:
                    return name
                out.createChar(-1, name)
                g = out[name]
                g.addReference(src, psMat.translate(0, dy))
                g.width = out[src].width
                g.unlinkRef()
                return name

            # OpenType/Word migration policy
            _OVERBRACE_TARGET_YMIN = 539
            over_ymin = min(
                out[q].boundingBox()[1] for q in (bld, ext, over_center, brd)
            )
            over_dy = round(_OVERBRACE_TARGET_YMIN - over_ymin)
            over_left = shifted_copy(bld, "brace.over.left", over_dy)
            over_ext = shifted_copy(ext, "brace.over.ext", over_dy)
            over_center_shift = shifted_copy(
                over_center, "brace.over.center.shift", over_dy
            )
            over_right = shifted_copy(brd, "brace.over.right", over_dy)

            # Cap curved-part connectors at the local straight rule band
            # minus tolerance. Extenders expose their full rule width.
            brace_fixed_connector = max(
                0, brace_rule_h - CONNECTOR_PROFILE_TOLERANCE
            )
            for q in (
                bld,
                brd,
                blu,
                bru,
                over_left,
                over_center_shift,
                over_right,
                over_center,
                under_center,
            ):
                connector_limits[q] = brace_fixed_connector
            connector_limits[ext] = out[ext].width
            connector_limits[over_ext] = out[over_ext].width
            brace_extenders = {ext, over_ext}

            for u, parts in [
                (
                    0x23DE,
                    [
                        over_left,
                        over_ext,
                        over_center_shift,
                        over_ext,
                        over_right,
                    ],
                ),
                (0x23DF, [blu, ext, under_center, ext, bru]),
            ]:
                gn = uname(u)
                if gn not in out:
                    out.createChar(u, gn)
                g = out[gn]
                # Build a small fallback; the assembly
                # handles arbitrary widths.
                fixed = [q for q in parts if q != ext]
                x = 0
                for q in fixed:
                    g.addReference(q, psMat.translate(x, 0))
                    x += out[q].width
                g.width = x
                uni_owner[u] = gn
                horiz_variants[gn] = [gn]
                vert_assembly[("H", gn)] = [
                    (q, 1 if q in brace_extenders else 0) for q in parts
                ]

            if LARGE_POLICY == "source":
                # Plain's dedicated middle pieces have a different
                # advance from the ordinary two-half cusp. Keep the
                # new assemblies isolated.
                for cp, middle_slot, source_parts, shift in (
                    (0x23DE, 135, (bld, ext, None, ext, brd), over_dy),
                    (0x23DF, 134, (blu, ext, None, ext, bru), 0),
                ):
                    middle = piece(middle_slot)
                    if middle is None:
                        raise RuntimeError(
                            "missing plain brace middle exa:%d" % middle_slot
                        )
                    label = "over" if cp == 0x23DE else "under"
                    # Translate each overbrace part by the ordinary
                    # brace's documented baseline offset;
                    # no contour is reshaped.

                    def plain_part(source, suffix):
                        return shifted_copy(
                            source,
                            "brace.plain." + label + "." + suffix,
                            shift,
                        )

                    parts = [
                        plain_part(
                            q,
                            (
                                "left"
                                if i == 0
                                else (
                                    "right"
                                    if i == 4
                                    else "middle"
                                    if i == 2
                                    else "ext"
                                )
                            ),
                        )
                        for i, q in enumerate(source_parts)
                        if q is not None
                    ]
                    center = plain_part(middle, "middle")
                    left, rule, right = parts[0], parts[1], parts[-1]
                    root = "brace.plain." + label
                    out.createChar(-1, root)
                    g = out[root]
                    x = 0
                    for q in (left, center, right):
                        g.addReference(q, psMat.translate(x, 0))
                        x += out[q].width
                    g.width = x
                    g.unlinkRef()
                    # Bound the original 116-unit joining rule by the
                    # measured edge profile of each source piece.
                    for q in (left, center, right):
                        connector_limits[q] = max(
                            0, brace_rule_h - CONNECTOR_PROFILE_TOLERANCE
                        )
                    connector_limits[rule] = out[rule].width
                    vert_assembly[("H", root)] = [
                        (left, 0),
                        (rule, 1),
                        (center, 0),
                        (rule, 1),
                        (right, 0),
                    ]

    # Original overcbrace/undercbrace select these complete designs by
    # width. Preserve the original fallback glyph and construction. The
    # source Unicode entrance uses the smallest dedicated glyph, so
    # fallback cannot exceed the first fixed variant. Names continue to
    # identify the original source import.
    _source_brace_fallbacks = set()
    if LARGE_POLICY == "source":
        for _cp, _series in U.SOURCE_BRACE_SERIES:
            _base = uni_owner[_cp]
            for _tag, _first, _last in _series:
                for _slot in range(_first, _last + 1):
                    _gn = import_glyph(
                        _tag, _slot, name=f"{_base}.curly.{_tag}{_slot}"
                    )
                    if _gn is None:
                        raise ValueError(
                            "missing dedicated curly brace %s:%d"
                            % (_tag, _slot)
                        )
                    # Adopt the existing public overbrace baseline
                    # convention; underbraces keep the
                    # scaled source baseline.
                    if _cp == 0x23DE:
                        _bb = out[_gn].boundingBox()
                        out[_gn].transform(
                            psMat.translate(0, round(539 - _bb[1]))
                        )
                    horiz_variants[_base].append(_gn)
            _source_brace_fallbacks.add(_base)
            _new_base = imported[(_series[0][0], _series[0][1])]
            out[_base].unicode = -1
            out[_new_base].unicode = _cp
            uni_owner[_cp] = _new_base
            horiz_variants[_new_base] = horiz_variants[_base][1:]
            vert_assembly[("H", _new_base)] = list(vert_assembly[("H", _base)])
    variants.brace_fallbacks = _source_brace_fallbacks


def install_variant_geometry(sources, limits, variants):
    CONNECTOR_PROFILE_TOLERANCE = limits.connector_tolerance
    LARGE_POLICY = limits.condition
    _source_brace_fallbacks = variants.brace_fallbacks
    connector_limits = variants.connector_limits
    horiz_variants = variants.horizontal
    out = sources.font
    uni_owner = sources.uni_owner
    vert_assembly = variants.assemblies
    vert_variants = variants.vertical

    def glyph_size(gname):
        bb = out[gname].boundingBox()
        return bb[3] - bb[1]

    extended_source_variants = {
        g for values in vert_variants.values() for g in values[1:]
    }
    if LARGE_POLICY == "source":
        select_growth(sources, variants)
    for base, var in vert_variants.items():
        var2 = sorted(dict.fromkeys(var), key=glyph_size)
        if len(var2) > 1 or base in vert_assembly:
            out[base].verticalVariants = " ".join(var2)
    for base, var in horiz_variants.items():
        var2 = sorted(
            dict.fromkeys(var),
            key=(
                (lambda g: out[g].boundingBox()[2] - out[g].boundingBox()[0])
                if LARGE_POLICY == "source"
                else (lambda g: out[g].width)
            ),
        )
        if base in _source_brace_fallbacks:
            # The legacy brace base is an assembly fallback, not a fixed
            # variant. Keep its outline, but do not interleave that
            # generic design with curly forms.
            var2 = [g for g in var2 if g != base]
        if len(var2) > 1:
            out[base].horizontalVariants = " ".join(var2)

    # Measure stable connector runs from flattened outline
    # cross-sections. TFM recipes provide no overlap lengths.

    def _flatten(gname, steps=8):
        polys = []
        for contour in out[gname].foreground:
            pts = []
            seq = [(p.x, p.y, p.on_curve) for p in contour]
            if not seq:
                continue
            seq.append(seq[0])
            i = 0
            while i < len(seq) - 1:
                x0, y0, _ = seq[i]
                x1, y1, on1 = seq[i + 1]
                if on1:
                    pts.append((x1, y1))
                    i += 1
                else:
                    ctrl = []
                    j = i + 1
                    while j < len(seq) and not seq[j][2]:
                        ctrl.append(seq[j][:2])
                        j += 1
                    if j >= len(seq):
                        break
                    x3, y3, _ = seq[j]
                    for k in range(1, steps + 1):
                        t = k / steps
                        if len(ctrl) == 1:
                            cx, cy = ctrl[0]
                            xx = (
                                (1 - t) ** 2 * x0
                                + 2 * (1 - t) * t * cx
                                + t * t * x3
                            )
                            yy = (
                                (1 - t) ** 2 * y0
                                + 2 * (1 - t) * t * cy
                                + t * t * y3
                            )
                        else:
                            c1, c2 = ctrl[0], ctrl[-1]
                            xx = (
                                (1 - t) ** 3 * x0
                                + 3 * (1 - t) ** 2 * t * c1[0]
                                + 3 * (1 - t) * t * t * c2[0]
                                + t**3 * x3
                            )
                            yy = (
                                (1 - t) ** 3 * y0
                                + 3 * (1 - t) ** 2 * t * c1[1]
                                + 3 * (1 - t) * t * t * c2[1]
                                + t**3 * y3
                            )
                        pts.append((xx, yy))
                    i = j
            if pts:
                polys.append(pts)
        return polys

    def _profile(gname, vertical, n=64):
        polys = _flatten(gname)
        if not polys:
            return []
        bb = out[gname].boundingBox()
        lo, hi = (bb[1], bb[3]) if vertical else (bb[0], bb[2])
        if hi - lo <= 0:
            return []
        res = []
        for k in range(n):
            v = lo + (hi - lo) * (k + 0.5) / n
            xs = []
            for pts in polys:
                m = len(pts)
                for a in range(m):
                    p, q = pts[a], pts[(a + 1) % m]
                    pa, qa = (p[1], q[1]) if vertical else (p[0], q[0])
                    if (pa <= v < qa) or (qa <= v < pa):
                        t = (v - pa) / (qa - pa)
                        pb, qb = (p[0], q[0]) if vertical else (p[1], q[1])
                        xs.append(pb + (qb - pb) * t)
            res.append((min(xs), max(xs)) if len(xs) >= 2 else None)
        return res

    def connector_run(
        gname, vertical, at_end, tol=CONNECTOR_PROFILE_TOLERANCE
    ):
        prof = _profile(gname, vertical)
        if not prof:
            return 0
        bb = out[gname].boundingBox()
        span = (bb[3] - bb[1]) if vertical else (bb[2] - bb[0])
        step = span / len(prof)
        seq = list(reversed(prof)) if at_end else list(prof)
        ref = seq[0]
        if ref is None:
            return 0
        run = 0
        for cur in seq:
            if (
                cur is None
                or abs(cur[0] - ref[0]) > tol
                or abs(cur[1] - ref[1]) > tol
            ):
                break
            run += 1
        length = max(0, int(run * step))
        limit = connector_limits.get(gname)
        return min(length, limit) if limit is not None else length

    def _norm_vpart(p):
        """Align vertical assembly parts to their ink bottom."""
        nm = f"{p}.vn"
        if nm not in out:
            bb = out[p].boundingBox()
            out.createChar(-1, nm)
            g = out[nm]
            g.addReference(p, psMat.translate(0, -bb[1]))
            g.width = out[p].width
            g.unlinkRef()
        return nm

    def part_tuple(gname, is_ext, vertical=True):
        g = out[gname]
        if vertical:
            bb = g.boundingBox()
            full = round(bb[3] - bb[1])
        else:
            full = g.width
        start = connector_run(gname, vertical, False)
        end = connector_run(gname, vertical, True)
        return (gname, is_ext, start, end, full)

    _runs = []
    for _b, _p in vert_assembly.items():
        n = len(_p)
        vert = not isinstance(_b, tuple)
        for i, (p, e) in enumerate(_p):
            if i or e:
                _runs.append(connector_run(p, vert, False))
            if i < n - 1 or e:
                _runs.append(connector_run(p, vert, True))
    _ov = int(getattr(out.math, "MinConnectorOverlap", 100) or 100)
    if _runs and min(_runs) < _ov:
        out.math.MinConnectorOverlap = max(0, int(min(_runs)))

    for base, parts in vert_assembly.items():
        if isinstance(base, tuple):  # Horizontal assemblies.
            _, gname = base
            # Only joining edges have connectors; outside fixed edges
            # must have zero length.
            tups = []
            n = len(parts)
            for i, (p, e) in enumerate(parts):
                q = list(part_tuple(p, e, vertical=False))
                if i == 0 and not e:
                    q[2] = 0  # StartConnectorLength
                if i == n - 1 and not e:
                    q[3] = 0  # EndConnectorLength
                tups.append(tuple(q))
            out[gname].horizontalComponents = tuple(tups)
        else:
            # Extenders need both connectors even
            # at an assembly boundary.
            parts = [(_norm_vpart(p), e) for (p, e) in parts]
            tups = []
            n = len(parts)
            for i, (p, e) in enumerate(parts):
                bb = out[p].boundingBox()
                full = round(bb[3] - bb[1])
                start = (
                    0 if i == 0 and not e else connector_run(p, True, False)
                )
                end = (
                    0 if i == n - 1 and not e else connector_run(p, True, True)
                )
                tups.append((p, e, start, end, full))
            out[base].verticalComponents = tuple(tups)

    # Extended shapes include larger variants, assembly parts,
    # and display operators.
    extended = set(extended_source_variants)
    for base, var in vert_variants.items():
        extended.update(var[1:])
    for base, parts in vert_assembly.items():
        if not isinstance(base, tuple):
            extended.update(p for (p, _) in parts)
    # Mark the base integral family ExtendedShape for OpenType
    # script/limit placement.
    for _u in range(0x222B, 0x2234):
        if _u in uni_owner:
            extended.add(uni_owner[_u])
    for gname in extended:
        try:
            out[gname].isExtendedShape = True
        except Exception as exc:
            raise ValueError(f"ExtendedShape {gname}: {exc}") from exc


def select_growth(sources, variants):
    """Only final ready-made series enter MATH; measurement outlines
    stay
    separate.
    """
    provenance = {}
    for identity, glyph in sources.imported.items():
        provenance.setdefault(glyph, set()).add(identity)
    for glyph, identity in sources.variant_sources.items():
        provenance[glyph] = {identity}
    plan = sources.growth
    for direction, table in (
        ("vertical", variants.vertical),
        ("horizontal", variants.horizontal),
    ):
        allowed_roots = {
            sources.uni_owner.get(int(cp)): {tuple(r) for r in refs}
            for cp, refs in plan[direction].items()
        }
        if direction == "vertical":
            allowed_roots.update(
                {
                    root: {tuple(ref) for ref in choice["refs"]}
                    for root, choice in plan["choices"].items()
                }
            )
            for slot, (cp, name, display) in U.EXA_OPS.items():
                root = sources.uni_owner.get(cp) if cp is not None else name
                allowed_roots[root] = {
                    tuple(r) for r in plan["operators"][name]
                }
        for root, allowed in allowed_roots.items():
            if root not in table:
                continue
            old = table[root]
            selected = [g for g in old if provenance.get(g, set()) & allowed]
            if (
                direction == "vertical"
                and old
                and old[0] == root
                and root not in selected
            ):
                selected.insert(0, root)
            if not selected:
                raise ValueError("empty planned growth: " + str(root))
            table[root] = selected
    for root in U.EXCLUDED_GLYPHS:
        variants.vertical.pop(root, None)
        variants.horizontal.pop(root, None)
