[feaLib] Implement table head with FontRevision statement

This commit is contained in:
Sascha Brawer 2016-01-11 18:01:47 +01:00
parent 2de6fc7744
commit 38f4ee7908
13 changed files with 164 additions and 4 deletions

View File

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

View File

@ -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_:

View File

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

View File

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

View File

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

View File

@ -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_)

View File

@ -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")

View File

@ -0,0 +1,3 @@
table head {
FontRevision 1.1;
} head;

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont>
<head>
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="1.0"/>
<fontRevision value="1.1"/>
<checkSumAdjustment value="0x0"/>
<magicNumber value="0x0"/>
<flags value="00000000 00000000"/>
<unitsPerEm value="0"/>
<created value="Thu Jan 1 00:00:00 1970"/>
<modified value="Thu Jan 1 00:00:00 1970"/>
<xMin value="0"/>
<yMin value="0"/>
<xMax value="0"/>
<yMax value="0"/>
<macStyle value="00000000 00000000"/>
<lowestRecPPEM value="0"/>
<fontDirectionHint value="0"/>
<indexToLocFormat value="0"/>
<glyphDataFormat value="0"/>
</head>
</ttFont>

View File

@ -0,0 +1,3 @@
table head {
FontRevision 1.001;
} head;

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont>
<head>
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="1.0"/>
<fontRevision value="1.001"/>
<checkSumAdjustment value="0x0"/>
<magicNumber value="0x0"/>
<flags value="00000000 00000000"/>
<unitsPerEm value="0"/>
<created value="Thu Jan 1 00:00:00 1970"/>
<modified value="Thu Jan 1 00:00:00 1970"/>
<xMin value="0"/>
<yMin value="0"/>
<xMax value="0"/>
<yMax value="0"/>
<macStyle value="00000000 00000000"/>
<lowestRecPPEM value="0"/>
<fontDirectionHint value="0"/>
<indexToLocFormat value="0"/>
<glyphDataFormat value="0"/>
</head>
</ttFont>

View File

@ -0,0 +1,3 @@
table head {
FontRevision 1.500;
} head;

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont>
<head>
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="1.0"/>
<fontRevision value="1.5"/>
<checkSumAdjustment value="0x0"/>
<magicNumber value="0x0"/>
<flags value="00000000 00000000"/>
<unitsPerEm value="0"/>
<created value="Thu Jan 1 00:00:00 1970"/>
<modified value="Thu Jan 1 00:00:00 1970"/>
<xMin value="0"/>
<yMin value="0"/>
<xMax value="0"/>
<yMax value="0"/>
<macStyle value="00000000 00000000"/>
<lowestRecPPEM value="0"/>
<fontDirectionHint value="0"/>
<indexToLocFormat value="0"/>
<glyphDataFormat value="0"/>
</head>
</ttFont>