diff --git a/Lib/fontTools/feaLib/ast.py b/Lib/fontTools/feaLib/ast.py index 9c4abb0fc..65eea9ba1 100644 --- a/Lib/fontTools/feaLib/ast.py +++ b/Lib/fontTools/feaLib/ast.py @@ -18,6 +18,14 @@ class Statement(object): pass +class Expression(object): + def __init__(self, location): + self.location = location + + def build(self, builder): + pass + + class Block(Statement): def __init__(self, location): Statement.__init__(self, location) @@ -74,12 +82,27 @@ class AlternateSubstitution(Statement): self.from_class) +class Anchor(Expression): + def __init__(self, location, x, y, contourpoint, + xDeviceTable, yDeviceTable): + Expression.__init__(self, location) + self.x, self.y, self.contourpoint = x, y, contourpoint + self.xDeviceTable, self.yDeviceTable = xDeviceTable, yDeviceTable + + class AnchorDefinition(Statement): def __init__(self, location, name, x, y, contourpoint): Statement.__init__(self, location) self.name, self.x, self.y, self.contourpoint = name, x, y, contourpoint +class CursiveAttachmentPositioning(Statement): + def __init__(self, location, glyphclass, entryAnchor, exitAnchor): + Statement.__init__(self, location) + self.glyphclass = glyphclass + self.entryAnchor, self.exitAnchor = entryAnchor, exitAnchor + + class LanguageStatement(Statement): def __init__(self, location, language, include_default, required): Statement.__init__(self, location) diff --git a/Lib/fontTools/feaLib/parser.py b/Lib/fontTools/feaLib/parser.py index 1424793f0..303bf1c72 100644 --- a/Lib/fontTools/feaLib/parser.py +++ b/Lib/fontTools/feaLib/parser.py @@ -46,6 +46,45 @@ class Parser(object): self.cur_token_location_) return self.doc_ + def parse_anchor_(self): + self.expect_symbol_("<") + self.expect_keyword_("anchor") + location = self.cur_token_location_ + + if self.next_token_ == "NULL": + self.expect_keyword_("NULL") + self.expect_symbol_(">") + return None + + if self.next_token_type_ == Lexer.NAME: + name = self.expect_name_() + anchordef = self.anchors_.resolve(name) + if anchordef is None: + raise FeatureLibError( + 'Unknown anchor "%s"' % name, + self.cur_token_location_) + self.expect_symbol_(">") + return ast.Anchor(location, anchordef.x, anchordef.y, + anchordef.contourpoint, + xDeviceTable=None, yDeviceTable=None) + + x, y = self.expect_number_(), self.expect_number_() + + contourpoint = None + if self.next_token_ == "contourpoint": + self.expect_keyword_("contourpoint") + contourpoint = self.expect_number_() + + if self.next_token_ == "<": + xDeviceTable = self.parse_device_() + yDeviceTable = self.parse_device_() + else: + xDeviceTable, yDeviceTable = None, None + + self.expect_symbol_(">") + return ast.Anchor(location, x, y, contourpoint, + xDeviceTable, yDeviceTable) + def parse_anchordef_(self): assert self.is_cur_keyword_("anchorDef") location = self.cur_token_location_ @@ -212,6 +251,20 @@ class Parser(object): def parse_position_(self, enumerated, vertical): assert self.cur_token_ in {"position", "pos"} location = self.cur_token_location_ + if self.next_token_ == "cursive": + self.expect_keyword_("cursive") + if enumerated: + raise FeatureLibError( + '"enumerate" is not allowed with ' + 'cursive attachment positioning', + location) + glyphclass = self.parse_glyphclass_(accept_glyphname=True) + entryAnchor = self.parse_anchor_() + exitAnchor = self.parse_anchor_() + self.expect_symbol_(";") + return ast.CursiveAttachmentPositioning( + location, glyphclass, entryAnchor, exitAnchor) + gc2, value2 = None, None gc1 = self.parse_glyphclass_(accept_glyphname=True) if self.is_next_glyphclass_(): diff --git a/Lib/fontTools/feaLib/parser_test.py b/Lib/fontTools/feaLib/parser_test.py index 5604de4a7..a9b7ca3cc 100644 --- a/Lib/fontTools/feaLib/parser_test.py +++ b/Lib/fontTools/feaLib/parser_test.py @@ -20,6 +20,76 @@ class ParserTest(unittest.TestCase): if not hasattr(self, "assertRaisesRegex"): self.assertRaisesRegex = self.assertRaisesRegexp + def test_anchor_format_a(self): + doc = self.parse( + "feature test {" + " pos cursive A ;" + "} test;") + anchor = doc.statements[0].statements[0].entryAnchor + self.assertEqual(type(anchor), ast.Anchor) + self.assertEqual(anchor.x, 120) + self.assertEqual(anchor.y, -20) + self.assertIsNone(anchor.contourpoint) + self.assertIsNone(anchor.xDeviceTable) + self.assertIsNone(anchor.yDeviceTable) + + def test_anchor_format_b(self): + doc = self.parse( + "feature test {" + " pos cursive A ;" + "} test;") + anchor = doc.statements[0].statements[0].entryAnchor + self.assertEqual(type(anchor), ast.Anchor) + self.assertEqual(anchor.x, 120) + self.assertEqual(anchor.y, -20) + self.assertEqual(anchor.contourpoint, 5) + self.assertIsNone(anchor.xDeviceTable) + self.assertIsNone(anchor.yDeviceTable) + + def test_anchor_format_c(self): + doc = self.parse( + "feature test {" + " pos cursive A " + " >" + " ;" + "} test;") + anchor = doc.statements[0].statements[0].entryAnchor + self.assertEqual(type(anchor), ast.Anchor) + self.assertEqual(anchor.x, 120) + self.assertEqual(anchor.y, -20) + self.assertIsNone(anchor.contourpoint) + self.assertEqual(anchor.xDeviceTable, ((11, 111), (12, 112))) + self.assertIsNone(anchor.yDeviceTable) + + def test_anchor_format_d(self): + doc = self.parse( + "feature test {" + " pos cursive A ;" + "} test;") + anchor = doc.statements[0].statements[0].exitAnchor + self.assertIsNone(anchor) + + def test_anchor_format_e(self): + doc = self.parse( + "feature test {" + " anchorDef 120 -20 contourpoint 7 Foo;" + " pos cursive A ;" + "} test;") + anchor = doc.statements[0].statements[1].entryAnchor + self.assertEqual(type(anchor), ast.Anchor) + self.assertEqual(anchor.x, 120) + self.assertEqual(anchor.y, -20) + self.assertEqual(anchor.contourpoint, 7) + self.assertIsNone(anchor.xDeviceTable) + self.assertIsNone(anchor.yDeviceTable) + + def test_anchor_format_e_undefined(self): + self.assertRaisesRegex( + FeatureLibError, 'Unknown anchor "UnknownName"', self.parse, + "feature test {" + " position cursive A ;" + "} test;") + def test_anchordef(self): [foo] = self.parse("anchorDef 123 456 foo;").statements self.assertEqual(type(foo), ast.AnchorDefinition) @@ -353,6 +423,16 @@ class ParserTest(unittest.TestCase): self.assertEqual(pos.glyphclass2, {"a", "b", "c"}) self.assertIsNone(pos.valuerecord2) + def test_gpos_type_3(self): + doc = self.parse("feature kern {" + " position cursive A ;" + "} kern;") + pos = doc.statements[0].statements[0] + self.assertEqual(type(pos), ast.CursiveAttachmentPositioning) + 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)) + 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]