[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:
parent
4ae2604dca
commit
cb0adb98b9
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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_
|
||||
|
@ -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 {"
|
||||
|
Loading…
x
Reference in New Issue
Block a user