Merge pull request #2052 from simoncozens/fealib-debug

feaLib source debugging
This commit is contained in:
Simon Cozens 2020-09-17 20:13:23 +01:00 committed by GitHub
commit a18b6bfb6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 203 additions and 8 deletions

View File

@ -38,6 +38,12 @@ def main(args=None):
nargs="+",
help="Specify the table(s) to be built.",
)
parser.add_argument(
"-d",
"--debug",
action="store_true",
help="Add source-level debugging information to font.",
)
parser.add_argument(
"-v",
"--verbose",
@ -58,7 +64,9 @@ def main(args=None):
font = TTFont(options.input_font)
try:
addOpenTypeFeatures(font, options.input_fea, tables=options.tables)
addOpenTypeFeatures(
font, options.input_fea, tables=options.tables, debug=options.debug
)
except FeatureLibError as e:
if options.traceback:
raise

View File

@ -2,6 +2,7 @@ from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.textTools import binary2num, safeEval
from fontTools.feaLib.error import FeatureLibError
from fontTools.feaLib.lookupDebugInfo import LookupDebugInfo, LOOKUP_DEBUG_INFO_KEY
from fontTools.feaLib.parser import Parser
from fontTools.feaLib.ast import FeatureFile
from fontTools.otlLib import builder as otl
@ -34,7 +35,7 @@ import logging
log = logging.getLogger(__name__)
def addOpenTypeFeatures(font, featurefile, tables=None):
def addOpenTypeFeatures(font, featurefile, tables=None, debug=False):
"""Add features from a file to a font. Note that this replaces any features
currently present.
@ -44,13 +45,17 @@ def addOpenTypeFeatures(font, featurefile, tables=None):
parse it into an AST), or a pre-parsed AST instance.
tables: If passed, restrict the set of affected tables to those in the
list.
debug: Whether to add source debugging information to the font in the
``Debg`` table
"""
builder = Builder(font, featurefile)
builder.build(tables=tables)
builder.build(tables=tables, debug=debug)
def addOpenTypeFeaturesFromString(font, features, filename=None, tables=None):
def addOpenTypeFeaturesFromString(
font, features, filename=None, tables=None, debug=False
):
"""Add features from a string to a font. Note that this replaces any
features currently present.
@ -62,13 +67,15 @@ def addOpenTypeFeaturesFromString(font, features, filename=None, tables=None):
directory is assumed.
tables: If passed, restrict the set of affected tables to those in the
list.
debug: Whether to add source debugging information to the font in the
``Debg`` table
"""
featurefile = UnicodeIO(tounicode(features))
if filename:
featurefile.name = filename
addOpenTypeFeatures(font, featurefile, tables=tables)
addOpenTypeFeatures(font, featurefile, tables=tables, debug=debug)
class Builder(object):
@ -108,6 +115,7 @@ class Builder(object):
self.cur_lookup_name_ = None
self.cur_feature_name_ = None
self.lookups_ = []
self.lookup_locations = {"GSUB": {}, "GPOS": {}}
self.features_ = {} # ('latn', 'DEU ', 'smcp') --> [LookupBuilder*]
self.required_features_ = {} # ('latn', 'DEU ') --> 'scmp'
# for feature 'aalt'
@ -146,7 +154,7 @@ class Builder(object):
# for table 'vhea'
self.vhea_ = {}
def build(self, tables=None):
def build(self, tables=None, debug=False):
if self.parseTree is None:
self.parseTree = Parser(self.file, self.glyphMap).parse()
self.parseTree.build(self)
@ -201,6 +209,8 @@ class Builder(object):
self.font["BASE"] = base
elif "BASE" in self.font:
del self.font["BASE"]
if debug:
self.buildDebg()
def get_chained_lookup_(self, location, builder_class):
result = builder_class(self.font, location)
@ -638,6 +648,12 @@ class Builder(object):
sets.append(glyphs)
return otl.buildMarkGlyphSetsDef(sets, self.glyphMap)
def buildDebg(self):
if "Debg" not in self.font:
self.font["Debg"] = newTable("Debg")
self.font["Debg"].data = {}
self.font["Debg"].data[LOOKUP_DEBUG_INFO_KEY] = self.lookup_locations
def buildLookups_(self, tag):
assert tag in ("GPOS", "GSUB"), tag
for lookup in self.lookups_:
@ -647,6 +663,11 @@ class Builder(object):
if lookup.table != tag:
continue
lookup.lookup_index = len(lookups)
self.lookup_locations[tag][str(lookup.lookup_index)] = LookupDebugInfo(
location=str(lookup.location),
name=self.get_lookup_name_(lookup),
feature=None,
)
lookups.append(lookup)
try:
otLookups = [l.build() for l in lookups]
@ -685,6 +706,11 @@ class Builder(object):
if len(lookup_indices) == 0 and not size_feature:
continue
for ix in lookup_indices:
self.lookup_locations[tag][str(ix)] = self.lookup_locations[tag][
str(ix)
]._replace(feature=key)
feature_key = (feature_tag, lookup_indices)
feature_index = feature_indices.get(feature_key)
if feature_index is None:
@ -737,6 +763,12 @@ class Builder(object):
table.LookupList.LookupCount = len(table.LookupList.Lookup)
return table
def get_lookup_name_(self, lookup):
rev = {v: k for k, v in self.named_lookups_.items()}
if lookup in rev:
return rev[lookup]
return None
def add_language_system(self, location, script, language):
# OpenType Feature File Specification, section 4.b.i
if script == "DFLT" and language == "dflt" and self.default_language_systems_:

View File

@ -0,0 +1,10 @@
from typing import NamedTuple
LOOKUP_DEBUG_INFO_KEY = "com.github.fonttools.feaLib"
class LookupDebugInfo(NamedTuple):
"""Information about where a lookup came from, to be embedded in a font"""
location: str
name: str
feature: list

View File

@ -0,0 +1,17 @@
import json
from . import DefaultTable
class table_D__e_b_g(DefaultTable.DefaultTable):
def decompile(self, data, ttFont):
self.data = json.loads(data)
def compile(self, ttFont):
return json.dumps(self.data).encode("utf-8")
def toXML(self, writer, ttFont):
writer.writecdata(json.dumps(self.data))
def fromXML(self, name, attrs, content, ttFont):
self.data = json.loads(content)

View File

@ -12,6 +12,7 @@ from fontTools.misc.py23 import *
from fontTools.misc.fixedTools import otRound
from fontTools.misc.textTools import pad, safeEval
from .otBase import BaseTable, FormatSwitchingBaseTable, ValueRecord, CountReference
from fontTools.feaLib.lookupDebugInfo import LookupDebugInfo, LOOKUP_DEBUG_INFO_KEY
import logging
import struct
@ -1187,6 +1188,44 @@ class COLR(BaseTable):
}
class LookupList(BaseTable):
@property
def table(self):
for l in self.Lookup:
for st in l.SubTable:
if type(st).__name__.endswith("Subst"):
return "GSUB"
if type(st).__name__.endswith("Pos"):
return "GPOS"
raise ValueError
def toXML2(self, xmlWriter, font):
if not font or "Debg" not in font or LOOKUP_DEBUG_INFO_KEY not in font["Debg"].data:
return super().toXML2(xmlWriter, font)
debugData = font["Debg"].data[LOOKUP_DEBUG_INFO_KEY][self.table]
for conv in self.getConverters():
if conv.repeat:
value = getattr(self, conv.name, [])
for lookupIndex, item in enumerate(value):
if str(lookupIndex) in debugData:
info = LookupDebugInfo(*debugData[str(lookupIndex)])
tag = info.location
if info.name:
tag = f'{info.name}: {tag}'
if info.feature:
script,language,feature = info.feature
tag = f'{tag} in {feature} ({script}/{language})'
xmlWriter.comment(tag)
xmlWriter.newline()
conv.xmlWrite(xmlWriter, font, item, conv.name,
[("index", lookupIndex)])
else:
if conv.aux and not eval(conv.aux, None, vars(self)):
continue
value = getattr(self, conv.name, None) # TODO Handle defaults instead of defaulting to None!
conv.xmlWrite(xmlWriter, font, value, conv.name, [])
class BaseGlyphRecordArray(BaseTable):
def preWrite(self, font):

View File

@ -114,12 +114,16 @@ class BuilderTest(unittest.TestCase):
lines.append(line.rstrip() + os.linesep)
return lines
def expect_ttx(self, font, expected_ttx):
def expect_ttx(self, font, expected_ttx, replace=None):
path = self.temp_path(suffix=".ttx")
font.saveXML(path, tables=['head', 'name', 'BASE', 'GDEF', 'GSUB',
'GPOS', 'OS/2', 'hhea', 'vhea'])
actual = self.read_ttx(path)
expected = self.read_ttx(expected_ttx)
if replace:
for i in range(len(expected)):
for k, v in replace.items():
expected[i] = expected[i].replace(k, v)
if actual != expected:
for line in difflib.unified_diff(
expected, actual, fromfile=expected_ttx, tofile=path):
@ -133,12 +137,17 @@ class BuilderTest(unittest.TestCase):
def check_feature_file(self, name):
font = makeTTFont()
addOpenTypeFeatures(font, self.getpath("%s.fea" % name))
feapath = self.getpath("%s.fea" % name)
addOpenTypeFeatures(font, feapath)
self.expect_ttx(font, self.getpath("%s.ttx" % name))
# Make sure we can produce binary OpenType tables, not just XML.
for tag in ('GDEF', 'GSUB', 'GPOS'):
if tag in font:
font[tag].compile(font)
debugttx = self.getpath("%s-debug.ttx" % name)
if os.path.exists(debugttx):
addOpenTypeFeatures(font, feapath, debug=True)
self.expect_ttx(font, debugttx, replace = {"__PATH__": feapath})
def check_fea2fea_file(self, name, base=None, parser=Parser):
font = makeTTFont()

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont>
<GSUB>
<Version value="0x00010000"/>
<ScriptList>
<!-- ScriptCount=1 -->
<ScriptRecord index="0">
<ScriptTag value="DFLT"/>
<Script>
<DefaultLangSys>
<ReqFeatureIndex value="65535"/>
<!-- FeatureCount=4 -->
<FeatureIndex index="0" value="0"/>
<FeatureIndex index="1" value="1"/>
<FeatureIndex index="2" value="2"/>
<FeatureIndex index="3" value="3"/>
</DefaultLangSys>
<!-- LangSysCount=0 -->
</Script>
</ScriptRecord>
</ScriptList>
<FeatureList>
<!-- FeatureCount=4 -->
<FeatureRecord index="0">
<FeatureTag value="tst1"/>
<Feature>
<!-- LookupCount=1 -->
<LookupListIndex index="0" value="0"/>
</Feature>
</FeatureRecord>
<FeatureRecord index="1">
<FeatureTag value="tst2"/>
<Feature>
<!-- LookupCount=1 -->
<LookupListIndex index="0" value="0"/>
</Feature>
</FeatureRecord>
<FeatureRecord index="2">
<FeatureTag value="tst3"/>
<Feature>
<!-- LookupCount=1 -->
<LookupListIndex index="0" value="1"/>
</Feature>
</FeatureRecord>
<FeatureRecord index="3">
<FeatureTag value="tst4"/>
<Feature>
<!-- LookupCount=1 -->
<LookupListIndex index="0" value="1"/>
</Feature>
</FeatureRecord>
</FeatureList>
<LookupList>
<!-- LookupCount=2 -->
<!-- SomeLookup: __PATH__:4:5 in tst2 (DFLT/dflt) -->
<Lookup index="0">
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<LigatureSubst index="0">
<LigatureSet glyph="f">
<Ligature components="f,i" glyph="f_f_i"/>
<Ligature components="i" glyph="f_i"/>
</LigatureSet>
</LigatureSubst>
</Lookup>
<!-- EmbeddedLookup: __PATH__:18:9 in tst4 (DFLT/dflt) -->
<Lookup index="1">
<LookupType value="1"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<SingleSubst index="0">
<Substitution in="A" out="A.sc"/>
</SingleSubst>
</Lookup>
</LookupList>
</GSUB>
</ttFont>