Introduce the concept of a ClassContextualRuleSet

Currently unused but will be super helpful for optimizing the formats of contextual lookups.

* Splits the rules of a class contextual lookup based on explicit subtable breaks
* Returns various properties on the ruleset to help determine appropriate layout format.
* (More properties, such as "touched glyphs", planned - will be added when needed.)
This commit is contained in:
Simon Cozens 2020-07-07 12:38:15 +01:00
parent 26072943c3
commit 9f4cc2f1cb
2 changed files with 116 additions and 0 deletions

View File

@ -244,11 +244,70 @@ class AlternateSubstBuilder(LookupBuilder):
self.alternates[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_ self.alternates[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
class ChainContextualRuleset():
def __init__(self):
self.rules = []
def addRule(self, prefix, glyphs, suffix, lookups):
self.rules.append((prefix, glyphs, suffix, lookups))
@property
def hasPrefixOrSuffix(self):
# Do we have any prefixes/suffixes? If this is False for all
# rulesets, we can express the whole lookup as GPOS5/GSUB7.
for (prefix, glyphs, suffix, lookups) in self.rules:
if len(prefix) > 0 or len(suffix) > 0: return True
return False
@property
def hasAnyGlyphClasses(self):
# Do we use glyph classes anywhere in the rules? If this is False
# we can express this subtable as a Format 1.
for (prefix, glyphs, suffix, lookups) in self.rules:
for coverage in (prefix, glyphs, suffix):
if any([len(x) > 1 for x in coverage]):
return True
return False
def format1Classdefs(self):
PREFIX, GLYPHS, SUFFIX = 0,1,2
classdefbuilders = []
for ix in [PREFIX, GLYPHS, SUFFIX]:
context = []
for r in self.rules:
context.append(r[ix])
classes = self._classBuilderForContext(context)
if not classes:
return None
classdefbuilders.append(classes)
return classdefbuilders
def _classBuilderForContext(self, context):
classdefbuilder = ClassDefBuilder(useClass0=False)
for position in context:
for glyphset in position:
if not classdefbuilder.canAdd(glyphset):
return None
classdefbuilder.add(glyphset)
return classdefbuilder
class ChainContextualBuilder(LookupBuilder): class ChainContextualBuilder(LookupBuilder):
def equals(self, other): def equals(self, other):
return (LookupBuilder.equals(self, other) and return (LookupBuilder.equals(self, other) and
self.rules == other.rules) self.rules == other.rules)
def rulesets(self):
# Return a list of ChainContextRuleset objects, taking explicit
# subtable breaks into account
ruleset = [ ChainContextualRuleset() ]
for (prefix, glyphs, suffix, lookups) in self.rules:
if prefix == self.SUBTABLE_BREAK_:
ruleset.append(ChainContextualRuleset() )
continue
ruleset[-1].addRule(prefix, glyphs, suffix, lookups)
# Squish any empty subtables
return [x for x in ruleset if len(x.rules) > 0]
def build(self): def build(self):
"""Build the lookup. """Build the lookup.

View File

@ -1392,6 +1392,63 @@ def test_stat_infinities():
assert struct.pack(">l", posInf) == b"\x7f\xff\xff\xff" assert struct.pack(">l", posInf) == b"\x7f\xff\xff\xff"
class ChainContextualRulesetTest(object):
def test_makeRulesets(self):
font = ttLib.TTFont()
font.setGlyphOrder(["a","b","c","d","A","B","C","D","E"])
sb = builder.ChainContextSubstBuilder(font, None)
prefix = [ ["a"], ["b"] ]
input_ = [ ["c"] ]
suffix = [ ]
lookups = [ None ]
sb.rules.append((prefix, input_, suffix, lookups))
prefix = [ ["a"], ["d"] ]
sb.rules.append((prefix, input_, suffix, lookups))
sb.add_subtable_break(None)
# Second subtable has some glyph classes
prefix = [ ["A"] ]
input_ = [ ["E"] ]
sb.rules.append((prefix, input_, suffix, lookups))
input_ = [ ["C","D"] ]
sb.rules.append((prefix, input_, suffix, lookups))
prefix = [ ["A", "B"] ]
input_ = [ ["E"] ]
sb.rules.append((prefix, input_, suffix, lookups))
sb.add_subtable_break(None)
# Third subtable has no pre/post context
prefix = []
suffix = []
sb.rules.append((prefix, input_, suffix, lookups))
input_ = [ ["C","D"] ]
sb.rules.append((prefix, input_, suffix, lookups))
rulesets = sb.rulesets()
assert len(rulesets) == 3
assert rulesets[0].hasPrefixOrSuffix
assert not rulesets[0].hasAnyGlyphClasses
cd = rulesets[0].format1Classdefs()
assert set(cd[0].classes()[1:]) == set([("d",),("b",),("a",)])
assert set(cd[1].classes()[1:]) == set([("c",)])
assert set(cd[2].classes()[1:]) == set()
assert rulesets[1].hasPrefixOrSuffix
assert rulesets[1].hasAnyGlyphClasses
assert not rulesets[1].format1Classdefs()
assert not rulesets[2].hasPrefixOrSuffix
assert rulesets[2].hasAnyGlyphClasses
assert rulesets[2].format1Classdefs()
cd = rulesets[2].format1Classdefs()
assert set(cd[0].classes()[1:]) == set()
assert set(cd[1].classes()[1:]) == set([("C","D"), ("E",)])
assert set(cd[2].classes()[1:]) == set()
if __name__ == "__main__": if __name__ == "__main__":
import sys import sys