diff --git a/Lib/fontTools/feaLib/parser.py b/Lib/fontTools/feaLib/parser.py index 804cba9fe..c4bdc55ee 100644 --- a/Lib/fontTools/feaLib/parser.py +++ b/Lib/fontTools/feaLib/parser.py @@ -473,14 +473,38 @@ class Parser(object): assert lookups == [] return ([], prefix, [None] * len(prefix), values, [], hasMarks) else: - assert not any(values[: len(prefix)]), values - format1 = values[len(prefix) :][: len(glyphs)] - format2 = values[(len(prefix) + len(glyphs)) :][: len(suffix)] - values = ( - format2 - if format2 and isinstance(format2[0], self.ast.ValueRecord) - else format1 - ) + if any(values[: len(prefix)]): + raise FeatureLibError( + "Positioning cannot be applied in the bactrack glyph sequence, " + "before the marked glyph sequence.", + self.cur_token_location_ + ) + marked_values = values[len(prefix) : len(prefix) + len(glyphs)] + if any(marked_values): + if any(values[len(prefix) + len(glyphs) :]): + raise FeatureLibError( + "Positioning values are allowed only in the marked glyph " + "sequence, or after the final glyph node when only one glyph " + "node is marked.", + self.cur_token_location_ + ) + values = marked_values + elif values and values[-1]: + if len(glyphs) > 1 or any(values[:-1]): + raise FeatureLibError( + "Positioning values are allowed only in the marked glyph " + "sequence, or after the final glyph node when only one glyph " + "node is marked.", + self.cur_token_location_ + ) + values = values[-1:] + elif any(values): + raise FeatureLibError( + "Positioning values are allowed only in the marked glyph " + "sequence, or after the final glyph node when only one glyph " + "node is marked.", + self.cur_token_location_ + ) return (prefix, glyphs, lookups, values, suffix, hasMarks) def parse_chain_context_(self): diff --git a/Tests/feaLib/parser_test.py b/Tests/feaLib/parser_test.py index de2bc3ca8..11808190a 100644 --- a/Tests/feaLib/parser_test.py +++ b/Tests/feaLib/parser_test.py @@ -870,6 +870,41 @@ class ParserTest(unittest.TestCase): self.assertEqual(glyphstr(pos.prefix), "[A B]") self.assertEqual(glyphstr(pos.suffix), "comma") + def test_gpos_type_1_chained_special_kern_format_valuerecord_format_b_bug2293(self): + # https://github.com/fonttools/fonttools/issues/2293 + doc = self.parse("feature kern {pos [A B] [T Y]' comma a <0 0 0 0>;} kern;") + pos = doc.statements[0].statements[0] + self.assertIsInstance(pos, ast.SinglePosStatement) + [(glyphs, value)] = pos.pos + self.assertEqual(glyphstr([glyphs]), "[T Y]") + self.assertEqual(value.asFea(), "<0 0 0 0>") + self.assertEqual(glyphstr(pos.prefix), "[A B]") + self.assertEqual(glyphstr(pos.suffix), "comma a") + + def test_gpos_type_1_chained_exception1(self): + with self.assertRaisesRegex(FeatureLibError, "Positioning values are allowed"): + doc = self.parse("feature kern {" + " pos [A B]' [T Y]' comma a <0 0 0 0>;" + "} kern;") + + def test_gpos_type_1_chained_exception2(self): + with self.assertRaisesRegex(FeatureLibError, "Positioning values are allowed"): + doc = self.parse("feature kern {" + " pos [A B]' <0 0 0 0> [T Y]' comma a <0 0 0 0>;" + "} kern;") + + def test_gpos_type_1_chained_exception3(self): + with self.assertRaisesRegex(FeatureLibError, "Positioning cannot be applied"): + doc = self.parse("feature kern {" + " pos [A B] <0 0 0 0> [T Y]' comma a <0 0 0 0>;" + "} kern;") + + def test_gpos_type_1_chained_exception4(self): + with self.assertRaisesRegex(FeatureLibError, "Positioning values are allowed"): + doc = self.parse("feature kern {" + " pos a' b c 123 d;" + "} kern;") + def test_gpos_type_2_format_a(self): doc = self.parse("feature kern {" " pos [T V] -60 [a b c] <1 2 3 4>;"