[feaLib] Parse GPOS type 8: Chaining contextual positioning

No output is generated yet, this change is just on the parser.

The OpenType Feature File specification is surprisingly vague about
the exact syntax of chaining contextual positioning rules, so I expect
that we will have to iterate on this parser. However, the test case
in parser_test.py gets recognized by `makeotf`, so the current
implementation is unlikely to be completely wrong.
This commit is contained in:
Sascha Brawer 2015-12-09 22:56:24 +01:00
parent 4ae2604dca
commit cb0adb98b9
4 changed files with 47 additions and 9 deletions

View File

@ -103,6 +103,17 @@ class AnchorDefinition(Statement):
self.name, self.x, self.y, self.contourpoint = name, x, y, contourpoint
class ChainContextPosStatement(Statement):
def __init__(self, location, prefix, glyphs, suffix, lookups):
Statement.__init__(self, location)
self.prefix, self.glyphs, self.suffix = prefix, glyphs, suffix
self.lookups = lookups
def build(self, builder):
builder.add_chain_context_pos(
self.location, self.prefix, self.glyphs, self.suffix, self.lookups)
class ChainContextSubstStatement(Statement):
def __init__(self, location, old_prefix, old, old_suffix, lookups):
Statement.__init__(self, location)

View File

@ -305,10 +305,13 @@ class Builder(object):
lookup_builders.append(None)
return lookup_builders
def add_chain_context_pos(self, location, prefix, glyphs, suffix, lookups):
pass # TODO: Implement.
def add_chain_context_subst(self, location,
old_prefix, old, old_suffix, lookups):
prefix, glyphs, suffix, lookups):
lookup = self.get_lookup_(location, ChainContextSubstBuilder)
lookup.substitutions.append((old_prefix, old, old_suffix,
lookup.substitutions.append((prefix, glyphs, suffix,
self.find_lookup_builders_(lookups)))
def add_alternate_subst(self, location, glyph, from_class):

View File

@ -177,7 +177,8 @@ class Parser(object):
def parse_glyph_pattern_(self):
prefix, glyphs, lookups, suffix = ([], [], [], [])
while self.next_token_ not in {"by", "from", ";"}:
while (self.next_token_ not in {"by", "from", ";", "<"} and
self.next_token_type_ != Lexer.NUMBER):
gc = self.parse_glyphclass_(accept_glyphname=True)
marked = False
if self.next_token_ == "'":
@ -293,26 +294,35 @@ class Parser(object):
return self.parse_position_mark_(enumerated, vertical)
location = self.cur_token_location_
prefix, glyphs, lookups, suffix = self.parse_glyph_pattern_()
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)
if not prefix and len(glyphs) == 2 and not suffix and not any(lookups):
# Pair positioning, format B: 'pos' glyphs gc2 value1
gc2 = glyphs[1]
glyphs = [glyphs[0]]
if prefix or len(glyphs) > 1 or suffix or any(lookups):
# GPOS type 8: Chaining contextual positioning
self.expect_symbol_(";")
return ast.ChainContextPosStatement(
location, prefix, glyphs, suffix, lookups)
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_(";")
if gc2 is None:
if enumerated:
raise FeatureLibError(
'"enumerate" is only allowed with pair positionings',
self.cur_token_location_)
return ast.SinglePosStatement(location, gc1, value1)
return ast.SinglePosStatement(location, glyphs[0], value1)
else:
return ast.PairPosStatement(location, enumerated,
gc1, value1, gc2, value2)
glyphs[0], value1, gc2, value2)
def parse_position_cursive_(self, enumerated, vertical):
location = self.cur_token_location_

View File

@ -540,6 +540,20 @@ class ParserTest(unittest.TestCase):
" enum pos mark hamza <anchor 221 301> mark @MARK_CLASS_1;"
"} test;")
def test_gpos_type_8(self):
doc = self.parse(
"lookup L1 {pos one 100;} L1; lookup L2 {pos two 200;} L2;"
"feature test {"
" pos [A a] [B b] I' lookup L1 [N n]' lookup L2 P' [Y y] [Z z];"
"} test;")
lookup1, lookup2 = doc.statements[0:2]
pos = doc.statements[-1].statements[0]
self.assertEqual(type(pos), ast.ChainContextPosStatement)
self.assertEqual(pos.prefix, [{"A", "a"}, {"B", "b"}])
self.assertEqual(pos.glyphs, [{"I"}, {"N", "n"}, {"P"}])
self.assertEqual(pos.suffix, [{"Y", "y"}, {"Z", "z"}])
self.assertEqual(pos.lookups, [lookup1, lookup2, None])
def test_markClass(self):
doc = self.parse("markClass [acute grave] <anchor 350 3> @MARKS;"
"feature test {"