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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+