[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)) (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): class SubstitutionRule(object):
def __init__(self, location, old, new): def __init__(self, location, old, new):
self.location, self.old, self.new = (location, old, new) self.location, self.old, self.new = (location, old, new)
self.ignored = False
self.old_prefix = [] self.old_prefix = []
self.old_suffix = [] self.old_suffix = []

View File

@ -105,25 +105,40 @@ class Parser(object):
self.expect_symbol_("]") self.expect_symbol_("]")
return result return result
def parse_substitute_(self): def parse_glyph_pattern_(self):
assert self.is_cur_keyword_("substitute") or self.is_cur_keyword("sub") prefix, glyphs, suffix = ([], [], [])
location = self.cur_token_location_ while self.next_token_ not in {"by", ";"}:
old_prefix, old, old_suffix = ([], [], [])
while self.next_token_ != "by":
gc = self.parse_glyphclass_(accept_glyphname=True) gc = self.parse_glyphclass_(accept_glyphname=True)
marked = False marked = False
if self.next_token_ == "'": if self.next_token_ == "'":
self.expect_symbol_("'") self.expect_symbol_("'")
marked = True
if marked: if marked:
old.append(gc) glyphs.append(gc)
elif old: elif glyphs:
old_suffix.append(gc) suffix.append(gc)
else: else:
old_prefix.append(gc) prefix.append(gc)
if not old and not old_suffix: # eg., "sub f f i by" if not glyphs and not suffix: # eg., "sub f f i by"
old = old_prefix return ([], prefix, [])
old_prefix = [] else:
assert self.expect_name_() == "by" 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) new = self.parse_glyphclass_(accept_glyphname=True)
self.expect_symbol_(";") self.expect_symbol_(";")
rule = ast.SubstitutionRule(location, old, [new]) rule = ast.SubstitutionRule(location, old, [new])
@ -193,6 +208,8 @@ class Parser(object):
self.advance_lexer_() self.advance_lexer_()
if self.cur_token_type_ is Lexer.GLYPHCLASS: if self.cur_token_type_ is Lexer.GLYPHCLASS:
statements.append(self.parse_glyphclass_definition_()) statements.append(self.parse_glyphclass_definition_())
elif self.is_cur_keyword_("ignore"):
statements.append(self.parse_ignore_())
elif (self.is_cur_keyword_("substitute") or elif (self.is_cur_keyword_("substitute") or
self.is_cur_keyword_("sub")): self.is_cur_keyword_("sub")):
statements.append(self.parse_substitute_()) statements.append(self.parse_substitute_())
@ -200,7 +217,8 @@ class Parser(object):
statements.append(self.parse_valuerecord_definition_(vertical)) statements.append(self.parse_valuerecord_definition_(vertical))
else: else:
raise ParserError( raise ParserError(
"Expected glyph class definition or valueRecordDef", "Expected glyph class definition, substitute, "
"or valueRecordDef",
self.cur_token_location_) self.cur_token_location_)
self.expect_symbol_("}") self.expect_symbol_("}")
@ -231,6 +249,13 @@ class Parser(object):
return symbol return symbol
raise ParserError("Expected '%s'" % symbol, self.cur_token_location_) 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): def expect_name_(self):
self.advance_lexer_() self.advance_lexer_()
if self.cur_token_type_ is Lexer.NAME: 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.lexer import LexerError
from fontTools.feaLib.parser import Parser, ParserError, SymbolTable from fontTools.feaLib.parser import Parser, ParserError, SymbolTable
from fontTools.misc.py23 import * from fontTools.misc.py23 import *
import fontTools.feaLib.ast as ast
import codecs import codecs
import os import os
import shutil import shutil
@ -109,6 +110,25 @@ class ParserTest(unittest.TestCase):
self.assertEqual(liga.statements[0].glyphs, {"a", "b", "l"}) self.assertEqual(liga.statements[0].glyphs, {"a", "b", "l"})
self.assertEqual(smcp.statements[0].glyphs, {"a", "b", "s"}) 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 def test_substitute_single_format_a(self): # GSUB LookupType 1
doc = self.parse("feature smcp {substitute a by a.sc;} smcp;") doc = self.parse("feature smcp {substitute a by a.sc;} smcp;")
sub = doc.statements[0].statements[0] sub = doc.statements[0].statements[0]
@ -147,6 +167,11 @@ class ParserTest(unittest.TestCase):
self.assertEqual(sub.old_suffix, []) self.assertEqual(sub.old_suffix, [])
self.assertEqual(sub.new, [{"f_f_i"}]) 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): def test_valuerecord_format_a_horizontal(self):
doc = self.parse("feature liga {valueRecordDef 123 foo;} liga;") doc = self.parse("feature liga {valueRecordDef 123 foo;} liga;")
value = doc.statements[0].statements[0].value value = doc.statements[0].statements[0].value