From c06a377aa382f0a5a54c873554bce1ac4f9a9442 Mon Sep 17 00:00:00 2001 From: Sascha Brawer Date: Tue, 4 Aug 2015 11:01:04 +0200 Subject: [PATCH] [feaLib] Implement valueRecordDef statements --- Lib/fontTools/feaLib/ast.py | 14 ++++++++ Lib/fontTools/feaLib/parser.py | 56 +++++++++++++++++++++++++++-- Lib/fontTools/feaLib/parser_test.py | 38 ++++++++++++++++++++ 3 files changed, 105 insertions(+), 3 deletions(-) diff --git a/Lib/fontTools/feaLib/ast.py b/Lib/fontTools/feaLib/ast.py index 133bdce0b..ee61df825 100644 --- a/Lib/fontTools/feaLib/ast.py +++ b/Lib/fontTools/feaLib/ast.py @@ -48,3 +48,17 @@ class LanguageSystemStatement(object): def write(self, out, linesep): write(out, "languagesystem %s %s;%s" % (self.script.strip(), self.language.strip(), linesep)) + + +class ValueRecord(object): + def __init__(self, location, xPlacement, yPlacement, xAdvance, yAdvance): + self.location = location + self.xPlacement, self.yPlacement = (xPlacement, yPlacement) + self.xAdvance, self.yAdvance = (xAdvance, yAdvance) + + +class ValueRecordDefinition(object): + def __init__(self, location, name, value): + self.location = location + self.name = name + self.value = value diff --git a/Lib/fontTools/feaLib/parser.py b/Lib/fontTools/feaLib/parser.py index 4a09f2954..623b38377 100644 --- a/Lib/fontTools/feaLib/parser.py +++ b/Lib/fontTools/feaLib/parser.py @@ -32,11 +32,14 @@ class Parser(object): self.advance_lexer_() def parse(self): + statements = self.doc_.statements while self.next_token_type_ is not None: self.advance_lexer_() if self.cur_token_type_ is Lexer.GLYPHCLASS: - glyphclass = self.parse_glyphclass_definition_() - self.doc_.statements.append(glyphclass) + statements.append(self.parse_glyphclass_definition_()) + elif self.is_cur_keyword_("valueRecordDef"): + statements.append( + self.parse_valuerecord_definition_(vertical=False)) elif self.is_cur_keyword_("languagesystem"): self.parse_languagesystem_() elif self.is_cur_keyword_("feature"): @@ -99,6 +102,43 @@ class Parser(object): self.expect_symbol_("]") return result + def parse_valuerecord_(self, vertical): + if self.next_token_type_ is Lexer.NUMBER: + number, location = self.expect_number_(), self.cur_token_location_ + if vertical: + val = ast.ValueRecord(location, 0, 0, 0, number) + else: + val = ast.ValueRecord(location, 0, 0, number, 0) + return val + self.expect_symbol_("<") + location = self.cur_token_location_ + if self.next_token_type_ is Lexer.NAME: + name = self.expect_name_() + vrd = self.valuerecords_.resolve(name) + if vrd is None: + raise ParserError("Unknown valueRecordDef \"%s\"" % name, + self.cur_token_location_) + value = vrd.value + xPlacement, yPlacement = (value.xPlacement, value.yPlacement) + xAdvance, yAdvance = (value.xAdvance, value.yAdvance) + else: + xPlacement, yPlacement, xAdvance, yAdvance = ( + self.expect_number_(), self.expect_number_(), + self.expect_number_(), self.expect_number_()) + self.expect_symbol_(">") + return ast.ValueRecord( + location, xPlacement, yPlacement, xAdvance, yAdvance) + + def parse_valuerecord_definition_(self, vertical): + assert self.is_cur_keyword_("valueRecordDef") + location = self.cur_token_location_ + value = self.parse_valuerecord_(vertical) + name = self.expect_name_() + self.expect_symbol_(";") + vrd = ast.ValueRecordDefinition(location, name, value) + self.valuerecords_.define(name, vrd) + return vrd + def parse_languagesystem_(self): assert self.cur_token_ == "languagesystem" location = self.cur_token_location_ @@ -111,6 +151,7 @@ class Parser(object): assert self.cur_token_ == "feature" location = self.cur_token_location_ tag = self.expect_tag_() + vertical = (tag == "vkrn") self.expect_symbol_("{") for symtab in self.symbol_tables_: @@ -118,11 +159,14 @@ class Parser(object): block = ast.FeatureBlock(location, tag) self.doc_.statements.append(block) + statements = block.statements while self.next_token_ != "}": self.advance_lexer_() if self.cur_token_type_ is Lexer.GLYPHCLASS: - block.statements.append(self.parse_glyphclass_definition_()) + statements.append(self.parse_glyphclass_definition_()) + elif self.is_cur_keyword_("valueRecordDef"): + statements.append(self.parse_valuerecord_definition_(vertical)) else: raise ParserError("Expected glyph class definition", self.cur_token_location_) @@ -161,6 +205,12 @@ class Parser(object): return self.cur_token_ raise ParserError("Expected a name", self.cur_token_location_) + def expect_number_(self): + self.advance_lexer_() + if self.cur_token_type_ is Lexer.NUMBER: + return self.cur_token_ + raise ParserError("Expected a number", self.cur_token_location_) + def advance_lexer_(self): self.cur_token_type_, self.cur_token_, self.cur_token_location_ = ( self.next_token_type_, self.next_token_, self.next_token_location_) diff --git a/Lib/fontTools/feaLib/parser_test.py b/Lib/fontTools/feaLib/parser_test.py index 7a34276b7..0efc5b904 100644 --- a/Lib/fontTools/feaLib/parser_test.py +++ b/Lib/fontTools/feaLib/parser_test.py @@ -109,6 +109,44 @@ class ParserTest(unittest.TestCase): self.assertEqual(liga.statements[0].glyphs, {"a", "b", "l"}) self.assertEqual(smcp.statements[0].glyphs, {"a", "b", "s"}) + def test_valuerecord_format_a_horizontal(self): + doc = self.parse("feature liga {valueRecordDef 123 foo;} liga;") + value = doc.statements[0].statements[0].value + self.assertEqual(value.xPlacement, 0) + self.assertEqual(value.yPlacement, 0) + self.assertEqual(value.xAdvance, 123) + self.assertEqual(value.yAdvance, 0) + + def test_valuerecord_format_a_vertical(self): + doc = self.parse("feature vkrn {valueRecordDef 123 foo;} vkrn;") + value = doc.statements[0].statements[0].value + self.assertEqual(value.xPlacement, 0) + self.assertEqual(value.yPlacement, 0) + self.assertEqual(value.xAdvance, 0) + self.assertEqual(value.yAdvance, 123) + + def test_valuerecord_format_b(self): + doc = self.parse("feature liga {valueRecordDef <1 2 3 4> foo;} liga;") + value = doc.statements[0].statements[0].value + self.assertEqual(value.xPlacement, 1) + self.assertEqual(value.yPlacement, 2) + self.assertEqual(value.xAdvance, 3) + self.assertEqual(value.yAdvance, 4) + + def test_valuerecord_named(self): + doc = self.parse("valueRecordDef <1 2 3 4> foo;" + "feature liga {valueRecordDef bar;} liga;") + value = doc.statements[1].statements[0].value + self.assertEqual(value.xPlacement, 1) + self.assertEqual(value.yPlacement, 2) + self.assertEqual(value.xAdvance, 3) + self.assertEqual(value.yAdvance, 4) + + def test_valuerecord_named_unknown(self): + self.assertRaisesRegex( + ParserError, "Unknown valueRecordDef \"unknown\"", + self.parse, "valueRecordDef foo;") + def test_languagesystem(self): [langsys] = self.parse("languagesystem latn DEU;").statements self.assertEqual(langsys.script, "latn")