[feaLib] Implement GSUB LookupType 8: Reverse chaining single substitutions
This commit is contained in:
parent
9f0aa03aec
commit
cab0067c7e
@ -138,6 +138,17 @@ class MultipleSubstitution(Statement):
|
||||
self.glyph, self.replacement)
|
||||
|
||||
|
||||
class ReverseChainingSingleSubstitution(Statement):
|
||||
def __init__(self, location, old_prefix, old_suffix, mapping):
|
||||
Statement.__init__(self, location)
|
||||
self.old_prefix, self.old_suffix = old_prefix, old_suffix
|
||||
self.mapping = mapping
|
||||
|
||||
def build(self, builder):
|
||||
builder.add_reverse_chaining_single_substitution(
|
||||
self.location, self.old_prefix, self.old_suffix, self.mapping)
|
||||
|
||||
|
||||
class SingleSubstitution(Statement):
|
||||
def __init__(self, location, mapping):
|
||||
Statement.__init__(self, location)
|
||||
@ -171,8 +182,7 @@ class SubstitutionRule(Statement):
|
||||
|
||||
def build(self, builder):
|
||||
builder.add_substitution(
|
||||
self.location,
|
||||
self.old_prefix, self.old, self.old_suffix,
|
||||
self.location, self.old_prefix, self.old, self.old_suffix,
|
||||
self.new, self.lookups)
|
||||
|
||||
|
||||
|
@ -247,18 +247,26 @@ class Builder(object):
|
||||
self.set_language(location, "dflt",
|
||||
include_default=True, required=False)
|
||||
|
||||
def add_substitution(self, location, old_prefix, old, old_suffix, new,
|
||||
lookups):
|
||||
assert len(new) == 0, new
|
||||
def find_lookup_builders_(self, lookups):
|
||||
"""Helper for building chain contextual substitutions
|
||||
|
||||
Given a list of lookup names, finds the LookupBuilder for each name.
|
||||
If an input name is None, it gets mapped to a None LookupBuilder.
|
||||
"""
|
||||
lookup_builders = []
|
||||
for lookup in lookups:
|
||||
if lookup is not None:
|
||||
lookup_builders.append(self.named_lookups_.get(lookup.name))
|
||||
else:
|
||||
lookup_builders.append(None)
|
||||
return lookup_builders
|
||||
|
||||
def add_substitution(self, location, old_prefix, old, old_suffix, new,
|
||||
lookups):
|
||||
assert len(new) == 0, new
|
||||
lookup = self.get_lookup_(location, ChainContextSubstBuilder)
|
||||
lookup.substitutions.append((old_prefix, old, old_suffix,
|
||||
lookup_builders))
|
||||
self.find_lookup_builders_(lookups)))
|
||||
|
||||
def add_alternate_substitution(self, location, glyph, from_class):
|
||||
lookup = self.get_lookup_(location, AlternateSubstBuilder)
|
||||
@ -280,6 +288,11 @@ class Builder(object):
|
||||
location)
|
||||
lookup.mapping[glyph] = replacements
|
||||
|
||||
def add_reverse_chaining_single_substitution(self, location, old_prefix,
|
||||
old_suffix, mapping):
|
||||
lookup = self.get_lookup_(location, ReverseChainSingleSubstBuilder)
|
||||
lookup.substitutions.append((old_prefix, old_suffix, mapping))
|
||||
|
||||
def add_single_substitution(self, location, mapping):
|
||||
lookup = self.get_lookup_(location, SingleSubstBuilder)
|
||||
for (from_glyph, to_glyph) in mapping.items():
|
||||
@ -304,6 +317,33 @@ class LookupBuilder(object):
|
||||
self.table == other.table and
|
||||
self.lookup_flag == other.lookup_flag)
|
||||
|
||||
@staticmethod
|
||||
def setBacktrackCoverage_(prefix, subtable):
|
||||
subtable.BacktrackGlyphCount = len(prefix)
|
||||
subtable.BacktrackCoverage = []
|
||||
for p in reversed(prefix):
|
||||
coverage = otTables.BacktrackCoverage()
|
||||
coverage.glyphs = sorted(list(p))
|
||||
subtable.BacktrackCoverage.append(coverage)
|
||||
|
||||
@staticmethod
|
||||
def setLookAheadCoverage_(suffix, subtable):
|
||||
subtable.LookAheadGlyphCount = len(suffix)
|
||||
subtable.LookAheadCoverage = []
|
||||
for s in suffix:
|
||||
coverage = otTables.LookAheadCoverage()
|
||||
coverage.glyphs = sorted(list(s))
|
||||
subtable.LookAheadCoverage.append(coverage)
|
||||
|
||||
@staticmethod
|
||||
def setInputCoverage_(glyphs, subtable):
|
||||
subtable.InputGlyphCount = len(glyphs)
|
||||
subtable.InputCoverage = []
|
||||
for g in glyphs:
|
||||
coverage = otTables.InputCoverage()
|
||||
coverage.glyphs = sorted(list(g))
|
||||
subtable.InputCoverage.append(coverage)
|
||||
|
||||
|
||||
class AlternateSubstBuilder(LookupBuilder):
|
||||
def __init__(self, location, lookup_flag):
|
||||
@ -343,27 +383,9 @@ class ChainContextSubstBuilder(LookupBuilder):
|
||||
st = otTables.ChainContextSubst()
|
||||
lookup.SubTable.append(st)
|
||||
st.Format = 3
|
||||
|
||||
st.BacktrackGlyphCount = len(prefix)
|
||||
st.BacktrackCoverage = []
|
||||
for p in reversed(prefix):
|
||||
coverage = otTables.BacktrackCoverage()
|
||||
coverage.glyphs = sorted(list(p))
|
||||
st.BacktrackCoverage.append(coverage)
|
||||
|
||||
st.InputGlyphCount = len(input)
|
||||
st.InputCoverage = []
|
||||
for i in input:
|
||||
coverage = otTables.InputCoverage()
|
||||
coverage.glyphs = sorted(list(i))
|
||||
st.InputCoverage.append(coverage)
|
||||
|
||||
st.LookAheadGlyphCount = len(suffix)
|
||||
st.LookAheadCoverage = []
|
||||
for s in suffix:
|
||||
coverage = otTables.LookAheadCoverage()
|
||||
coverage.glyphs = sorted(list(s))
|
||||
st.LookAheadCoverage.append(coverage)
|
||||
self.setBacktrackCoverage_(prefix, st)
|
||||
self.setLookAheadCoverage_(suffix, st)
|
||||
self.setInputCoverage_(input, st)
|
||||
|
||||
st.SubstCount = len([l for l in lookups if l is not None])
|
||||
st.SubstLookupRecord = []
|
||||
@ -442,6 +464,36 @@ class MultipleSubstBuilder(LookupBuilder):
|
||||
return lookup
|
||||
|
||||
|
||||
class ReverseChainSingleSubstBuilder(LookupBuilder):
|
||||
def __init__(self, location, lookup_flag):
|
||||
LookupBuilder.__init__(self, location, 'GSUB', 8, lookup_flag)
|
||||
self.substitutions = [] # (prefix, suffix, mapping)
|
||||
|
||||
def equals(self, other):
|
||||
return (LookupBuilder.equals(self, other) and
|
||||
self.substitutions == other.substitutions)
|
||||
|
||||
def build(self):
|
||||
lookup = otTables.Lookup()
|
||||
lookup.SubTable = []
|
||||
for prefix, suffix, mapping in self.substitutions:
|
||||
st = otTables.ReverseChainSingleSubst()
|
||||
st.Format = 1
|
||||
lookup.SubTable.append(st)
|
||||
self.setBacktrackCoverage_(prefix, st)
|
||||
self.setLookAheadCoverage_(suffix, st)
|
||||
coverage = sorted(list(mapping.keys()))
|
||||
st.Coverage = otTables.Coverage()
|
||||
st.Coverage.glyphs = coverage
|
||||
st.GlyphCount = len(coverage)
|
||||
st.Substitute = [mapping[g] for g in coverage]
|
||||
|
||||
lookup.LookupFlag = self.lookup_flag
|
||||
lookup.LookupType = self.lookup_type
|
||||
lookup.SubTableCount = len(lookup.SubTable)
|
||||
return lookup
|
||||
|
||||
|
||||
class SingleSubstBuilder(LookupBuilder):
|
||||
def __init__(self, location, lookup_flag):
|
||||
LookupBuilder.__init__(self, location, 'GSUB', 1, lookup_flag)
|
||||
|
@ -104,6 +104,11 @@ class BuilderTest(unittest.TestCase):
|
||||
" sub f_f_i by f f i;"
|
||||
"} test;")
|
||||
|
||||
def test_reverseChainingSingleSubst(self):
|
||||
font = TTFont()
|
||||
addOpenTypeFeatures(self.getpath("GSUB_8.fea"), font)
|
||||
self.expect_ttx(font, self.getpath("GSUB_8.ttx"))
|
||||
|
||||
def test_singleSubst_multipleSubstitutionsForSameGlyph(self):
|
||||
self.assertRaisesRegex(
|
||||
FeatureLibError,
|
||||
@ -138,6 +143,12 @@ class BuilderTest(unittest.TestCase):
|
||||
addOpenTypeFeatures(self.getpath("spec5fi1.fea"), font)
|
||||
self.expect_ttx(font, self.getpath("spec5fi1.ttx"))
|
||||
|
||||
def test_spec5h1(self):
|
||||
# OpenType Feature File specification, section 5.h, example 1.
|
||||
font = TTFont()
|
||||
addOpenTypeFeatures(self.getpath("spec5h1.fea"), font)
|
||||
self.expect_ttx(font, self.getpath("spec5h1.ttx"))
|
||||
|
||||
def test_languagesystem(self):
|
||||
builder = Builder(None, TTFont())
|
||||
builder.add_language_system(None, 'latn', 'FRA')
|
||||
|
@ -207,8 +207,9 @@ class Parser(object):
|
||||
return ast.ScriptStatement(location, script)
|
||||
|
||||
def parse_substitute_(self):
|
||||
assert self.cur_token_ in {"substitute", "sub"}
|
||||
assert self.cur_token_ in {"substitute", "sub", "reversesub", "rsub"}
|
||||
location = self.cur_token_location_
|
||||
reverse = self.cur_token_ in {"reversesub", "rsub"}
|
||||
old_prefix, old, lookups, old_suffix = self.parse_glyph_pattern_()
|
||||
|
||||
new = []
|
||||
@ -230,6 +231,10 @@ class Parser(object):
|
||||
# GSUB lookup type 3: Alternate substitution.
|
||||
# Format: "substitute a from [a.1 a.2 a.3];"
|
||||
if keyword == "from":
|
||||
if reverse:
|
||||
raise FeatureLibError(
|
||||
'Reverse chaining substitutions do not support "from"',
|
||||
location)
|
||||
if len(old) != 1 or len(old[0]) != 1:
|
||||
raise FeatureLibError(
|
||||
'Expected a single glyph before "from"',
|
||||
@ -246,7 +251,7 @@ class Parser(object):
|
||||
# Format A: "substitute a by a.sc;"
|
||||
# Format B: "substitute [one.fitted one.oldstyle] by one;"
|
||||
# Format C: "substitute [a-d] by [A.sc-D.sc];"
|
||||
if (len(old_prefix) == 0 and len(old_suffix) == 0 and
|
||||
if (not reverse and len(old_prefix) == 0 and len(old_suffix) == 0 and
|
||||
len(old) == 1 and len(new) == 1 and num_lookups == 0):
|
||||
glyphs, replacements = sorted(list(old[0])), sorted(list(new[0]))
|
||||
if len(replacements) == 1:
|
||||
@ -261,7 +266,7 @@ class Parser(object):
|
||||
|
||||
# GSUB lookup type 2: Multiple substitution.
|
||||
# Format: "substitute f_f_i by f f i;"
|
||||
if (len(old_prefix) == 0 and len(old_suffix) == 0 and
|
||||
if (not reverse and len(old_prefix) == 0 and len(old_suffix) == 0 and
|
||||
len(old) == 1 and len(old[0]) == 1 and
|
||||
len(new) > 1 and max([len(n) for n in new]) == 1 and
|
||||
num_lookups == 0):
|
||||
@ -270,14 +275,43 @@ class Parser(object):
|
||||
|
||||
# GSUB lookup type 4: Ligature substitution.
|
||||
# Format: "substitute f f i by f_f_i;"
|
||||
if (len(old_prefix) == 0 and len(old_suffix) == 0 and
|
||||
if (not reverse and len(old_prefix) == 0 and len(old_suffix) == 0 and
|
||||
len(old) > 1 and len(new) == 1 and len(new[0]) == 1 and
|
||||
num_lookups == 0):
|
||||
return ast.LigatureSubstitution(location, old, list(new[0])[0])
|
||||
|
||||
# GSUB lookup type 8: Reverse chaining substitution.
|
||||
if reverse:
|
||||
if len(old) != 1:
|
||||
raise FeatureLibError(
|
||||
"In reverse chaining single substitutions, "
|
||||
"only a single glyph or glyph class can be replaced",
|
||||
location)
|
||||
if len(new) != 1:
|
||||
raise FeatureLibError(
|
||||
'In reverse chaining single substitutions, '
|
||||
'the replacement (after "by") must be a single glyph '
|
||||
'or glyph class', location)
|
||||
if num_lookups != 0:
|
||||
raise FeatureLibError(
|
||||
"Reverse chaining substitutions cannot call named lookups",
|
||||
location)
|
||||
glyphs, replacements = sorted(list(old[0])), sorted(list(new[0]))
|
||||
if len(replacements) == 1:
|
||||
replacements = replacements * len(glyphs)
|
||||
if len(glyphs) != len(replacements):
|
||||
raise FeatureLibError(
|
||||
'Expected a glyph class with %d elements after "by", '
|
||||
'but found a glyph class with %d elements' %
|
||||
(len(glyphs), len(replacements)), location)
|
||||
return ast.ReverseChainingSingleSubstitution(
|
||||
location, old_prefix, old_suffix,
|
||||
dict(zip(glyphs, replacements)))
|
||||
|
||||
rule = ast.SubstitutionRule(location, old, new)
|
||||
rule.old_prefix, rule.old_suffix = old_prefix, old_suffix
|
||||
rule.lookups = lookups
|
||||
rule.reverse = reverse
|
||||
return rule
|
||||
|
||||
def parse_subtable_(self):
|
||||
@ -370,8 +404,8 @@ class Parser(object):
|
||||
statements.append(self.parse_lookup_(vertical))
|
||||
elif self.is_cur_keyword_("script"):
|
||||
statements.append(self.parse_script_())
|
||||
elif (self.is_cur_keyword_("substitute") or
|
||||
self.is_cur_keyword_("sub")):
|
||||
elif (self.is_cur_keyword_({"sub", "substitute",
|
||||
"rsub", "reversesub"})):
|
||||
statements.append(self.parse_substitute_())
|
||||
elif self.is_cur_keyword_("subtable"):
|
||||
statements.append(self.parse_subtable_())
|
||||
@ -393,7 +427,12 @@ class Parser(object):
|
||||
self.expect_symbol_(";")
|
||||
|
||||
def is_cur_keyword_(self, k):
|
||||
return (self.cur_token_type_ is Lexer.NAME) and (self.cur_token_ == k)
|
||||
if self.cur_token_type_ is Lexer.NAME:
|
||||
if isinstance(k, type("")): # basestring is gone in Python3
|
||||
return self.cur_token_ == k
|
||||
else:
|
||||
return self.cur_token_ in k
|
||||
return False
|
||||
|
||||
def expect_tag_(self):
|
||||
self.advance_lexer_()
|
||||
|
@ -256,6 +256,64 @@ class ParserTest(unittest.TestCase):
|
||||
FeatureLibError, 'Unknown lookup "Huh"',
|
||||
self.parse, "feature liga {lookup Huh;} liga;")
|
||||
|
||||
def test_rsub_format_a(self):
|
||||
doc = self.parse("feature test {rsub a [b B] c' d [e E] by C;} test;")
|
||||
rsub = doc.statements[0].statements[0]
|
||||
self.assertEqual(type(rsub), ast.ReverseChainingSingleSubstitution)
|
||||
self.assertEqual(rsub.old_prefix, [{"a"}, {"b", "B"}])
|
||||
self.assertEqual(rsub.mapping, {"c": "C"})
|
||||
self.assertEqual(rsub.old_suffix, [{"d"}, {"e", "E"}])
|
||||
|
||||
def test_rsub_format_b(self):
|
||||
doc = self.parse(
|
||||
"feature smcp {"
|
||||
" reversesub A B [one.fitted one.oldstyle]' C [d D] by one;"
|
||||
"} smcp;")
|
||||
rsub = doc.statements[0].statements[0]
|
||||
self.assertEqual(type(rsub), ast.ReverseChainingSingleSubstitution)
|
||||
self.assertEqual(rsub.old_prefix, [{"A"}, {"B"}])
|
||||
self.assertEqual(rsub.old_suffix, [{"C"}, {"d", "D"}])
|
||||
self.assertEqual(rsub.mapping, {
|
||||
"one.fitted": "one",
|
||||
"one.oldstyle": "one"
|
||||
})
|
||||
|
||||
def test_rsub_format_c(self):
|
||||
doc = self.parse(
|
||||
"feature test {"
|
||||
" reversesub BACK TRACK [a-d]' LOOK AHEAD by [A.sc-D.sc];"
|
||||
"} test;")
|
||||
rsub = doc.statements[0].statements[0]
|
||||
self.assertEqual(type(rsub), ast.ReverseChainingSingleSubstitution)
|
||||
self.assertEqual(rsub.old_prefix, [{"BACK"}, {"TRACK"}])
|
||||
self.assertEqual(rsub.old_suffix, [{"LOOK"}, {"AHEAD"}])
|
||||
self.assertEqual(rsub.mapping, {
|
||||
"a": "A.sc",
|
||||
"b": "B.sc",
|
||||
"c": "C.sc",
|
||||
"d": "D.sc"
|
||||
})
|
||||
|
||||
def test_rsub_from(self):
|
||||
self.assertRaisesRegex(
|
||||
FeatureLibError,
|
||||
'Reverse chaining substitutions do not support "from"',
|
||||
self.parse, "feature test {rsub a from [a.1 a.2 a.3];} test;")
|
||||
|
||||
def test_rsub_nonsingle(self):
|
||||
self.assertRaisesRegex(
|
||||
FeatureLibError,
|
||||
"In reverse chaining single substitutions, only a single glyph "
|
||||
"or glyph class can be replaced",
|
||||
self.parse, "feature test {rsub c d by c_d;} test;")
|
||||
|
||||
def test_rsub_multiple_replacement_glyphs(self):
|
||||
self.assertRaisesRegex(
|
||||
FeatureLibError,
|
||||
'In reverse chaining single substitutions, the replacement '
|
||||
'\(after "by"\) must be a single glyph or glyph class',
|
||||
self.parse, "feature test {rsub f_i by f i;} test;")
|
||||
|
||||
def test_script(self):
|
||||
doc = self.parse("feature test {script cyrl;} test;")
|
||||
s = doc.statements[0].statements[0]
|
||||
|
11
Lib/fontTools/feaLib/testdata/GSUB_8.fea
vendored
Normal file
11
Lib/fontTools/feaLib/testdata/GSUB_8.fea
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
languagesystem DFLT dflt;
|
||||
|
||||
feature test {
|
||||
reversesub [a A] [b B] [c C] q' [d D] [e E] [f F] by Q;
|
||||
reversesub [a A] [b B] [c C] [s-z]' [d D] [e E] [f F] by [S-Z];
|
||||
|
||||
# Having no context for a reverse chaining substitution rule
|
||||
# is a little degenerate (we define a chain without linking it
|
||||
# to anything else), but makeotf accepts this.
|
||||
reversesub p by P;
|
||||
} test;
|
142
Lib/fontTools/feaLib/testdata/GSUB_8.ttx
vendored
Normal file
142
Lib/fontTools/feaLib/testdata/GSUB_8.ttx
vendored
Normal file
@ -0,0 +1,142 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ttFont>
|
||||
|
||||
<GSUB>
|
||||
<Version value="1.0"/>
|
||||
<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="test"/>
|
||||
<Feature>
|
||||
<!-- LookupCount=1 -->
|
||||
<LookupListIndex index="0" value="0"/>
|
||||
</Feature>
|
||||
</FeatureRecord>
|
||||
</FeatureList>
|
||||
<LookupList>
|
||||
<!-- LookupCount=1 -->
|
||||
<Lookup index="0">
|
||||
<!-- LookupType=8 -->
|
||||
<LookupFlag value="0"/>
|
||||
<!-- SubTableCount=3 -->
|
||||
<ReverseChainSingleSubst index="0" Format="1">
|
||||
<Coverage>
|
||||
<Glyph value="q"/>
|
||||
</Coverage>
|
||||
<!-- BacktrackGlyphCount=3 -->
|
||||
<BacktrackCoverage index="0">
|
||||
<Glyph value="C"/>
|
||||
<Glyph value="c"/>
|
||||
</BacktrackCoverage>
|
||||
<BacktrackCoverage index="1">
|
||||
<Glyph value="B"/>
|
||||
<Glyph value="b"/>
|
||||
</BacktrackCoverage>
|
||||
<BacktrackCoverage index="2">
|
||||
<Glyph value="A"/>
|
||||
<Glyph value="a"/>
|
||||
</BacktrackCoverage>
|
||||
<!-- LookAheadGlyphCount=3 -->
|
||||
<LookAheadCoverage index="0">
|
||||
<Glyph value="D"/>
|
||||
<Glyph value="d"/>
|
||||
</LookAheadCoverage>
|
||||
<LookAheadCoverage index="1">
|
||||
<Glyph value="E"/>
|
||||
<Glyph value="e"/>
|
||||
</LookAheadCoverage>
|
||||
<LookAheadCoverage index="2">
|
||||
<Glyph value="F"/>
|
||||
<Glyph value="f"/>
|
||||
</LookAheadCoverage>
|
||||
<!-- GlyphCount=1 -->
|
||||
<Substitute index="0" value="Q"/>
|
||||
</ReverseChainSingleSubst>
|
||||
<ReverseChainSingleSubst index="1" Format="1">
|
||||
<Coverage>
|
||||
<Glyph value="s"/>
|
||||
<Glyph value="t"/>
|
||||
<Glyph value="u"/>
|
||||
<Glyph value="v"/>
|
||||
<Glyph value="w"/>
|
||||
<Glyph value="x"/>
|
||||
<Glyph value="y"/>
|
||||
<Glyph value="z"/>
|
||||
</Coverage>
|
||||
<!-- BacktrackGlyphCount=3 -->
|
||||
<BacktrackCoverage index="0">
|
||||
<Glyph value="C"/>
|
||||
<Glyph value="c"/>
|
||||
</BacktrackCoverage>
|
||||
<BacktrackCoverage index="1">
|
||||
<Glyph value="B"/>
|
||||
<Glyph value="b"/>
|
||||
</BacktrackCoverage>
|
||||
<BacktrackCoverage index="2">
|
||||
<Glyph value="A"/>
|
||||
<Glyph value="a"/>
|
||||
</BacktrackCoverage>
|
||||
<!-- LookAheadGlyphCount=3 -->
|
||||
<LookAheadCoverage index="0">
|
||||
<Glyph value="D"/>
|
||||
<Glyph value="d"/>
|
||||
</LookAheadCoverage>
|
||||
<LookAheadCoverage index="1">
|
||||
<Glyph value="E"/>
|
||||
<Glyph value="e"/>
|
||||
</LookAheadCoverage>
|
||||
<LookAheadCoverage index="2">
|
||||
<Glyph value="F"/>
|
||||
<Glyph value="f"/>
|
||||
</LookAheadCoverage>
|
||||
<!-- GlyphCount=8 -->
|
||||
<Substitute index="0" value="S"/>
|
||||
<Substitute index="1" value="T"/>
|
||||
<Substitute index="2" value="U"/>
|
||||
<Substitute index="3" value="V"/>
|
||||
<Substitute index="4" value="W"/>
|
||||
<Substitute index="5" value="X"/>
|
||||
<Substitute index="6" value="Y"/>
|
||||
<Substitute index="7" value="Z"/>
|
||||
</ReverseChainSingleSubst>
|
||||
<ReverseChainSingleSubst index="2" Format="1">
|
||||
<Coverage>
|
||||
<Glyph value="p"/>
|
||||
</Coverage>
|
||||
<!-- BacktrackGlyphCount=0 -->
|
||||
<!-- LookAheadGlyphCount=0 -->
|
||||
<!-- GlyphCount=1 -->
|
||||
<Substitute index="0" value="P"/>
|
||||
</ReverseChainSingleSubst>
|
||||
</Lookup>
|
||||
</LookupList>
|
||||
</GSUB>
|
||||
|
||||
<GPOS>
|
||||
<Version value="1.0"/>
|
||||
<ScriptList>
|
||||
<!-- ScriptCount=0 -->
|
||||
</ScriptList>
|
||||
<FeatureList>
|
||||
<!-- FeatureCount=0 -->
|
||||
</FeatureList>
|
||||
<LookupList>
|
||||
<!-- LookupCount=0 -->
|
||||
</LookupList>
|
||||
</GPOS>
|
||||
|
||||
</ttFont>
|
8
Lib/fontTools/feaLib/testdata/spec5h1.fea
vendored
Normal file
8
Lib/fontTools/feaLib/testdata/spec5h1.fea
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# OpenType Feature File specification, section 5.h, example 1.
|
||||
# http://www.adobe.com/devnet/opentype/afdko/topic_feature_file_syntax.html
|
||||
|
||||
languagesystem DFLT dflt;
|
||||
|
||||
feature test {
|
||||
reversesub [a e n] d' by d.alt;
|
||||
} test;
|
67
Lib/fontTools/feaLib/testdata/spec5h1.ttx
vendored
Normal file
67
Lib/fontTools/feaLib/testdata/spec5h1.ttx
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ttFont>
|
||||
|
||||
<GSUB>
|
||||
<Version value="1.0"/>
|
||||
<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="test"/>
|
||||
<Feature>
|
||||
<!-- LookupCount=1 -->
|
||||
<LookupListIndex index="0" value="0"/>
|
||||
</Feature>
|
||||
</FeatureRecord>
|
||||
</FeatureList>
|
||||
<LookupList>
|
||||
<!-- LookupCount=1 -->
|
||||
<Lookup index="0">
|
||||
<!-- LookupType=8 -->
|
||||
<LookupFlag value="0"/>
|
||||
<!-- SubTableCount=1 -->
|
||||
<ReverseChainSingleSubst index="0" Format="1">
|
||||
<Coverage>
|
||||
<Glyph value="d"/>
|
||||
</Coverage>
|
||||
<!-- BacktrackGlyphCount=1 -->
|
||||
<BacktrackCoverage index="0">
|
||||
<Glyph value="a"/>
|
||||
<Glyph value="e"/>
|
||||
<Glyph value="n"/>
|
||||
</BacktrackCoverage>
|
||||
<!-- LookAheadGlyphCount=0 -->
|
||||
<!-- GlyphCount=1 -->
|
||||
<Substitute index="0" value="d.alt"/>
|
||||
</ReverseChainSingleSubst>
|
||||
</Lookup>
|
||||
</LookupList>
|
||||
</GSUB>
|
||||
|
||||
<GPOS>
|
||||
<Version value="1.0"/>
|
||||
<ScriptList>
|
||||
<!-- ScriptCount=0 -->
|
||||
</ScriptList>
|
||||
<FeatureList>
|
||||
<!-- FeatureCount=0 -->
|
||||
</FeatureList>
|
||||
<LookupList>
|
||||
<!-- LookupCount=0 -->
|
||||
</LookupList>
|
||||
</GPOS>
|
||||
|
||||
</ttFont>
|
Loading…
x
Reference in New Issue
Block a user