[feaLib] Parse GPOS type 2 format A and B, and ValueRecord format D

This is just the change on the parser. Building the output tables
is still marked as a TODO.
This commit is contained in:
Sascha Brawer 2015-12-07 17:18:18 +01:00
parent 5133e3777e
commit 46983f573f
4 changed files with 137 additions and 8 deletions

View File

@ -145,6 +145,20 @@ class MultipleSubstitution(Statement):
self.glyph, self.replacement)
class PairAdjustmentPositioning(Statement):
def __init__(self, location, enumerated,
glyphclass1, valuerecord1, glyphclass2, valuerecord2):
Statement.__init__(self, location)
self.enumerated = enumerated
self.glyphclass1, self.valuerecord1 = glyphclass1, valuerecord1
self.glyphclass2, self.valuerecord2 = glyphclass2, valuerecord2
def build(self, builder):
builder.add_pair_pos(self.location, self.enumerated,
self.glyphclass1, self.valuerecord1,
self.glyphclass2, self.valuerecord2)
class ReverseChainingSingleSubstitution(Statement):
def __init__(self, location, old_prefix, old_suffix, mapping):
Statement.__init__(self, location)

View File

@ -304,6 +304,10 @@ class Builder(object):
location)
lookup.mapping[from_glyph] = to_glyph
def add_pair_pos(self, location, enumerated,
glyph1, value1, glyph2, value2):
pass # TODO: Implement.
def add_single_pos(self, location, glyph, valuerecord):
lookup = self.get_lookup_(location, SinglePosBuilder)
curValue = lookup.mapping.get(glyph)

View File

@ -60,6 +60,11 @@ class Parser(object):
self.anchors_.define(name, anchordef)
return anchordef
def parse_enumerate_(self, vertical):
assert self.cur_token_ in {"enumerate", "enum"}
self.advance_lexer_()
return self.parse_position_(enumerated=True, vertical=vertical)
def parse_glyphclass_definition_(self):
location, name = self.cur_token_location_, self.cur_token_
self.expect_symbol_("=")
@ -200,13 +205,33 @@ class Parser(object):
self.lookups_.define(name, block)
return block
def parse_position_(self, vertical):
def is_next_glyphclass_(self):
return (self.next_token_ == "[" or
self.next_token_type_ in (Lexer.GLYPHCLASS, Lexer.NAME))
def parse_position_(self, enumerated, vertical):
assert self.cur_token_ in {"position", "pos"}
location = self.cur_token_location_
glyphclass = self.parse_glyphclass_(accept_glyphname=True)
valuerec = self.parse_valuerecord_(vertical)
gc2, value2 = None, None
gc1 = self.parse_glyphclass_(accept_glyphname=True)
if self.is_next_glyphclass_():
# Pair positioning, format B: 'pos' gc1 gc2 value1
gc2 = self.parse_glyphclass_(accept_glyphname=True)
value1 = self.parse_valuerecord_(vertical)
if self.next_token_ != ";" and gc2 is None:
# Pair positioning, format A: 'pos' gc1 value1 gc2 value2
gc2 = self.parse_glyphclass_(accept_glyphname=True)
value2 = self.parse_valuerecord_(vertical)
self.expect_symbol_(";")
return ast.SingleAdjustmentPositioning(location, glyphclass, valuerec)
if gc2 is None:
if enumerated:
raise FeatureLibError(
'"enumerate" is only allowed with pair positionings',
self.cur_token_location_)
return ast.SingleAdjustmentPositioning(location, gc1, value1)
else:
return ast.PairAdjustmentPositioning(location, enumerated,
gc1, value1, gc2, value2)
def parse_script_(self):
assert self.is_cur_keyword_("script")
@ -357,6 +382,9 @@ class Parser(object):
location = self.cur_token_location_
if self.next_token_type_ is Lexer.NAME:
name = self.expect_name_()
if name == "NULL":
self.expect_symbol_(">")
return None
vrd = self.valuerecords_.resolve(name)
if vrd is None:
raise FeatureLibError("Unknown valueRecordDef \"%s\"" % name,
@ -442,6 +470,8 @@ class Parser(object):
statements.append(self.parse_glyphclass_definition_())
elif self.is_cur_keyword_("anchorDef"):
statements.append(self.parse_anchordef_())
elif self.is_cur_keyword_({"enum", "enumerate"}):
statements.append(self.parse_enumerate_(vertical=vertical))
elif self.is_cur_keyword_("ignore"):
statements.append(self.parse_ignore_())
elif self.is_cur_keyword_("language"):
@ -449,7 +479,8 @@ class Parser(object):
elif self.is_cur_keyword_("lookup"):
statements.append(self.parse_lookup_(vertical))
elif self.is_cur_keyword_({"pos", "position"}):
statements.append(self.parse_position_(vertical))
statements.append(
self.parse_position_(enumerated=False, vertical=vertical))
elif self.is_cur_keyword_("script"):
statements.append(self.parse_script_())
elif (self.is_cur_keyword_({"sub", "substitute",

View File

@ -256,7 +256,7 @@ class ParserTest(unittest.TestCase):
FeatureLibError, 'Unknown lookup "Huh"',
self.parse, "feature liga {lookup Huh;} liga;")
def test_gpos_type1_glyph(self):
def test_gpos_type_1_glyph(self):
doc = self.parse("feature kern {pos one <1 2 3 4>;} kern;")
pos = doc.statements[0].statements[0]
self.assertEqual(type(pos), ast.SingleAdjustmentPositioning)
@ -264,20 +264,95 @@ class ParserTest(unittest.TestCase):
self.assertEqual(pos.valuerecord.makeString(vertical=False),
"<1 2 3 4>")
def test_gpos_type1_glyphclass_horizontal(self):
def test_gpos_type_1_glyphclass_horizontal(self):
doc = self.parse("feature kern {pos [one two] -300;} kern;")
pos = doc.statements[0].statements[0]
self.assertEqual(type(pos), ast.SingleAdjustmentPositioning)
self.assertEqual(pos.glyphclass, {"one", "two"})
self.assertEqual(pos.valuerecord.makeString(vertical=False), "-300")
def test_gpos_type1_glyphclass_vertical(self):
def test_gpos_type_1_glyphclass_vertical(self):
doc = self.parse("feature vkrn {pos [one two] -300;} vkrn;")
pos = doc.statements[0].statements[0]
self.assertEqual(type(pos), ast.SingleAdjustmentPositioning)
self.assertEqual(pos.glyphclass, {"one", "two"})
self.assertEqual(pos.valuerecord.makeString(vertical=True), "-300")
def test_gpos_type_1_enumerated(self):
self.assertRaisesRegex(
FeatureLibError,
'"enumerate" is only allowed with pair positionings',
self.parse, "feature test {enum pos T 100;} test;")
self.assertRaisesRegex(
FeatureLibError,
'"enumerate" is only allowed with pair positionings',
self.parse, "feature test {enumerate pos T 100;} test;")
def test_gpos_type_2_format_a(self):
doc = self.parse("feature kern {"
" pos [T V] -60 [a b c] <1 2 3 4>;"
"} kern;")
pos = doc.statements[0].statements[0]
self.assertEqual(type(pos), ast.PairAdjustmentPositioning)
self.assertFalse(pos.enumerated)
self.assertEqual(pos.glyphclass1, {"T", "V"})
self.assertEqual(pos.valuerecord1.makeString(vertical=False), "-60")
self.assertEqual(pos.glyphclass2, {"a", "b", "c"})
self.assertEqual(pos.valuerecord2.makeString(vertical=False),
"<1 2 3 4>")
def test_gpos_type_2_format_a_enumerated(self):
doc = self.parse("feature kern {"
" enum pos [T V] -60 [a b c] <1 2 3 4>;"
"} kern;")
pos = doc.statements[0].statements[0]
self.assertEqual(type(pos), ast.PairAdjustmentPositioning)
self.assertTrue(pos.enumerated)
self.assertEqual(pos.glyphclass1, {"T", "V"})
self.assertEqual(pos.valuerecord1.makeString(vertical=False), "-60")
self.assertEqual(pos.glyphclass2, {"a", "b", "c"})
self.assertEqual(pos.valuerecord2.makeString(vertical=False),
"<1 2 3 4>")
def test_gpos_type_2_format_a_with_null(self):
doc = self.parse("feature kern {"
" pos [T V] <1 2 3 4> [a b c] <NULL>;"
"} kern;")
pos = doc.statements[0].statements[0]
self.assertEqual(type(pos), ast.PairAdjustmentPositioning)
self.assertFalse(pos.enumerated)
self.assertEqual(pos.glyphclass1, {"T", "V"})
self.assertEqual(pos.valuerecord1.makeString(vertical=False),
"<1 2 3 4>")
self.assertEqual(pos.glyphclass2, {"a", "b", "c"})
self.assertIsNone(pos.valuerecord2)
def test_gpos_type_2_format_b(self):
doc = self.parse("feature kern {"
" pos [T V] [a b c] <1 2 3 4>;"
"} kern;")
pos = doc.statements[0].statements[0]
self.assertEqual(type(pos), ast.PairAdjustmentPositioning)
self.assertFalse(pos.enumerated)
self.assertEqual(pos.glyphclass1, {"T", "V"})
self.assertEqual(pos.valuerecord1.makeString(vertical=False),
"<1 2 3 4>")
self.assertEqual(pos.glyphclass2, {"a", "b", "c"})
self.assertIsNone(pos.valuerecord2)
def test_gpos_type_2_format_b_enumerated(self):
doc = self.parse("feature kern {"
" enumerate position [T V] [a b c] <1 2 3 4>;"
"} kern;")
pos = doc.statements[0].statements[0]
self.assertEqual(type(pos), ast.PairAdjustmentPositioning)
self.assertTrue(pos.enumerated)
self.assertEqual(pos.glyphclass1, {"T", "V"})
self.assertEqual(pos.valuerecord1.makeString(vertical=False),
"<1 2 3 4>")
self.assertEqual(pos.glyphclass2, {"a", "b", "c"})
self.assertIsNone(pos.valuerecord2)
def test_rsub_format_a(self):
doc = self.parse("feature test {rsub a [b B] c' d [e E] by C;} test;")
rsub = doc.statements[0].statements[0]
@ -494,6 +569,11 @@ class ParserTest(unittest.TestCase):
self.assertIsNone(value.xAdvDevice)
self.assertEqual(value.yAdvDevice, ((33, -113), (44, -114), (55, 115)))
def test_valuerecord_format_d(self):
doc = self.parse("feature test {valueRecordDef <NULL> foo;} test;")
value = doc.statements[0].statements[0].value
self.assertIsNone(value)
def test_valuerecord_named(self):
doc = self.parse("valueRecordDef <1 2 3 4> foo;"
"feature liga {valueRecordDef <foo> bar;} liga;")