[feaLib] glyph class as list and tuple instead of set and frozenset

This commit is contained in:
moyogo 2016-04-21 10:46:52 +01:00
parent cdda278bd4
commit 17c8e582d0
5 changed files with 62 additions and 58 deletions

View File

@ -34,7 +34,7 @@ class GlyphName(Expression):
self.glyph = glyph
def glyphSet(self):
return frozenset((self.glyph,))
return (self.glyph,)
class GlyphClass(Expression):
@ -44,7 +44,7 @@ class GlyphClass(Expression):
self.glyphs = glyphs
def glyphSet(self):
return frozenset(self.glyphs)
return tuple(self.glyphs)
class GlyphClassName(Expression):
@ -55,7 +55,7 @@ class GlyphClassName(Expression):
self.glyphclass = glyphclass
def glyphSet(self):
return frozenset(self.glyphclass.glyphs)
return tuple(self.glyphclass.glyphs)
class MarkClassName(Expression):
@ -123,7 +123,7 @@ class GlyphClassDefinition(Statement):
self.glyphs = glyphs
def glyphSet(self):
return frozenset(self.glyphs)
return tuple(self.glyphs)
class GlyphClassDefStatement(Statement):
@ -136,11 +136,12 @@ class GlyphClassDefStatement(Statement):
self.componentGlyphs = componentGlyphs
def build(self, builder):
base = self.baseGlyphs.glyphSet() if self.baseGlyphs else set()
liga = self.ligatureGlyphs.glyphSet() if self.ligatureGlyphs else set()
mark = self.markGlyphs.glyphSet() if self.markGlyphs else set()
base = self.baseGlyphs.glyphSet() if self.baseGlyphs else tuple()
liga = self.ligatureGlyphs.glyphSet() \
if self.ligatureGlyphs else tuple()
mark = self.markGlyphs.glyphSet() if self.markGlyphs else tuple()
comp = (self.componentGlyphs.glyphSet()
if self.componentGlyphs else set())
if self.componentGlyphs else tuple())
builder.add_glyphClassDef(self.location, base, liga, mark, comp)
@ -169,7 +170,7 @@ class MarkClass(object):
self.glyphs[glyph] = definition
def glyphSet(self):
return frozenset(self.glyphs.keys())
return tuple(self.glyphs.keys())
class MarkClassDefinition(Statement):

View File

@ -901,7 +901,7 @@ class Builder(object):
if value is None:
subs.append(None)
continue
if not glyphs.isdisjoint(sub.mapping.keys()):
if not set(glyphs).isdisjoint(sub.mapping.keys()):
sub = self.get_chained_lookup_(location, SinglePosBuilder)
for glyph in glyphs:
sub.add_pos(location, glyph, value)

View File

@ -58,7 +58,7 @@ class BuilderTest(unittest.TestCase):
spec5h1 spec6b_ii spec6d2 spec6e spec6f
spec6h_ii spec6h_iii_1 spec6h_iii_3d spec8a spec8b spec8c
spec9a spec9b spec9c1 spec9c2 spec9c3 spec9d spec9e spec9f
bug453 bug463 bug501 bug502 bug505 bug506 bug509 bug512 bug568
bug453 bug463 bug501 bug502 bug504 bug505 bug506 bug509 bug512 bug568
name size size2
""".split()

View File

@ -3,6 +3,7 @@ from __future__ import unicode_literals
from fontTools.feaLib.error import FeatureLibError
from fontTools.feaLib.lexer import Lexer, IncludingLexer
from fontTools.misc.py23 import *
from collections import OrderedDict
import fontTools.feaLib.ast as ast
import logging
import os
@ -190,7 +191,7 @@ class Parser(object):
return ast.GlyphClassName(self.cur_token_location_, gc)
self.expect_symbol_("[")
glyphs = set()
glyphs = list()
location = self.cur_token_location_
while self.next_token_ != "]":
if self.next_token_type_ is Lexer.NAME:
@ -200,11 +201,11 @@ class Parser(object):
range_start = glyph
self.expect_symbol_("-")
range_end = self.expect_glyph_()
glyphs.update(self.make_glyph_range_(range_location,
glyphs.extend(self.make_glyph_range_(range_location,
range_start,
range_end))
else:
glyphs.add(glyph)
glyphs.append(glyph)
elif self.next_token_type_ is Lexer.CID:
glyph = self.expect_glyph_()
if self.next_token_ == "-":
@ -212,10 +213,10 @@ class Parser(object):
range_start = self.cur_token_
self.expect_symbol_("-")
range_end = self.expect_cid_()
glyphs.update(self.make_cid_range_(range_location,
glyphs.extend(self.make_cid_range_(range_location,
range_start, range_end))
else:
glyphs.add("cid%05d" % self.cur_token_)
glyphs.append("cid%05d" % self.cur_token_)
elif self.next_token_type_ is Lexer.GLYPHCLASS:
self.advance_lexer_()
gc = self.glyphclasses_.resolve(self.cur_token_)
@ -223,7 +224,7 @@ class Parser(object):
raise FeatureLibError(
"Unknown glyph class @%s" % self.cur_token_,
self.cur_token_location_)
glyphs.update(gc.glyphSet())
glyphs.extend(gc.glyphSet())
else:
raise FeatureLibError(
"Expected glyph name, glyph range, "
@ -592,8 +593,8 @@ class Parser(object):
# Format C: "substitute [a-d] by [A.sc-D.sc];"
if (not reverse and len(old) == 1 and len(new) == 1 and
num_lookups == 0):
glyphs = sorted(list(old[0].glyphSet()))
replacements = sorted(list(new[0].glyphSet()))
glyphs = list(old[0].glyphSet())
replacements = list(new[0].glyphSet())
if len(replacements) == 1:
replacements = replacements * len(glyphs)
if len(glyphs) != len(replacements):
@ -601,10 +602,12 @@ class Parser(object):
'Expected a glyph class with %d elements after "by", '
'but found a glyph class with %d elements' %
(len(glyphs), len(replacements)), location)
return ast.SingleSubstStatement(location,
dict(zip(glyphs, replacements)),
old_prefix, old_suffix,
forceChain=hasMarks)
return ast.SingleSubstStatement(
location,
OrderedDict(zip(glyphs, replacements)),
old_prefix, old_suffix,
forceChain=hasMarks
)
# GSUB lookup type 2: Multiple substitution.
# Format: "substitute f_f_i by f f i;"
@ -1257,18 +1260,18 @@ class Parser(object):
return ''.join(reversed(list(s)))
def make_cid_range_(self, location, start, limit):
"""(location, 999, 1001) --> {"cid00999", "cid01000", "cid01001"}"""
result = set()
"""(location, 999, 1001) --> ["cid00999", "cid01000", "cid01001"]"""
result = list()
if start > limit:
raise FeatureLibError(
"Bad range: start should be less than limit", location)
for cid in range(start, limit + 1):
result.add("cid%05d" % cid)
result.append("cid%05d" % cid)
return result
def make_glyph_range_(self, location, start, limit):
"""(location, "a.sc", "d.sc") --> {"a.sc", "b.sc", "c.sc", "d.sc"}"""
result = set()
"""(location, "a.sc", "d.sc") --> ["a.sc", "b.sc", "c.sc", "d.sc"]"""
result = list()
if len(start) != len(limit):
raise FeatureLibError(
"Bad range: \"%s\" and \"%s\" should have the same length" %
@ -1292,20 +1295,20 @@ class Parser(object):
uppercase = re.compile(r'^[A-Z]$')
if uppercase.match(start_range) and uppercase.match(limit_range):
for c in range(ord(start_range), ord(limit_range) + 1):
result.add("%s%c%s" % (prefix, c, suffix))
result.append("%s%c%s" % (prefix, c, suffix))
return result
lowercase = re.compile(r'^[a-z]$')
if lowercase.match(start_range) and lowercase.match(limit_range):
for c in range(ord(start_range), ord(limit_range) + 1):
result.add("%s%c%s" % (prefix, c, suffix))
result.append("%s%c%s" % (prefix, c, suffix))
return result
digits = re.compile(r'^[0-9]{1,3}$')
if digits.match(start_range) and digits.match(limit_range):
for i in range(int(start_range, 10), int(limit_range, 10) + 1):
number = ("000" + str(i))[-len(start_range):]
result.add("%s%s%s" % (prefix, number, suffix))
result.append("%s%s%s" % (prefix, number, suffix))
return result
raise FeatureLibError("Bad range: \"%s-%s\"" % (start, limit),

View File

@ -151,7 +151,7 @@ class ParserTest(unittest.TestCase):
def test_glyphclass(self):
[gc] = self.parse("@dash = [endash emdash figuredash];").statements
self.assertEqual(gc.name, "dash")
self.assertEqual(gc.glyphs, {"endash", "emdash", "figuredash"})
self.assertEqual(gc.glyphs, ("endash", "emdash", "figuredash"))
def test_glyphclass_glyphNameTooLong(self):
self.assertRaisesRegex(
@ -173,12 +173,12 @@ class ParserTest(unittest.TestCase):
def test_glyphclass_empty(self):
[gc] = self.parse("@empty_set = [];").statements
self.assertEqual(gc.name, "empty_set")
self.assertEqual(gc.glyphs, set())
self.assertEqual(gc.glyphs, tuple())
def test_glyphclass_equality(self):
[foo, bar] = self.parse("@foo = [a b]; @bar = @foo;").statements
self.assertEqual(foo.glyphs, {"a", "b"})
self.assertEqual(bar.glyphs, {"a", "b"})
self.assertEqual(foo.glyphs, ("a", "b"))
self.assertEqual(bar.glyphs, ("a", "b"))
def test_glyphclass_from_markClass(self):
doc = self.parse(
@ -187,12 +187,12 @@ class ParserTest(unittest.TestCase):
"@MARKS = [@TOP_MARKS @BOTTOM_MARKS ogonek];"
"@ALL = @MARKS;")
self.assertEqual(doc.statements[-1].glyphSet(),
{"acute", "cedilla", "grave", "ogonek"})
("acute", "grave", "cedilla", "ogonek"))
def test_glyphclass_range_cid(self):
[gc] = self.parse(r"@GlyphClass = [\999-\1001];").statements
self.assertEqual(gc.name, "GlyphClass")
self.assertEqual(gc.glyphs, {"cid00999", "cid01000", "cid01001"})
self.assertEqual(gc.glyphs, ("cid00999", "cid01000", "cid01001"))
def test_glyphclass_range_cid_bad(self):
self.assertRaisesRegex(
@ -203,24 +203,24 @@ class ParserTest(unittest.TestCase):
def test_glyphclass_range_uppercase(self):
[gc] = self.parse("@swashes = [X.swash-Z.swash];").statements
self.assertEqual(gc.name, "swashes")
self.assertEqual(gc.glyphs, {"X.swash", "Y.swash", "Z.swash"})
self.assertEqual(gc.glyphs, ("X.swash", "Y.swash", "Z.swash"))
def test_glyphclass_range_lowercase(self):
[gc] = self.parse("@defg.sc = [d.sc-g.sc];").statements
self.assertEqual(gc.name, "defg.sc")
self.assertEqual(gc.glyphs, {"d.sc", "e.sc", "f.sc", "g.sc"})
self.assertEqual(gc.glyphs, ("d.sc", "e.sc", "f.sc", "g.sc"))
def test_glyphclass_range_digit1(self):
[gc] = self.parse("@range = [foo.2-foo.5];").statements
self.assertEqual(gc.glyphs, {"foo.2", "foo.3", "foo.4", "foo.5"})
self.assertEqual(gc.glyphs, ("foo.2", "foo.3", "foo.4", "foo.5"))
def test_glyphclass_range_digit2(self):
[gc] = self.parse("@range = [foo.09-foo.11];").statements
self.assertEqual(gc.glyphs, {"foo.09", "foo.10", "foo.11"})
self.assertEqual(gc.glyphs, ("foo.09", "foo.10", "foo.11"))
def test_glyphclass_range_digit3(self):
[gc] = self.parse("@range = [foo.123-foo.125];").statements
self.assertEqual(gc.glyphs, {"foo.123", "foo.124", "foo.125"})
self.assertEqual(gc.glyphs, ("foo.123", "foo.124", "foo.125"))
def test_glyphclass_range_bad(self):
self.assertRaisesRegex(
@ -239,17 +239,17 @@ class ParserTest(unittest.TestCase):
def test_glyphclass_range_mixed(self):
[gc] = self.parse("@range = [a foo.09-foo.11 X.sc-Z.sc];").statements
self.assertEqual(gc.glyphs, {
self.assertEqual(gc.glyphs, (
"a", "foo.09", "foo.10", "foo.11", "X.sc", "Y.sc", "Z.sc"
})
))
def test_glyphclass_reference(self):
[vowels_lc, vowels_uc, vowels] = self.parse(
"@Vowels.lc = [a e i o u]; @Vowels.uc = [A E I O U];"
"@Vowels = [@Vowels.lc @Vowels.uc y Y];").statements
self.assertEqual(vowels_lc.glyphs, set(list("aeiou")))
self.assertEqual(vowels_uc.glyphs, set(list("AEIOU")))
self.assertEqual(vowels.glyphs, set(list("aeiouyAEIOUY")))
self.assertEqual(vowels_lc.glyphs, tuple("aeiou"))
self.assertEqual(vowels_uc.glyphs, tuple("AEIOU"))
self.assertEqual(vowels.glyphs, tuple("aeiouAEIOUyY"))
self.assertRaisesRegex(
FeatureLibError, "Unknown glyph class @unknown",
self.parse, "@bad = [@unknown];")
@ -260,9 +260,9 @@ class ParserTest(unittest.TestCase):
"feature liga { @bar = [@foo l]; } liga;"
"feature smcp { @bar = [@foo s]; } smcp;"
).statements
self.assertEqual(foo.glyphs, {"a", "b"})
self.assertEqual(liga.statements[0].glyphs, {"a", "b", "l"})
self.assertEqual(smcp.statements[0].glyphs, {"a", "b", "s"})
self.assertEqual(foo.glyphs, ("a", "b"))
self.assertEqual(liga.statements[0].glyphs, ("a", "b", "l"))
self.assertEqual(smcp.statements[0].glyphs, ("a", "b", "s"))
def test_glyphclass_scoping_bug496(self):
# https://github.com/behdad/fonttools/issues/496
@ -270,7 +270,7 @@ class ParserTest(unittest.TestCase):
"feature F1 { lookup L { @GLYPHCLASS = [A B C];} L; } F1;"
"feature F2 { sub @GLYPHCLASS by D; } F2;"
).statements
self.assertEqual(set(f2.statements[0].mapping.keys()), {"A", "B", "C"})
self.assertEqual(f2.statements[0].mapping.keys(), ["A", "B", "C"])
def test_GlyphClassDef(self):
doc = self.parse("table GDEF {GlyphClassDef [b],[l],[m],[C c];} GDEF;")
@ -508,7 +508,7 @@ class ParserTest(unittest.TestCase):
self.assertEqual(flag.value, 1)
self.assertIsInstance(flag.markAttachment, ast.GlyphClassName)
self.assertEqual(flag.markAttachment.glyphSet(),
{"acute", "grave", "macron"})
("acute", "grave", "macron"))
self.assertIsNone(flag.markFilteringSet)
def test_lookupflag_format_A_UseMarkFilteringSet(self):
@ -520,7 +520,7 @@ class ParserTest(unittest.TestCase):
self.assertIsNone(flag.markAttachment)
self.assertIsInstance(flag.markFilteringSet, ast.GlyphClassName)
self.assertEqual(flag.markFilteringSet.glyphSet(),
{"cedilla", "ogonek"})
("cedilla", "ogonek"))
def test_lookupflag_format_B(self):
flag = self.parse_lookupflag_("lookupflag 7;")
@ -671,7 +671,7 @@ class ParserTest(unittest.TestCase):
"} kern;")
pos = doc.statements[0].statements[0]
self.assertEqual(type(pos), ast.CursivePosStatement)
self.assertEqual(pos.glyphclass, {"A"})
self.assertEqual(pos.glyphclass, ("A",))
self.assertEqual((pos.entryAnchor.x, pos.entryAnchor.y), (12, -2))
self.assertEqual((pos.exitAnchor.x, pos.exitAnchor.y), (2, 3))
@ -696,7 +696,7 @@ class ParserTest(unittest.TestCase):
"} test;")
pos = doc.statements[-1].statements[0]
self.assertEqual(type(pos), ast.MarkBasePosStatement)
self.assertEqual(pos.base, {"a", "e", "o", "u"})
self.assertEqual(pos.base, ("a", "e", "o", "u"))
(a1, m1), (a2, m2) = pos.marks
self.assertEqual((a1.x, a1.y, m1.name), (250, 450, "TOP_MARKS"))
self.assertEqual((a2.x, a2.y, m2.name), (210, -10, "BOTTOM_MARKS"))
@ -738,7 +738,7 @@ class ParserTest(unittest.TestCase):
"} test;")
pos = doc.statements[-1].statements[0]
self.assertEqual(type(pos), ast.MarkLigPosStatement)
self.assertEqual(pos.ligatures, {"a_f_f_i", "o_f_f_i"})
self.assertEqual(pos.ligatures, ("a_f_f_i", "o_f_f_i"))
[(a11, m11), (a12, m12)], [(a2, m2)], [], [(a4, m4)] = pos.marks
self.assertEqual((a11.x, a11.y, m11.name), (50, 600, "TOP_MARKS"))
self.assertEqual((a12.x, a12.y, m12.name), (50, -10, "BOTTOM_MARKS"))
@ -774,7 +774,7 @@ class ParserTest(unittest.TestCase):
"} test;")
pos = doc.statements[-1].statements[0]
self.assertEqual(type(pos), ast.MarkMarkPosStatement)
self.assertEqual(pos.baseMarks, {"hamza"})
self.assertEqual(pos.baseMarks, ("hamza",))
[(a1, m1)] = pos.marks
self.assertEqual((a1.x, a1.y, m1.name), (221, 301, "MARK_CLASS_1"))
@ -826,7 +826,7 @@ class ParserTest(unittest.TestCase):
mc = doc.statements[0]
self.assertIsInstance(mc, ast.MarkClassDefinition)
self.assertEqual(mc.markClass.name, "MARKS")
self.assertEqual(mc.glyphSet(), {"acute", "grave"})
self.assertEqual(mc.glyphSet(), ("acute", "grave"))
self.assertEqual((mc.anchor.x, mc.anchor.y), (350, 3))
def test_rsub_format_a(self):