aboutsummaryrefslogtreecommitdiff
path: root/Tests/ttLib/ttFont_test.py
diff options
context:
space:
mode:
Diffstat (limited to 'Tests/ttLib/ttFont_test.py')
-rw-r--r--Tests/ttLib/ttFont_test.py166
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__