[feaLib] Implement table head with FontRevision statement
This commit is contained in:
parent
2de6fc7744
commit
38f4ee7908
@ -302,6 +302,15 @@ class IgnoreSubstitutionRule(Statement):
|
|||||||
self.prefix, self.glyphs, self.suffix = (prefix, glyphs, suffix)
|
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):
|
class LigatureCaretByIndexStatement(Statement):
|
||||||
def __init__(self, location, glyphs, carets):
|
def __init__(self, location, glyphs, carets):
|
||||||
Statement.__init__(self, location)
|
Statement.__init__(self, location)
|
||||||
|
@ -33,6 +33,8 @@ class Builder(object):
|
|||||||
# for feature 'aalt'
|
# for feature 'aalt'
|
||||||
self.aalt_features_ = [] # [(location, featureName)*], for 'aalt'
|
self.aalt_features_ = [] # [(location, featureName)*], for 'aalt'
|
||||||
self.aalt_location_ = None
|
self.aalt_location_ = None
|
||||||
|
# for table 'head'
|
||||||
|
self.fontRevision_ = None # 2.71
|
||||||
# for table 'GDEF'
|
# for table 'GDEF'
|
||||||
self.attachPoints_ = {} # "a" --> {3, 7}
|
self.attachPoints_ = {} # "a" --> {3, 7}
|
||||||
self.ligatureCaretByIndex_ = {} # "f_f_i" --> {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 = Parser(self.featurefile_path).parse()
|
||||||
self.parseTree.build(self)
|
self.parseTree.build(self)
|
||||||
self.build_feature_aalt_()
|
self.build_feature_aalt_()
|
||||||
|
self.build_head()
|
||||||
for tag in ('GPOS', 'GSUB'):
|
for tag in ('GPOS', 'GSUB'):
|
||||||
table = self.makeTable(tag)
|
table = self.makeTable(tag)
|
||||||
if (table.ScriptList.ScriptCount > 0 or
|
if (table.ScriptList.ScriptCount > 0 or
|
||||||
@ -142,6 +145,16 @@ class Builder(object):
|
|||||||
self.end_feature()
|
self.end_feature()
|
||||||
self.lookups_.extend(old_lookups)
|
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):
|
def buildGDEF(self):
|
||||||
gdef = otTables.GDEF()
|
gdef = otTables.GDEF()
|
||||||
gdef.GlyphClassDef = self.buildGDEFGlyphClassDef_()
|
gdef.GlyphClassDef = self.buildGDEFGlyphClassDef_()
|
||||||
@ -408,6 +421,9 @@ class Builder(object):
|
|||||||
lookup = self.named_lookups_[lookup_name]
|
lookup = self.named_lookups_[lookup_name]
|
||||||
self.add_lookup_to_feature_(lookup, self.cur_feature_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):
|
def set_language(self, location, language, include_default, required):
|
||||||
assert(len(language) == 4)
|
assert(len(language) == 4)
|
||||||
if self.cur_lookup_name_:
|
if self.cur_lookup_name_:
|
||||||
|
@ -80,7 +80,7 @@ class BuilderTest(unittest.TestCase):
|
|||||||
|
|
||||||
def expect_ttx(self, font, expected_ttx):
|
def expect_ttx(self, font, expected_ttx):
|
||||||
path = self.temp_path(suffix=".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)
|
actual = self.read_ttx(path)
|
||||||
expected = self.read_ttx(expected_ttx)
|
expected = self.read_ttx(expected_ttx)
|
||||||
if actual != expected:
|
if actual != expected:
|
||||||
@ -168,7 +168,7 @@ class BuilderTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_spec(self):
|
def test_spec(self):
|
||||||
for name in ("4h1 5d1 5d2 5fi1 5fi2 5fi3 5fi4 5h1 "
|
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()
|
font = makeTTFont()
|
||||||
addOpenTypeFeatures(self.getpath("spec%s.fea" % name), font)
|
addOpenTypeFeatures(self.getpath("spec%s.fea" % name), font)
|
||||||
self.expect_ttx(font, self.getpath("spec%s.ttx" % name))
|
self.expect_ttx(font, self.getpath("spec%s.ttx" % name))
|
||||||
|
@ -8,6 +8,7 @@ import os
|
|||||||
|
|
||||||
class Lexer(object):
|
class Lexer(object):
|
||||||
NUMBER = "NUMBER"
|
NUMBER = "NUMBER"
|
||||||
|
FLOAT = "FLOAT"
|
||||||
STRING = "STRING"
|
STRING = "STRING"
|
||||||
NAME = "NAME"
|
NAME = "NAME"
|
||||||
FILENAME = "FILENAME"
|
FILENAME = "FILENAME"
|
||||||
@ -123,11 +124,19 @@ class Lexer(object):
|
|||||||
return (Lexer.NUMBER, int(text[start:self.pos_], 16), location)
|
return (Lexer.NUMBER, int(text[start:self.pos_], 16), location)
|
||||||
if cur_char in Lexer.CHAR_DIGIT_:
|
if cur_char in Lexer.CHAR_DIGIT_:
|
||||||
self.scan_over_(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_:
|
if cur_char == "-" and next_char in Lexer.CHAR_DIGIT_:
|
||||||
self.pos_ += 1
|
self.pos_ += 1
|
||||||
self.scan_over_(Lexer.CHAR_DIGIT_)
|
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_:
|
if cur_char in Lexer.CHAR_SYMBOL_:
|
||||||
self.pos_ += 1
|
self.pos_ += 1
|
||||||
return (Lexer.SYMBOL, cur_char, location)
|
return (Lexer.SYMBOL, cur_char, location)
|
||||||
|
@ -66,6 +66,10 @@ class LexerTest(unittest.TestCase):
|
|||||||
self.assertEqual(lex("0xCAFED00D"), [(Lexer.NUMBER, 0xCAFED00D)])
|
self.assertEqual(lex("0xCAFED00D"), [(Lexer.NUMBER, 0xCAFED00D)])
|
||||||
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):
|
def test_symbol(self):
|
||||||
self.assertEqual(lex("a'"), [(Lexer.NAME, "a"), (Lexer.SYMBOL, "'")])
|
self.assertEqual(lex("a'"), [(Lexer.NAME, "a"), (Lexer.SYMBOL, "'")])
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
@ -628,6 +628,7 @@ class Parser(object):
|
|||||||
self.expect_symbol_("{")
|
self.expect_symbol_("{")
|
||||||
handler = {
|
handler = {
|
||||||
"GDEF": self.parse_table_GDEF_,
|
"GDEF": self.parse_table_GDEF_,
|
||||||
|
"head": self.parse_table_head_,
|
||||||
}.get(name)
|
}.get(name)
|
||||||
if handler:
|
if handler:
|
||||||
handler(table)
|
handler(table)
|
||||||
@ -660,6 +661,16 @@ class Parser(object):
|
|||||||
"or LigatureCaretByPos",
|
"or LigatureCaretByPos",
|
||||||
self.cur_token_location_)
|
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):
|
def parse_device_(self):
|
||||||
result = None
|
result = None
|
||||||
self.expect_symbol_("<")
|
self.expect_symbol_("<")
|
||||||
@ -772,6 +783,15 @@ class Parser(object):
|
|||||||
self.expect_symbol_(";")
|
self.expect_symbol_(";")
|
||||||
return ast.FeatureReferenceStatement(location, featureName)
|
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):
|
def parse_block_(self, block, vertical):
|
||||||
self.expect_symbol_("{")
|
self.expect_symbol_("{")
|
||||||
for symtab in self.symbol_tables_:
|
for symtab in self.symbol_tables_:
|
||||||
@ -901,6 +921,13 @@ class Parser(object):
|
|||||||
return self.cur_token_
|
return self.cur_token_
|
||||||
raise FeatureLibError("Expected a number", self.cur_token_location_)
|
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):
|
def advance_lexer_(self):
|
||||||
self.cur_token_type_, self.cur_token_, self.cur_token_location_ = (
|
self.cur_token_type_, self.cur_token_, self.cur_token_location_ = (
|
||||||
self.next_token_type_, self.next_token_, self.next_token_location_)
|
self.next_token_type_, self.next_token_, self.next_token_location_)
|
||||||
|
@ -138,6 +138,17 @@ class ParserTest(unittest.TestCase):
|
|||||||
self.assertIsInstance(ref, ast.FeatureReferenceStatement)
|
self.assertIsInstance(ref, ast.FeatureReferenceStatement)
|
||||||
self.assertEqual(ref.featureName, "salt")
|
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):
|
def test_glyphclass(self):
|
||||||
[gc] = self.parse("@dash = [endash emdash figuredash];").statements
|
[gc] = self.parse("@dash = [endash emdash figuredash];").statements
|
||||||
self.assertEqual(gc.name, "dash")
|
self.assertEqual(gc.name, "dash")
|
||||||
|
3
Lib/fontTools/feaLib/testdata/spec9c1.fea
vendored
Normal file
3
Lib/fontTools/feaLib/testdata/spec9c1.fea
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
table head {
|
||||||
|
FontRevision 1.1;
|
||||||
|
} head;
|
25
Lib/fontTools/feaLib/testdata/spec9c1.ttx
vendored
Normal file
25
Lib/fontTools/feaLib/testdata/spec9c1.ttx
vendored
Normal 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>
|
3
Lib/fontTools/feaLib/testdata/spec9c2.fea
vendored
Normal file
3
Lib/fontTools/feaLib/testdata/spec9c2.fea
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
table head {
|
||||||
|
FontRevision 1.001;
|
||||||
|
} head;
|
25
Lib/fontTools/feaLib/testdata/spec9c2.ttx
vendored
Normal file
25
Lib/fontTools/feaLib/testdata/spec9c2.ttx
vendored
Normal 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>
|
3
Lib/fontTools/feaLib/testdata/spec9c3.fea
vendored
Normal file
3
Lib/fontTools/feaLib/testdata/spec9c3.fea
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
table head {
|
||||||
|
FontRevision 1.500;
|
||||||
|
} head;
|
25
Lib/fontTools/feaLib/testdata/spec9c3.ttx
vendored
Normal file
25
Lib/fontTools/feaLib/testdata/spec9c3.ttx
vendored
Normal 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>
|
Loading…
x
Reference in New Issue
Block a user