Merge pull request #2052 from simoncozens/fealib-debug
feaLib source debugging
This commit is contained in:
commit
a18b6bfb6c
@ -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
|
||||
|
@ -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_:
|
||||
|
10
Lib/fontTools/feaLib/lookupDebugInfo.py
Normal file
10
Lib/fontTools/feaLib/lookupDebugInfo.py
Normal 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
|
17
Lib/fontTools/ttLib/tables/D__e_b_g.py
Normal file
17
Lib/fontTools/ttLib/tables/D__e_b_g.py
Normal 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)
|
@ -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):
|
||||
|
@ -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()
|
||||
|
80
Tests/feaLib/data/lookup-debug.ttx
Normal file
80
Tests/feaLib/data/lookup-debug.ttx
Normal 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>
|
Loading…
x
Reference in New Issue
Block a user