diff options
Diffstat (limited to 'Tests/ttLib/ttFont_test.py')
-rw-r--r-- | Tests/ttLib/ttFont_test.py | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/Tests/ttLib/ttFont_test.py b/Tests/ttLib/ttFont_test.py index 47cedeb7..e0e82b24 100644 --- a/Tests/ttLib/ttFont_test.py +++ b/Tests/ttLib/ttFont_test.py @@ -1,6 +1,15 @@ import io +import os +import re +import random +from fontTools.feaLib.builder import addOpenTypeFeaturesFromString from fontTools.ttLib import TTFont, newTable, registerCustomTableClass, unregisterCustomTableClass from fontTools.ttLib.tables.DefaultTable import DefaultTable +from fontTools.ttLib.tables._c_m_a_p import CmapSubtable +import pytest + + +DATA_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), "data") class CustomTableClass(DefaultTable): @@ -20,6 +29,13 @@ table_C_U_S_T_ = CustomTableClass # alias for testing TABLETAG = "CUST" +def normalize_TTX(string): + string = re.sub(' ttLibVersion=".*"', "", string) + string = re.sub('checkSumAdjustment value=".*"', "", string) + string = re.sub('modified value=".*"', "", string) + return string + + def test_registerCustomTableClass(): font = TTFont() font[TABLETAG] = newTable(TABLETAG) @@ -46,3 +62,153 @@ def test_registerCustomTableClassStandardName(): assert font[TABLETAG].compile(font) == b"\x04\x05\x06" finally: unregisterCustomTableClass(TABLETAG) + + +ttxTTF = r"""<?xml version="1.0" encoding="UTF-8"?> +<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.9.0"> + <hmtx> + <mtx name=".notdef" width="300" lsb="0"/> + </hmtx> +</ttFont> +""" + + +ttxOTF = """<?xml version="1.0" encoding="UTF-8"?> +<ttFont sfntVersion="OTTO" ttLibVersion="4.9.0"> + <hmtx> + <mtx name=".notdef" width="300" lsb="0"/> + </hmtx> +</ttFont> +""" + + +def test_sfntVersionFromTTX(): + # https://github.com/fonttools/fonttools/issues/2370 + font = TTFont() + assert font.sfntVersion == "\x00\x01\x00\x00" + ttx = io.StringIO(ttxOTF) + # Font is "empty", TTX file will determine sfntVersion + font.importXML(ttx) + assert font.sfntVersion == "OTTO" + ttx = io.StringIO(ttxTTF) + # Font is not "empty", sfntVersion in TTX file will be ignored + font.importXML(ttx) + assert font.sfntVersion == "OTTO" + + +def test_virtualGlyphId(): + otfpath = os.path.join(DATA_DIR, "TestVGID-Regular.otf") + ttxpath = os.path.join(DATA_DIR, "TestVGID-Regular.ttx") + + otf = TTFont(otfpath) + + ttx = TTFont() + ttx.importXML(ttxpath) + + with open(ttxpath, encoding="utf-8") as fp: + xml = normalize_TTX(fp.read()).splitlines() + + for font in (otf, ttx): + GSUB = font["GSUB"].table + assert GSUB.LookupList.LookupCount == 37 + lookup = GSUB.LookupList.Lookup[32] + assert lookup.LookupType == 8 + subtable = lookup.SubTable[0] + assert subtable.LookAheadGlyphCount == 1 + lookahead = subtable.LookAheadCoverage[0] + assert len(lookahead.glyphs) == 46 + assert "glyph00453" in lookahead.glyphs + + out = io.StringIO() + font.saveXML(out) + outxml = normalize_TTX(out.getvalue()).splitlines() + assert xml == outxml + + +def test_setGlyphOrder_also_updates_glyf_glyphOrder(): + # https://github.com/fonttools/fonttools/issues/2060#issuecomment-1063932428 + font = TTFont() + font.importXML(os.path.join(DATA_DIR, "TestTTF-Regular.ttx")) + current_order = font.getGlyphOrder() + + assert current_order == font["glyf"].glyphOrder + + new_order = list(current_order) + while new_order == current_order: + random.shuffle(new_order) + + font.setGlyphOrder(new_order) + + assert font.getGlyphOrder() == new_order + assert font["glyf"].glyphOrder == new_order + + +@pytest.mark.parametrize("lazy", [None, True, False]) +def test_ensureDecompiled(lazy): + # test that no matter the lazy value, ensureDecompiled decompiles all tables + font = TTFont() + font.importXML(os.path.join(DATA_DIR, "TestTTF-Regular.ttx")) + # test font has no OTL so we add some, as an example of otData-driven tables + addOpenTypeFeaturesFromString( + font, + """ + feature calt { + sub period' period' period' space by ellipsis; + } calt; + + feature dist { + pos period period -30; + } dist; + """ + ) + # also add an additional cmap subtable that will be lazily-loaded + cm = CmapSubtable.newSubtable(14) + cm.platformID = 0 + cm.platEncID = 5 + cm.language = 0 + cm.cmap = {} + cm.uvsDict = {0xFE00: [(0x002e, None)]} + font["cmap"].tables.append(cm) + + # save and reload, potentially lazily + buf = io.BytesIO() + font.save(buf) + buf.seek(0) + font = TTFont(buf, lazy=lazy) + + # check no table is loaded until/unless requested, no matter the laziness + for tag in font.keys(): + assert not font.isLoaded(tag) + + if lazy is not False: + # additional cmap doesn't get decompiled automatically unless lazy=False; + # can't use hasattr or else cmap's maginc __getattr__ kicks in... + cm = next(st for st in font["cmap"].tables if st.__dict__["format"] == 14) + assert cm.data is not None + assert "uvsDict" not in cm.__dict__ + # glyf glyphs are not expanded unless lazy=False + assert font["glyf"].glyphs["period"].data is not None + assert not hasattr(font["glyf"].glyphs["period"], "coordinates") + + if lazy is True: + # OTL tables hold a 'reader' to lazily load when lazy=True + assert "reader" in font["GSUB"].table.LookupList.__dict__ + assert "reader" in font["GPOS"].table.LookupList.__dict__ + + font.ensureDecompiled() + + # all tables are decompiled now + for tag in font.keys(): + assert font.isLoaded(tag) + # including the additional cmap + cm = next(st for st in font["cmap"].tables if st.__dict__["format"] == 14) + assert cm.data is None + assert "uvsDict" in cm.__dict__ + # expanded glyf glyphs lost the 'data' attribute + assert not hasattr(font["glyf"].glyphs["period"], "data") + assert hasattr(font["glyf"].glyphs["period"], "coordinates") + # and OTL tables have read their 'reader' + assert "reader" not in font["GSUB"].table.LookupList.__dict__ + assert "Lookup" in font["GSUB"].table.LookupList.__dict__ + assert "reader" not in font["GPOS"].table.LookupList.__dict__ + assert "Lookup" in font["GPOS"].table.LookupList.__dict__ |