xref: /aosp_15_r20/external/fonttools/Tests/ttLib/ttFont_test.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughesimport io
2*e1fe3e4aSElliott Hughesimport os
3*e1fe3e4aSElliott Hughesimport re
4*e1fe3e4aSElliott Hughesimport random
5*e1fe3e4aSElliott Hughesimport tempfile
6*e1fe3e4aSElliott Hughesfrom fontTools.feaLib.builder import addOpenTypeFeaturesFromString
7*e1fe3e4aSElliott Hughesfrom fontTools.ttLib import (
8*e1fe3e4aSElliott Hughes    TTFont,
9*e1fe3e4aSElliott Hughes    TTLibError,
10*e1fe3e4aSElliott Hughes    newTable,
11*e1fe3e4aSElliott Hughes    registerCustomTableClass,
12*e1fe3e4aSElliott Hughes    unregisterCustomTableClass,
13*e1fe3e4aSElliott Hughes)
14*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.standardGlyphOrder import standardGlyphOrder
15*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables.DefaultTable import DefaultTable
16*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables._c_m_a_p import CmapSubtable
17*e1fe3e4aSElliott Hughesimport pytest
18*e1fe3e4aSElliott Hughes
19*e1fe3e4aSElliott Hughes
20*e1fe3e4aSElliott HughesDATA_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), "data")
21*e1fe3e4aSElliott Hughes
22*e1fe3e4aSElliott Hughes
23*e1fe3e4aSElliott Hughesclass CustomTableClass(DefaultTable):
24*e1fe3e4aSElliott Hughes    def decompile(self, data, ttFont):
25*e1fe3e4aSElliott Hughes        self.numbers = list(data)
26*e1fe3e4aSElliott Hughes
27*e1fe3e4aSElliott Hughes    def compile(self, ttFont):
28*e1fe3e4aSElliott Hughes        return bytes(self.numbers)
29*e1fe3e4aSElliott Hughes
30*e1fe3e4aSElliott Hughes    # not testing XML read/write
31*e1fe3e4aSElliott Hughes
32*e1fe3e4aSElliott Hughes
33*e1fe3e4aSElliott Hughestable_C_U_S_T_ = CustomTableClass  # alias for testing
34*e1fe3e4aSElliott Hughes
35*e1fe3e4aSElliott Hughes
36*e1fe3e4aSElliott HughesTABLETAG = "CUST"
37*e1fe3e4aSElliott Hughes
38*e1fe3e4aSElliott Hughes
39*e1fe3e4aSElliott Hughesdef normalize_TTX(string):
40*e1fe3e4aSElliott Hughes    string = re.sub(' ttLibVersion=".*"', "", string)
41*e1fe3e4aSElliott Hughes    string = re.sub('checkSumAdjustment value=".*"', "", string)
42*e1fe3e4aSElliott Hughes    string = re.sub('modified value=".*"', "", string)
43*e1fe3e4aSElliott Hughes    return string
44*e1fe3e4aSElliott Hughes
45*e1fe3e4aSElliott Hughes
46*e1fe3e4aSElliott Hughesdef test_registerCustomTableClass():
47*e1fe3e4aSElliott Hughes    font = TTFont()
48*e1fe3e4aSElliott Hughes    font[TABLETAG] = newTable(TABLETAG)
49*e1fe3e4aSElliott Hughes    font[TABLETAG].data = b"\x00\x01\xff"
50*e1fe3e4aSElliott Hughes    f = io.BytesIO()
51*e1fe3e4aSElliott Hughes    font.save(f)
52*e1fe3e4aSElliott Hughes    f.seek(0)
53*e1fe3e4aSElliott Hughes    assert font[TABLETAG].data == b"\x00\x01\xff"
54*e1fe3e4aSElliott Hughes    registerCustomTableClass(TABLETAG, "ttFont_test", "CustomTableClass")
55*e1fe3e4aSElliott Hughes    try:
56*e1fe3e4aSElliott Hughes        font = TTFont(f)
57*e1fe3e4aSElliott Hughes        assert font[TABLETAG].numbers == [0, 1, 255]
58*e1fe3e4aSElliott Hughes        assert font[TABLETAG].compile(font) == b"\x00\x01\xff"
59*e1fe3e4aSElliott Hughes    finally:
60*e1fe3e4aSElliott Hughes        unregisterCustomTableClass(TABLETAG)
61*e1fe3e4aSElliott Hughes
62*e1fe3e4aSElliott Hughes
63*e1fe3e4aSElliott Hughesdef test_registerCustomTableClassStandardName():
64*e1fe3e4aSElliott Hughes    registerCustomTableClass(TABLETAG, "ttFont_test")
65*e1fe3e4aSElliott Hughes    try:
66*e1fe3e4aSElliott Hughes        font = TTFont()
67*e1fe3e4aSElliott Hughes        font[TABLETAG] = newTable(TABLETAG)
68*e1fe3e4aSElliott Hughes        font[TABLETAG].numbers = [4, 5, 6]
69*e1fe3e4aSElliott Hughes        assert font[TABLETAG].compile(font) == b"\x04\x05\x06"
70*e1fe3e4aSElliott Hughes    finally:
71*e1fe3e4aSElliott Hughes        unregisterCustomTableClass(TABLETAG)
72*e1fe3e4aSElliott Hughes
73*e1fe3e4aSElliott Hughes
74*e1fe3e4aSElliott HughesttxTTF = r"""<?xml version="1.0" encoding="UTF-8"?>
75*e1fe3e4aSElliott Hughes<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.9.0">
76*e1fe3e4aSElliott Hughes  <hmtx>
77*e1fe3e4aSElliott Hughes    <mtx name=".notdef" width="300" lsb="0"/>
78*e1fe3e4aSElliott Hughes  </hmtx>
79*e1fe3e4aSElliott Hughes</ttFont>
80*e1fe3e4aSElliott Hughes"""
81*e1fe3e4aSElliott Hughes
82*e1fe3e4aSElliott Hughes
83*e1fe3e4aSElliott HughesttxOTF = """<?xml version="1.0" encoding="UTF-8"?>
84*e1fe3e4aSElliott Hughes<ttFont sfntVersion="OTTO" ttLibVersion="4.9.0">
85*e1fe3e4aSElliott Hughes  <hmtx>
86*e1fe3e4aSElliott Hughes    <mtx name=".notdef" width="300" lsb="0"/>
87*e1fe3e4aSElliott Hughes  </hmtx>
88*e1fe3e4aSElliott Hughes</ttFont>
89*e1fe3e4aSElliott Hughes"""
90*e1fe3e4aSElliott Hughes
91*e1fe3e4aSElliott Hughes
92*e1fe3e4aSElliott Hughesdef test_sfntVersionFromTTX():
93*e1fe3e4aSElliott Hughes    # https://github.com/fonttools/fonttools/issues/2370
94*e1fe3e4aSElliott Hughes    font = TTFont()
95*e1fe3e4aSElliott Hughes    assert font.sfntVersion == "\x00\x01\x00\x00"
96*e1fe3e4aSElliott Hughes    ttx = io.StringIO(ttxOTF)
97*e1fe3e4aSElliott Hughes    # Font is "empty", TTX file will determine sfntVersion
98*e1fe3e4aSElliott Hughes    font.importXML(ttx)
99*e1fe3e4aSElliott Hughes    assert font.sfntVersion == "OTTO"
100*e1fe3e4aSElliott Hughes    ttx = io.StringIO(ttxTTF)
101*e1fe3e4aSElliott Hughes    # Font is not "empty", sfntVersion in TTX file will be ignored
102*e1fe3e4aSElliott Hughes    font.importXML(ttx)
103*e1fe3e4aSElliott Hughes    assert font.sfntVersion == "OTTO"
104*e1fe3e4aSElliott Hughes
105*e1fe3e4aSElliott Hughes
106*e1fe3e4aSElliott Hughesdef test_virtualGlyphId():
107*e1fe3e4aSElliott Hughes    otfpath = os.path.join(DATA_DIR, "TestVGID-Regular.otf")
108*e1fe3e4aSElliott Hughes    ttxpath = os.path.join(DATA_DIR, "TestVGID-Regular.ttx")
109*e1fe3e4aSElliott Hughes
110*e1fe3e4aSElliott Hughes    otf = TTFont(otfpath)
111*e1fe3e4aSElliott Hughes
112*e1fe3e4aSElliott Hughes    ttx = TTFont()
113*e1fe3e4aSElliott Hughes    ttx.importXML(ttxpath)
114*e1fe3e4aSElliott Hughes
115*e1fe3e4aSElliott Hughes    with open(ttxpath, encoding="utf-8") as fp:
116*e1fe3e4aSElliott Hughes        xml = normalize_TTX(fp.read()).splitlines()
117*e1fe3e4aSElliott Hughes
118*e1fe3e4aSElliott Hughes    for font in (otf, ttx):
119*e1fe3e4aSElliott Hughes        GSUB = font["GSUB"].table
120*e1fe3e4aSElliott Hughes        assert GSUB.LookupList.LookupCount == 37
121*e1fe3e4aSElliott Hughes        lookup = GSUB.LookupList.Lookup[32]
122*e1fe3e4aSElliott Hughes        assert lookup.LookupType == 8
123*e1fe3e4aSElliott Hughes        subtable = lookup.SubTable[0]
124*e1fe3e4aSElliott Hughes        assert subtable.LookAheadGlyphCount == 1
125*e1fe3e4aSElliott Hughes        lookahead = subtable.LookAheadCoverage[0]
126*e1fe3e4aSElliott Hughes        assert len(lookahead.glyphs) == 46
127*e1fe3e4aSElliott Hughes        assert "glyph00453" in lookahead.glyphs
128*e1fe3e4aSElliott Hughes
129*e1fe3e4aSElliott Hughes        out = io.StringIO()
130*e1fe3e4aSElliott Hughes        font.saveXML(out)
131*e1fe3e4aSElliott Hughes        outxml = normalize_TTX(out.getvalue()).splitlines()
132*e1fe3e4aSElliott Hughes        assert xml == outxml
133*e1fe3e4aSElliott Hughes
134*e1fe3e4aSElliott Hughes
135*e1fe3e4aSElliott Hughesdef test_setGlyphOrder_also_updates_glyf_glyphOrder():
136*e1fe3e4aSElliott Hughes    # https://github.com/fonttools/fonttools/issues/2060#issuecomment-1063932428
137*e1fe3e4aSElliott Hughes    font = TTFont()
138*e1fe3e4aSElliott Hughes    font.importXML(os.path.join(DATA_DIR, "TestTTF-Regular.ttx"))
139*e1fe3e4aSElliott Hughes    current_order = font.getGlyphOrder()
140*e1fe3e4aSElliott Hughes
141*e1fe3e4aSElliott Hughes    assert current_order == font["glyf"].glyphOrder
142*e1fe3e4aSElliott Hughes
143*e1fe3e4aSElliott Hughes    new_order = list(current_order)
144*e1fe3e4aSElliott Hughes    while new_order == current_order:
145*e1fe3e4aSElliott Hughes        random.shuffle(new_order)
146*e1fe3e4aSElliott Hughes
147*e1fe3e4aSElliott Hughes    font.setGlyphOrder(new_order)
148*e1fe3e4aSElliott Hughes
149*e1fe3e4aSElliott Hughes    assert font.getGlyphOrder() == new_order
150*e1fe3e4aSElliott Hughes    assert font["glyf"].glyphOrder == new_order
151*e1fe3e4aSElliott Hughes
152*e1fe3e4aSElliott Hughes
153*e1fe3e4aSElliott Hughesdef test_getGlyphOrder_not_true_post_format_1(caplog):
154*e1fe3e4aSElliott Hughes    # https://github.com/fonttools/fonttools/issues/2736
155*e1fe3e4aSElliott Hughes    caplog.set_level("WARNING")
156*e1fe3e4aSElliott Hughes    font = TTFont(os.path.join(DATA_DIR, "bogus_post_format_1.ttf"))
157*e1fe3e4aSElliott Hughes    hmtx = font["hmtx"]
158*e1fe3e4aSElliott Hughes    assert len(hmtx.metrics) > len(standardGlyphOrder)
159*e1fe3e4aSElliott Hughes    log_rec = caplog.records[-1]
160*e1fe3e4aSElliott Hughes    assert log_rec.levelname == "WARNING"
161*e1fe3e4aSElliott Hughes    assert "Not enough names found in the 'post' table" in log_rec.message
162*e1fe3e4aSElliott Hughes
163*e1fe3e4aSElliott Hughes
164*e1fe3e4aSElliott Hughes@pytest.mark.parametrize("lazy", [None, True, False])
165*e1fe3e4aSElliott Hughesdef test_ensureDecompiled(lazy):
166*e1fe3e4aSElliott Hughes    # test that no matter the lazy value, ensureDecompiled decompiles all tables
167*e1fe3e4aSElliott Hughes    font = TTFont()
168*e1fe3e4aSElliott Hughes    font.importXML(os.path.join(DATA_DIR, "TestTTF-Regular.ttx"))
169*e1fe3e4aSElliott Hughes    # test font has no OTL so we add some, as an example of otData-driven tables
170*e1fe3e4aSElliott Hughes    addOpenTypeFeaturesFromString(
171*e1fe3e4aSElliott Hughes        font,
172*e1fe3e4aSElliott Hughes        """
173*e1fe3e4aSElliott Hughes        feature calt {
174*e1fe3e4aSElliott Hughes            sub period' period' period' space by ellipsis;
175*e1fe3e4aSElliott Hughes        } calt;
176*e1fe3e4aSElliott Hughes
177*e1fe3e4aSElliott Hughes        feature dist {
178*e1fe3e4aSElliott Hughes            pos period period -30;
179*e1fe3e4aSElliott Hughes        } dist;
180*e1fe3e4aSElliott Hughes        """,
181*e1fe3e4aSElliott Hughes    )
182*e1fe3e4aSElliott Hughes    # also add an additional cmap subtable that will be lazily-loaded
183*e1fe3e4aSElliott Hughes    cm = CmapSubtable.newSubtable(14)
184*e1fe3e4aSElliott Hughes    cm.platformID = 0
185*e1fe3e4aSElliott Hughes    cm.platEncID = 5
186*e1fe3e4aSElliott Hughes    cm.language = 0
187*e1fe3e4aSElliott Hughes    cm.cmap = {}
188*e1fe3e4aSElliott Hughes    cm.uvsDict = {0xFE00: [(0x002E, None)]}
189*e1fe3e4aSElliott Hughes    font["cmap"].tables.append(cm)
190*e1fe3e4aSElliott Hughes
191*e1fe3e4aSElliott Hughes    # save and reload, potentially lazily
192*e1fe3e4aSElliott Hughes    buf = io.BytesIO()
193*e1fe3e4aSElliott Hughes    font.save(buf)
194*e1fe3e4aSElliott Hughes    buf.seek(0)
195*e1fe3e4aSElliott Hughes    font = TTFont(buf, lazy=lazy)
196*e1fe3e4aSElliott Hughes
197*e1fe3e4aSElliott Hughes    # check no table is loaded until/unless requested, no matter the laziness
198*e1fe3e4aSElliott Hughes    for tag in font.keys():
199*e1fe3e4aSElliott Hughes        assert not font.isLoaded(tag)
200*e1fe3e4aSElliott Hughes
201*e1fe3e4aSElliott Hughes    if lazy is not False:
202*e1fe3e4aSElliott Hughes        # additional cmap doesn't get decompiled automatically unless lazy=False;
203*e1fe3e4aSElliott Hughes        # can't use hasattr or else cmap's maginc __getattr__ kicks in...
204*e1fe3e4aSElliott Hughes        cm = next(st for st in font["cmap"].tables if st.__dict__["format"] == 14)
205*e1fe3e4aSElliott Hughes        assert cm.data is not None
206*e1fe3e4aSElliott Hughes        assert "uvsDict" not in cm.__dict__
207*e1fe3e4aSElliott Hughes        # glyf glyphs are not expanded unless lazy=False
208*e1fe3e4aSElliott Hughes        assert font["glyf"].glyphs["period"].data is not None
209*e1fe3e4aSElliott Hughes        assert not hasattr(font["glyf"].glyphs["period"], "coordinates")
210*e1fe3e4aSElliott Hughes
211*e1fe3e4aSElliott Hughes    if lazy is True:
212*e1fe3e4aSElliott Hughes        # OTL tables hold a 'reader' to lazily load when lazy=True
213*e1fe3e4aSElliott Hughes        assert "reader" in font["GSUB"].table.LookupList.__dict__
214*e1fe3e4aSElliott Hughes        assert "reader" in font["GPOS"].table.LookupList.__dict__
215*e1fe3e4aSElliott Hughes
216*e1fe3e4aSElliott Hughes    font.ensureDecompiled()
217*e1fe3e4aSElliott Hughes
218*e1fe3e4aSElliott Hughes    # all tables are decompiled now
219*e1fe3e4aSElliott Hughes    for tag in font.keys():
220*e1fe3e4aSElliott Hughes        assert font.isLoaded(tag)
221*e1fe3e4aSElliott Hughes    # including the additional cmap
222*e1fe3e4aSElliott Hughes    cm = next(st for st in font["cmap"].tables if st.__dict__["format"] == 14)
223*e1fe3e4aSElliott Hughes    assert cm.data is None
224*e1fe3e4aSElliott Hughes    assert "uvsDict" in cm.__dict__
225*e1fe3e4aSElliott Hughes    # expanded glyf glyphs lost the 'data' attribute
226*e1fe3e4aSElliott Hughes    assert not hasattr(font["glyf"].glyphs["period"], "data")
227*e1fe3e4aSElliott Hughes    assert hasattr(font["glyf"].glyphs["period"], "coordinates")
228*e1fe3e4aSElliott Hughes    # and OTL tables have read their 'reader'
229*e1fe3e4aSElliott Hughes    assert "reader" not in font["GSUB"].table.LookupList.__dict__
230*e1fe3e4aSElliott Hughes    assert "Lookup" in font["GSUB"].table.LookupList.__dict__
231*e1fe3e4aSElliott Hughes    assert "reader" not in font["GPOS"].table.LookupList.__dict__
232*e1fe3e4aSElliott Hughes    assert "Lookup" in font["GPOS"].table.LookupList.__dict__
233*e1fe3e4aSElliott Hughes
234*e1fe3e4aSElliott Hughes
235*e1fe3e4aSElliott Hughes@pytest.fixture
236*e1fe3e4aSElliott Hughesdef testFont_fvar_avar():
237*e1fe3e4aSElliott Hughes    ttxpath = os.path.join(DATA_DIR, "TestTTF_normalizeLocation.ttx")
238*e1fe3e4aSElliott Hughes    ttf = TTFont()
239*e1fe3e4aSElliott Hughes    ttf.importXML(ttxpath)
240*e1fe3e4aSElliott Hughes    return ttf
241*e1fe3e4aSElliott Hughes
242*e1fe3e4aSElliott Hughes
243*e1fe3e4aSElliott Hughes@pytest.mark.parametrize(
244*e1fe3e4aSElliott Hughes    "userLocation, expectedNormalizedLocation",
245*e1fe3e4aSElliott Hughes    [
246*e1fe3e4aSElliott Hughes        ({}, {"wght": 0.0}),
247*e1fe3e4aSElliott Hughes        ({"wght": 100}, {"wght": -1.0}),
248*e1fe3e4aSElliott Hughes        ({"wght": 250}, {"wght": -0.75}),
249*e1fe3e4aSElliott Hughes        ({"wght": 400}, {"wght": 0.0}),
250*e1fe3e4aSElliott Hughes        ({"wght": 550}, {"wght": 0.75}),
251*e1fe3e4aSElliott Hughes        ({"wght": 625}, {"wght": 0.875}),
252*e1fe3e4aSElliott Hughes        ({"wght": 700}, {"wght": 1.0}),
253*e1fe3e4aSElliott Hughes    ],
254*e1fe3e4aSElliott Hughes)
255*e1fe3e4aSElliott Hughesdef test_font_normalizeLocation(
256*e1fe3e4aSElliott Hughes    testFont_fvar_avar, userLocation, expectedNormalizedLocation
257*e1fe3e4aSElliott Hughes):
258*e1fe3e4aSElliott Hughes    normalizedLocation = testFont_fvar_avar.normalizeLocation(userLocation)
259*e1fe3e4aSElliott Hughes    assert expectedNormalizedLocation == normalizedLocation
260*e1fe3e4aSElliott Hughes
261*e1fe3e4aSElliott Hughes
262*e1fe3e4aSElliott Hughesdef test_font_normalizeLocation_no_VF():
263*e1fe3e4aSElliott Hughes    ttf = TTFont()
264*e1fe3e4aSElliott Hughes    with pytest.raises(TTLibError, match="Not a variable font"):
265*e1fe3e4aSElliott Hughes        ttf.normalizeLocation({})
266*e1fe3e4aSElliott Hughes
267*e1fe3e4aSElliott Hughes
268*e1fe3e4aSElliott Hughesdef test_getGlyphID():
269*e1fe3e4aSElliott Hughes    font = TTFont()
270*e1fe3e4aSElliott Hughes    font.importXML(os.path.join(DATA_DIR, "TestTTF-Regular.ttx"))
271*e1fe3e4aSElliott Hughes
272*e1fe3e4aSElliott Hughes    assert font.getGlyphID("space") == 3
273*e1fe3e4aSElliott Hughes    assert font.getGlyphID("glyph12345") == 12345  # virtual glyph
274*e1fe3e4aSElliott Hughes    with pytest.raises(KeyError):
275*e1fe3e4aSElliott Hughes        font.getGlyphID("non_existent")
276*e1fe3e4aSElliott Hughes    with pytest.raises(KeyError):
277*e1fe3e4aSElliott Hughes        font.getGlyphID("glyph_prefix_but_invalid_id")
278*e1fe3e4aSElliott Hughes
279*e1fe3e4aSElliott Hughes
280*e1fe3e4aSElliott Hughesdef test_spooled_tempfile_may_not_have_attribute_seekable():
281*e1fe3e4aSElliott Hughes    # SpooledTemporaryFile only got a seekable attribute on Python 3.11
282*e1fe3e4aSElliott Hughes    # https://github.com/fonttools/fonttools/issues/3052
283*e1fe3e4aSElliott Hughes    font = TTFont()
284*e1fe3e4aSElliott Hughes    font.importXML(os.path.join(DATA_DIR, "TestTTF-Regular.ttx"))
285*e1fe3e4aSElliott Hughes    tmp = tempfile.SpooledTemporaryFile()
286*e1fe3e4aSElliott Hughes    font.save(tmp)
287*e1fe3e4aSElliott Hughes    # this should not fail
288*e1fe3e4aSElliott Hughes    _ = TTFont(tmp)
289*e1fe3e4aSElliott Hughes
290*e1fe3e4aSElliott Hughes
291*e1fe3e4aSElliott Hughesdef test_unseekable_file_lazy_loading_fails():
292*e1fe3e4aSElliott Hughes    class NonSeekableFile:
293*e1fe3e4aSElliott Hughes        def __init__(self):
294*e1fe3e4aSElliott Hughes            self.file = io.BytesIO()
295*e1fe3e4aSElliott Hughes
296*e1fe3e4aSElliott Hughes        def read(self, size):
297*e1fe3e4aSElliott Hughes            return self.file.read(size)
298*e1fe3e4aSElliott Hughes
299*e1fe3e4aSElliott Hughes        def seekable(self):
300*e1fe3e4aSElliott Hughes            return False
301*e1fe3e4aSElliott Hughes
302*e1fe3e4aSElliott Hughes    f = NonSeekableFile()
303*e1fe3e4aSElliott Hughes    with pytest.raises(TTLibError, match="Input file must be seekable when lazy=True"):
304*e1fe3e4aSElliott Hughes        TTFont(f, lazy=True)
305*e1fe3e4aSElliott Hughes
306*e1fe3e4aSElliott Hughes
307*e1fe3e4aSElliott Hughesdef test_unsupported_seek_operation_lazy_loading_fails():
308*e1fe3e4aSElliott Hughes    class UnsupportedSeekFile:
309*e1fe3e4aSElliott Hughes        def __init__(self):
310*e1fe3e4aSElliott Hughes            self.file = io.BytesIO()
311*e1fe3e4aSElliott Hughes
312*e1fe3e4aSElliott Hughes        def read(self, size):
313*e1fe3e4aSElliott Hughes            return self.file.read(size)
314*e1fe3e4aSElliott Hughes
315*e1fe3e4aSElliott Hughes        def seek(self, offset):
316*e1fe3e4aSElliott Hughes            raise io.UnsupportedOperation("Unsupported seek operation")
317*e1fe3e4aSElliott Hughes
318*e1fe3e4aSElliott Hughes    f = UnsupportedSeekFile()
319*e1fe3e4aSElliott Hughes    with pytest.raises(TTLibError, match="Input file must be seekable when lazy=True"):
320*e1fe3e4aSElliott Hughes        TTFont(f, lazy=True)
321