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:
Cosimo Lupo 2024-06-11 17:08:59 +01:00 committed by GitHub
commit 4b030d2db8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 176 additions and 8 deletions

View File

@ -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:

View File

@ -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

View File

@ -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 = [

View 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;

View 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>