[feaLib.parser] make Parser accept glyphNames iterable...

... instead of a glyphMap dict.

The parser does not actually need a reverse glyph order mapping as
it is not interested in knowing the glyphID from the glyph name,
but only whether a glyph is in the font or not.

This makes it easier for client code (e.g. ufo2ft feature compiler)
to use the feaLib Parser, without having to first construct and pass
it a glyphMap argument.
This commit is contained in:
Cosimo Lupo 2017-11-16 13:46:27 +00:00
parent a35707560c
commit e8535f2280
3 changed files with 18 additions and 22 deletions

View File

@ -17,8 +17,8 @@ class Parser(object):
extensions = {}
ast = ast
def __init__(self, featurefile, glyphMap):
self.glyphMap_ = glyphMap
def __init__(self, featurefile, glyphNames):
self.glyphNames_ = set(glyphNames)
self.doc_ = self.ast.FeatureFile()
self.anchors_ = SymbolTable()
self.glyphclasses_ = SymbolTable()
@ -220,7 +220,7 @@ class Parser(object):
solutions = []
for i in range(len(parts)):
start, limit = "-".join(parts[0:i]), "-".join(parts[i:])
if start in self.glyphMap_ and limit in self.glyphMap_:
if start in self.glyphNames_ and limit in self.glyphNames_:
solutions.append((start, limit))
if len(solutions) == 1:
start, limit = solutions[0]
@ -260,7 +260,7 @@ class Parser(object):
if self.next_token_type_ is Lexer.NAME:
glyph = self.expect_glyph_()
location = self.cur_token_location_
if '-' in glyph and glyph not in self.glyphMap_:
if '-' in glyph and glyph not in self.glyphNames_:
start, limit = self.split_glyph_range_(glyph, location)
glyphs.add_range(
start, limit,

View File

@ -138,7 +138,7 @@ class BuilderTest(unittest.TestCase):
def check_fea2fea_file(self, name, base=None, parser=Parser):
font = makeTTFont()
fname = (name + ".fea") if '.' not in name else name
p = parser(self.getpath(fname), glyphMap=font.getReverseGlyphMap())
p = parser(self.getpath(fname), glyphNames=font.getGlyphOrder())
doc = p.parse()
actual = self.normal_fea(doc.asFea().split("\n"))
with open(self.getpath(base or fname), "r", encoding="utf-8") as ofile:

View File

@ -30,11 +30,7 @@ def mapping(s):
return dict(zip(b, c))
def makeGlyphMap(glyphs):
return {g: i for i, g in enumerate(glyphs)}
GLYPHMAP = makeGlyphMap(("""
GLYPHNAMES = ("""
.notdef space A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
A.sc B.sc C.sc D.sc E.sc F.sc G.sc H.sc I.sc J.sc K.sc L.sc M.sc
N.sc O.sc P.sc Q.sc R.sc S.sc T.sc U.sc V.sc W.sc X.sc Y.sc Z.sc
@ -44,7 +40,7 @@ GLYPHMAP = makeGlyphMap(("""
n.sc o.sc p.sc q.sc r.sc s.sc t.sc u.sc v.sc w.sc x.sc y.sc z.sc
a.swash b.swash x.swash y.swash z.swash
foobar foo.09 foo.1234 foo.9876
""").split() + ["foo.%d" % i for i in range(1, 200)])
""").split() + ["foo.%d" % i for i in range(1, 200)]
class ParserTest(unittest.TestCase):
@ -297,31 +293,31 @@ class ParserTest(unittest.TestCase):
self.assertEqual(gc.glyphSet(), ("d.sc", "e.sc", "f.sc", "g.sc"))
def test_glyphclass_range_dash(self):
glyphMap = makeGlyphMap("A-foo.sc B-foo.sc C-foo.sc".split())
[gc] = self.parse("@range = [A-foo.sc-C-foo.sc];", glyphMap).statements
glyphNames = "A-foo.sc B-foo.sc C-foo.sc".split()
[gc] = self.parse("@range = [A-foo.sc-C-foo.sc];", glyphNames).statements
self.assertEqual(gc.glyphSet(), ("A-foo.sc", "B-foo.sc", "C-foo.sc"))
def test_glyphclass_range_dash_with_space(self):
g = makeGlyphMap("A-foo.sc B-foo.sc C-foo.sc".split())
[gc] = self.parse("@range = [A-foo.sc - C-foo.sc];", g).statements
gn = "A-foo.sc B-foo.sc C-foo.sc".split()
[gc] = self.parse("@range = [A-foo.sc - C-foo.sc];", gn).statements
self.assertEqual(gc.glyphSet(), ("A-foo.sc", "B-foo.sc", "C-foo.sc"))
def test_glyphclass_glyph_name_should_win_over_range(self):
# The OpenType Feature File Specification v1.20 makes it clear
# that if a dashed name could be interpreted either as a glyph name
# or as a range, then the semantics should be the single dashed name.
glyphMap = makeGlyphMap(
glyphNames = (
"A-foo.sc-C-foo.sc A-foo.sc B-foo.sc C-foo.sc".split())
[gc] = self.parse("@range = [A-foo.sc-C-foo.sc];", glyphMap).statements
[gc] = self.parse("@range = [A-foo.sc-C-foo.sc];", glyphNames).statements
self.assertEqual(gc.glyphSet(), ("A-foo.sc-C-foo.sc",))
def test_glyphclass_range_dash_ambiguous(self):
glyphMap = makeGlyphMap("A B C A-B B-C".split())
glyphNames = "A B C A-B B-C".split()
self.assertRaisesRegex(
FeatureLibError,
'Ambiguous glyph range "A-B-C"; '
'please use "A - B-C" or "A-B - C" to clarify what you mean',
self.parse, r"@bad = [A-B-C];", glyphMap)
self.parse, r"@bad = [A-B-C];", glyphNames)
def test_glyphclass_range_digit1(self):
[gc] = self.parse("@range = [foo.2-foo.5];").statements
@ -1268,7 +1264,7 @@ class ParserTest(unittest.TestCase):
self.assertEqual(glyphstr(sub.suffix), "Z")
def test_substitute_lookups(self): # GSUB LookupType 6
doc = Parser(self.getpath("spec5fi1.fea"), GLYPHMAP).parse()
doc = Parser(self.getpath("spec5fi1.fea"), GLYPHNAMES).parse()
[_, _, _, langsys, ligs, sub, feature] = doc.statements
self.assertEqual(feature.statements[0].lookups, [ligs, None, sub])
self.assertEqual(feature.statements[1].lookups, [ligs, None, sub])
@ -1483,9 +1479,9 @@ class ParserTest(unittest.TestCase):
doc = self.parse("table %s { ;;; } %s;" % (table, table))
self.assertEqual(doc.statements[0].statements, [])
def parse(self, text, glyphMap=GLYPHMAP):
def parse(self, text, glyphNames=GLYPHNAMES):
featurefile = UnicodeIO(text)
p = Parser(featurefile, glyphMap)
p = Parser(featurefile, glyphNames)
return p.parse()
@staticmethod