diff --git a/Lib/fontTools/feaLib/ast.py b/Lib/fontTools/feaLib/ast.py index 956a5932b..d49f9e24f 100644 --- a/Lib/fontTools/feaLib/ast.py +++ b/Lib/fontTools/feaLib/ast.py @@ -302,6 +302,15 @@ class IgnoreSubstitutionRule(Statement): self.prefix, self.glyphs, self.suffix = (prefix, glyphs, suffix) +class FontRevisionStatement(Statement): + def __init__(self, location, revision): + Statement.__init__(self, location) + self.revision = revision + + def build(self, builder): + builder.set_font_revision(self.location, self.revision) + + class LigatureCaretByIndexStatement(Statement): def __init__(self, location, glyphs, carets): Statement.__init__(self, location) diff --git a/Lib/fontTools/feaLib/builder.py b/Lib/fontTools/feaLib/builder.py index f0714e904..20c0b9969 100644 --- a/Lib/fontTools/feaLib/builder.py +++ b/Lib/fontTools/feaLib/builder.py @@ -33,6 +33,8 @@ class Builder(object): # for feature 'aalt' self.aalt_features_ = [] # [(location, featureName)*], for 'aalt' self.aalt_location_ = None + # for table 'head' + self.fontRevision_ = None # 2.71 # for table 'GDEF' self.attachPoints_ = {} # "a" --> {3, 7} self.ligatureCaretByIndex_ = {} # "f_f_i" --> {3, 7} @@ -46,6 +48,7 @@ class Builder(object): self.parseTree = Parser(self.featurefile_path).parse() self.parseTree.build(self) self.build_feature_aalt_() + self.build_head() for tag in ('GPOS', 'GSUB'): table = self.makeTable(tag) if (table.ScriptList.ScriptCount > 0 or @@ -142,6 +145,16 @@ class Builder(object): self.end_feature() self.lookups_.extend(old_lookups) + def build_head(self): + if not self.fontRevision_: + return + table = self.font.get("head") + if not table: # this only happens for unit tests + table = self.font["head"] = getTableClass("head")() + table.decompile(b"\0" * 54, self.font) + table.tableVersion = 1.0 + table.fontRevision = self.fontRevision_ + def buildGDEF(self): gdef = otTables.GDEF() gdef.GlyphClassDef = self.buildGDEFGlyphClassDef_() @@ -408,6 +421,9 @@ class Builder(object): lookup = self.named_lookups_[lookup_name] self.add_lookup_to_feature_(lookup, self.cur_feature_name_) + def set_font_revision(self, location, revision): + self.fontRevision_ = revision + def set_language(self, location, language, include_default, required): assert(len(language) == 4) if self.cur_lookup_name_: diff --git a/Lib/fontTools/feaLib/builder_test.py b/Lib/fontTools/feaLib/builder_test.py index bfa6f2238..db16ad76e 100644 --- a/Lib/fontTools/feaLib/builder_test.py +++ b/Lib/fontTools/feaLib/builder_test.py @@ -80,7 +80,7 @@ class BuilderTest(unittest.TestCase): def expect_ttx(self, font, expected_ttx): path = self.temp_path(suffix=".ttx") - font.saveXML(path, quiet=True, tables=['GDEF', 'GSUB', 'GPOS']) + font.saveXML(path, quiet=True, tables=['head', 'GDEF', 'GSUB', 'GPOS']) actual = self.read_ttx(path) expected = self.read_ttx(expected_ttx) if actual != expected: @@ -168,7 +168,7 @@ class BuilderTest(unittest.TestCase): def test_spec(self): for name in ("4h1 5d1 5d2 5fi1 5fi2 5fi3 5fi4 5h1 " - "6d2 6e 6f 6h_ii 8a 9b").split(): + "6d2 6e 6f 6h_ii 8a 9b 9c1 9c2 9c3").split(): font = makeTTFont() addOpenTypeFeatures(self.getpath("spec%s.fea" % name), font) self.expect_ttx(font, self.getpath("spec%s.ttx" % name)) diff --git a/Lib/fontTools/feaLib/lexer.py b/Lib/fontTools/feaLib/lexer.py index 7a6e54a69..a5fdf55c9 100644 --- a/Lib/fontTools/feaLib/lexer.py +++ b/Lib/fontTools/feaLib/lexer.py @@ -8,6 +8,7 @@ import os class Lexer(object): NUMBER = "NUMBER" + FLOAT = "FLOAT" STRING = "STRING" NAME = "NAME" FILENAME = "FILENAME" @@ -123,11 +124,19 @@ class Lexer(object): return (Lexer.NUMBER, int(text[start:self.pos_], 16), location) if cur_char in Lexer.CHAR_DIGIT_: self.scan_over_(Lexer.CHAR_DIGIT_) - return (Lexer.NUMBER, int(text[start:self.pos_], 10), location) + if next_char != ".": + return (Lexer.NUMBER, int(text[start:self.pos_], 10), location) + self.scan_over_(".") + self.scan_over_(Lexer.CHAR_DIGIT_) + return (Lexer.FLOAT, float(text[start:self.pos_]), location) if cur_char == "-" and next_char in Lexer.CHAR_DIGIT_: self.pos_ += 1 self.scan_over_(Lexer.CHAR_DIGIT_) - return (Lexer.NUMBER, int(text[start:self.pos_], 10), location) + if self.pos_ >= limit or text[self.pos_] != ".": + return (Lexer.NUMBER, int(text[start:self.pos_], 10), location) + self.scan_over_(".") + self.scan_over_(Lexer.CHAR_DIGIT_) + return (Lexer.FLOAT, float(text[start:self.pos_]), location) if cur_char in Lexer.CHAR_SYMBOL_: self.pos_ += 1 return (Lexer.SYMBOL, cur_char, location) diff --git a/Lib/fontTools/feaLib/lexer_test.py b/Lib/fontTools/feaLib/lexer_test.py index 2ce1b6c6d..3758948b1 100644 --- a/Lib/fontTools/feaLib/lexer_test.py +++ b/Lib/fontTools/feaLib/lexer_test.py @@ -66,6 +66,10 @@ class LexerTest(unittest.TestCase): self.assertEqual(lex("0xCAFED00D"), [(Lexer.NUMBER, 0xCAFED00D)]) self.assertEqual(lex("0xcafed00d"), [(Lexer.NUMBER, 0xCAFED00D)]) + def test_float(self): + self.assertEqual(lex("1.23 -4.5"), + [(Lexer.FLOAT, 1.23), (Lexer.FLOAT, -4.5)]) + def test_symbol(self): self.assertEqual(lex("a'"), [(Lexer.NAME, "a"), (Lexer.SYMBOL, "'")]) self.assertEqual( diff --git a/Lib/fontTools/feaLib/parser.py b/Lib/fontTools/feaLib/parser.py index b4dde3283..9f921c440 100644 --- a/Lib/fontTools/feaLib/parser.py +++ b/Lib/fontTools/feaLib/parser.py @@ -628,6 +628,7 @@ class Parser(object): self.expect_symbol_("{") handler = { "GDEF": self.parse_table_GDEF_, + "head": self.parse_table_head_, }.get(name) if handler: handler(table) @@ -660,6 +661,16 @@ class Parser(object): "or LigatureCaretByPos", self.cur_token_location_) + def parse_table_head_(self, table): + statements = table.statements + while self.next_token_ != "}": + self.advance_lexer_() + if self.is_cur_keyword_("FontRevision"): + statements.append(self.parse_FontRevision_()) + else: + raise FeatureLibError("Expected FontRevision", + self.cur_token_location_) + def parse_device_(self): result = None self.expect_symbol_("<") @@ -772,6 +783,15 @@ class Parser(object): self.expect_symbol_(";") return ast.FeatureReferenceStatement(location, featureName) + def parse_FontRevision_(self): + assert self.cur_token_ == "FontRevision", self.cur_token_ + location, version = self.cur_token_location_, self.expect_float_() + self.expect_symbol_(";") + if version <= 0: + raise FeatureLibError("Font revision numbers must be positive", + location) + return ast.FontRevisionStatement(location, version) + def parse_block_(self, block, vertical): self.expect_symbol_("{") for symtab in self.symbol_tables_: @@ -901,6 +921,13 @@ class Parser(object): return self.cur_token_ raise FeatureLibError("Expected a number", self.cur_token_location_) + def expect_float_(self): + self.advance_lexer_() + if self.cur_token_type_ is Lexer.FLOAT: + return self.cur_token_ + raise FeatureLibError("Expected a floating-point 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 41b2476cb..130438846 100644 --- a/Lib/fontTools/feaLib/parser_test.py +++ b/Lib/fontTools/feaLib/parser_test.py @@ -138,6 +138,17 @@ class ParserTest(unittest.TestCase): self.assertIsInstance(ref, ast.FeatureReferenceStatement) self.assertEqual(ref.featureName, "salt") + def test_FontRevision(self): + doc = self.parse("table head {FontRevision 2.5;} head;") + s = doc.statements[0].statements[0] + self.assertIsInstance(s, ast.FontRevisionStatement) + self.assertEqual(s.revision, 2.5) + + def test_FontRevision_negative(self): + self.assertRaisesRegex( + FeatureLibError, "Font revision numbers must be positive", + self.parse, "table head {FontRevision -17.2;} head;") + def test_glyphclass(self): [gc] = self.parse("@dash = [endash emdash figuredash];").statements self.assertEqual(gc.name, "dash") diff --git a/Lib/fontTools/feaLib/testdata/spec9c1.fea b/Lib/fontTools/feaLib/testdata/spec9c1.fea new file mode 100644 index 000000000..01cd6084f --- /dev/null +++ b/Lib/fontTools/feaLib/testdata/spec9c1.fea @@ -0,0 +1,3 @@ +table head { + FontRevision 1.1; +} head; diff --git a/Lib/fontTools/feaLib/testdata/spec9c1.ttx b/Lib/fontTools/feaLib/testdata/spec9c1.ttx new file mode 100644 index 000000000..f1985b17e --- /dev/null +++ b/Lib/fontTools/feaLib/testdata/spec9c1.ttx @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Lib/fontTools/feaLib/testdata/spec9c2.fea b/Lib/fontTools/feaLib/testdata/spec9c2.fea new file mode 100644 index 000000000..333fa1674 --- /dev/null +++ b/Lib/fontTools/feaLib/testdata/spec9c2.fea @@ -0,0 +1,3 @@ +table head { + FontRevision 1.001; +} head; diff --git a/Lib/fontTools/feaLib/testdata/spec9c2.ttx b/Lib/fontTools/feaLib/testdata/spec9c2.ttx new file mode 100644 index 000000000..7a996c157 --- /dev/null +++ b/Lib/fontTools/feaLib/testdata/spec9c2.ttx @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Lib/fontTools/feaLib/testdata/spec9c3.fea b/Lib/fontTools/feaLib/testdata/spec9c3.fea new file mode 100644 index 000000000..e9433a872 --- /dev/null +++ b/Lib/fontTools/feaLib/testdata/spec9c3.fea @@ -0,0 +1,3 @@ +table head { + FontRevision 1.500; +} head; diff --git a/Lib/fontTools/feaLib/testdata/spec9c3.ttx b/Lib/fontTools/feaLib/testdata/spec9c3.ttx new file mode 100644 index 000000000..16819e41b --- /dev/null +++ b/Lib/fontTools/feaLib/testdata/spec9c3.ttx @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + +