123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569 |
- from fontTools.ttLib import TTFont
- from fontTools.misc.testTools import getXML
- from axisregistry import (
- build_name_table,
- build_filename,
- build_fvar_instances,
- build_stat,
- build_variations_ps_name,
- _fvar_dflts,
- _fvar_instance_collisions,
- )
- import pytest
- import os
- CWD = os.path.dirname(__file__)
- DATA_DIR = os.path.join(CWD, "data")
- roboto_flex_fp = os.path.join(
- DATA_DIR,
- "RobotoFlex[GRAD,XOPQ,XTRA,YOPQ,YTAS,YTDE,YTFI,YTLC,YTUC,opsz,slnt,wdth,wght].ttf",
- )
- mavenpro_fp = os.path.join(DATA_DIR, "MavenPro-Regular.ttf")
- opensans_roman_fp = os.path.join(DATA_DIR, "OpenSans[wdth,wght].ttf")
- opensans_italic_fp = os.path.join(DATA_DIR, "OpenSans-Italic[wdth,wght].ttf")
- opensans_cond_roman_fp = os.path.join(DATA_DIR, "OpenSansCondensed[wght].ttf")
- opensans_cond_italic_fp = os.path.join(DATA_DIR, "OpenSansCondensed-Italic[wght].ttf")
- wonky_fp = os.path.join(DATA_DIR, "Wonky[wdth,wght].ttf")
- playfair_fp = os.path.join(DATA_DIR, "Playfair[opsz,wdth,wght].ttf")
- wavefont_fp = os.path.join(DATA_DIR, "Wavefont[ROND,YALN,wght].ttf")
- @pytest.fixture
- def static_font():
- return TTFont(mavenpro_fp)
- def _test_names(ttFont, expected):
- name_table = ttFont["name"]
- for k, v in expected.items():
- record = name_table.getName(*k)
- if not record:
- assert record == v, f"{k} record is incorrect, expected {v} got {record}"
- else:
- record_string = record.toUnicode()
- assert (
- record_string == v
- ), f"{k} record is incorrect, expected {v} got {record}"
- @pytest.mark.parametrize(
- "fp, family_name, style_name, siblings, expected",
- [
- # Maven Pro Regular
- (
- mavenpro_fp,
- "Maven Pro",
- "Regular",
- [],
- {
- (1, 3, 1, 0x409): "Maven Pro",
- (2, 3, 1, 0x409): "Regular",
- (3, 3, 1, 0x409): "2.003;NONE;MavenPro-Regular",
- (4, 3, 1, 0x409): "Maven Pro Regular",
- (6, 3, 1, 0x409): "MavenPro-Regular",
- (16, 3, 1, 0x409): None,
- (17, 3, 1, 0x409): None,
- },
- ),
- # Maven Pro Italic
- (
- mavenpro_fp,
- "Maven Pro",
- "Italic",
- [],
- {
- (1, 3, 1, 0x409): "Maven Pro",
- (2, 3, 1, 0x409): "Italic",
- (3, 3, 1, 0x409): "2.003;NONE;MavenPro-Italic",
- (4, 3, 1, 0x409): "Maven Pro Italic",
- (6, 3, 1, 0x409): "MavenPro-Italic",
- (16, 3, 1, 0x409): None,
- (17, 3, 1, 0x409): None,
- },
- ),
- # Maven Pro Bold
- (
- mavenpro_fp,
- "Maven Pro",
- "Bold",
- [],
- {
- (1, 3, 1, 0x409): "Maven Pro",
- (2, 3, 1, 0x409): "Bold",
- (3, 3, 1, 0x409): "2.003;NONE;MavenPro-Bold",
- (4, 3, 1, 0x409): "Maven Pro Bold",
- (6, 3, 1, 0x409): "MavenPro-Bold",
- (16, 3, 1, 0x409): None,
- (17, 3, 1, 0x409): None,
- },
- ),
- # Maven Pro Bold Italic
- (
- mavenpro_fp,
- "Maven Pro",
- "Bold Italic",
- [],
- {
- (1, 3, 1, 0x409): "Maven Pro",
- (2, 3, 1, 0x409): "Bold Italic",
- (3, 3, 1, 0x409): "2.003;NONE;MavenPro-BoldItalic",
- (4, 3, 1, 0x409): "Maven Pro Bold Italic",
- (6, 3, 1, 0x409): "MavenPro-BoldItalic",
- (16, 3, 1, 0x409): None,
- (17, 3, 1, 0x409): None,
- },
- ),
- # Maven Pro Black
- (
- mavenpro_fp,
- "Maven Pro",
- "Black",
- [],
- {
- (1, 3, 1, 0x409): "Maven Pro Black",
- (2, 3, 1, 0x409): "Regular",
- (3, 3, 1, 0x409): "2.003;NONE;MavenPro-Black",
- (4, 3, 1, 0x409): "Maven Pro Black",
- (6, 3, 1, 0x409): "MavenPro-Black",
- (16, 3, 1, 0x409): "Maven Pro",
- (17, 3, 1, 0x409): "Black",
- },
- ),
- # Maven Pro Black Italic
- (
- mavenpro_fp,
- "Maven Pro",
- "Black Italic",
- [],
- {
- (1, 3, 1, 0x409): "Maven Pro Black",
- (2, 3, 1, 0x409): "Italic",
- (3, 3, 1, 0x409): "2.003;NONE;MavenPro-BlackItalic",
- (4, 3, 1, 0x409): "Maven Pro Black Italic",
- (6, 3, 1, 0x409): "MavenPro-BlackItalic",
- (16, 3, 1, 0x409): "Maven Pro",
- (17, 3, 1, 0x409): "Black Italic",
- },
- ),
- # Maven Pro ExtraLight Italic
- (
- mavenpro_fp,
- "Maven Pro",
- "ExtraLight Italic",
- [],
- {
- (1, 3, 1, 0x409): "Maven Pro ExtraLight",
- (2, 3, 1, 0x409): "Italic",
- (3, 3, 1, 0x409): "2.003;NONE;MavenPro-ExtraLightItalic",
- (4, 3, 1, 0x409): "Maven Pro ExtraLight Italic",
- (6, 3, 1, 0x409): "MavenPro-ExtraLightItalic",
- (16, 3, 1, 0x409): "Maven Pro",
- (17, 3, 1, 0x409): "ExtraLight Italic",
- },
- ),
- # check non-weight styles get appended to family name
- # Maven Pro UltraExpanded Regular
- (
- mavenpro_fp,
- "Maven Pro",
- "UltraExpanded Regular",
- [],
- {
- (1, 3, 1, 0x409): "Maven Pro UltraExpanded",
- (2, 3, 1, 0x409): "Regular",
- (3, 3, 1, 0x409): "2.003;NONE;MavenProUltraExpanded-Regular",
- (4, 3, 1, 0x409): "Maven Pro UltraExpanded Regular",
- (6, 3, 1, 0x409): "MavenProUltraExpanded-Regular",
- (16, 3, 1, 0x409): None,
- (17, 3, 1, 0x409): None,
- },
- ),
- # Maven Pro Condensed ExtraLight Italic
- (
- mavenpro_fp,
- "Maven Pro",
- "Condensed ExtraLight Italic",
- [],
- {
- (1, 3, 1, 0x409): "Maven Pro Condensed ExtraLight",
- (2, 3, 1, 0x409): "Italic",
- (3, 3, 1, 0x409): "2.003;NONE;MavenProCondensed-ExtraLightItalic",
- (4, 3, 1, 0x409): "Maven Pro Condensed ExtraLight Italic",
- (6, 3, 1, 0x409): "MavenProCondensed-ExtraLightItalic",
- (16, 3, 1, 0x409): "Maven Pro Condensed",
- (17, 3, 1, 0x409): "ExtraLight Italic",
- },
- ),
- ## VFs
- # Open Sans Roman
- (
- opensans_roman_fp,
- "Open Sans",
- None,
- [opensans_italic_fp, opensans_cond_roman_fp, opensans_cond_italic_fp],
- {
- (1, 3, 1, 0x409): "Open Sans",
- (2, 3, 1, 0x409): "Regular",
- (3, 3, 1, 0x409): "3.000;GOOG;OpenSans-Regular",
- (4, 3, 1, 0x409): "Open Sans Regular",
- (6, 3, 1, 0x409): "OpenSans-Regular",
- (16, 3, 1, 0x409): None,
- (17, 3, 1, 0x409): None,
- (25, 3, 1, 0x409): "OpenSans",
- },
- ),
- # Open Sans Italic
- (
- opensans_italic_fp,
- "Open Sans",
- None,
- [opensans_roman_fp, opensans_cond_roman_fp, opensans_cond_italic_fp],
- {
- (1, 3, 1, 0x409): "Open Sans",
- (2, 3, 1, 0x409): "Italic",
- (3, 3, 1, 0x409): "3.000;GOOG;OpenSans-Italic",
- (4, 3, 1, 0x409): "Open Sans Italic",
- (6, 3, 1, 0x409): "OpenSans-Italic",
- (16, 3, 1, 0x409): None,
- (17, 3, 1, 0x409): None,
- (25, 3, 1, 0x409): "OpenSansItalic",
- },
- ),
- # Open Sans Cond Roman
- (
- opensans_cond_roman_fp,
- "Open Sans Condensed",
- None,
- [opensans_roman_fp, opensans_italic_fp, opensans_cond_italic_fp],
- {
- (1, 3, 1, 0x409): "Open Sans Condensed",
- (2, 3, 1, 0x409): "Regular",
- (3, 3, 1, 0x409): "3.000;GOOG;OpenSansCondensed-Regular",
- (4, 3, 1, 0x409): "Open Sans Condensed Regular",
- (6, 3, 1, 0x409): "OpenSansCondensed-Regular",
- (16, 3, 1, 0x409): None,
- (17, 3, 1, 0x409): None,
- (25, 3, 1, 0x409): "OpenSansCondensed",
- },
- ),
- # Open Sans Cond Italic
- (
- opensans_cond_italic_fp,
- "Open Sans Condensed",
- None,
- [opensans_roman_fp, opensans_italic_fp, opensans_cond_roman_fp],
- {
- (1, 3, 1, 0x409): "Open Sans Condensed",
- (2, 3, 1, 0x409): "Italic",
- (3, 3, 1, 0x409): "3.000;GOOG;OpenSansCondensed-Italic",
- (4, 3, 1, 0x409): "Open Sans Condensed Italic",
- (6, 3, 1, 0x409): "OpenSansCondensed-Italic",
- (16, 3, 1, 0x409): None,
- (17, 3, 1, 0x409): None,
- (25, 3, 1, 0x409): "OpenSansCondensedItalic",
- },
- ),
- # Bad names
- (
- mavenpro_fp,
- "Maven Pro",
- "Fat", # this should get appended to the family name
- [],
- {
- (1, 3, 1, 0x409): "Maven Pro Fat",
- (2, 3, 1, 0x409): "Regular",
- (3, 3, 1, 0x409): "2.003;NONE;MavenProFat-Regular",
- (4, 3, 1, 0x409): "Maven Pro Fat Regular",
- (6, 3, 1, 0x409): "MavenProFat-Regular",
- (16, 3, 1, 0x409): None,
- (17, 3, 1, 0x409): None,
- },
- ),
- (
- wonky_fp,
- "Wonky", # name exists in axis reg!
- None,
- [],
- {
- (1, 3, 1, 0x409): "Wonky",
- (2, 3, 1, 0x409): "Regular",
- (3, 3, 1, 0x409): "3.000;GOOG;Wonky-Regular",
- (4, 3, 1, 0x409): "Wonky Regular",
- (6, 3, 1, 0x409): "Wonky-Regular",
- (16, 3, 1, 0x409): None,
- (17, 3, 1, 0x409): None,
- },
- ),
- # Test opsz particle is kept.
- # Fixes https://github.com/googlefonts/axisregistry/issues/130
- (
- playfair_fp,
- "Playfair",
- None,
- [],
- {
- (1, 3, 1, 0x409): "Playfair 5pt SemiExpanded Light",
- (2, 3, 1, 0x409): "Regular",
- (3, 3, 1, 0x409): "2.000;FTH;Playfair-5ptSemiExpandedLight",
- (4, 3, 1, 0x409): "Playfair 5pt SemiExpanded Light",
- (6, 3, 1, 0x409): "Playfair-5ptSemiExpandedLight",
- (16, 3, 1, 0x409): "Playfair",
- (17, 3, 1, 0x409): "5pt SemiExpanded Light",
- },
- ),
- ],
- )
- def test_name_table(fp, family_name, style_name, siblings, expected):
- font = TTFont(fp)
- siblings = [TTFont(fp) for fp in siblings]
- build_name_table(font, family_name, style_name, siblings)
- _test_names(font, expected)
- @pytest.mark.parametrize(
- "font_fp, dflt_coords, expected",
- [
- # Condensed variants
- (
- opensans_cond_roman_fp,
- {},
- [
- ("Thin", {"wght": 100.0}),
- ("ExtraLight", {"wght": 200.0}),
- ("Light", {"wght": 300.0}),
- ("Regular", {"wght": 400.0}),
- ("Medium", {"wght": 500.0}),
- ("SemiBold", {"wght": 600.0}),
- ("Bold", {"wght": 700.0}),
- ("ExtraBold", {"wght": 800.0}),
- ],
- ),
- (
- opensans_cond_italic_fp,
- {},
- [
- ("Thin Italic", {"wght": 100.0}),
- ("ExtraLight Italic", {"wght": 200.0}),
- ("Light Italic", {"wght": 300.0}),
- ("Italic", {"wght": 400.0}),
- ("Medium Italic", {"wght": 500.0}),
- ("SemiBold Italic", {"wght": 600.0}),
- ("Bold Italic", {"wght": 700.0}),
- ("ExtraBold Italic", {"wght": 800.0}),
- ],
- ),
- # Multi axis + roman & ital with dflt coords
- (
- roboto_flex_fp,
- {},
- [
- ("Thin", {"wght": 100.0}),
- ("ExtraLight", {"wght": 200.0}),
- ("Light", {"wght": 300.0}),
- ("Regular", {"wght": 400.0}),
- ("Medium", {"wght": 500.0}),
- ("SemiBold", {"wght": 600.0}),
- ("Bold", {"wght": 700.0}),
- ("ExtraBold", {"wght": 800.0}),
- ("Black", {"wght": 900.0}),
- ("Thin Italic", {"wght": 100.0, "slnt": -10}),
- ("ExtraLight Italic", {"wght": 200.0, "slnt": -10}),
- ("Light Italic", {"wght": 300.0, "slnt": -10}),
- ("Italic", {"wght": 400.0, "slnt": -10}),
- ("Medium Italic", {"wght": 500.0, "slnt": -10}),
- ("SemiBold Italic", {"wght": 600.0, "slnt": -10}),
- ("Bold Italic", {"wght": 700.0, "slnt": -10}),
- ("ExtraBold Italic", {"wght": 800.0, "slnt": -10}),
- ("Black Italic", {"wght": 900.0, "slnt": -10}),
- ],
- ),
- # multi axis with METADATA.pb registry_default_overrides
- # https://github.com/google/fonts/blob/main/ofl/robotoflex/METADATA.pb
- (
- roboto_flex_fp,
- {
- "XOPQ": 96.0,
- "XTRA": 468.0,
- "YOPQ": 79.0,
- "YTDE": -203.0,
- "YTFI": 738.0,
- "YTLC": 514.0,
- "YTUC": 712.0,
- },
- [
- ("Thin", {"wght": 100.0}),
- ("ExtraLight", {"wght": 200.0}),
- ("Light", {"wght": 300.0}),
- ("Regular", {"wght": 400.0}),
- ("Medium", {"wght": 500.0}),
- ("SemiBold", {"wght": 600.0}),
- ("Bold", {"wght": 700.0}),
- ("ExtraBold", {"wght": 800.0}),
- ("Black", {"wght": 900.0}),
- ("Thin Italic", {"wght": 100.0, "slnt": -10}),
- ("ExtraLight Italic", {"wght": 200.0, "slnt": -10}),
- ("Light Italic", {"wght": 300.0, "slnt": -10}),
- ("Italic", {"wght": 400.0, "slnt": -10}),
- ("Medium Italic", {"wght": 500.0, "slnt": -10}),
- ("SemiBold Italic", {"wght": 600.0, "slnt": -10}),
- ("Bold Italic", {"wght": 700.0, "slnt": -10}),
- ("ExtraBold Italic", {"wght": 800.0, "slnt": -10}),
- ("Black Italic", {"wght": 900.0, "slnt": -10}),
- ],
- ),
- ],
- )
- def test_fvar_instances(font_fp, dflt_coords, expected):
- font = TTFont(font_fp)
- builder = build_fvar_instances(font, dflt_coords)
- fvar = font["fvar"]
- wght_axis = next((a for a in fvar.axes if a.axisTag == "wght"), None)
- wght_min = wght_axis.minValue
- wght_max = wght_axis.maxValue
- instances = fvar.instances
- if not dflt_coords:
- dflt_coords = {k: v["value"] for k, v in _fvar_dflts(font).items()}
- assert len(instances) == len(expected)
- for inst, (expected_name, coords) in zip(instances, expected):
- inst_name = font["name"].getName(inst.subfamilyNameID, 3, 1, 0x409).toUnicode()
- assert inst_name == expected_name
- for tag, val in coords.items():
- dflt_coords[tag] = val
- assert inst.coordinates == dflt_coords
- def dump(table, ttFont=None):
- return "\n".join(getXML(table.toXML, ttFont))
- @pytest.mark.parametrize(
- "fp, sibling_fps",
- [
- (roboto_flex_fp, []),
- (
- opensans_roman_fp,
- [opensans_italic_fp, opensans_cond_roman_fp, opensans_cond_italic_fp],
- ),
- (
- opensans_italic_fp,
- [opensans_roman_fp, opensans_cond_roman_fp, opensans_cond_italic_fp],
- ),
- (
- opensans_cond_roman_fp,
- [opensans_roman_fp, opensans_italic_fp, opensans_cond_italic_fp],
- ),
- (
- opensans_cond_italic_fp,
- [opensans_roman_fp, opensans_italic_fp, opensans_cond_roman_fp],
- ),
- (wonky_fp, []),
- # don't add a linkedValue for Regular to Bold since Bold doesn't exist
- # Fixes https://github.com/googlefonts/axisregistry/issues/104
- (wavefont_fp, []),
- ],
- )
- def test_stat(fp, sibling_fps):
- font = TTFont(fp)
- siblings = [TTFont(f) for f in sibling_fps]
- build_stat(font, siblings)
- stat_fp = fp.replace(".ttf", "_STAT.ttx")
- ## output good files
- # with open(stat_fp, "w") as doc:
- # got = dump(font["STAT"], font)
- # doc.write(got)
- with open(stat_fp) as doc:
- expected = doc.read()
- got = dump(font["STAT"], font)
- assert got == expected
- @pytest.mark.parametrize(
- "fp, expected",
- [
- (mavenpro_fp, "MavenPro-Regular.ttf"),
- (opensans_roman_fp, "OpenSans[wdth,wght].ttf"),
- (opensans_italic_fp, "OpenSans-Italic[wdth,wght].ttf"),
- (opensans_cond_roman_fp, "OpenSansCondensed[wght].ttf"),
- (opensans_cond_italic_fp, "OpenSansCondensed-Italic[wght].ttf"),
- ],
- )
- def test_filename(fp, expected):
- font = TTFont(fp)
- assert build_filename(font) == expected
- @pytest.mark.parametrize(
- "fp, sibling_fps, result",
- [
- (roboto_flex_fp, [], False),
- (
- opensans_roman_fp,
- [opensans_italic_fp, opensans_cond_roman_fp, opensans_cond_italic_fp],
- True,
- ),
- (
- opensans_italic_fp,
- [opensans_roman_fp, opensans_cond_roman_fp, opensans_cond_italic_fp],
- True,
- ),
- (
- opensans_cond_roman_fp,
- [opensans_roman_fp, opensans_italic_fp, opensans_cond_italic_fp],
- True,
- ),
- (
- opensans_cond_italic_fp,
- [opensans_roman_fp, opensans_italic_fp, opensans_cond_roman_fp],
- True,
- ),
- (opensans_roman_fp, [opensans_cond_roman_fp], True),
- (opensans_roman_fp, [opensans_italic_fp], False),
- (wonky_fp, [], False),
- ],
- )
- def test_fvar_instance_collisions(fp, sibling_fps, result):
- ttFont = TTFont(fp)
- siblings = [TTFont(fp) for fp in sibling_fps]
- assert _fvar_instance_collisions(ttFont, siblings) == result
- @pytest.mark.parametrize(
- "fp, result",
- [
- (roboto_flex_fp, "RobotoFlex"),
- (opensans_roman_fp, "OpenSans"),
- (opensans_italic_fp, "OpenSansItalic"),
- (opensans_cond_roman_fp, "OpenSansCondensed"),
- (opensans_cond_italic_fp, "OpenSansCondensedItalic"),
- (wonky_fp, "Wonky"),
- ],
- )
- def test_build_variations_ps_name(fp, result):
- ttFont = TTFont(fp)
- build_variations_ps_name(ttFont)
- variation_ps_name = ttFont["name"].getName(25, 3, 1, 0x409).toUnicode()
- assert variation_ps_name == result
- @pytest.mark.parametrize(
- "fp, style_name, result",
- [
- (mavenpro_fp, "Regular", 400),
- (mavenpro_fp, "Italic", 400),
- (mavenpro_fp, "Black Italic", 900),
- (mavenpro_fp, "ExtraBold Italic", 800),
- (mavenpro_fp, "ExtraBold", 800),
- (mavenpro_fp, "Bold", 700),
- (mavenpro_fp, "Bold Italic", 700),
- (mavenpro_fp, "Thin Italic", 100),
- (mavenpro_fp, "Thin", 100),
- ],
- )
- def test_us_weight_class(fp, style_name, result):
- ttFont = TTFont(fp)
- build_name_table(ttFont, style_name=style_name)
- assert ttFont["OS/2"].usWeightClass == result
|