Merge pull request #2551 from fonttools/unlazy
add ensureDecompiled method to decompile all the tables irrespective of lazy attribute
This commit is contained in:
commit
d7169774ac
@ -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):
|
||||
|
@ -110,6 +110,9 @@ 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
|
||||
self.ensureDecompiled()
|
||||
|
||||
def ensureDecompiled(self):
|
||||
for glyph in self.glyphs.values():
|
||||
glyph.expand(self)
|
||||
|
||||
|
@ -4,6 +4,7 @@ import sys
|
||||
import array
|
||||
import struct
|
||||
import logging
|
||||
from typing import Iterator, NamedTuple, Optional
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -108,6 +109,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 +600,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.value.ensureDecompiled(recurse)
|
||||
|
||||
@classmethod
|
||||
def getRecordSize(cls, reader):
|
||||
@ -851,6 +858,37 @@ class BaseTable(object):
|
||||
|
||||
return self.__dict__ == other.__dict__
|
||||
|
||||
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():
|
||||
name = conv.name
|
||||
value = getattr(self, name, None)
|
||||
if value is None:
|
||||
continue
|
||||
if isinstance(value, BaseTable):
|
||||
yield self.SubTableEntry(name, value)
|
||||
elif isinstance(value, list):
|
||||
yield from (
|
||||
self.SubTableEntry(name, v, index=i)
|
||||
for i, v in enumerate(value)
|
||||
if isinstance(v, BaseTable)
|
||||
)
|
||||
|
||||
|
||||
class FormatSwitchingBaseTable(BaseTable):
|
||||
|
||||
@ -862,6 +900,15 @@ 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. 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, [])
|
||||
|
||||
def getConverterByName(self, name):
|
||||
|
@ -378,6 +378,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()))
|
||||
|
||||
|
@ -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__
|
||||
|
Loading…
x
Reference in New Issue
Block a user