[feaLib] Parse ignore sub and ignore substitute statements

This commit is contained in:
Sascha Brawer 2015-08-05 10:41:04 +02:00
parent 482c498943
commit bc8279bab1
3 changed files with 70 additions and 15 deletions

View File

@ -50,10 +50,15 @@ class LanguageSystemStatement(object):
(self.script.strip(), self.language.strip(), linesep))
class IgnoreSubstitutionRule(object):
def __init__(self, location, prefix, glyphs, suffix):
self.location = location
self.prefix, self.glyphs, self.suffix = (prefix, glyphs, suffix)
class SubstitutionRule(object):
def __init__(self, location, old, new):
self.location, self.old, self.new = (location, old, new)
self.ignored = False
self.old_prefix = []
self.old_suffix = []

View File

@ -105,25 +105,40 @@ class Parser(object):
self.expect_symbol_("]")
return result
def parse_substitute_(self):
assert self.is_cur_keyword_("substitute") or self.is_cur_keyword("sub")
location = self.cur_token_location_
old_prefix, old, old_suffix = ([], [], [])
while self.next_token_ != "by":
def parse_glyph_pattern_(self):
prefix, glyphs, suffix = ([], [], [])
while self.next_token_ not in {"by", ";"}:
gc = self.parse_glyphclass_(accept_glyphname=True)
marked = False
if self.next_token_ == "'":
self.expect_symbol_("'")
marked = True
if marked:
old.append(gc)
elif old:
old_suffix.append(gc)
glyphs.append(gc)
elif glyphs:
suffix.append(gc)
else:
old_prefix.append(gc)
if not old and not old_suffix: # eg., "sub f f i by"
old = old_prefix
old_prefix = []
assert self.expect_name_() == "by"
prefix.append(gc)
if not glyphs and not suffix: # eg., "sub f f i by"
return ([], prefix, [])
else:
return (prefix, glyphs, suffix)
def parse_ignore_(self):
assert self.is_cur_keyword_("ignore")
location = self.cur_token_location_
self.advance_lexer_()
if self.cur_token_ in ["substitute", "sub"]:
prefix, glyphs, suffix = self.parse_glyph_pattern_()
self.expect_symbol_(";")
return ast.IgnoreSubstitutionRule(location, prefix, glyphs, suffix)
raise ParserError("Expected \"substitute\"", self.next_token_location_)
def parse_substitute_(self):
assert self.cur_token_ in {"substitute", "sub"}
location = self.cur_token_location_
old_prefix, old, old_suffix = self.parse_glyph_pattern_()
self.expect_keyword_("by")
new = self.parse_glyphclass_(accept_glyphname=True)
self.expect_symbol_(";")
rule = ast.SubstitutionRule(location, old, [new])
@ -193,6 +208,8 @@ class Parser(object):
self.advance_lexer_()
if self.cur_token_type_ is Lexer.GLYPHCLASS:
statements.append(self.parse_glyphclass_definition_())
elif self.is_cur_keyword_("ignore"):
statements.append(self.parse_ignore_())
elif (self.is_cur_keyword_("substitute") or
self.is_cur_keyword_("sub")):
statements.append(self.parse_substitute_())
@ -200,7 +217,8 @@ class Parser(object):
statements.append(self.parse_valuerecord_definition_(vertical))
else:
raise ParserError(
"Expected glyph class definition or valueRecordDef",
"Expected glyph class definition, substitute, "
"or valueRecordDef",
self.cur_token_location_)
self.expect_symbol_("}")
@ -231,6 +249,13 @@ class Parser(object):
return symbol
raise ParserError("Expected '%s'" % symbol, self.cur_token_location_)
def expect_keyword_(self, keyword):
self.advance_lexer_()
if self.cur_token_type_ is Lexer.NAME and self.cur_token_ == keyword:
return self.cur_token_
raise ParserError("Expected \"%s\"" % keyword,
self.cur_token_location_)
def expect_name_(self):
self.advance_lexer_()
if self.cur_token_type_ is Lexer.NAME:

View File

@ -3,6 +3,7 @@ from __future__ import unicode_literals
from fontTools.feaLib.lexer import LexerError
from fontTools.feaLib.parser import Parser, ParserError, SymbolTable
from fontTools.misc.py23 import *
import fontTools.feaLib.ast as ast
import codecs
import os
import shutil
@ -109,6 +110,25 @@ class ParserTest(unittest.TestCase):
self.assertEqual(liga.statements[0].glyphs, {"a", "b", "l"})
self.assertEqual(smcp.statements[0].glyphs, {"a", "b", "s"})
def test_ignore_sub(self):
doc = self.parse("feature test {ignore sub e t' c;} test;")
s = doc.statements[0].statements[0]
self.assertEqual(type(s), ast.IgnoreSubstitutionRule)
self.assertEqual(s.prefix, [{"e"}])
self.assertEqual(s.glyphs, [{"t"}])
self.assertEqual(s.suffix, [{"c"}])
def test_ignore_substitute(self):
doc = self.parse(
"feature test {"
" ignore substitute f [a e] d' [a u]' [e y];"
"} test;")
s = doc.statements[0].statements[0]
self.assertEqual(type(s), ast.IgnoreSubstitutionRule)
self.assertEqual(s.prefix, [{"f"}, {"a", "e"}])
self.assertEqual(s.glyphs, [{"d"}, {"a", "u"}])
self.assertEqual(s.suffix, [{"e", "y"}])
def test_substitute_single_format_a(self): # GSUB LookupType 1
doc = self.parse("feature smcp {substitute a by a.sc;} smcp;")
sub = doc.statements[0].statements[0]
@ -147,6 +167,11 @@ class ParserTest(unittest.TestCase):
self.assertEqual(sub.old_suffix, [])
self.assertEqual(sub.new, [{"f_f_i"}])
def test_substitute_missing_by(self):
self.assertRaisesRegex(
ParserError, "Expected \"by\"",
self.parse, "feature liga {substitute f f i;} liga;")
def test_valuerecord_format_a_horizontal(self):
doc = self.parse("feature liga {valueRecordDef 123 foo;} liga;")
value = doc.statements[0].statements[0].value