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:
parent
26072943c3
commit
9f4cc2f1cb
@ -244,11 +244,70 @@ class AlternateSubstBuilder(LookupBuilder):
|
||||
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):
|
||||
def equals(self, other):
|
||||
return (LookupBuilder.equals(self, other) and
|
||||
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):
|
||||
"""Build the lookup.
|
||||
|
||||
|
@ -1392,6 +1392,63 @@ def test_stat_infinities():
|
||||
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__":
|
||||
import sys
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user