From 4119d8f5829dac08185ea9f488de4c29eff298b6 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Thu, 17 Mar 2022 12:46:57 +0000 Subject: [PATCH 01/11] cmap: if lazy=False, decompile all cmap subtables upfront previously cmap was completely ignoring lazy attribute, always loading lazily --- Lib/fontTools/ttLib/tables/_c_m_a_p.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/Lib/fontTools/ttLib/tables/_c_m_a_p.py b/Lib/fontTools/ttLib/tables/_c_m_a_p.py index c927791a0..a31b5059c 100644 --- a/Lib/fontTools/ttLib/tables/_c_m_a_p.py +++ b/Lib/fontTools/ttLib/tables/_c_m_a_p.py @@ -156,6 +156,12 @@ class table__c_m_a_p(DefaultTable.DefaultTable): else: seenOffsets[offset] = i tables.append(table) + if ttFont.lazy is False: # Be lazy for None and True + self.ensureDecompiled() + + def ensureDecompiled(self): + for st in self.tables: + st.ensureDecompiled() def compile(self, ttFont): self.tables.sort() # sort according to the spec; see CmapSubtable.__lt__() @@ -232,16 +238,21 @@ class CmapSubtable(object): self.platEncID = None #: The encoding ID of this subtable (interpretation depends on ``platformID``) self.language = None #: The language ID of this subtable (Macintosh platform only) + def ensureDecompiled(self): + if self.data is None: + return + self.decompile(None, None) # use saved data. + self.data = None # Once this table has been decompiled, make sure we don't + # just return the original data. Also avoids recursion when + # called with an attribute that the cmap subtable doesn't have. + def __getattr__(self, attr): # allow lazy decompilation of subtables. if attr[:2] == '__': # don't handle requests for member functions like '__lt__' raise AttributeError(attr) if self.data is None: raise AttributeError(attr) - self.decompile(None, None) # use saved data. - self.data = None # Once this table has been decompiled, make sure we don't - # just return the original data. Also avoids recursion when - # called with an attribute that the cmap subtable doesn't have. + self.ensureDecompiled() return getattr(self, attr) def decompileHeader(self, data, ttFont): From 7f1e5e1fc735fb1f90dda858972b2d8baecad3c3 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Thu, 17 Mar 2022 12:51:56 +0000 Subject: [PATCH 02/11] glyf: add ensureDecompiled method to 'expand' all the lazy glyphs --- Lib/fontTools/ttLib/tables/_g_l_y_f.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/fontTools/ttLib/tables/_g_l_y_f.py b/Lib/fontTools/ttLib/tables/_g_l_y_f.py index 9ec3a014a..14c4519db 100644 --- a/Lib/fontTools/ttLib/tables/_g_l_y_f.py +++ b/Lib/fontTools/ttLib/tables/_g_l_y_f.py @@ -110,8 +110,11 @@ class table__g_l_y_f(DefaultTable.DefaultTable): if noname: log.warning('%s glyphs have no name', noname) if ttFont.lazy is False: # Be lazy for None and True - for glyph in self.glyphs.values(): - glyph.expand(self) + self.ensureDecompiled() + + def ensureDecompiled(self): + for glyph in self.glyphs.values(): + glyph.expand(self) def compile(self, ttFont): if not hasattr(self, "glyphOrder"): From 64dc37fc010ae1374d668ec1218c78c741d4f0b2 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Thu, 17 Mar 2022 12:53:06 +0000 Subject: [PATCH 03/11] otBase: add iterSubTables method to iterate over all BaseTables can be useful to traverse a tree of otTables --- Lib/fontTools/ttLib/tables/otBase.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Lib/fontTools/ttLib/tables/otBase.py b/Lib/fontTools/ttLib/tables/otBase.py index 5d379cb86..a3f5ac8ea 100644 --- a/Lib/fontTools/ttLib/tables/otBase.py +++ b/Lib/fontTools/ttLib/tables/otBase.py @@ -851,6 +851,16 @@ class BaseTable(object): return self.__dict__ == other.__dict__ + def iterSubTables(self): + for conv in self.getConverters(): + value = getattr(self, conv.name, None) + if value is None: + continue + if isinstance(value, BaseTable): + yield value + elif isinstance(value, list): + yield from (v for v in value if isinstance(v, BaseTable)) + class FormatSwitchingBaseTable(BaseTable): @@ -862,6 +872,13 @@ class FormatSwitchingBaseTable(BaseTable): return NotImplemented def getConverters(self): + try: + fmt = self.Format + except AttributeError: + # some FormatSwitchingBaseTables (e.g. Coverage) no longer have 'Format' + # attribute after fully decompiled, only gain one in preWrite before being + # recompiled. + return [] return self.converters.get(self.Format, []) def getConverterByName(self, name): From ee27b73d7c3fddd251ab7bc9e2b311b106c5b095 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Thu, 17 Mar 2022 12:54:22 +0000 Subject: [PATCH 04/11] BaseTTXConverter: add ensureDecompiled method to unlazify a whole tree of otTables, recursively --- Lib/fontTools/ttLib/tables/otBase.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Lib/fontTools/ttLib/tables/otBase.py b/Lib/fontTools/ttLib/tables/otBase.py index a3f5ac8ea..c9c1d138d 100644 --- a/Lib/fontTools/ttLib/tables/otBase.py +++ b/Lib/fontTools/ttLib/tables/otBase.py @@ -108,6 +108,9 @@ class BaseTTXConverter(DefaultTable): self.table.fromXML(name, attrs, content, font) self.table.populateDefaults() + def ensureDecompiled(self): + self.table.ensureDecompiled(recurse=True) + # https://github.com/fonttools/fonttools/pull/2285#issuecomment-834652928 assert len(struct.pack('i', 0)) == 4 @@ -596,13 +599,16 @@ class BaseTable(object): raise AttributeError(attr) - def ensureDecompiled(self): + def ensureDecompiled(self, recurse=False): reader = self.__dict__.get("reader") if reader: del self.reader font = self.font del self.font self.decompile(reader, font) + if recurse: + for subtable in self.iterSubTables(): + subtable.ensureDecompiled(recurse) @classmethod def getRecordSize(cls, reader): From 795bccd966b8dd251af737b7a04c2e94c5a99009 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Thu, 17 Mar 2022 13:00:30 +0000 Subject: [PATCH 05/11] TTFont: load all on open w/ lazy=False; add ensureDecompiled Make lazy=False actually do what it says, 'load everything eagerly'. It feels weird that one has to, not only say, open with lazy=False, but also have to load each tables individually... Didn't I say don't be lazy?! Also it can be useful to get to a eager, non-lazy font whether or not it was originally loaded lazily, so I added an ensureDecompiled method that decompiles all the tables and calls ensureDecompiled for those (e.g. cmap, glyf and otData-driven tables like GSUB, GPOS, etc.) that respect the lazy attribute. --- Lib/fontTools/ttLib/ttFont.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Lib/fontTools/ttLib/ttFont.py b/Lib/fontTools/ttLib/ttFont.py index 692f99ac8..4adf34af1 100644 --- a/Lib/fontTools/ttLib/ttFont.py +++ b/Lib/fontTools/ttLib/ttFont.py @@ -129,7 +129,7 @@ class TTFont(object): closeStream = False file.seek(0) - if not self.lazy: + if self.lazy is None: # read input file in memory and wrap a stream around it to allow overwriting file.seek(0) tmp = BytesIO(file.read()) @@ -139,12 +139,19 @@ class TTFont(object): if closeStream: file.close() file = tmp + self._tableCache = _tableCache self.reader = SFNTReader(file, checkChecksums, fontNumber=fontNumber) self.sfntVersion = self.reader.sfntVersion self.flavor = self.reader.flavor self.flavorData = self.reader.flavorData + if self.lazy is False: + # if lazy=False immediately load all the tables + self.ensureDecompiled() + if closeStream: + file.close() + def __enter__(self): return self @@ -378,6 +385,14 @@ class TTFont(object): keys = sortedTagList(keys) return ["GlyphOrder"] + keys + def ensureDecompiled(self): + """Decompile all the tables, even if a TTFont was opened in 'lazy' mode.""" + for tag in self.keys(): + table = self[tag] + if self.lazy is not False and hasattr(table, "ensureDecompiled"): + table.ensureDecompiled() + self.lazy = False + def __len__(self): return len(list(self.keys())) From 46f33357cb38666886e315d2de81b92128ad3c1a Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Thu, 17 Mar 2022 13:47:25 +0000 Subject: [PATCH 06/11] TTFont: don't load all tables upon opening even with lazy=False one has to call TTFont.ensureDecompiled to load everything --- Lib/fontTools/ttLib/ttFont.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Lib/fontTools/ttLib/ttFont.py b/Lib/fontTools/ttLib/ttFont.py index 4adf34af1..3929e2f3e 100644 --- a/Lib/fontTools/ttLib/ttFont.py +++ b/Lib/fontTools/ttLib/ttFont.py @@ -129,7 +129,7 @@ class TTFont(object): closeStream = False file.seek(0) - if self.lazy is None: + if not self.lazy: # read input file in memory and wrap a stream around it to allow overwriting file.seek(0) tmp = BytesIO(file.read()) @@ -139,19 +139,12 @@ class TTFont(object): if closeStream: file.close() file = tmp - self._tableCache = _tableCache self.reader = SFNTReader(file, checkChecksums, fontNumber=fontNumber) self.sfntVersion = self.reader.sfntVersion self.flavor = self.reader.flavor self.flavorData = self.reader.flavorData - if self.lazy is False: - # if lazy=False immediately load all the tables - self.ensureDecompiled() - if closeStream: - file.close() - def __enter__(self): return self From 61e7b294483cbe729b1437804406b4571ae57cfb Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Thu, 17 Mar 2022 15:44:02 +0000 Subject: [PATCH 07/11] return self so one can do font=TTFont(file).ensureDecompiled() --- Lib/fontTools/ttLib/tables/_c_m_a_p.py | 2 ++ Lib/fontTools/ttLib/tables/_g_l_y_f.py | 1 + Lib/fontTools/ttLib/tables/otBase.py | 2 ++ Lib/fontTools/ttLib/ttFont.py | 6 +++++- 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Lib/fontTools/ttLib/tables/_c_m_a_p.py b/Lib/fontTools/ttLib/tables/_c_m_a_p.py index a31b5059c..92e1c17a0 100644 --- a/Lib/fontTools/ttLib/tables/_c_m_a_p.py +++ b/Lib/fontTools/ttLib/tables/_c_m_a_p.py @@ -162,6 +162,7 @@ class table__c_m_a_p(DefaultTable.DefaultTable): def ensureDecompiled(self): for st in self.tables: st.ensureDecompiled() + return self def compile(self, ttFont): self.tables.sort() # sort according to the spec; see CmapSubtable.__lt__() @@ -245,6 +246,7 @@ class CmapSubtable(object): self.data = None # Once this table has been decompiled, make sure we don't # just return the original data. Also avoids recursion when # called with an attribute that the cmap subtable doesn't have. + return self def __getattr__(self, attr): # allow lazy decompilation of subtables. diff --git a/Lib/fontTools/ttLib/tables/_g_l_y_f.py b/Lib/fontTools/ttLib/tables/_g_l_y_f.py index 14c4519db..3de9d28e3 100644 --- a/Lib/fontTools/ttLib/tables/_g_l_y_f.py +++ b/Lib/fontTools/ttLib/tables/_g_l_y_f.py @@ -115,6 +115,7 @@ class table__g_l_y_f(DefaultTable.DefaultTable): def ensureDecompiled(self): for glyph in self.glyphs.values(): glyph.expand(self) + return self def compile(self, ttFont): if not hasattr(self, "glyphOrder"): diff --git a/Lib/fontTools/ttLib/tables/otBase.py b/Lib/fontTools/ttLib/tables/otBase.py index c9c1d138d..5bc4f0eed 100644 --- a/Lib/fontTools/ttLib/tables/otBase.py +++ b/Lib/fontTools/ttLib/tables/otBase.py @@ -110,6 +110,7 @@ class BaseTTXConverter(DefaultTable): def ensureDecompiled(self): self.table.ensureDecompiled(recurse=True) + return self # https://github.com/fonttools/fonttools/pull/2285#issuecomment-834652928 @@ -609,6 +610,7 @@ class BaseTable(object): if recurse: for subtable in self.iterSubTables(): subtable.ensureDecompiled(recurse) + return self @classmethod def getRecordSize(cls, reader): diff --git a/Lib/fontTools/ttLib/ttFont.py b/Lib/fontTools/ttLib/ttFont.py index 3929e2f3e..8bf6614cc 100644 --- a/Lib/fontTools/ttLib/ttFont.py +++ b/Lib/fontTools/ttLib/ttFont.py @@ -379,12 +379,16 @@ class TTFont(object): return ["GlyphOrder"] + keys def ensureDecompiled(self): - """Decompile all the tables, even if a TTFont was opened in 'lazy' mode.""" + """Decompile all the tables, even if a TTFont was opened in 'lazy' mode. + + Returns the same TTFont instance, fully decompiled. + """ for tag in self.keys(): table = self[tag] if self.lazy is not False and hasattr(table, "ensureDecompiled"): table.ensureDecompiled() self.lazy = False + return self def __len__(self): return len(list(self.keys())) From 43d2ee28223d1e8e278f27addf5cf9150a731af0 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Thu, 17 Mar 2022 15:55:20 +0000 Subject: [PATCH 08/11] Revert "return self so one can do font=TTFont(file).ensureDecompiled()" This reverts commit 61e7b294483cbe729b1437804406b4571ae57cfb. --- Lib/fontTools/ttLib/tables/_c_m_a_p.py | 2 -- Lib/fontTools/ttLib/tables/_g_l_y_f.py | 1 - Lib/fontTools/ttLib/tables/otBase.py | 2 -- Lib/fontTools/ttLib/ttFont.py | 6 +----- 4 files changed, 1 insertion(+), 10 deletions(-) diff --git a/Lib/fontTools/ttLib/tables/_c_m_a_p.py b/Lib/fontTools/ttLib/tables/_c_m_a_p.py index 92e1c17a0..a31b5059c 100644 --- a/Lib/fontTools/ttLib/tables/_c_m_a_p.py +++ b/Lib/fontTools/ttLib/tables/_c_m_a_p.py @@ -162,7 +162,6 @@ class table__c_m_a_p(DefaultTable.DefaultTable): def ensureDecompiled(self): for st in self.tables: st.ensureDecompiled() - return self def compile(self, ttFont): self.tables.sort() # sort according to the spec; see CmapSubtable.__lt__() @@ -246,7 +245,6 @@ class CmapSubtable(object): self.data = None # Once this table has been decompiled, make sure we don't # just return the original data. Also avoids recursion when # called with an attribute that the cmap subtable doesn't have. - return self def __getattr__(self, attr): # allow lazy decompilation of subtables. diff --git a/Lib/fontTools/ttLib/tables/_g_l_y_f.py b/Lib/fontTools/ttLib/tables/_g_l_y_f.py index 3de9d28e3..14c4519db 100644 --- a/Lib/fontTools/ttLib/tables/_g_l_y_f.py +++ b/Lib/fontTools/ttLib/tables/_g_l_y_f.py @@ -115,7 +115,6 @@ class table__g_l_y_f(DefaultTable.DefaultTable): def ensureDecompiled(self): for glyph in self.glyphs.values(): glyph.expand(self) - return self def compile(self, ttFont): if not hasattr(self, "glyphOrder"): diff --git a/Lib/fontTools/ttLib/tables/otBase.py b/Lib/fontTools/ttLib/tables/otBase.py index 5bc4f0eed..c9c1d138d 100644 --- a/Lib/fontTools/ttLib/tables/otBase.py +++ b/Lib/fontTools/ttLib/tables/otBase.py @@ -110,7 +110,6 @@ class BaseTTXConverter(DefaultTable): def ensureDecompiled(self): self.table.ensureDecompiled(recurse=True) - return self # https://github.com/fonttools/fonttools/pull/2285#issuecomment-834652928 @@ -610,7 +609,6 @@ class BaseTable(object): if recurse: for subtable in self.iterSubTables(): subtable.ensureDecompiled(recurse) - return self @classmethod def getRecordSize(cls, reader): diff --git a/Lib/fontTools/ttLib/ttFont.py b/Lib/fontTools/ttLib/ttFont.py index 8bf6614cc..3929e2f3e 100644 --- a/Lib/fontTools/ttLib/ttFont.py +++ b/Lib/fontTools/ttLib/ttFont.py @@ -379,16 +379,12 @@ class TTFont(object): return ["GlyphOrder"] + keys def ensureDecompiled(self): - """Decompile all the tables, even if a TTFont was opened in 'lazy' mode. - - Returns the same TTFont instance, fully decompiled. - """ + """Decompile all the tables, even if a TTFont was opened in 'lazy' mode.""" for tag in self.keys(): table = self[tag] if self.lazy is not False and hasattr(table, "ensureDecompiled"): table.ensureDecompiled() self.lazy = False - return self def __len__(self): return len(list(self.keys())) From 6e0cebc487432dbf9c826b201cf328be01e8ba45 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Thu, 17 Mar 2022 17:13:50 +0000 Subject: [PATCH 09/11] ttFont_test: add test for ensureDecompiled --- Tests/ttLib/ttFont_test.py | 75 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/Tests/ttLib/ttFont_test.py b/Tests/ttLib/ttFont_test.py index d40832728..e0e82b244 100644 --- a/Tests/ttLib/ttFont_test.py +++ b/Tests/ttLib/ttFont_test.py @@ -2,8 +2,12 @@ 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") @@ -137,3 +141,74 @@ def test_setGlyphOrder_also_updates_glyf_glyphOrder(): 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__ From ab8fc321a76a960322a39f92cf9895066bb52458 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Thu, 17 Mar 2022 17:47:41 +0000 Subject: [PATCH 10/11] otBase: also return name and index from iterSubTables makes it more useful for constructing generic traversals of trees of otTables --- Lib/fontTools/ttLib/tables/otBase.py | 32 +++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/Lib/fontTools/ttLib/tables/otBase.py b/Lib/fontTools/ttLib/tables/otBase.py index c9c1d138d..17df69df3 100644 --- a/Lib/fontTools/ttLib/tables/otBase.py +++ b/Lib/fontTools/ttLib/tables/otBase.py @@ -4,6 +4,7 @@ import sys import array import struct import logging +from typing import Iterator, NamedTuple, Optional log = logging.getLogger(__name__) @@ -608,7 +609,7 @@ class BaseTable(object): self.decompile(reader, font) if recurse: for subtable in self.iterSubTables(): - subtable.ensureDecompiled(recurse) + subtable.value.ensureDecompiled(recurse) @classmethod def getRecordSize(cls, reader): @@ -857,15 +858,36 @@ class BaseTable(object): return self.__dict__ == other.__dict__ - def iterSubTables(self): + class SubTableEntry(NamedTuple): + """See BaseTable.iterSubTables()""" + name: str + value: "BaseTable" + index: Optional[int] = None # index into given array, None for single values + + def iterSubTables(self) -> Iterator[SubTableEntry]: + """Yield (name, value, index) namedtuples for all subtables of current table. + + A sub-table is an instance of BaseTable (or subclass thereof) that is a child + of self, the current parent table. + The tuples also contain the attribute name (str) of the of parent table to get + a subtable, and optionally, for lists of subtables (i.e. attributes associated + with a converter that has a 'repeat'), an index into the list containing the + given subtable value. + This method can be useful to traverse trees of otTables. + """ for conv in self.getConverters(): - value = getattr(self, conv.name, None) + name = conv.name + value = getattr(self, name, None) if value is None: continue if isinstance(value, BaseTable): - yield value + yield self.SubTableEntry(name, value) elif isinstance(value, list): - yield from (v for v in value if isinstance(v, BaseTable)) + yield from ( + self.SubTableEntry(name, v, index=i) + for i, v in enumerate(value) + if isinstance(v, BaseTable) + ) class FormatSwitchingBaseTable(BaseTable): From 25746a36009536799b8ee0ddac2d0441b235543a Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 18 Mar 2022 09:22:33 +0000 Subject: [PATCH 11/11] add clarifying comment as per review --- Lib/fontTools/ttLib/tables/otBase.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/fontTools/ttLib/tables/otBase.py b/Lib/fontTools/ttLib/tables/otBase.py index 17df69df3..bc2c9fba8 100644 --- a/Lib/fontTools/ttLib/tables/otBase.py +++ b/Lib/fontTools/ttLib/tables/otBase.py @@ -905,7 +905,9 @@ class FormatSwitchingBaseTable(BaseTable): except AttributeError: # some FormatSwitchingBaseTables (e.g. Coverage) no longer have 'Format' # attribute after fully decompiled, only gain one in preWrite before being - # recompiled. + # recompiled. In the decompiled state, these hand-coded classes defined in + # otTables.py lose their format-specific nature and gain more high-level + # attributes that are not tied to converters. return [] return self.converters.get(self.Format, [])