[otTables] Simplify API for MultipleSubst
Resolves https://github.com/behdad/fonttools/issues/355 For making sure that `pyftsubset` still works after this change, I have done the following steps: * invoked Adobe's `makeotf` tool to build a custom font with a MultipleSubst lookup. This lookup decomposes two two ligatures, `c_t` and `f_f_i`, into their respective components. * invoked the `pyftsubset` tool to produce a subset font with just the `c_t` ligature; * checked with `ttx` that the newly produced subset font contains the requested `c_t` ligature and its components `c` and `t`, but does not contain not any of `f_f_i`, `f`, or `i`.
This commit is contained in:
parent
fb145d1a82
commit
1356a6775b
@ -401,20 +401,15 @@ def subset_glyphs(self, s):
|
||||
|
||||
@_add_method(otTables.MultipleSubst)
|
||||
def closure_glyphs(self, s, cur_glyphs):
|
||||
indices = self.Coverage.intersect(cur_glyphs)
|
||||
_set_update(s.glyphs, *(self.Sequence[i].Substitute for i in indices))
|
||||
for glyph, subst in self.mapping.items():
|
||||
if glyph in cur_glyphs:
|
||||
_set_update(s.glyphs, subst)
|
||||
|
||||
@_add_method(otTables.MultipleSubst)
|
||||
def subset_glyphs(self, s):
|
||||
indices = self.Coverage.subset(s.glyphs)
|
||||
self.Sequence = [self.Sequence[i] for i in indices]
|
||||
# Now drop rules generating glyphs we don't want
|
||||
indices = [i for i,seq in enumerate(self.Sequence)
|
||||
if all(sub in s.glyphs for sub in seq.Substitute)]
|
||||
self.Sequence = [self.Sequence[i] for i in indices]
|
||||
self.Coverage.remap(indices)
|
||||
self.SequenceCount = len(self.Sequence)
|
||||
return bool(self.SequenceCount)
|
||||
self.mapping = {g:v for g,v in self.mapping.items()
|
||||
if g in s.glyphs and all(sub in s.glyphs for sub in v)}
|
||||
return bool(self.mapping)
|
||||
|
||||
@_add_method(otTables.AlternateSubst)
|
||||
def closure_glyphs(self, s, cur_glyphs):
|
||||
|
@ -203,6 +203,55 @@ class SingleSubst(FormatSwitchingBaseTable):
|
||||
mapping[attrs["in"]] = attrs["out"]
|
||||
|
||||
|
||||
class MultipleSubst(FormatSwitchingBaseTable):
|
||||
def postRead(self, rawTable, font):
|
||||
mapping = {}
|
||||
if self.Format == 1:
|
||||
glyphs = _getGlyphsFromCoverageTable(rawTable["Coverage"])
|
||||
subst = [s.Substitute for s in rawTable["Sequence"]]
|
||||
mapping = dict(zip(glyphs, subst))
|
||||
else:
|
||||
assert 0, "unknown format: %s" % self.Format
|
||||
self.mapping = mapping
|
||||
del self.Format # Don't need this anymore
|
||||
|
||||
def preWrite(self, font):
|
||||
mapping = getattr(self, "mapping", None)
|
||||
if mapping is None:
|
||||
mapping = self.mapping = {}
|
||||
cov = Coverage()
|
||||
cov.glyphs = sorted(list(mapping.keys()), key=font.getGlyphID)
|
||||
self.Format = 1
|
||||
rawTable = {
|
||||
"Coverage": cov,
|
||||
"Sequence": [self.makeSequence_(mapping[glyph])
|
||||
for glyph in cov.glyphs],
|
||||
}
|
||||
return rawTable
|
||||
|
||||
def toXML2(self, xmlWriter, font):
|
||||
items = sorted(self.mapping.items())
|
||||
for inGlyph, outGlyphs in items:
|
||||
out = ",".join(outGlyphs)
|
||||
xmlWriter.simpletag("Substitution",
|
||||
[("in", inGlyph), ("out", out)])
|
||||
xmlWriter.newline()
|
||||
|
||||
def fromXML(self, name, attrs, content, font):
|
||||
mapping = getattr(self, "mapping", None)
|
||||
if mapping is None:
|
||||
mapping = {}
|
||||
self.mapping = mapping
|
||||
outGlyphs = attrs["out"].split(",")
|
||||
mapping[attrs["in"]] = [g.strip() for g in outGlyphs]
|
||||
|
||||
@staticmethod
|
||||
def makeSequence_(g):
|
||||
seq = Sequence()
|
||||
seq.Substitute = g
|
||||
return seq
|
||||
|
||||
|
||||
class ClassDef(FormatSwitchingBaseTable):
|
||||
|
||||
def postRead(self, rawTable, font):
|
||||
|
@ -89,6 +89,60 @@ class SingleSubstTest(unittest.TestCase):
|
||||
self.assertEqual(table.mapping, {"A": "a", "B": "b", "C": "c"})
|
||||
|
||||
|
||||
class MultipleSubstTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.glyphs = ".notdef c f i t c_t f_f_i".split()
|
||||
self.font = FakeFont(self.glyphs)
|
||||
|
||||
def test_postRead_format1(self):
|
||||
makeSequence = otTables.MultipleSubst.makeSequence_
|
||||
table = otTables.MultipleSubst()
|
||||
table.Format = 1
|
||||
rawTable = {
|
||||
"Coverage": makeCoverage(["c_t", "f_f_i"]),
|
||||
"Sequence": [
|
||||
makeSequence(["c", "t"]),
|
||||
makeSequence(["f", "f", "i"])
|
||||
]
|
||||
}
|
||||
table.postRead(rawTable, self.font)
|
||||
self.assertEqual(table.mapping, {
|
||||
"c_t": ["c", "t"],
|
||||
"f_f_i": ["f", "f", "i"]
|
||||
})
|
||||
|
||||
def test_postRead_formatUnknown(self):
|
||||
table = otTables.MultipleSubst()
|
||||
table.Format = 987
|
||||
self.assertRaises(AssertionError, table.postRead, {}, self.font)
|
||||
|
||||
def test_preWrite_format1(self):
|
||||
table = otTables.MultipleSubst()
|
||||
table.mapping = {"c_t": ["c", "t"], "f_f_i": ["f", "f", "i"]}
|
||||
rawTable = table.preWrite(self.font)
|
||||
self.assertEqual(table.Format, 1)
|
||||
self.assertEqual(rawTable["Coverage"].glyphs, ["c_t", "f_f_i"])
|
||||
|
||||
def test_toXML2(self):
|
||||
writer = XMLWriter(StringIO())
|
||||
table = otTables.MultipleSubst()
|
||||
table.mapping = {"c_t": ["c", "t"], "f_f_i": ["f", "f", "i"]}
|
||||
table.toXML2(writer, self.font)
|
||||
self.assertEqual(writer.file.getvalue().splitlines()[1:], [
|
||||
'<Substitution in="c_t" out="c,t"/>',
|
||||
'<Substitution in="f_f_i" out="f,f,i"/>',
|
||||
])
|
||||
|
||||
def test_fromXML(self):
|
||||
table = otTables.MultipleSubst()
|
||||
table.fromXML("Substitution",
|
||||
{"in": "c_t", "out": "c,t"}, [], self.font)
|
||||
table.fromXML("Substitution",
|
||||
{"in": "f_f_i", "out": "f,f,i"}, [], self.font)
|
||||
self.assertEqual(table.mapping,
|
||||
{'c_t': ['c', 't'], 'f_f_i': ['f', 'f', 'i']})
|
||||
|
||||
|
||||
class LigatureSubstTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.glyphs = ".notdef c f i t c_t f_f f_i f_f_i".split()
|
||||
|
Loading…
x
Reference in New Issue
Block a user