diff --git a/Lib/fontTools/feaLib/ast.py b/Lib/fontTools/feaLib/ast.py index 36b4161d8..3c1e61307 100644 --- a/Lib/fontTools/feaLib/ast.py +++ b/Lib/fontTools/feaLib/ast.py @@ -80,14 +80,13 @@ class MarkClassDefinition(object): self.glyphLocations = {} # glyph --> (filepath, line, column) -class AlternateSubstitution(Statement): +class AlternateSubstStatement(Statement): def __init__(self, location, glyph, from_class): Statement.__init__(self, location) self.glyph, self.from_class = (glyph, from_class) def build(self, builder): - builder.add_alternate_substitution(self.location, self.glyph, - self.from_class) + builder.add_alternate_subst(self.location, self.glyph, self.from_class) class Anchor(Expression): @@ -104,7 +103,19 @@ class AnchorDefinition(Statement): self.name, self.x, self.y, self.contourpoint = name, x, y, contourpoint -class CursiveAttachmentPositioning(Statement): +class ChainContextSubstStatement(Statement): + def __init__(self, location, old_prefix, old, old_suffix, lookups): + Statement.__init__(self, location) + self.old, self.lookups = old, lookups + self.old_prefix, self.old_suffix = old_prefix, old_suffix + + def build(self, builder): + builder.add_chain_context_subst( + self.location, self.old_prefix, self.old, self.old_suffix, + self.lookups) + + +class CursivePosStatement(Statement): def __init__(self, location, glyphclass, entryAnchor, exitAnchor): Statement.__init__(self, location) self.glyphclass = glyphclass @@ -144,7 +155,7 @@ class IgnoreSubstitutionRule(Statement): self.prefix, self.glyphs, self.suffix = (prefix, glyphs, suffix) -class LigatureSubstitution(Statement): +class LigatureSubstStatement(Statement): def __init__(self, location, glyphs, replacement): Statement.__init__(self, location) self.glyphs, self.replacement = (glyphs, replacement) @@ -156,8 +167,7 @@ class LigatureSubstitution(Statement): # glyph classes, the implementation software will enumerate # all specific glyph sequences if glyph classes are detected" for glyphs in sorted(itertools.product(*self.glyphs)): - builder.add_ligature_substitution( - self.location, glyphs, self.replacement) + builder.add_ligature_subst(self.location, glyphs, self.replacement) class LookupReferenceStatement(Statement): @@ -170,7 +180,7 @@ class LookupReferenceStatement(Statement): s.build(builder) -class MarkToBaseAttachmentPositioning(Statement): +class MarkBasePosStatement(Statement): def __init__(self, location, base, marks): Statement.__init__(self, location) self.base, self.marks = base, marks @@ -179,7 +189,7 @@ class MarkToBaseAttachmentPositioning(Statement): builder.add_mark_base_pos(self.location, self.base, self.marks) -class MarkToLigatureAttachmentPositioning(Statement): +class MarkLigPosStatement(Statement): def __init__(self, location, ligatures, marks): Statement.__init__(self, location) self.ligatures, self.marks = ligatures, marks @@ -188,17 +198,16 @@ class MarkToLigatureAttachmentPositioning(Statement): pass # TODO: Implement this. -class MultipleSubstitution(Statement): +class MultipleSubstStatement(Statement): def __init__(self, location, glyph, replacement): Statement.__init__(self, location) self.glyph, self.replacement = glyph, replacement def build(self, builder): - builder.add_multiple_substitution(self.location, - self.glyph, self.replacement) + builder.add_multiple_subst(self.location, self.glyph, self.replacement) -class PairAdjustmentPositioning(Statement): +class PairPosStatement(Statement): def __init__(self, location, enumerated, glyphclass1, valuerecord1, glyphclass2, valuerecord2): Statement.__init__(self, location) @@ -212,24 +221,24 @@ class PairAdjustmentPositioning(Statement): self.glyphclass2, self.valuerecord2) -class ReverseChainingSingleSubstitution(Statement): +class ReverseChainSingleSubstStatement(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( + builder.add_reverse_chain_single_subst( self.location, self.old_prefix, self.old_suffix, self.mapping) -class SingleSubstitution(Statement): +class SingleSubstStatement(Statement): def __init__(self, location, mapping): Statement.__init__(self, location) self.mapping = mapping def build(self, builder): - builder.add_single_substitution(self.location, self.mapping) + builder.add_single_subst(self.location, self.mapping) class ScriptStatement(Statement): @@ -241,7 +250,7 @@ class ScriptStatement(Statement): builder.set_script(self.location, self.script) -class SingleAdjustmentPositioning(Statement): +class SinglePosStatement(Statement): def __init__(self, location, glyphclass, valuerecord): Statement.__init__(self, location) self.glyphclass, self.valuerecord = glyphclass, valuerecord @@ -256,20 +265,6 @@ class SubtableStatement(Statement): Statement.__init__(self, location) -class SubstitutionRule(Statement): - def __init__(self, location, old, new): - Statement.__init__(self, location) - self.old, self.new = (old, new) - self.old_prefix = [] - self.old_suffix = [] - self.lookups = [None] * len(old) - - def build(self, builder): - builder.add_substitution( - self.location, self.old_prefix, self.old, self.old_suffix, - self.new, self.lookups) - - class ValueRecord(Statement): def __init__(self, location, xPlacement, yPlacement, xAdvance, yAdvance, xPlaDevice, yPlaDevice, xAdvDevice, yAdvDevice): diff --git a/Lib/fontTools/feaLib/builder.py b/Lib/fontTools/feaLib/builder.py index be3da4e9d..d7ebebee6 100644 --- a/Lib/fontTools/feaLib/builder.py +++ b/Lib/fontTools/feaLib/builder.py @@ -305,14 +305,13 @@ class Builder(object): lookup_builders.append(None) return lookup_builders - def add_substitution(self, location, old_prefix, old, old_suffix, new, - lookups): - assert len(new) == 0, new + def add_chain_context_subst(self, location, + old_prefix, old, old_suffix, lookups): lookup = self.get_lookup_(location, ChainContextSubstBuilder) lookup.substitutions.append((old_prefix, old, old_suffix, self.find_lookup_builders_(lookups))) - def add_alternate_substitution(self, location, glyph, from_class): + def add_alternate_subst(self, location, glyph, from_class): lookup = self.get_lookup_(location, AlternateSubstBuilder) if glyph in lookup.alternates: raise FeatureLibError( @@ -320,11 +319,11 @@ class Builder(object): location) lookup.alternates[glyph] = from_class - def add_ligature_substitution(self, location, glyphs, replacement): + def add_ligature_subst(self, location, glyphs, replacement): lookup = self.get_lookup_(location, LigatureSubstBuilder) lookup.ligatures[glyphs] = replacement - def add_multiple_substitution(self, location, glyph, replacements): + def add_multiple_subst(self, location, glyph, replacements): lookup = self.get_lookup_(location, MultipleSubstBuilder) if glyph in lookup.mapping: raise FeatureLibError( @@ -332,12 +331,12 @@ class Builder(object): location) lookup.mapping[glyph] = replacements - def add_reverse_chaining_single_substitution(self, location, old_prefix, - old_suffix, mapping): + def add_reverse_chain_single_subst(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): + def add_single_subst(self, location, mapping): lookup = self.get_lookup_(location, SingleSubstBuilder) for (from_glyph, to_glyph) in mapping.items(): if from_glyph in lookup.mapping: diff --git a/Lib/fontTools/feaLib/parser.py b/Lib/fontTools/feaLib/parser.py index 30747038f..1e4c9f6e6 100644 --- a/Lib/fontTools/feaLib/parser.py +++ b/Lib/fontTools/feaLib/parser.py @@ -304,10 +304,10 @@ class Parser(object): raise FeatureLibError( '"enumerate" is only allowed with pair positionings', self.cur_token_location_) - return ast.SingleAdjustmentPositioning(location, gc1, value1) + return ast.SinglePosStatement(location, gc1, value1) else: - return ast.PairAdjustmentPositioning(location, enumerated, - gc1, value1, gc2, value2) + return ast.PairPosStatement(location, enumerated, + gc1, value1, gc2, value2) def parse_position_cursive_(self, enumerated, vertical): location = self.cur_token_location_ @@ -321,7 +321,7 @@ class Parser(object): entryAnchor = self.parse_anchor_() exitAnchor = self.parse_anchor_() self.expect_symbol_(";") - return ast.CursiveAttachmentPositioning( + return ast.CursivePosStatement( location, glyphclass, entryAnchor, exitAnchor) def parse_position_base_(self, enumerated, vertical): @@ -335,7 +335,7 @@ class Parser(object): base = self.parse_glyphclass_(accept_glyphname=True) marks = self.parse_anchor_marks_() self.expect_symbol_(";") - return ast.MarkToBaseAttachmentPositioning(location, base, marks) + return ast.MarkBasePosStatement(location, base, marks) def parse_position_ligature_(self, enumerated, vertical): location = self.cur_token_location_ @@ -351,8 +351,7 @@ class Parser(object): self.expect_keyword_("ligComponent") marks.append(self.parse_anchor_marks_()) self.expect_symbol_(";") - return ast.MarkToLigatureAttachmentPositioning( - location, ligatures, marks) + return ast.MarkLigPosStatement(location, ligatures, marks) def parse_script_(self): assert self.is_cur_keyword_("script") @@ -397,7 +396,8 @@ class Parser(object): raise FeatureLibError( 'Expected a single glyphclass after "from"', location) - return ast.AlternateSubstitution(location, list(old[0])[0], new[0]) + return ast.AlternateSubstStatement(location, + list(old[0])[0], new[0]) num_lookups = len([l for l in lookups if l is not None]) @@ -415,8 +415,8 @@ class Parser(object): 'Expected a glyph class with %d elements after "by", ' 'but found a glyph class with %d elements' % (len(glyphs), len(replacements)), location) - return ast.SingleSubstitution(location, - dict(zip(glyphs, replacements))) + return ast.SingleSubstStatement(location, + dict(zip(glyphs, replacements))) # GSUB lookup type 2: Multiple substitution. # Format: "substitute f_f_i by f f i;" @@ -424,15 +424,15 @@ class Parser(object): 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): - return ast.MultipleSubstitution(location, tuple(old[0])[0], - tuple([list(n)[0] for n in new])) + return ast.MultipleSubstStatement(location, tuple(old[0])[0], + tuple([list(n)[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 num_lookups == 0): - return ast.LigatureSubstitution(location, old, list(new[0])[0]) + return ast.LigatureSubstStatement(location, old, list(new[0])[0]) # GSUB lookup type 8: Reverse chaining substitution. if reverse: @@ -458,14 +458,14 @@ class Parser(object): '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( + return ast.ReverseChainSingleSubstStatement( 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 + # GSUB lookup type 6: Chaining contextual substitution. + assert len(new) == 0, new + rule = ast.ChainContextSubstStatement( + location, old_prefix, old, old_suffix, lookups) return rule def parse_subtable_(self): diff --git a/Lib/fontTools/feaLib/parser_test.py b/Lib/fontTools/feaLib/parser_test.py index 8bb5ba5f2..f818fa70b 100644 --- a/Lib/fontTools/feaLib/parser_test.py +++ b/Lib/fontTools/feaLib/parser_test.py @@ -329,7 +329,7 @@ class ParserTest(unittest.TestCase): def test_gpos_type_1_glyph(self): doc = self.parse("feature kern {pos one <1 2 3 4>;} kern;") pos = doc.statements[0].statements[0] - self.assertEqual(type(pos), ast.SingleAdjustmentPositioning) + self.assertEqual(type(pos), ast.SinglePosStatement) self.assertEqual(pos.glyphclass, {"one"}) self.assertEqual(pos.valuerecord.makeString(vertical=False), "<1 2 3 4>") @@ -337,14 +337,14 @@ class ParserTest(unittest.TestCase): def test_gpos_type_1_glyphclass_horizontal(self): doc = self.parse("feature kern {pos [one two] -300;} kern;") pos = doc.statements[0].statements[0] - self.assertEqual(type(pos), ast.SingleAdjustmentPositioning) + self.assertEqual(type(pos), ast.SinglePosStatement) self.assertEqual(pos.glyphclass, {"one", "two"}) self.assertEqual(pos.valuerecord.makeString(vertical=False), "-300") def test_gpos_type_1_glyphclass_vertical(self): doc = self.parse("feature vkrn {pos [one two] -300;} vkrn;") pos = doc.statements[0].statements[0] - self.assertEqual(type(pos), ast.SingleAdjustmentPositioning) + self.assertEqual(type(pos), ast.SinglePosStatement) self.assertEqual(pos.glyphclass, {"one", "two"}) self.assertEqual(pos.valuerecord.makeString(vertical=True), "-300") @@ -363,7 +363,7 @@ class ParserTest(unittest.TestCase): " pos [T V] -60 [a b c] <1 2 3 4>;" "} kern;") pos = doc.statements[0].statements[0] - self.assertEqual(type(pos), ast.PairAdjustmentPositioning) + self.assertEqual(type(pos), ast.PairPosStatement) self.assertFalse(pos.enumerated) self.assertEqual(pos.glyphclass1, {"T", "V"}) self.assertEqual(pos.valuerecord1.makeString(vertical=False), "-60") @@ -376,7 +376,7 @@ class ParserTest(unittest.TestCase): " enum pos [T V] -60 [a b c] <1 2 3 4>;" "} kern;") pos = doc.statements[0].statements[0] - self.assertEqual(type(pos), ast.PairAdjustmentPositioning) + self.assertEqual(type(pos), ast.PairPosStatement) self.assertTrue(pos.enumerated) self.assertEqual(pos.glyphclass1, {"T", "V"}) self.assertEqual(pos.valuerecord1.makeString(vertical=False), "-60") @@ -389,7 +389,7 @@ class ParserTest(unittest.TestCase): " pos [T V] <1 2 3 4> [a b c] ;" "} kern;") pos = doc.statements[0].statements[0] - self.assertEqual(type(pos), ast.PairAdjustmentPositioning) + self.assertEqual(type(pos), ast.PairPosStatement) self.assertFalse(pos.enumerated) self.assertEqual(pos.glyphclass1, {"T", "V"}) self.assertEqual(pos.valuerecord1.makeString(vertical=False), @@ -402,7 +402,7 @@ class ParserTest(unittest.TestCase): " pos [T V] [a b c] <1 2 3 4>;" "} kern;") pos = doc.statements[0].statements[0] - self.assertEqual(type(pos), ast.PairAdjustmentPositioning) + self.assertEqual(type(pos), ast.PairPosStatement) self.assertFalse(pos.enumerated) self.assertEqual(pos.glyphclass1, {"T", "V"}) self.assertEqual(pos.valuerecord1.makeString(vertical=False), @@ -415,7 +415,7 @@ class ParserTest(unittest.TestCase): " enumerate position [T V] [a b c] <1 2 3 4>;" "} kern;") pos = doc.statements[0].statements[0] - self.assertEqual(type(pos), ast.PairAdjustmentPositioning) + self.assertEqual(type(pos), ast.PairPosStatement) self.assertTrue(pos.enumerated) self.assertEqual(pos.glyphclass1, {"T", "V"}) self.assertEqual(pos.valuerecord1.makeString(vertical=False), @@ -428,7 +428,7 @@ class ParserTest(unittest.TestCase): " position cursive A ;" "} kern;") pos = doc.statements[0].statements[0] - self.assertEqual(type(pos), ast.CursiveAttachmentPositioning) + self.assertEqual(type(pos), ast.CursivePosStatement) self.assertEqual(pos.glyphclass, {"A"}) self.assertEqual((pos.entryAnchor.x, pos.entryAnchor.y), (12, -2)) self.assertEqual((pos.exitAnchor.x, pos.exitAnchor.y), (2, 3)) @@ -453,7 +453,7 @@ class ParserTest(unittest.TestCase): " mark @BOTTOM_MARKS;" "} test;") pos = doc.statements[-1].statements[0] - self.assertEqual(type(pos), ast.MarkToBaseAttachmentPositioning) + self.assertEqual(type(pos), ast.MarkBasePosStatement) self.assertEqual(pos.base, {"a", "e", "o", "u"}) (a1, m1), (a2, m2) = pos.marks self.assertEqual((a1.x, a1.y), (250, 450)) @@ -489,7 +489,7 @@ class ParserTest(unittest.TestCase): " mark @BOTTOM_MARKS;" "} test;") pos = doc.statements[-1].statements[0] - self.assertEqual(type(pos), ast.MarkToLigatureAttachmentPositioning) + self.assertEqual(type(pos), ast.MarkLigPosStatement) self.assertEqual(pos.ligatures, {"a_f_f_i", "o_f_f_i"}) [(a11, m11), (a12, m12)], [(a2, m2)], [], [(a4, m4)] = pos.marks self.assertEqual((a11.x, a11.y, m11.name), (50, 600, "TOP_MARKS")) @@ -528,7 +528,7 @@ class ParserTest(unittest.TestCase): 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(type(rsub), ast.ReverseChainSingleSubstStatement) self.assertEqual(rsub.old_prefix, [{"a"}, {"b", "B"}]) self.assertEqual(rsub.mapping, {"c": "C"}) self.assertEqual(rsub.old_suffix, [{"d"}, {"e", "E"}]) @@ -539,7 +539,7 @@ class ParserTest(unittest.TestCase): " 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(type(rsub), ast.ReverseChainSingleSubstStatement) self.assertEqual(rsub.old_prefix, [{"A"}, {"B"}]) self.assertEqual(rsub.old_suffix, [{"C"}, {"d", "D"}]) self.assertEqual(rsub.mapping, { @@ -553,7 +553,7 @@ class ParserTest(unittest.TestCase): " 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(type(rsub), ast.ReverseChainSingleSubstStatement) self.assertEqual(rsub.old_prefix, [{"BACK"}, {"TRACK"}]) self.assertEqual(rsub.old_suffix, [{"LOOK"}, {"AHEAD"}]) self.assertEqual(rsub.mapping, { @@ -598,7 +598,7 @@ class ParserTest(unittest.TestCase): def test_substitute_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.SingleSubstitution) + self.assertEqual(type(sub), ast.SingleSubstStatement) self.assertEqual(sub.mapping, {"a": "a.sc"}) def test_substitute_single_format_b(self): # GSUB LookupType 1 @@ -607,7 +607,7 @@ class ParserTest(unittest.TestCase): " substitute [one.fitted one.oldstyle] by one;" "} smcp;") sub = doc.statements[0].statements[0] - self.assertEqual(type(sub), ast.SingleSubstitution) + self.assertEqual(type(sub), ast.SingleSubstStatement) self.assertEqual(sub.mapping, { "one.fitted": "one", "one.oldstyle": "one" @@ -619,7 +619,7 @@ class ParserTest(unittest.TestCase): " substitute [a-d] by [A.sc-D.sc];" "} smcp;") sub = doc.statements[0].statements[0] - self.assertEqual(type(sub), ast.SingleSubstitution) + self.assertEqual(type(sub), ast.SingleSubstStatement) self.assertEqual(sub.mapping, { "a": "A.sc", "b": "B.sc", @@ -637,7 +637,7 @@ class ParserTest(unittest.TestCase): def test_substitute_multiple(self): # GSUB LookupType 2 doc = self.parse("lookup Look {substitute f_f_i by f f i;} Look;") sub = doc.statements[0].statements[0] - self.assertEqual(type(sub), ast.MultipleSubstitution) + self.assertEqual(type(sub), ast.MultipleSubstStatement) self.assertEqual(sub.glyph, "f_f_i") self.assertEqual(sub.replacement, ("f", "f", "i")) @@ -646,7 +646,7 @@ class ParserTest(unittest.TestCase): " substitute a from [a.1 a.2 a.3];" "} test;") sub = doc.statements[0].statements[0] - self.assertEqual(type(sub), ast.AlternateSubstitution) + self.assertEqual(type(sub), ast.AlternateSubstStatement) self.assertEqual(sub.glyph, "a") self.assertEqual(sub.from_class, {"a.1", "a.2", "a.3"}) @@ -656,14 +656,14 @@ class ParserTest(unittest.TestCase): " substitute ampersand from @Ampersands;" "} test;") [glyphclass, sub] = doc.statements[0].statements - self.assertEqual(type(sub), ast.AlternateSubstitution) + self.assertEqual(type(sub), ast.AlternateSubstStatement) self.assertEqual(sub.glyph, "ampersand") self.assertEqual(sub.from_class, {"ampersand.1", "ampersand.2"}) def test_substitute_ligature(self): # GSUB LookupType 4 doc = self.parse("feature liga {substitute f f i by f_f_i;} liga;") sub = doc.statements[0].statements[0] - self.assertEqual(type(sub), ast.LigatureSubstitution) + self.assertEqual(type(sub), ast.LigatureSubstStatement) self.assertEqual(sub.glyphs, [{"f"}, {"f"}, {"i"}]) self.assertEqual(sub.replacement, "f_f_i")