#!/usr/bin/env python3
"""Prove that Pivot Sans changes only its declared overlay surface."""

from __future__ import annotations

from copy import deepcopy
from io import BytesIO
from pathlib import Path

import pytest
from fontTools.ttLib import TTFont
from fontTools.ttLib.tables.otBase import OTTableWriter
from fontTools.varLib.instancer import instantiateVariableFont

ROOT = Path(__file__).resolve().parent.parent
UPSTREAM = ROOT / "upstream"
DIST = ROOT / "dist"

VARIABLE_PAIRS = [
    (
        UPSTREAM / "InclusiveSans-VariableFont_wght.ttf",
        DIST / "PivotSans[wght].ttf",
    ),
    (
        UPSTREAM / "InclusiveSans-Italic-VariableFont_wght.ttf",
        DIST / "PivotSans-Italic[wght].ttf",
    ),
]
STATIC_PAIRS = [
    (
        UPSTREAM / "static" / "InclusiveSans-Light.ttf",
        DIST / "static" / "PivotSans-Light.ttf",
    ),
    (
        UPSTREAM / "static" / "InclusiveSans-Regular.ttf",
        DIST / "static" / "PivotSans-Regular.ttf",
    ),
    (
        UPSTREAM / "static" / "InclusiveSans-Medium.ttf",
        DIST / "static" / "PivotSans-Medium.ttf",
    ),
    (
        UPSTREAM / "static" / "InclusiveSans-SemiBold.ttf",
        DIST / "static" / "PivotSans-SemiBold.ttf",
    ),
    (
        UPSTREAM / "static" / "InclusiveSans-Bold.ttf",
        DIST / "static" / "PivotSans-Bold.ttf",
    ),
    (
        UPSTREAM / "static" / "InclusiveSans-LightItalic.ttf",
        DIST / "static" / "PivotSans-LightItalic.ttf",
    ),
    (
        UPSTREAM / "static" / "InclusiveSans-Italic.ttf",
        DIST / "static" / "PivotSans-Italic.ttf",
    ),
    (
        UPSTREAM / "static" / "InclusiveSans-MediumItalic.ttf",
        DIST / "static" / "PivotSans-MediumItalic.ttf",
    ),
    (
        UPSTREAM / "static" / "InclusiveSans-SemiBoldItalic.ttf",
        DIST / "static" / "PivotSans-SemiBoldItalic.ttf",
    ),
    (
        UPSTREAM / "static" / "InclusiveSans-BoldItalic.ttf",
        DIST / "static" / "PivotSans-BoldItalic.ttf",
    ),
]
PIN_CASES = [
    (DIST / "PivotSans[wght].ttf", DIST / "pins/PivotSans-Wght450.ttf", 450),
    (DIST / "PivotSans[wght].ttf", DIST / "pins/PivotSans-Wght550.ttf", 550),
    (
        DIST / "PivotSans-Italic[wght].ttf",
        DIST / "pins/PivotSans-Wght450Italic.ttf",
        450,
    ),
    (
        DIST / "PivotSans-Italic[wght].ttf",
        DIST / "pins/PivotSans-Wght550Italic.ttf",
        550,
    ),
]

TABULAR_SOURCES = [
    "zero",
    "one",
    "two",
    "three",
    "four",
    "five",
    "six",
    "seven",
    "eight",
    "nine",
    "dollar",
    "sterling",
    "yen",
    "uni20B9",
    "Euro",
    "dollar.BRACKET.varAlt01",
]
MATH_GLYPHS = [
    "notequal",
    "lessequal",
    "greaterequal",
    "approxequal",
    "perthousand",
]
MATH_CMAP = {
    0x2030: "perthousand",
    0x2248: "approxequal",
    0x2260: "notequal",
    0x2264: "lessequal",
    0x2265: "greaterequal",
}
MATH_COMPONENTS = {
    "notequal": ("equal", "slash"),
    "lessequal": ("less", "minus"),
    "greaterequal": ("greater", "minus"),
    "approxequal": ("asciitilde", "asciitilde"),
    "perthousand": ("percent", "degree"),
}
SS02_SOURCES = [
    "l",
    "lacute",
    "uni013C",
    "uni013C.loclMAH",
    "lcaron",
    "ldot",
    "lslash",
    "uni1E37",
    "uni1E3B",
]
SS02_TARGETS = {
    source: f"{source}.ss02" for source in SS02_SOURCES
}
SS02_FOOT = "l.foot.ss02"
SS02_GLYPHS = [SS02_FOOT, *SS02_TARGETS.values()]
OVERLAP_COMPOUND = 0x0400
ROUND_XY_TO_GRID = 0x0004


def glyph_semantics(glyph):
    if glyph.isComposite():
        return (
            "composite",
            tuple(
                (
                    component.glyphName,
                    getattr(component, "x", 0),
                    getattr(component, "y", 0),
                    getattr(component, "flags", 0),
                    getattr(component, "scale", None),
                    getattr(component, "xscale", None),
                    getattr(component, "yscale", None),
                    getattr(component, "scale01", None),
                    getattr(component, "scale10", None),
                )
                for component in glyph.components
            ),
            bytes(glyph.program.getBytecode())
            if hasattr(glyph, "program")
            else b"",
        )
    if glyph.numberOfContours == 0:
        return ("empty",)
    return (
        "simple",
        tuple(glyph.coordinates),
        tuple(glyph.endPtsOfContours),
        tuple(glyph.flags),
        bytes(glyph.program.getBytecode()) if hasattr(glyph, "program") else b"",
    )


def component_nonflag_semantics(component):
    return (
        component.glyphName,
        getattr(component, "x", 0),
        getattr(component, "y", 0),
        getattr(component, "scale", None),
        getattr(component, "xscale", None),
        getattr(component, "yscale", None),
        getattr(component, "scale01", None),
        getattr(component, "scale10", None),
        getattr(component, "transform", None),
    )


def native_l_offset(font: TTFont, source: str) -> tuple[int, int]:
    if source == "l":
        return (0, 0)
    glyf = font["glyf"]
    source_glyph = glyf[source]
    if source_glyph.isComposite():
        matches = [
            component
            for component in source_glyph.components
            if component.glyphName == "l"
        ]
        assert len(matches) == 1
        assert getattr(matches[0], "transform", None) is None
        return (round(matches[0].x), round(matches[0].y))

    l_coordinates, l_endpoints, _ = glyf["l"].getCoordinates(glyf)
    assert list(l_endpoints) == [3]
    native_l = [(round(x), round(y)) for x, y in l_coordinates]
    source_coordinates, source_endpoints, _ = source_glyph.getCoordinates(glyf)
    source_points = [(round(x), round(y)) for x, y in source_coordinates]
    matches = []
    start = 0
    for end in source_endpoints:
        contour = source_points[start : end + 1]
        start = end + 1
        if len(contour) != len(native_l):
            continue
        dx = contour[0][0] - native_l[0][0]
        dy = contour[0][1] - native_l[0][1]
        if all(
            (x + dx, y + dy) == point
            for (x, y), point in zip(native_l, contour)
        ):
            matches.append((dx, dy))
    assert len(matches) == 1
    return matches[0]


def resolved_geometry(glyph, glyf):
    coordinates, endpoints, flags = glyph.getCoordinates(glyf)
    return (
        tuple((round(x), round(y)) for x, y in coordinates),
        tuple(endpoints),
        tuple(flags),
    )


def assert_ss02_target_recipe(font: TTFont, source: str, target: str):
    glyf = font["glyf"]
    source_glyph = glyf[source]
    target_glyph = glyf[target]
    assert target_glyph.isComposite()
    body_components = target_glyph.components[:-1]
    foot_component = target_glyph.components[-1]

    if source_glyph.isComposite():
        assert len(body_components) == len(source_glyph.components)
        for index, (actual, native) in enumerate(
            zip(body_components, source_glyph.components)
        ):
            assert component_nonflag_semantics(
                actual
            ) == component_nonflag_semantics(native)
            assert actual.flags == native.flags | (
                OVERLAP_COMPOUND if index == 0 else 0
            )
    else:
        assert len(body_components) == 1
        body = body_components[0]
        assert component_nonflag_semantics(body) == (
            source,
            0,
            0,
            None,
            None,
            None,
            None,
            None,
            None,
        )
        assert body.flags == ROUND_XY_TO_GRID | OVERLAP_COMPOUND

    assert component_nonflag_semantics(foot_component) == (
        SS02_FOOT,
        *native_l_offset(font, source),
        None,
        None,
        None,
        None,
        None,
        None,
    )
    assert foot_component.flags == ROUND_XY_TO_GRID
    assert all(
        not glyf[component.glyphName].isComposite()
        for component in target_glyph.components
    )

    body_glyph = deepcopy(target_glyph)
    body_glyph.components = body_components
    assert resolved_geometry(body_glyph, glyf) == resolved_geometry(
        source_glyph, glyf
    )


def tuple_variations(variations):
    return [
        (tuple(sorted(variation.axes.items())), tuple(variation.coordinates))
        for variation in variations
    ]


def compile_lookup(lookup, font):
    writer = OTTableWriter(tableTag="GSUB")
    lookup.compile(writer, font)
    return writer.getAllData()


def single_substitution_mapping(font: TTFont, feature_tag: str):
    gsub = font["GSUB"].table
    feature_records = gsub.FeatureList.FeatureRecord
    matching_features = [
        record.Feature
        for record in feature_records
        if record.FeatureTag == feature_tag
    ]
    assert len(matching_features) == 1
    lookup_indices = matching_features[0].LookupListIndex
    assert len(lookup_indices) == 1
    lookup = gsub.LookupList.Lookup[lookup_indices[0]]
    assert lookup.LookupType == 1
    assert len(lookup.SubTable) == 1
    return lookup.SubTable[0].mapping


def tnum_mapping(font: TTFont):
    return single_substitution_mapping(font, "tnum")


def ss02_mapping(font: TTFont):
    return single_substitution_mapping(font, "ss02")


def ot_semantics(value):
    if value is None or isinstance(value, (str, int, float, bytes, bool)):
        return value
    if isinstance(value, (list, tuple)):
        return tuple(ot_semantics(item) for item in value)
    if isinstance(value, dict):
        return tuple(
            (key, ot_semantics(item))
            for key, item in sorted(value.items())
        )
    if hasattr(value, "__dict__"):
        return (
            type(value).__name__,
            tuple(
                (key, ot_semantics(item))
                for key, item in sorted(vars(value).items())
                if not key.startswith("_")
            ),
        )
    return repr(value)


def pair_value_semantics(record):
    return (
        ot_semantics(getattr(record, "Value1", None)),
        ot_semantics(getattr(record, "Value2", None)),
    )


def pairpos_format1_mapping(subtable):
    assert subtable.Format == 1
    assert len(subtable.Coverage.glyphs) == len(subtable.PairSet)
    mapping = {}
    for first_glyph, pair_set in zip(
        subtable.Coverage.glyphs, subtable.PairSet
    ):
        assert first_glyph not in mapping
        pairs = {}
        for record in pair_set.PairValueRecord:
            assert record.SecondGlyph not in pairs
            pairs[record.SecondGlyph] = pair_value_semantics(record)
        mapping[first_glyph] = pairs
    return mapping


def expanded_ss02_pair_mapping(upstream_mapping):
    def expand_second_glyphs(pairs):
        expanded = dict(pairs)
        for source, target in SS02_TARGETS.items():
            if source in pairs:
                expanded[target] = pairs[source]
        return expanded

    expected = {
        first_glyph: expand_second_glyphs(pairs)
        for first_glyph, pairs in upstream_mapping.items()
    }
    for source, target in SS02_TARGETS.items():
        if source in upstream_mapping:
            expected[target] = expand_second_glyphs(
                upstream_mapping[source]
            )
    return expected


def markbase_mapping(subtable):
    assert subtable.Format == 1
    assert len(subtable.BaseCoverage.glyphs) == len(
        subtable.BaseArray.BaseRecord
    )
    assert subtable.BaseArray.BaseCount == len(
        subtable.BaseArray.BaseRecord
    )
    return {
        glyph_name: ot_semantics(base_record)
        for glyph_name, base_record in zip(
            subtable.BaseCoverage.glyphs,
            subtable.BaseArray.BaseRecord,
        )
    }


def assert_gdef_ss02_overlay(upstream: TTFont, fork: TTFont):
    upstream_gdef = upstream["GDEF"].table
    fork_gdef = fork["GDEF"].table
    upstream_classes = upstream_gdef.GlyphClassDef.classDefs
    expected_classes = dict(upstream_classes)
    expected_classes.update(
        {
            target: upstream_classes[source]
            for source, target in SS02_TARGETS.items()
        }
    )
    assert fork_gdef.GlyphClassDef.classDefs == expected_classes
    assert SS02_FOOT not in fork_gdef.GlyphClassDef.classDefs

    for field, value in vars(upstream_gdef).items():
        if field.startswith("_") or field == "GlyphClassDef":
            continue
        assert ot_semantics(getattr(fork_gdef, field)) == ot_semantics(
            value
        ), field


def assert_gpos_ss02_overlay(upstream: TTFont, fork: TTFont):
    upstream_gpos = upstream["GPOS"].table
    fork_gpos = fork["GPOS"].table

    for field in ("Version", "ScriptList", "FeatureList"):
        assert ot_semantics(getattr(fork_gpos, field)) == ot_semantics(
            getattr(upstream_gpos, field)
        ), field
    assert len(fork_gpos.LookupList.Lookup) == len(
        upstream_gpos.LookupList.Lookup
    )

    markbase_target_counts = {target: 0 for target in SS02_TARGETS.values()}
    for lookup_index, (upstream_lookup, fork_lookup) in enumerate(
        zip(
            upstream_gpos.LookupList.Lookup,
            fork_gpos.LookupList.Lookup,
        )
    ):
        for field, value in vars(upstream_lookup).items():
            if field.startswith("_") or field == "SubTable":
                continue
            assert ot_semantics(getattr(fork_lookup, field)) == ot_semantics(
                value
            ), (lookup_index, field)
        assert len(fork_lookup.SubTable) == len(upstream_lookup.SubTable)

        for subtable_index, (upstream_subtable, fork_subtable) in enumerate(
            zip(upstream_lookup.SubTable, fork_lookup.SubTable)
        ):
            location = (lookup_index, subtable_index)
            if upstream_lookup.LookupType == 2 and upstream_subtable.Format == 1:
                for field in ("Format", "ValueFormat1", "ValueFormat2"):
                    assert getattr(fork_subtable, field) == getattr(
                        upstream_subtable, field
                    ), (location, field)
                upstream_mapping = pairpos_format1_mapping(
                    upstream_subtable
                )
                assert pairpos_format1_mapping(
                    fork_subtable
                ) == expanded_ss02_pair_mapping(upstream_mapping), location
                continue

            if upstream_lookup.LookupType == 4:
                for field in (
                    "Format",
                    "ClassCount",
                    "MarkCoverage",
                    "MarkArray",
                ):
                    assert ot_semantics(
                        getattr(fork_subtable, field)
                    ) == ot_semantics(
                        getattr(upstream_subtable, field)
                    ), (location, field)
                upstream_mapping = markbase_mapping(upstream_subtable)
                expected_mapping = dict(upstream_mapping)
                for source, target in SS02_TARGETS.items():
                    if source in upstream_mapping:
                        expected_mapping[target] = upstream_mapping[source]
                        markbase_target_counts[target] += 1
                assert markbase_mapping(fork_subtable) == expected_mapping, (
                    location
                )
                continue

            assert ot_semantics(fork_subtable) == ot_semantics(
                upstream_subtable
            ), location

    # Inclusive Sans has two mark-to-base subtables carrying all nine l forms.
    assert set(markbase_target_counts.values()) == {2}


def assert_gsub_feature_overlay(upstream: TTFont, fork: TTFont):
    upstream_gsub = upstream["GSUB"].table
    fork_gsub = fork["GSUB"].table
    upstream_records = upstream_gsub.FeatureList.FeatureRecord
    fork_records = fork_gsub.FeatureList.FeatureRecord
    assert len(fork_records) == len(upstream_records) + 2

    for upstream_record, fork_record in zip(
        upstream_records, fork_records
    ):
        assert fork_record.FeatureTag == upstream_record.FeatureTag
        assert ot_semantics(fork_record.Feature) == ot_semantics(
            upstream_record.Feature
        )

    added_feature_indices = [
        index
        for index, record in enumerate(fork_records)
        if record.FeatureTag in {"ss02", "tnum"}
        and index >= len(upstream_records)
    ]
    assert added_feature_indices == [
        len(upstream_records),
        len(upstream_records) + 1,
    ]

    upstream_scripts = upstream_gsub.ScriptList.ScriptRecord
    fork_scripts = fork_gsub.ScriptList.ScriptRecord
    assert [record.ScriptTag for record in fork_scripts] == [
        record.ScriptTag for record in upstream_scripts
    ]
    for upstream_script_record, fork_script_record in zip(
        upstream_scripts, fork_scripts
    ):
        upstream_script = upstream_script_record.Script
        fork_script = fork_script_record.Script
        assert [
            record.LangSysTag for record in fork_script.LangSysRecord
        ] == [
            record.LangSysTag for record in upstream_script.LangSysRecord
        ]
        upstream_language_systems = [
            upstream_script.DefaultLangSys,
            *[
                record.LangSys
                for record in upstream_script.LangSysRecord
            ],
        ]
        fork_language_systems = [
            fork_script.DefaultLangSys,
            *[record.LangSys for record in fork_script.LangSysRecord],
        ]
        assert [
            language_system is None
            for language_system in fork_language_systems
        ] == [
            language_system is None
            for language_system in upstream_language_systems
        ]
        for upstream_language, fork_language in zip(
            upstream_language_systems, fork_language_systems
        ):
            if upstream_language is None:
                continue
            assert fork_language.LookupOrder == upstream_language.LookupOrder
            assert (
                fork_language.ReqFeatureIndex
                == upstream_language.ReqFeatureIndex
            )
            assert fork_language.FeatureIndex == [
                *upstream_language.FeatureIndex,
                *added_feature_indices,
            ]

    assert fork_gsub.Version == upstream_gsub.Version
    assert ot_semantics(
        getattr(fork_gsub, "FeatureVariations", None)
    ) == ot_semantics(
        getattr(upstream_gsub, "FeatureVariations", None)
    )


def assert_common_overlay_invariants(upstream: TTFont, fork: TTFont):
    upstream_order = upstream.getGlyphOrder()
    available_sources = [
        source for source in TABULAR_SOURCES if source in upstream_order
    ]
    tabular_glyphs = [f"{source}.tf" for source in available_sources]
    expected_added = tabular_glyphs + MATH_GLYPHS + SS02_GLYPHS
    assert fork.getGlyphOrder() == upstream_order + expected_added

    expected_cmap = dict(upstream.getBestCmap())
    expected_cmap.update(MATH_CMAP)
    assert fork.getBestCmap() == expected_cmap

    for glyph_name in upstream_order:
        assert glyph_semantics(upstream["glyf"][glyph_name]) == glyph_semantics(
            fork["glyf"][glyph_name]
        ), glyph_name
        assert upstream["hmtx"][glyph_name] == fork["hmtx"][glyph_name], glyph_name

    for source in available_sources:
        alternate = f"{source}.tf"
        glyph = fork["glyf"][alternate]
        assert glyph.isComposite()
        assert tuple(c.glyphName for c in glyph.components) == (source,)
    for glyph_name, components in MATH_COMPONENTS.items():
        glyph = fork["glyf"][glyph_name]
        assert glyph.isComposite()
        assert tuple(c.glyphName for c in glyph.components) == components
    foot = fork["glyf"][SS02_FOOT]
    assert not foot.isComposite()
    assert foot.numberOfContours == 1
    assert len(foot.coordinates) == 4
    assert fork["hmtx"][SS02_FOOT][0] == fork["hmtx"]["l"][0]
    for source, target in SS02_TARGETS.items():
        assert_ss02_target_recipe(fork, source, target)
        assert fork["hmtx"][target][0] == upstream["hmtx"][source][0]

    upstream_gsub = upstream["GSUB"].table
    fork_gsub = fork["GSUB"].table
    assert len(fork_gsub.LookupList.Lookup) == len(
        upstream_gsub.LookupList.Lookup
    ) + 2
    for index, lookup in enumerate(upstream_gsub.LookupList.Lookup):
        assert compile_lookup(lookup, upstream) == compile_lookup(
            fork_gsub.LookupList.Lookup[index], fork
        ), index
    upstream_features = [
        record.FeatureTag for record in upstream_gsub.FeatureList.FeatureRecord
    ]
    fork_features = [
        record.FeatureTag for record in fork_gsub.FeatureList.FeatureRecord
    ]
    assert fork_features == upstream_features + ["ss02", "tnum"]
    assert ss02_mapping(fork) == SS02_TARGETS
    assert tnum_mapping(fork) == {
        source: f"{source}.tf" for source in available_sources
    }
    assert_gsub_feature_overlay(upstream, fork)
    assert_gdef_ss02_overlay(upstream, fork)
    assert_gpos_ss02_overlay(upstream, fork)

    upstream_os2 = upstream["OS/2"]
    fork_os2 = fork["OS/2"]
    for field in (
        "version",
        "usWeightClass",
        "usWidthClass",
        "fsType",
        "ySubscriptXSize",
        "ySubscriptYSize",
        "ySubscriptXOffset",
        "ySubscriptYOffset",
        "ySuperscriptXSize",
        "ySuperscriptYSize",
        "ySuperscriptXOffset",
        "ySuperscriptYOffset",
        "yStrikeoutSize",
        "yStrikeoutPosition",
        "sFamilyClass",
        "achVendID",
        "fsSelection",
        "sTypoAscender",
        "sTypoDescender",
        "sTypoLineGap",
        "usWinDescent",
        "ulCodePageRange1",
        "ulCodePageRange2",
        "sxHeight",
        "sCapHeight",
        "usDefaultChar",
        "usBreakChar",
        "usMaxContext",
    ):
        assert getattr(upstream_os2, field) == getattr(fork_os2, field), field
    assert vars(upstream_os2.panose) == vars(fork_os2.panose)
    for field in (
        "ulUnicodeRange1",
        "ulUnicodeRange2",
        "ulUnicodeRange3",
        "ulUnicodeRange4",
    ):
        upstream_bits = getattr(upstream_os2, field)
        fork_bits = getattr(fork_os2, field)
        assert fork_bits | upstream_bits == fork_bits
    assert fork_os2.usFirstCharIndex == min(
        cp for cp in fork.getBestCmap() if cp <= 0xFFFF
    )
    assert fork_os2.usLastCharIndex == max(
        cp for cp in fork.getBestCmap() if cp <= 0xFFFF
    )
    expected_avg_width = fork_os2.xAvgCharWidth
    fork_os2.recalcAvgCharWidth(fork)
    assert fork_os2.xAvgCharWidth == expected_avg_width
    assert upstream_os2.usWinAscent == 1150
    assert fork_os2.usWinAscent == 1159

    name = fork["name"]
    assert name.getDebugName(1).startswith("Pivot Sans")
    assert name.getDebugName(5) == "Version 1.200"
    assert name.getDebugName(6).startswith("PivotSans-")
    assert name.getDebugName(8) == "Beacon Bay Technologies, Inc."
    assert "Olivia King" in name.getDebugName(9)
    assert name.getDebugName(11) == "https://pivotsans.com"
    assert name.getDebugName(12) == "https://www.oliviaking.com"
    assert "Inclusive Sans Project Authors" in name.getDebugName(0)
    assert "Beacon Bay Technologies, Inc." in name.getDebugName(0)

    upstream_head = upstream["head"]
    fork_head = fork["head"]
    for field, value in vars(upstream_head).items():
        if field.startswith("_") or field in {
            "checkSumAdjustment",
            "fontRevision",
            "flags",
            "modified",
            "xMax",
            "xMin",
            "yMax",
            "yMin",
        }:
            continue
        assert getattr(fork_head, field) == value, field
    assert fork_head.flags & ~0x02 == upstream_head.flags & ~0x02
    assert fork_head.fontRevision == pytest.approx(1.200, abs=1 / 65536)
    assert fork_head.modified == 3_868_128_000


@pytest.mark.parametrize("upstream_path,fork_path", VARIABLE_PAIRS)
def test_variable_overlay_purity(upstream_path, fork_path):
    upstream = TTFont(upstream_path)
    fork = TTFont(fork_path)
    assert_common_overlay_invariants(upstream, fork)

    upstream_order = upstream.getGlyphOrder()
    for glyph_name in upstream_order:
        assert tuple_variations(
            upstream["gvar"].variations.get(glyph_name, [])
        ) == tuple_variations(
            fork["gvar"].variations.get(glyph_name, [])
        ), glyph_name
        assert (
            upstream["HVAR"].table.AdvWidthMap.mapping.get(glyph_name)
            == fork["HVAR"].table.AdvWidthMap.mapping.get(glyph_name)
        ), glyph_name

    available_sources = [
        source for source in TABULAR_SOURCES if source in upstream_order
    ]
    expected_added = [
        *[f"{source}.tf" for source in available_sources],
        *MATH_GLYPHS,
        *SS02_GLYPHS,
    ]
    assert set(fork["gvar"].variations) - set(
        upstream["gvar"].variations
    ) == set(expected_added)
    for source, target in SS02_TARGETS.items():
        assert (
            fork["HVAR"].table.AdvWidthMap.mapping[target]
            == upstream["HVAR"].table.AdvWidthMap.mapping[source]
        )
        assert len(fork["gvar"].variations[target]) == 2
        assert all(
            len(variation.coordinates)
            == len(fork["glyf"][target].components) + 4
            for variation in fork["gvar"].variations[target]
        )
    assert (
        fork["HVAR"].table.AdvWidthMap.mapping[SS02_FOOT]
        == upstream["HVAR"].table.AdvWidthMap.mapping["l"]
    )
    assert len(fork["gvar"].variations[SS02_FOOT]) == 2

    for weight in (300, 400, 700):
        instance = instantiateVariableFont(
            TTFont(fork_path),
            {"wght": weight},
            inplace=False,
            optimize=False,
            updateFontNames=False,
        )
        for source, target in SS02_TARGETS.items():
            assert_ss02_target_recipe(instance, source, target)
            assert (
                instance["hmtx"][target][0]
                == instance["hmtx"][source][0]
            )

    upstream_var_data = upstream["HVAR"].table.VarStore.VarData[0]
    fork_var_data = fork["HVAR"].table.VarStore.VarData[0]
    assert fork_var_data.Item[: upstream_var_data.ItemCount] == (
        upstream_var_data.Item
    )
    assert fork_var_data.ItemCount == upstream_var_data.ItemCount + 2
    assert fork_var_data.NumShorts == upstream_var_data.NumShorts
    assert fork_var_data.Item[-2:] == [[8, 17], [-13, 104]]

    allowed_changed = {
        "glyf",
        "gvar",
        "loca",
        "hmtx",
        "HVAR",
        "GSUB",
        "GDEF",
        "GPOS",
        "cmap",
        "name",
        "head",
        "post",
        "maxp",
        "hhea",
        "OS/2",
    }
    for tag in sorted(set(upstream.keys()) | set(fork.keys())):
        if tag == "GlyphOrder" or tag in allowed_changed:
            continue
        assert upstream.getTableData(tag) == fork.getTableData(tag), tag


@pytest.mark.parametrize("upstream_path,fork_path", STATIC_PAIRS)
def test_static_overlay_purity(upstream_path, fork_path):
    upstream = TTFont(upstream_path)
    fork = TTFont(fork_path)
    assert_common_overlay_invariants(upstream, fork)

    allowed_changed = {
        "glyf",
        "loca",
        "hmtx",
        "GSUB",
        "GDEF",
        "GPOS",
        "cmap",
        "name",
        "head",
        "post",
        "maxp",
        "hhea",
        "OS/2",
    }
    for tag in sorted(set(upstream.keys()) | set(fork.keys())):
        if tag == "GlyphOrder" or tag in allowed_changed:
            continue
        assert upstream.getTableData(tag) == fork.getTableData(tag), tag


@pytest.mark.parametrize("variable_path,pin_path,weight", PIN_CASES)
def test_custom_pin_is_exact_variable_instantiation_except_pin_metadata(
    variable_path, pin_path, weight
):
    expected = instantiateVariableFont(
        TTFont(variable_path),
        {"wght": weight},
        inplace=False,
        updateFontNames=False,
    )
    # The glyf format stores integer coordinates. Round-trip the independently
    # instantiated expectation before comparing it with the saved pin.
    stream = BytesIO()
    expected.save(stream)
    stream.seek(0)
    expected = TTFont(stream)
    pin = TTFont(pin_path)
    pruned = {
        "uni0E3F.BRACKET.varAlt01",
        "cent.BRACKET.varAlt01",
        "dollar.BRACKET.varAlt01",
        "dollar.BRACKET.varAlt01.tf",
    }
    assert set(expected.getGlyphOrder()) - set(pin.getGlyphOrder()) == pruned
    assert pin.getGlyphOrder() == [
        glyph_name
        for glyph_name in expected.getGlyphOrder()
        if glyph_name not in pruned
    ]
    assert pin.getBestCmap() == expected.getBestCmap()
    for glyph_name in pin.getGlyphOrder():
        assert glyph_semantics(pin["glyf"][glyph_name]) == glyph_semantics(
            expected["glyf"][glyph_name]
        ), glyph_name
        assert pin["hmtx"][glyph_name] == expected["hmtx"][glyph_name], glyph_name

    assert tnum_mapping(pin) == {
        source: f"{source}.tf" for source in TABULAR_SOURCES[:-1]
    }
    assert ss02_mapping(pin) == SS02_TARGETS
    assert [
        record.FeatureTag
        for record in pin["GSUB"].table.FeatureList.FeatureRecord
    ] == [
        record.FeatureTag
        for record in expected["GSUB"].table.FeatureList.FeatureRecord
    ]


def main() -> int:
    return pytest.main([str(Path(__file__).resolve()), "-q"])


if __name__ == "__main__":
    raise SystemExit(main())
