[feaLib] Support compact syntax for chaining to single substitutions
Resolves https://github.com/behdad/fonttools/issues/445 for single substitutions. The compact forms for chaining to other GSUB types are not yet supported; these will get fixed in follow-up changes.
This commit is contained in:
parent
adc2862bbb
commit
a543b2e3b9
@ -198,8 +198,8 @@ class ChainContextPosStatement(Statement):
|
||||
class ChainContextSubstStatement(Statement):
|
||||
def __init__(self, location, prefix, glyphs, suffix, lookups):
|
||||
Statement.__init__(self, location)
|
||||
self.glyphs, self.lookups = glyphs, lookups
|
||||
self.prefix, self.suffix = prefix, suffix
|
||||
self.prefix, self.glyphs, self.suffix = prefix, glyphs, suffix
|
||||
self.lookups = lookups
|
||||
|
||||
def build(self, builder):
|
||||
prefix = [p.glyphSet() for p in self.prefix]
|
||||
@ -373,12 +373,14 @@ class ReverseChainSingleSubstStatement(Statement):
|
||||
|
||||
|
||||
class SingleSubstStatement(Statement):
|
||||
def __init__(self, location, mapping):
|
||||
def __init__(self, location, mapping, prefix, suffix):
|
||||
Statement.__init__(self, location)
|
||||
self.mapping = mapping
|
||||
self.mapping, self.prefix, self.suffix = mapping, prefix, suffix
|
||||
|
||||
def build(self, builder):
|
||||
builder.add_single_subst(self.location, self.mapping)
|
||||
prefix = [p.glyphSet() for p in self.prefix]
|
||||
suffix = [s.glyphSet() for s in self.suffix]
|
||||
builder.add_single_subst(self.location, prefix, suffix, self.mapping)
|
||||
|
||||
|
||||
class ScriptStatement(Statement):
|
||||
|
@ -51,6 +51,13 @@ class Builder(object):
|
||||
elif "GDEF" in self.font:
|
||||
del self.font["GDEF"]
|
||||
|
||||
def get_chained_lookup_(self, location, builder_class):
|
||||
result = builder_class(self.font, location)
|
||||
result.lookupflag = self.lookupflag_
|
||||
result.markFilterSet = self.lookupflag_markFilterSet_
|
||||
self.lookups_.append(result)
|
||||
return result
|
||||
|
||||
def get_lookup_(self, location, builder_class):
|
||||
if (self.cur_lookup_ and
|
||||
type(self.cur_lookup_) == builder_class and
|
||||
@ -410,7 +417,14 @@ class Builder(object):
|
||||
lookup = self.get_lookup_(location, ReverseChainSingleSubstBuilder)
|
||||
lookup.substitutions.append((old_prefix, old_suffix, mapping))
|
||||
|
||||
def add_single_subst(self, location, mapping):
|
||||
def add_single_subst(self, location, prefix, suffix, mapping):
|
||||
if prefix or suffix:
|
||||
sub = self.get_chained_lookup_(location, SingleSubstBuilder)
|
||||
sub.mapping.update(mapping)
|
||||
lookup = self.get_lookup_(location, ChainContextSubstBuilder)
|
||||
lookup.substitutions.append(
|
||||
(prefix, [mapping.keys()], suffix, [sub]))
|
||||
return
|
||||
lookup = self.get_lookup_(location, SingleSubstBuilder)
|
||||
for (from_glyph, to_glyph) in mapping.items():
|
||||
if from_glyph in lookup.mapping:
|
||||
|
@ -174,7 +174,7 @@ class BuilderTest(unittest.TestCase):
|
||||
self.expect_ttx(font, self.getpath("GPOS_%s.ttx" % name))
|
||||
|
||||
def test_spec(self):
|
||||
for name in "4h1 5d1 5d2 5fi1 5h1 6d2 6e 6f 6h_ii".split():
|
||||
for name in "4h1 5d1 5d2 5fi1 5fi2 5h1 6d2 6e 6f 6h_ii".split():
|
||||
font = makeTTFont()
|
||||
addOpenTypeFeatures(self.getpath("spec%s.fea" % name), font)
|
||||
self.expect_ttx(font, self.getpath("spec%s.ttx" % name))
|
||||
|
@ -446,10 +446,10 @@ class Parser(object):
|
||||
keyword = self.expect_keyword_("by")
|
||||
while self.next_token_ != ";":
|
||||
gc = self.parse_glyphclass_(accept_glyphname=True)
|
||||
new.append(gc.glyphSet())
|
||||
new.append(gc)
|
||||
elif self.next_token_ == "from":
|
||||
keyword = self.expect_keyword_("from")
|
||||
new = [self.parse_glyphclass_(accept_glyphname=False).glyphSet()]
|
||||
new = [self.parse_glyphclass_(accept_glyphname=False)]
|
||||
else:
|
||||
keyword = None
|
||||
self.expect_symbol_(";")
|
||||
@ -475,7 +475,7 @@ class Parser(object):
|
||||
location)
|
||||
return ast.AlternateSubstStatement(location,
|
||||
list(old[0].glyphSet())[0],
|
||||
new[0])
|
||||
new[0].glyphSet())
|
||||
|
||||
num_lookups = len([l for l in lookups if l is not None])
|
||||
|
||||
@ -483,10 +483,10 @@ 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 (not reverse and len(old_prefix) == 0 and len(old_suffix) == 0 and
|
||||
len(old) == 1 and len(new) == 1 and num_lookups == 0):
|
||||
if (not reverse and len(old) == 1 and len(new) == 1 and
|
||||
num_lookups == 0):
|
||||
glyphs = sorted(list(old[0].glyphSet()))
|
||||
replacements = sorted(list(new[0]))
|
||||
replacements = sorted(list(new[0].glyphSet()))
|
||||
if len(replacements) == 1:
|
||||
replacements = replacements * len(glyphs)
|
||||
if len(glyphs) != len(replacements):
|
||||
@ -495,24 +495,28 @@ class Parser(object):
|
||||
'but found a glyph class with %d elements' %
|
||||
(len(glyphs), len(replacements)), location)
|
||||
return ast.SingleSubstStatement(location,
|
||||
dict(zip(glyphs, replacements)))
|
||||
dict(zip(glyphs, replacements)),
|
||||
old_prefix, old_suffix)
|
||||
|
||||
# GSUB lookup type 2: Multiple substitution.
|
||||
# Format: "substitute f_f_i by f f i;"
|
||||
if (not reverse and len(old_prefix) == 0 and len(old_suffix) == 0 and
|
||||
len(old) == 1 and len(old[0].glyphSet()) == 1 and
|
||||
len(new) > 1 and max([len(n) for n in new]) == 1 and
|
||||
len(new) > 1 and max([len(n.glyphSet()) for n in new]) == 1 and
|
||||
num_lookups == 0):
|
||||
return ast.MultipleSubstStatement(location,
|
||||
tuple(old[0].glyphSet())[0],
|
||||
tuple([list(n)[0] for n in new]))
|
||||
return ast.MultipleSubstStatement(
|
||||
location,
|
||||
tuple(old[0].glyphSet())[0],
|
||||
tuple([list(n.glyphSet())[0] for n in new]))
|
||||
|
||||
# GSUB lookup type 4: Ligature substitution.
|
||||
# Format: "substitute f f i by f_f_i;"
|
||||
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
|
||||
len(old) > 1 and len(new) == 1 and
|
||||
len(new[0].glyphSet()) == 1 and
|
||||
num_lookups == 0):
|
||||
return ast.LigatureSubstStatement(location, old, list(new[0])[0])
|
||||
return ast.LigatureSubstStatement(
|
||||
location, old, list(new[0].glyphSet())[0])
|
||||
|
||||
# GSUB lookup type 8: Reverse chaining substitution.
|
||||
if reverse:
|
||||
@ -531,7 +535,7 @@ class Parser(object):
|
||||
"Reverse chaining substitutions cannot call named lookups",
|
||||
location)
|
||||
glyphs = sorted(list(old[0].glyphSet()))
|
||||
replacements = sorted(list(new[0]))
|
||||
replacements = sorted(list(new[0].glyphSet()))
|
||||
if len(replacements) == 1:
|
||||
replacements = replacements * len(glyphs)
|
||||
if len(glyphs) != len(replacements):
|
||||
|
@ -717,39 +717,83 @@ class ParserTest(unittest.TestCase):
|
||||
'"dflt" is not a valid script tag; use "DFLT" instead',
|
||||
self.parse, "feature test {script dflt;} test;")
|
||||
|
||||
def test_substitute_single_format_a(self): # GSUB LookupType 1
|
||||
def test_sub_single_format_a(self): # GSUB LookupType 1
|
||||
doc = self.parse("feature smcp {substitute a by a.sc;} smcp;")
|
||||
sub = doc.statements[0].statements[0]
|
||||
self.assertEqual(type(sub), ast.SingleSubstStatement)
|
||||
self.assertEqual(glyphstr(sub.prefix), "")
|
||||
self.assertEqual(sub.mapping, {"a": "a.sc"})
|
||||
self.assertEqual(glyphstr(sub.suffix), "")
|
||||
|
||||
def test_substitute_single_format_b(self): # GSUB LookupType 1
|
||||
def test_sub_single_format_a_chained(self): # chain to GSUB LookupType 1
|
||||
doc = self.parse("feature test {sub [A a] d' [C] by d.alt;} test;")
|
||||
sub = doc.statements[0].statements[0]
|
||||
self.assertIsInstance(sub, ast.SingleSubstStatement)
|
||||
self.assertEqual(sub.mapping, {"d": "d.alt"})
|
||||
self.assertEqual(glyphstr(sub.prefix), "[A a]")
|
||||
self.assertEqual(glyphstr(sub.suffix), "C")
|
||||
|
||||
def test_sub_single_format_b(self): # GSUB LookupType 1
|
||||
doc = self.parse(
|
||||
"feature smcp {"
|
||||
" substitute [one.fitted one.oldstyle] by one;"
|
||||
"} smcp;")
|
||||
sub = doc.statements[0].statements[0]
|
||||
self.assertEqual(type(sub), ast.SingleSubstStatement)
|
||||
self.assertIsInstance(sub, ast.SingleSubstStatement)
|
||||
self.assertEqual(sub.mapping, {
|
||||
"one.fitted": "one",
|
||||
"one.oldstyle": "one"
|
||||
})
|
||||
self.assertEqual(glyphstr(sub.prefix), "")
|
||||
self.assertEqual(glyphstr(sub.suffix), "")
|
||||
|
||||
def test_substitute_single_format_c(self): # GSUB LookupType 1
|
||||
def test_sub_single_format_b_chained(self): # chain to GSUB LookupType 1
|
||||
doc = self.parse(
|
||||
"feature smcp {"
|
||||
" substitute PRE FIX [one.fitted one.oldstyle]' SUF FIX by one;"
|
||||
"} smcp;")
|
||||
sub = doc.statements[0].statements[0]
|
||||
self.assertIsInstance(sub, ast.SingleSubstStatement)
|
||||
self.assertEqual(sub.mapping, {
|
||||
"one.fitted": "one",
|
||||
"one.oldstyle": "one"
|
||||
})
|
||||
self.assertEqual(glyphstr(sub.prefix), "PRE FIX")
|
||||
self.assertEqual(glyphstr(sub.suffix), "SUF FIX")
|
||||
|
||||
def test_sub_single_format_c(self): # GSUB LookupType 1
|
||||
doc = self.parse(
|
||||
"feature smcp {"
|
||||
" substitute [a-d] by [A.sc-D.sc];"
|
||||
"} smcp;")
|
||||
sub = doc.statements[0].statements[0]
|
||||
self.assertEqual(type(sub), ast.SingleSubstStatement)
|
||||
self.assertIsInstance(sub, ast.SingleSubstStatement)
|
||||
self.assertEqual(sub.mapping, {
|
||||
"a": "A.sc",
|
||||
"b": "B.sc",
|
||||
"c": "C.sc",
|
||||
"d": "D.sc"
|
||||
})
|
||||
self.assertEqual(glyphstr(sub.prefix), "")
|
||||
self.assertEqual(glyphstr(sub.suffix), "")
|
||||
|
||||
def test_substitute_single_format_c_different_num_elements(self):
|
||||
def test_sub_single_format_c_chained(self): # chain to GSUB LookupType 1
|
||||
doc = self.parse(
|
||||
"feature smcp {"
|
||||
" substitute [a-d]' X Y [Z z] by [A.sc-D.sc];"
|
||||
"} smcp;")
|
||||
sub = doc.statements[0].statements[0]
|
||||
self.assertIsInstance(sub, ast.SingleSubstStatement)
|
||||
self.assertEqual(sub.mapping, {
|
||||
"a": "A.sc",
|
||||
"b": "B.sc",
|
||||
"c": "C.sc",
|
||||
"d": "D.sc"
|
||||
})
|
||||
self.assertEqual(glyphstr(sub.prefix), "")
|
||||
self.assertEqual(glyphstr(sub.suffix), "X Y [Z z]")
|
||||
|
||||
def test_sub_single_format_c_different_num_elements(self):
|
||||
self.assertRaisesRegex(
|
||||
FeatureLibError,
|
||||
'Expected a glyph class with 4 elements after "by", '
|
||||
@ -789,7 +833,7 @@ class ParserTest(unittest.TestCase):
|
||||
self.assertEqual(glyphstr(sub.glyphs), "f f i")
|
||||
self.assertEqual(sub.replacement, "f_f_i")
|
||||
|
||||
def test_substitute_lookups(self):
|
||||
def test_substitute_lookups(self): # GSUB LookupType 6
|
||||
doc = Parser(self.getpath("spec5fi1.fea")).parse()
|
||||
[langsys, ligs, sub, feature] = doc.statements
|
||||
self.assertEqual(feature.statements[0].lookups, [ligs, None, sub])
|
||||
|
10
Lib/fontTools/feaLib/testdata/spec5fi2.fea
vendored
Normal file
10
Lib/fontTools/feaLib/testdata/spec5fi2.fea
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
# OpenType Feature File specification, section 5.f.i, example 2
|
||||
# "Specifying a Chain Sub rule and marking sub-runs"
|
||||
# http://www.adobe.com/devnet/opentype/afdko/topic_feature_file_syntax.html
|
||||
|
||||
languagesystem latn dflt;
|
||||
|
||||
feature test {
|
||||
lookupflag 7;
|
||||
substitute [a e n] d' by d.alt;
|
||||
} test;
|
66
Lib/fontTools/feaLib/testdata/spec5fi2.ttx
vendored
Normal file
66
Lib/fontTools/feaLib/testdata/spec5fi2.ttx
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ttFont>
|
||||
|
||||
<GSUB>
|
||||
<Version value="1.0"/>
|
||||
<ScriptList>
|
||||
<!-- ScriptCount=1 -->
|
||||
<ScriptRecord index="0">
|
||||
<ScriptTag value="latn"/>
|
||||
<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="1"/>
|
||||
</Feature>
|
||||
</FeatureRecord>
|
||||
</FeatureList>
|
||||
<LookupList>
|
||||
<!-- LookupCount=2 -->
|
||||
<Lookup index="0">
|
||||
<!-- LookupType=1 -->
|
||||
<LookupFlag value="7"/>
|
||||
<!-- SubTableCount=1 -->
|
||||
<SingleSubst index="0">
|
||||
<Substitution in="d" out="d.alt"/>
|
||||
</SingleSubst>
|
||||
</Lookup>
|
||||
<Lookup index="1">
|
||||
<!-- LookupType=6 -->
|
||||
<LookupFlag value="7"/>
|
||||
<!-- SubTableCount=1 -->
|
||||
<ChainContextSubst index="0" Format="3">
|
||||
<!-- BacktrackGlyphCount=1 -->
|
||||
<BacktrackCoverage index="0">
|
||||
<Glyph value="a"/>
|
||||
<Glyph value="e"/>
|
||||
<Glyph value="n"/>
|
||||
</BacktrackCoverage>
|
||||
<!-- InputGlyphCount=1 -->
|
||||
<InputCoverage index="0">
|
||||
<Glyph value="d"/>
|
||||
</InputCoverage>
|
||||
<!-- LookAheadGlyphCount=0 -->
|
||||
<!-- SubstCount=1 -->
|
||||
<SubstLookupRecord index="0">
|
||||
<SequenceIndex value="0"/>
|
||||
<LookupListIndex value="0"/>
|
||||
</SubstLookupRecord>
|
||||
</ChainContextSubst>
|
||||
</Lookup>
|
||||
</LookupList>
|
||||
</GSUB>
|
||||
|
||||
</ttFont>
|
9
Lib/fontTools/feaLib/testdata/spec5fi3.fea
vendored
Normal file
9
Lib/fontTools/feaLib/testdata/spec5fi3.fea
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
# OpenType Feature File specification, section 5.f.i, example 2
|
||||
# "Specifying a Chain Sub rule and marking sub-runs"
|
||||
# http://www.adobe.com/devnet/opentype/afdko/topic_feature_file_syntax.html
|
||||
|
||||
languagesystem latn dflt;
|
||||
|
||||
feature test {
|
||||
substitute [A-Z] [A.sc-Z.sc]' by [a-z];
|
||||
} test;
|
139
Lib/fontTools/feaLib/testdata/spec5fi3.ttx
vendored
Normal file
139
Lib/fontTools/feaLib/testdata/spec5fi3.ttx
vendored
Normal file
@ -0,0 +1,139 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ttFont>
|
||||
|
||||
<GSUB>
|
||||
<Version value="1.0"/>
|
||||
<ScriptList>
|
||||
<!-- ScriptCount=1 -->
|
||||
<ScriptRecord index="0">
|
||||
<ScriptTag value="latn"/>
|
||||
<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=2 -->
|
||||
<Lookup index="0">
|
||||
<!-- LookupType=1 -->
|
||||
<LookupFlag value="0"/>
|
||||
<!-- SubTableCount=1 -->
|
||||
<SingleSubst index="0">
|
||||
<Substitution in="A.sc" out="a"/>
|
||||
<Substitution in="B.sc" out="b"/>
|
||||
<Substitution in="C.sc" out="c"/>
|
||||
<Substitution in="D.sc" out="d"/>
|
||||
<Substitution in="E.sc" out="e"/>
|
||||
<Substitution in="F.sc" out="f"/>
|
||||
<Substitution in="G.sc" out="g"/>
|
||||
<Substitution in="H.sc" out="h"/>
|
||||
<Substitution in="I.sc" out="i"/>
|
||||
<Substitution in="J.sc" out="j"/>
|
||||
<Substitution in="K.sc" out="k"/>
|
||||
<Substitution in="L.sc" out="l"/>
|
||||
<Substitution in="M.sc" out="m"/>
|
||||
<Substitution in="N.sc" out="n"/>
|
||||
<Substitution in="O.sc" out="o"/>
|
||||
<Substitution in="P.sc" out="p"/>
|
||||
<Substitution in="Q.sc" out="q"/>
|
||||
<Substitution in="R.sc" out="r"/>
|
||||
<Substitution in="S.sc" out="s"/>
|
||||
<Substitution in="T.sc" out="t"/>
|
||||
<Substitution in="U.sc" out="u"/>
|
||||
<Substitution in="V.sc" out="v"/>
|
||||
<Substitution in="W.sc" out="w"/>
|
||||
<Substitution in="X.sc" out="x"/>
|
||||
<Substitution in="Y.sc" out="y"/>
|
||||
<Substitution in="Z.sc" out="z"/>
|
||||
</SingleSubst>
|
||||
</Lookup>
|
||||
<Lookup index="1">
|
||||
<!-- LookupType=6 -->
|
||||
<LookupFlag value="0"/>
|
||||
<!-- SubTableCount=1 -->
|
||||
<ChainContextSubst index="0" Format="3">
|
||||
<!-- BacktrackGlyphCount=1 -->
|
||||
<BacktrackCoverage index="0">
|
||||
<Glyph value="A"/>
|
||||
<Glyph value="B"/>
|
||||
<Glyph value="C"/>
|
||||
<Glyph value="D"/>
|
||||
<Glyph value="E"/>
|
||||
<Glyph value="F"/>
|
||||
<Glyph value="G"/>
|
||||
<Glyph value="H"/>
|
||||
<Glyph value="I"/>
|
||||
<Glyph value="J"/>
|
||||
<Glyph value="K"/>
|
||||
<Glyph value="L"/>
|
||||
<Glyph value="M"/>
|
||||
<Glyph value="N"/>
|
||||
<Glyph value="O"/>
|
||||
<Glyph value="P"/>
|
||||
<Glyph value="Q"/>
|
||||
<Glyph value="R"/>
|
||||
<Glyph value="S"/>
|
||||
<Glyph value="T"/>
|
||||
<Glyph value="U"/>
|
||||
<Glyph value="V"/>
|
||||
<Glyph value="W"/>
|
||||
<Glyph value="X"/>
|
||||
<Glyph value="Y"/>
|
||||
<Glyph value="Z"/>
|
||||
</BacktrackCoverage>
|
||||
<!-- InputGlyphCount=1 -->
|
||||
<InputCoverage index="0">
|
||||
<Glyph value="A.sc"/>
|
||||
<Glyph value="B.sc"/>
|
||||
<Glyph value="C.sc"/>
|
||||
<Glyph value="D.sc"/>
|
||||
<Glyph value="E.sc"/>
|
||||
<Glyph value="F.sc"/>
|
||||
<Glyph value="G.sc"/>
|
||||
<Glyph value="H.sc"/>
|
||||
<Glyph value="I.sc"/>
|
||||
<Glyph value="J.sc"/>
|
||||
<Glyph value="K.sc"/>
|
||||
<Glyph value="L.sc"/>
|
||||
<Glyph value="M.sc"/>
|
||||
<Glyph value="N.sc"/>
|
||||
<Glyph value="O.sc"/>
|
||||
<Glyph value="P.sc"/>
|
||||
<Glyph value="Q.sc"/>
|
||||
<Glyph value="R.sc"/>
|
||||
<Glyph value="S.sc"/>
|
||||
<Glyph value="T.sc"/>
|
||||
<Glyph value="U.sc"/>
|
||||
<Glyph value="V.sc"/>
|
||||
<Glyph value="W.sc"/>
|
||||
<Glyph value="X.sc"/>
|
||||
<Glyph value="Y.sc"/>
|
||||
<Glyph value="Z.sc"/>
|
||||
</InputCoverage>
|
||||
<!-- LookAheadGlyphCount=0 -->
|
||||
<!-- SubstCount=1 -->
|
||||
<SubstLookupRecord index="0">
|
||||
<SequenceIndex value="0"/>
|
||||
<LookupListIndex value="0"/>
|
||||
</SubstLookupRecord>
|
||||
</ChainContextSubst>
|
||||
</Lookup>
|
||||
</LookupList>
|
||||
</GSUB>
|
||||
|
||||
</ttFont>
|
Loading…
x
Reference in New Issue
Block a user