Merge pull request #3559 from fonttools/reuse-chain-multi-subst
[feaLib] try reuse existing inline chained multiple subst lookups when possible
This commit is contained in:
commit
4b030d2db8
@ -1286,10 +1286,7 @@ class Builder(object):
|
||||
self, location, prefix, glyph, suffix, replacements, forceChain=False
|
||||
):
|
||||
if prefix or suffix or forceChain:
|
||||
chain = self.get_lookup_(location, ChainContextSubstBuilder)
|
||||
sub = self.get_chained_lookup_(location, MultipleSubstBuilder)
|
||||
sub.mapping[glyph] = replacements
|
||||
chain.rules.append(ChainContextualRule(prefix, [{glyph}], suffix, [sub]))
|
||||
self.add_multi_subst_chained_(location, prefix, glyph, suffix, replacements)
|
||||
return
|
||||
lookup = self.get_lookup_(location, MultipleSubstBuilder)
|
||||
if glyph in lookup.mapping:
|
||||
@ -1369,7 +1366,7 @@ class Builder(object):
|
||||
# https://github.com/fonttools/fonttools/issues/512
|
||||
# https://github.com/fonttools/fonttools/issues/2150
|
||||
chain = self.get_lookup_(location, ChainContextSubstBuilder)
|
||||
sub = chain.find_chainable_single_subst(mapping)
|
||||
sub = chain.find_chainable_subst(mapping, SingleSubstBuilder)
|
||||
if sub is None:
|
||||
sub = self.get_chained_lookup_(location, SingleSubstBuilder)
|
||||
sub.mapping.update(mapping)
|
||||
@ -1377,6 +1374,19 @@ class Builder(object):
|
||||
ChainContextualRule(prefix, [list(mapping.keys())], suffix, [sub])
|
||||
)
|
||||
|
||||
def add_multi_subst_chained_(self, location, prefix, glyph, suffix, replacements):
|
||||
if not all(prefix) or not all(suffix):
|
||||
raise FeatureLibError(
|
||||
"Empty glyph class in contextual substitution", location
|
||||
)
|
||||
# https://github.com/fonttools/fonttools/issues/3551
|
||||
chain = self.get_lookup_(location, ChainContextSubstBuilder)
|
||||
sub = chain.find_chainable_subst({glyph: replacements}, MultipleSubstBuilder)
|
||||
if sub is None:
|
||||
sub = self.get_chained_lookup_(location, MultipleSubstBuilder)
|
||||
sub.mapping[glyph] = replacements
|
||||
chain.rules.append(ChainContextualRule(prefix, [{glyph}], suffix, [sub]))
|
||||
|
||||
# GSUB 8
|
||||
def add_reverse_chain_single_subst(self, location, old_prefix, old_suffix, mapping):
|
||||
if not mapping:
|
||||
|
@ -544,6 +544,10 @@ class ChainContextualBuilder(LookupBuilder):
|
||||
f"{classRuleAttr}Count",
|
||||
getattr(setForThisRule, f"{classRuleAttr}Count") + 1,
|
||||
)
|
||||
for i, classSet in enumerate(classSets):
|
||||
if not getattr(classSet, classRuleAttr):
|
||||
# class sets can be null so replace nop sets with None
|
||||
classSets[i] = None
|
||||
setattr(st, self.ruleSetAttr_(format=2, chaining=chaining), classSets)
|
||||
setattr(
|
||||
st, self.ruleSetAttr_(format=2, chaining=chaining) + "Count", len(classSets)
|
||||
@ -781,14 +785,14 @@ class ChainContextSubstBuilder(ChainContextualBuilder):
|
||||
)
|
||||
return result
|
||||
|
||||
def find_chainable_single_subst(self, mapping):
|
||||
"""Helper for add_single_subst_chained_()"""
|
||||
def find_chainable_subst(self, mapping, builder_class):
|
||||
"""Helper for add_{single,multi}_subst_chained_()"""
|
||||
res = None
|
||||
for rule in self.rules[::-1]:
|
||||
if rule.is_subtable_break:
|
||||
return res
|
||||
for sub in rule.lookups:
|
||||
if isinstance(sub, SingleSubstBuilder) and not any(
|
||||
if isinstance(sub, builder_class) and not any(
|
||||
g in mapping and mapping[g] != sub.mapping[g] for g in sub.mapping
|
||||
):
|
||||
res = sub
|
||||
|
@ -48,6 +48,7 @@ def makeTTFont():
|
||||
grave acute dieresis macron circumflex cedilla umlaut ogonek caron
|
||||
damma hamza sukun kasratan lam_meem_jeem noon.final noon.initial
|
||||
by feature lookup sub table uni0327 uni0328 e.fina
|
||||
idotbelow idotless iogonek acutecomb brevecomb ogonekcomb dotbelowcomb
|
||||
""".split()
|
||||
glyphs.extend("cid{:05d}".format(cid) for cid in range(800, 1001 + 1))
|
||||
font = TTFont()
|
||||
@ -82,6 +83,7 @@ class BuilderTest(unittest.TestCase):
|
||||
GSUB_5_formats delete_glyph STAT_test STAT_test_elidedFallbackNameID
|
||||
variable_scalar_valuerecord variable_scalar_anchor variable_conditionset
|
||||
variable_mark_anchor duplicate_lookup_reference
|
||||
contextual_inline_multi_sub_format_2
|
||||
""".split()
|
||||
|
||||
VARFONT_AXES = [
|
||||
|
17
Tests/feaLib/data/contextual_inline_multi_sub_format_2.fea
Normal file
17
Tests/feaLib/data/contextual_inline_multi_sub_format_2.fea
Normal file
@ -0,0 +1,17 @@
|
||||
# reduced from the ccmp feature in Oswald
|
||||
feature ccmp {
|
||||
lookup ccmp_Other_1 {
|
||||
@CombiningTopAccents = [acutecomb brevecomb];
|
||||
@CombiningNonTopAccents = [dotbelowcomb ogonekcomb];
|
||||
lookupflag UseMarkFilteringSet @CombiningTopAccents;
|
||||
# we should only generate two lookups; one contextual and one multiple sub,
|
||||
# containing 'sub idotbelow by idotless dotbelowcomb' and
|
||||
# 'sub iogonek by idotless ogonekcomb'
|
||||
sub idotbelow' @CombiningTopAccents by idotless dotbelowcomb;
|
||||
sub iogonek' @CombiningTopAccents by idotless ogonekcomb;
|
||||
sub idotbelow' @CombiningNonTopAccents @CombiningTopAccents by idotless dotbelowcomb;
|
||||
sub iogonek' @CombiningNonTopAccents @CombiningTopAccents by idotless ogonekcomb;
|
||||
} ccmp_Other_1;
|
||||
} ccmp;
|
||||
|
||||
|
135
Tests/feaLib/data/contextual_inline_multi_sub_format_2.ttx
Normal file
135
Tests/feaLib/data/contextual_inline_multi_sub_format_2.ttx
Normal file
@ -0,0 +1,135 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.53">
|
||||
|
||||
<GDEF>
|
||||
<Version value="0x00010002"/>
|
||||
<MarkGlyphSetsDef>
|
||||
<MarkSetTableFormat value="1"/>
|
||||
<!-- MarkSetCount=1 -->
|
||||
<Coverage index="0">
|
||||
<Glyph value="acutecomb"/>
|
||||
<Glyph value="brevecomb"/>
|
||||
</Coverage>
|
||||
</MarkGlyphSetsDef>
|
||||
</GDEF>
|
||||
|
||||
<GSUB>
|
||||
<Version value="0x00010000"/>
|
||||
<ScriptList>
|
||||
<!-- ScriptCount=1 -->
|
||||
<ScriptRecord index="0">
|
||||
<ScriptTag value="DFLT"/>
|
||||
<Script>
|
||||
<DefaultLangSys>
|
||||
<ReqFeatureIndex value="65535"/>
|
||||
<!-- FeatureCount=1 -->
|
||||
<FeatureIndex index="0" value="0"/>
|
||||
</DefaultLangSys>
|
||||
<!-- LangSysCount=0 -->
|
||||
</Script>
|
||||
</ScriptRecord>
|
||||
</ScriptList>
|
||||
<FeatureList>
|
||||
<!-- FeatureCount=1 -->
|
||||
<FeatureRecord index="0">
|
||||
<FeatureTag value="ccmp"/>
|
||||
<Feature>
|
||||
<!-- LookupCount=1 -->
|
||||
<LookupListIndex index="0" value="0"/>
|
||||
</Feature>
|
||||
</FeatureRecord>
|
||||
</FeatureList>
|
||||
<LookupList>
|
||||
<!-- LookupCount=2 -->
|
||||
<Lookup index="0">
|
||||
<LookupType value="6"/>
|
||||
<LookupFlag value="16"/><!-- useMarkFilteringSet -->
|
||||
<!-- SubTableCount=1 -->
|
||||
<ChainContextSubst index="0" Format="2">
|
||||
<Coverage>
|
||||
<Glyph value="idotbelow"/>
|
||||
<Glyph value="iogonek"/>
|
||||
</Coverage>
|
||||
<BacktrackClassDef>
|
||||
</BacktrackClassDef>
|
||||
<InputClassDef>
|
||||
<ClassDef glyph="idotbelow" class="1"/>
|
||||
<ClassDef glyph="iogonek" class="2"/>
|
||||
</InputClassDef>
|
||||
<LookAheadClassDef>
|
||||
<ClassDef glyph="acutecomb" class="1"/>
|
||||
<ClassDef glyph="brevecomb" class="1"/>
|
||||
<ClassDef glyph="dotbelowcomb" class="2"/>
|
||||
<ClassDef glyph="ogonekcomb" class="2"/>
|
||||
</LookAheadClassDef>
|
||||
<!-- ChainSubClassSetCount=3 -->
|
||||
<ChainSubClassSet index="0" empty="1"/>
|
||||
<ChainSubClassSet index="1">
|
||||
<!-- ChainSubClassRuleCount=2 -->
|
||||
<ChainSubClassRule index="0">
|
||||
<!-- BacktrackGlyphCount=0 -->
|
||||
<!-- InputGlyphCount=1 -->
|
||||
<!-- LookAheadGlyphCount=1 -->
|
||||
<LookAhead index="0" value="1"/>
|
||||
<!-- SubstCount=1 -->
|
||||
<SubstLookupRecord index="0">
|
||||
<SequenceIndex value="0"/>
|
||||
<LookupListIndex value="1"/>
|
||||
</SubstLookupRecord>
|
||||
</ChainSubClassRule>
|
||||
<ChainSubClassRule index="1">
|
||||
<!-- BacktrackGlyphCount=0 -->
|
||||
<!-- InputGlyphCount=1 -->
|
||||
<!-- LookAheadGlyphCount=2 -->
|
||||
<LookAhead index="0" value="2"/>
|
||||
<LookAhead index="1" value="1"/>
|
||||
<!-- SubstCount=1 -->
|
||||
<SubstLookupRecord index="0">
|
||||
<SequenceIndex value="0"/>
|
||||
<LookupListIndex value="1"/>
|
||||
</SubstLookupRecord>
|
||||
</ChainSubClassRule>
|
||||
</ChainSubClassSet>
|
||||
<ChainSubClassSet index="2">
|
||||
<!-- ChainSubClassRuleCount=2 -->
|
||||
<ChainSubClassRule index="0">
|
||||
<!-- BacktrackGlyphCount=0 -->
|
||||
<!-- InputGlyphCount=1 -->
|
||||
<!-- LookAheadGlyphCount=1 -->
|
||||
<LookAhead index="0" value="1"/>
|
||||
<!-- SubstCount=1 -->
|
||||
<SubstLookupRecord index="0">
|
||||
<SequenceIndex value="0"/>
|
||||
<LookupListIndex value="1"/>
|
||||
</SubstLookupRecord>
|
||||
</ChainSubClassRule>
|
||||
<ChainSubClassRule index="1">
|
||||
<!-- BacktrackGlyphCount=0 -->
|
||||
<!-- InputGlyphCount=1 -->
|
||||
<!-- LookAheadGlyphCount=2 -->
|
||||
<LookAhead index="0" value="2"/>
|
||||
<LookAhead index="1" value="1"/>
|
||||
<!-- SubstCount=1 -->
|
||||
<SubstLookupRecord index="0">
|
||||
<SequenceIndex value="0"/>
|
||||
<LookupListIndex value="1"/>
|
||||
</SubstLookupRecord>
|
||||
</ChainSubClassRule>
|
||||
</ChainSubClassSet>
|
||||
</ChainContextSubst>
|
||||
<MarkFilteringSet value="0"/>
|
||||
</Lookup>
|
||||
<Lookup index="1">
|
||||
<LookupType value="2"/>
|
||||
<LookupFlag value="16"/><!-- useMarkFilteringSet -->
|
||||
<!-- SubTableCount=1 -->
|
||||
<MultipleSubst index="0">
|
||||
<Substitution in="idotbelow" out="idotless,dotbelowcomb"/>
|
||||
<Substitution in="iogonek" out="idotless,ogonekcomb"/>
|
||||
</MultipleSubst>
|
||||
<MarkFilteringSet value="0"/>
|
||||
</Lookup>
|
||||
</LookupList>
|
||||
</GSUB>
|
||||
|
||||
</ttFont>
|
Loading…
x
Reference in New Issue
Block a user