Merge pull request #2026 from simoncozens/named-tuple-contextuals
Use namedtuple to represent contextual rules
This commit is contained in:
commit
d6ba64fcb6
@ -23,6 +23,7 @@ from fontTools.otlLib.builder import (
|
|||||||
ClassPairPosSubtableBuilder,
|
ClassPairPosSubtableBuilder,
|
||||||
PairPosBuilder,
|
PairPosBuilder,
|
||||||
SinglePosBuilder,
|
SinglePosBuilder,
|
||||||
|
ChainContextualRule,
|
||||||
)
|
)
|
||||||
from fontTools.otlLib.error import OpenTypeLibError
|
from fontTools.otlLib.error import OpenTypeLibError
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
@ -948,13 +949,17 @@ class Builder(object):
|
|||||||
def add_chain_context_pos(self, location, prefix, glyphs, suffix, lookups):
|
def add_chain_context_pos(self, location, prefix, glyphs, suffix, lookups):
|
||||||
lookup = self.get_lookup_(location, ChainContextPosBuilder)
|
lookup = self.get_lookup_(location, ChainContextPosBuilder)
|
||||||
lookup.rules.append(
|
lookup.rules.append(
|
||||||
(prefix, glyphs, suffix, self.find_lookup_builders_(lookups))
|
ChainContextualRule(
|
||||||
|
prefix, glyphs, suffix, self.find_lookup_builders_(lookups)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def add_chain_context_subst(self, location, prefix, glyphs, suffix, lookups):
|
def add_chain_context_subst(self, location, prefix, glyphs, suffix, lookups):
|
||||||
lookup = self.get_lookup_(location, ChainContextSubstBuilder)
|
lookup = self.get_lookup_(location, ChainContextSubstBuilder)
|
||||||
lookup.rules.append(
|
lookup.rules.append(
|
||||||
(prefix, glyphs, suffix, self.find_lookup_builders_(lookups))
|
ChainContextualRule(
|
||||||
|
prefix, glyphs, suffix, self.find_lookup_builders_(lookups)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def add_alternate_subst(self, location, prefix, glyph, suffix, replacement):
|
def add_alternate_subst(self, location, prefix, glyph, suffix, replacement):
|
||||||
@ -965,7 +970,7 @@ class Builder(object):
|
|||||||
if prefix or suffix:
|
if prefix or suffix:
|
||||||
chain = self.get_lookup_(location, ChainContextSubstBuilder)
|
chain = self.get_lookup_(location, ChainContextSubstBuilder)
|
||||||
lookup = self.get_chained_lookup_(location, AlternateSubstBuilder)
|
lookup = self.get_chained_lookup_(location, AlternateSubstBuilder)
|
||||||
chain.rules.append((prefix, [{glyph}], suffix, [lookup]))
|
chain.rules.append(ChainContextualRule(prefix, [{glyph}], suffix, [lookup]))
|
||||||
else:
|
else:
|
||||||
lookup = self.get_lookup_(location, AlternateSubstBuilder)
|
lookup = self.get_lookup_(location, AlternateSubstBuilder)
|
||||||
if glyph in lookup.alternates:
|
if glyph in lookup.alternates:
|
||||||
@ -1024,7 +1029,7 @@ class Builder(object):
|
|||||||
if prefix or suffix or forceChain:
|
if prefix or suffix or forceChain:
|
||||||
chain = self.get_lookup_(location, ChainContextSubstBuilder)
|
chain = self.get_lookup_(location, ChainContextSubstBuilder)
|
||||||
lookup = self.get_chained_lookup_(location, LigatureSubstBuilder)
|
lookup = self.get_chained_lookup_(location, LigatureSubstBuilder)
|
||||||
chain.rules.append((prefix, glyphs, suffix, [lookup]))
|
chain.rules.append(ChainContextualRule(prefix, glyphs, suffix, [lookup]))
|
||||||
else:
|
else:
|
||||||
lookup = self.get_lookup_(location, LigatureSubstBuilder)
|
lookup = self.get_lookup_(location, LigatureSubstBuilder)
|
||||||
|
|
||||||
@ -1043,7 +1048,7 @@ class Builder(object):
|
|||||||
chain = self.get_lookup_(location, ChainContextSubstBuilder)
|
chain = self.get_lookup_(location, ChainContextSubstBuilder)
|
||||||
sub = self.get_chained_lookup_(location, MultipleSubstBuilder)
|
sub = self.get_chained_lookup_(location, MultipleSubstBuilder)
|
||||||
sub.mapping[glyph] = replacements
|
sub.mapping[glyph] = replacements
|
||||||
chain.rules.append((prefix, [{glyph}], suffix, [sub]))
|
chain.rules.append(ChainContextualRule(prefix, [{glyph}], suffix, [sub]))
|
||||||
return
|
return
|
||||||
lookup = self.get_lookup_(location, MultipleSubstBuilder)
|
lookup = self.get_lookup_(location, MultipleSubstBuilder)
|
||||||
if glyph in lookup.mapping:
|
if glyph in lookup.mapping:
|
||||||
@ -1100,7 +1105,9 @@ class Builder(object):
|
|||||||
if sub is None:
|
if sub is None:
|
||||||
sub = self.get_chained_lookup_(location, SingleSubstBuilder)
|
sub = self.get_chained_lookup_(location, SingleSubstBuilder)
|
||||||
sub.mapping.update(mapping)
|
sub.mapping.update(mapping)
|
||||||
chain.rules.append((prefix, [mapping.keys()], suffix, [sub]))
|
chain.rules.append(
|
||||||
|
ChainContextualRule(prefix, [list(mapping.keys())], suffix, [sub])
|
||||||
|
)
|
||||||
|
|
||||||
def add_cursive_pos(self, location, glyphclass, entryAnchor, exitAnchor):
|
def add_cursive_pos(self, location, glyphclass, entryAnchor, exitAnchor):
|
||||||
lookup = self.get_lookup_(location, CursivePosBuilder)
|
lookup = self.get_lookup_(location, CursivePosBuilder)
|
||||||
@ -1206,7 +1213,9 @@ class Builder(object):
|
|||||||
sub.add_pos(location, glyph, otValue)
|
sub.add_pos(location, glyph, otValue)
|
||||||
subs.append(sub)
|
subs.append(sub)
|
||||||
assert len(pos) == len(subs), (pos, subs)
|
assert len(pos) == len(subs), (pos, subs)
|
||||||
chain.rules.append((prefix, [g for g, v in pos], suffix, subs))
|
chain.rules.append(
|
||||||
|
ChainContextualRule(prefix, [g for g, v in pos], suffix, subs)
|
||||||
|
)
|
||||||
|
|
||||||
def setGlyphClass_(self, location, glyph, glyphClass):
|
def setGlyphClass_(self, location, glyph, glyphClass):
|
||||||
oldClass, oldLocation = self.glyphClassDefs_.get(glyph, (None, None))
|
oldClass, oldLocation = self.glyphClassDefs_.get(glyph, (None, None))
|
||||||
|
@ -257,19 +257,27 @@ class AlternateSubstBuilder(LookupBuilder):
|
|||||||
self.alternates[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
|
self.alternates[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
|
||||||
|
|
||||||
|
|
||||||
|
class ChainContextualRule(
|
||||||
|
namedtuple("ChainContextualRule", ["prefix", "glyphs", "suffix", "lookups"])
|
||||||
|
):
|
||||||
|
@property
|
||||||
|
def is_subtable_break(self):
|
||||||
|
return self.prefix == LookupBuilder.SUBTABLE_BREAK_
|
||||||
|
|
||||||
|
|
||||||
class ChainContextualRuleset:
|
class ChainContextualRuleset:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.rules = []
|
self.rules = []
|
||||||
|
|
||||||
def addRule(self, prefix, glyphs, suffix, lookups):
|
def addRule(self, rule):
|
||||||
self.rules.append((prefix, glyphs, suffix, lookups))
|
self.rules.append(rule)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hasPrefixOrSuffix(self):
|
def hasPrefixOrSuffix(self):
|
||||||
# Do we have any prefixes/suffixes? If this is False for all
|
# Do we have any prefixes/suffixes? If this is False for all
|
||||||
# rulesets, we can express the whole lookup as GPOS5/GSUB7.
|
# rulesets, we can express the whole lookup as GPOS5/GSUB7.
|
||||||
for (prefix, glyphs, suffix, lookups) in self.rules:
|
for rule in self.rules:
|
||||||
if len(prefix) > 0 or len(suffix) > 0:
|
if len(rule.prefix) > 0 or len(rule.suffix) > 0:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -277,8 +285,8 @@ class ChainContextualRuleset:
|
|||||||
def hasAnyGlyphClasses(self):
|
def hasAnyGlyphClasses(self):
|
||||||
# Do we use glyph classes anywhere in the rules? If this is False
|
# Do we use glyph classes anywhere in the rules? If this is False
|
||||||
# we can express this subtable as a Format 1.
|
# we can express this subtable as a Format 1.
|
||||||
for (prefix, glyphs, suffix, lookups) in self.rules:
|
for rule in self.rules:
|
||||||
for coverage in (prefix, glyphs, suffix):
|
for coverage in (rule.prefix, rule.glyphs, rule.suffix):
|
||||||
if any(len(x) > 1 for x in coverage):
|
if any(len(x) > 1 for x in coverage):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@ -314,11 +322,11 @@ class ChainContextualBuilder(LookupBuilder):
|
|||||||
# Return a list of ChainContextRuleset objects, taking explicit
|
# Return a list of ChainContextRuleset objects, taking explicit
|
||||||
# subtable breaks into account
|
# subtable breaks into account
|
||||||
ruleset = [ChainContextualRuleset()]
|
ruleset = [ChainContextualRuleset()]
|
||||||
for (prefix, glyphs, suffix, lookups) in self.rules:
|
for rule in self.rules:
|
||||||
if prefix == self.SUBTABLE_BREAK_:
|
if rule.is_subtable_break:
|
||||||
ruleset.append(ChainContextualRuleset())
|
ruleset.append(ChainContextualRuleset())
|
||||||
continue
|
continue
|
||||||
ruleset[-1].addRule(prefix, glyphs, suffix, lookups)
|
ruleset[-1].addRule(rule)
|
||||||
# Squish any empty subtables
|
# Squish any empty subtables
|
||||||
return [x for x in ruleset if len(x.rules) > 0]
|
return [x for x in ruleset if len(x.rules) > 0]
|
||||||
|
|
||||||
@ -341,17 +349,16 @@ class ChainContextualBuilder(LookupBuilder):
|
|||||||
return self.buildLookup_(subtables)
|
return self.buildLookup_(subtables)
|
||||||
|
|
||||||
def buildFormat3Subtable(self, rule, chaining=True):
|
def buildFormat3Subtable(self, rule, chaining=True):
|
||||||
(prefix, glyphs, suffix, lookups) = rule
|
|
||||||
st = self.newSubtable_(chaining=chaining)
|
st = self.newSubtable_(chaining=chaining)
|
||||||
st.Format = 3
|
st.Format = 3
|
||||||
if chaining:
|
if chaining:
|
||||||
self.setBacktrackCoverage_(prefix, st)
|
self.setBacktrackCoverage_(rule.prefix, st)
|
||||||
self.setLookAheadCoverage_(suffix, st)
|
self.setLookAheadCoverage_(rule.suffix, st)
|
||||||
self.setInputCoverage_(glyphs, st)
|
self.setInputCoverage_(rule.glyphs, st)
|
||||||
else:
|
else:
|
||||||
self.setCoverage_(glyphs, st)
|
self.setCoverage_(rule.glyphs, st)
|
||||||
|
|
||||||
for sequenceIndex, lookupList in enumerate(lookups):
|
for sequenceIndex, lookupList in enumerate(rule.lookups):
|
||||||
if lookupList is not None:
|
if lookupList is not None:
|
||||||
if not isinstance(lookupList, list):
|
if not isinstance(lookupList, list):
|
||||||
# Can happen with synthesised lookups
|
# Can happen with synthesised lookups
|
||||||
@ -374,7 +381,7 @@ class ChainContextualBuilder(LookupBuilder):
|
|||||||
|
|
||||||
def add_subtable_break(self, location):
|
def add_subtable_break(self, location):
|
||||||
self.rules.append(
|
self.rules.append(
|
||||||
(
|
ChainContextualRule(
|
||||||
self.SUBTABLE_BREAK_,
|
self.SUBTABLE_BREAK_,
|
||||||
self.SUBTABLE_BREAK_,
|
self.SUBTABLE_BREAK_,
|
||||||
self.SUBTABLE_BREAK_,
|
self.SUBTABLE_BREAK_,
|
||||||
@ -455,7 +462,7 @@ class ChainContextPosBuilder(ChainContextualBuilder):
|
|||||||
|
|
||||||
def __init__(self, font, location):
|
def __init__(self, font, location):
|
||||||
LookupBuilder.__init__(self, font, location, "GPOS", 8)
|
LookupBuilder.__init__(self, font, location, "GPOS", 8)
|
||||||
self.rules = [] # (prefix, input, suffix, lookups)
|
self.rules = []
|
||||||
self.subtable_type = "Pos"
|
self.subtable_type = "Pos"
|
||||||
|
|
||||||
def find_chainable_single_pos(self, lookups, glyphs, value):
|
def find_chainable_single_pos(self, lookups, glyphs, value):
|
||||||
@ -505,10 +512,10 @@ class ChainContextSubstBuilder(ChainContextualBuilder):
|
|||||||
|
|
||||||
def getAlternateGlyphs(self):
|
def getAlternateGlyphs(self):
|
||||||
result = {}
|
result = {}
|
||||||
for (prefix, _, _, lookuplist) in self.rules:
|
for rule in self.rules:
|
||||||
if prefix == self.SUBTABLE_BREAK_:
|
if rule.is_subtable_break:
|
||||||
continue
|
continue
|
||||||
for lookups in lookuplist:
|
for lookups in rule.lookups:
|
||||||
if not isinstance(lookups, list):
|
if not isinstance(lookups, list):
|
||||||
lookups = [lookups]
|
lookups = [lookups]
|
||||||
for lookup in lookups:
|
for lookup in lookups:
|
||||||
@ -521,10 +528,10 @@ class ChainContextSubstBuilder(ChainContextualBuilder):
|
|||||||
def find_chainable_single_subst(self, glyphs):
|
def find_chainable_single_subst(self, glyphs):
|
||||||
"""Helper for add_single_subst_chained_()"""
|
"""Helper for add_single_subst_chained_()"""
|
||||||
res = None
|
res = None
|
||||||
for prefix, _, _, rules in self.rules[::-1]:
|
for rule in self.rules[::-1]:
|
||||||
if prefix == self.SUBTABLE_BREAK_:
|
if rule.is_subtable_break:
|
||||||
return res
|
return res
|
||||||
for sub in rules:
|
for sub in rule.lookups:
|
||||||
if isinstance(sub, SingleSubstBuilder) and not any(
|
if isinstance(sub, SingleSubstBuilder) and not any(
|
||||||
g in glyphs for g in sub.mapping.keys()
|
g in glyphs for g in sub.mapping.keys()
|
||||||
):
|
):
|
||||||
|
@ -1398,28 +1398,28 @@ class ChainContextualRulesetTest(object):
|
|||||||
font.setGlyphOrder(["a","b","c","d","A","B","C","D","E"])
|
font.setGlyphOrder(["a","b","c","d","A","B","C","D","E"])
|
||||||
sb = builder.ChainContextSubstBuilder(font, None)
|
sb = builder.ChainContextSubstBuilder(font, None)
|
||||||
prefix, input_, suffix, lookups = [["a"], ["b"]], [["c"]], [], [None]
|
prefix, input_, suffix, lookups = [["a"], ["b"]], [["c"]], [], [None]
|
||||||
sb.rules.append((prefix, input_, suffix, lookups))
|
sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups))
|
||||||
|
|
||||||
prefix, input_, suffix, lookups = [["a"], ["d"]], [["c"]], [], [None]
|
prefix, input_, suffix, lookups = [["a"], ["d"]], [["c"]], [], [None]
|
||||||
sb.rules.append((prefix, input_, suffix, lookups))
|
sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups))
|
||||||
|
|
||||||
sb.add_subtable_break(None)
|
sb.add_subtable_break(None)
|
||||||
|
|
||||||
# Second subtable has some glyph classes
|
# Second subtable has some glyph classes
|
||||||
prefix, input_, suffix, lookups = [["A"]], [["E"]], [], [None]
|
prefix, input_, suffix, lookups = [["A"]], [["E"]], [], [None]
|
||||||
sb.rules.append((prefix, input_, suffix, lookups))
|
sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups))
|
||||||
prefix, input_, suffix, lookups = [["A"]], [["C","D"]], [], [None]
|
prefix, input_, suffix, lookups = [["A"]], [["C","D"]], [], [None]
|
||||||
sb.rules.append((prefix, input_, suffix, lookups))
|
sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups))
|
||||||
prefix, input_, suffix, lookups = [["A", "B"]], [["E"]], [], [None]
|
prefix, input_, suffix, lookups = [["A", "B"]], [["E"]], [], [None]
|
||||||
sb.rules.append((prefix, input_, suffix, lookups))
|
sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups))
|
||||||
|
|
||||||
sb.add_subtable_break(None)
|
sb.add_subtable_break(None)
|
||||||
|
|
||||||
# Third subtable has no pre/post context
|
# Third subtable has no pre/post context
|
||||||
prefix, input_, suffix, lookups = [], [["E"]], [], [None]
|
prefix, input_, suffix, lookups = [], [["E"]], [], [None]
|
||||||
sb.rules.append((prefix, input_, suffix, lookups))
|
sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups))
|
||||||
prefix, input_, suffix, lookups = [], [["C","D"]], [], [None]
|
prefix, input_, suffix, lookups = [], [["C","D"]], [], [None]
|
||||||
sb.rules.append((prefix, input_, suffix, lookups))
|
sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups))
|
||||||
|
|
||||||
rulesets = sb.rulesets()
|
rulesets = sb.rulesets()
|
||||||
assert len(rulesets) == 3
|
assert len(rulesets) == 3
|
||||||
|
@ -13,6 +13,7 @@ from fontTools.otlLib.builder import (
|
|||||||
ClassPairPosSubtableBuilder,
|
ClassPairPosSubtableBuilder,
|
||||||
PairPosBuilder,
|
PairPosBuilder,
|
||||||
SinglePosBuilder,
|
SinglePosBuilder,
|
||||||
|
ChainContextualRule
|
||||||
)
|
)
|
||||||
from fontTools.otlLib.error import OpenTypeLibError
|
from fontTools.otlLib.error import OpenTypeLibError
|
||||||
from fontTools.ttLib import TTFont
|
from fontTools.ttLib import TTFont
|
||||||
@ -79,7 +80,7 @@ def test_chain_pos_references_GSUB_lookup(ttfont):
|
|||||||
location = MockBuilderLocation((0, "alpha"))
|
location = MockBuilderLocation((0, "alpha"))
|
||||||
builder = ChainContextPosBuilder(ttfont, location)
|
builder = ChainContextPosBuilder(ttfont, location)
|
||||||
builder2 = SingleSubstBuilder(ttfont, location)
|
builder2 = SingleSubstBuilder(ttfont, location)
|
||||||
builder.rules.append(([], [], [], [[builder2]]))
|
builder.rules.append(ChainContextualRule([], [], [], [[builder2]]))
|
||||||
|
|
||||||
with pytest.raises(OpenTypeLibError, match="0:alpha: Missing index of the specified lookup, might be a substitution lookup"):
|
with pytest.raises(OpenTypeLibError, match="0:alpha: Missing index of the specified lookup, might be a substitution lookup"):
|
||||||
builder.build()
|
builder.build()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user