[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:
parent
5133e3777e
commit
46983f573f
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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",
|
||||
|
@ -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;")
|
||||
|
Loading…
x
Reference in New Issue
Block a user