From 5696b50fac191f076b3c50ea804f2b9fa9b84579 Mon Sep 17 00:00:00 2001 From: Sascha Brawer Date: Fri, 8 Jan 2016 19:06:52 +0100 Subject: [PATCH] [feaLib] Implement the GlyphClassDef statement --- Lib/fontTools/feaLib/ast.py | 10 +++--- Lib/fontTools/feaLib/builder.py | 32 +++++++++++++++---- Lib/fontTools/feaLib/builder_test.py | 11 ++++++- Lib/fontTools/feaLib/parser.py | 12 +++---- Lib/fontTools/feaLib/parser_test.py | 6 ++-- .../feaLib/testdata/GlyphClassDef.fea | 5 +++ .../feaLib/testdata/GlyphClassDef.ttx | 22 +++++++++++++ 7 files changed, 77 insertions(+), 21 deletions(-) create mode 100644 Lib/fontTools/feaLib/testdata/GlyphClassDef.fea create mode 100644 Lib/fontTools/feaLib/testdata/GlyphClassDef.ttx diff --git a/Lib/fontTools/feaLib/ast.py b/Lib/fontTools/feaLib/ast.py index d59f086ec..511055dd2 100644 --- a/Lib/fontTools/feaLib/ast.py +++ b/Lib/fontTools/feaLib/ast.py @@ -136,12 +136,12 @@ class GlyphClassDefStatement(Statement): self.componentGlyphs = componentGlyphs def build(self, builder): - base = self.baseGlyphs.glyphSet() if self.baseGlyphs else None - mark = self.markGlyphs.glyphSet() if self.markGlyphs else None - liga = self.ligatureGlyphs.glyphSet() if self.ligatureGlyphs else None + 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() comp = (self.componentGlyphs.glyphSet() - if self.componentGlyphs else None) - builder.add_glyphClassDef(self.location, base, mark, liga, comp) + if self.componentGlyphs else set()) + builder.add_glyphClassDef(self.location, base, liga, mark, comp) # While glyph classes can be defined only once, the feature file format diff --git a/Lib/fontTools/feaLib/builder.py b/Lib/fontTools/feaLib/builder.py index 0ba39e13d..a70238a4e 100644 --- a/Lib/fontTools/feaLib/builder.py +++ b/Lib/fontTools/feaLib/builder.py @@ -33,6 +33,7 @@ class Builder(object): self.ligatureCaretByPos_ = {} # "f_f_i" --> {300, 600} self.parseTree = None self.required_features_ = {} # ('latn', 'DEU ') --> 'scmp' + self.glyphClassDefs_ = {} # "fi" --> (2, (file, line, column)) self.markAttach_ = {} # "acute" --> (4, (file, line, column)) self.markAttachClassID_ = {} # frozenset({"acute", "grave"}) --> 4 self.markFilterSets_ = {} # frozenset({"acute", "grave"}) --> 4 @@ -141,9 +142,13 @@ class Builder(object): (glyph, name1, name2), markClassDef.location) marks[glyph] = markClass inferredGlyphClass[glyph] = 3 - if inferredGlyphClass: + if self.glyphClassDefs_: + classes = {g: c for (g, (c, _)) in self.glyphClassDefs_.items()} + else: + classes = inferredGlyphClass + if classes: result = otTables.GlyphClassDef() - result.classDefs = inferredGlyphClass + result.classDefs = classes return result else: return None @@ -600,10 +605,25 @@ class Builder(object): location) lookup.mapping[glyph] = valuerecord - def add_glyphClassDef(self, location, baseGlyphs, markGlyphs, - ligatureGlyphs, componentGlyphs): - # TODO: Not yet implemented. - pass + def setGlyphClass_(self, location, glyph, glyphClass): + oldClass, oldLocation = self.glyphClassDefs_.get(glyph, (None, None)) + if oldClass and oldClass != glyphClass: + raise FeatureLibError( + "Glyph %s was assigned to a different class at %s:%s:%s" % + (glyph, oldLocation[0], oldLocation[1], oldLocation[2]), + location) + self.glyphClassDefs_[glyph] = (glyphClass, location) + + def add_glyphClassDef(self, location, baseGlyphs, ligatureGlyphs, + markGlyphs, componentGlyphs): + for glyph in baseGlyphs: + self.setGlyphClass_(location, glyph, 1) + for glyph in ligatureGlyphs: + self.setGlyphClass_(location, glyph, 2) + for glyph in markGlyphs: + self.setGlyphClass_(location, glyph, 3) + for glyph in componentGlyphs: + self.setGlyphClass_(location, glyph, 4) def add_ligatureCaretByIndex_(self, location, glyphs, carets): for glyph in glyphs: diff --git a/Lib/fontTools/feaLib/builder_test.py b/Lib/fontTools/feaLib/builder_test.py index 375d8d3b8..32161df66 100644 --- a/Lib/fontTools/feaLib/builder_test.py +++ b/Lib/fontTools/feaLib/builder_test.py @@ -147,7 +147,7 @@ class BuilderTest(unittest.TestCase): def test_constructs(self): for name in ("Attach enum markClass language_required " - "LigatureCaretByIndex LigatureCaretByPos " + "GlyphClassDef LigatureCaretByIndex LigatureCaretByPos " "lookup lookupflag").split(): font = makeTTFont() addOpenTypeFeatures(self.getpath("%s.fea" % name), font) @@ -172,6 +172,15 @@ class BuilderTest(unittest.TestCase): addOpenTypeFeatures(self.getpath("spec%s.fea" % name), font) self.expect_ttx(font, self.getpath("spec%s.ttx" % name)) + def test_GlyphClassDef_conflictingClasses(self): + self.assertRaisesRegex( + FeatureLibError, "Glyph X was assigned to a different class", + self.build, + "table GDEF {" + " GlyphClassDef [a b], [X], , ;" + " GlyphClassDef [a b X], , , ;" + "} GDEF;") + def test_languagesystem(self): builder = Builder(None, makeTTFont()) builder.add_language_system(None, 'latn', 'FRA') diff --git a/Lib/fontTools/feaLib/parser.py b/Lib/fontTools/feaLib/parser.py index e6ef7f241..cc704fcf8 100644 --- a/Lib/fontTools/feaLib/parser.py +++ b/Lib/fontTools/feaLib/parser.py @@ -132,7 +132,7 @@ class Parser(object): return self.parse_position_(enumerated=True, vertical=vertical) def parse_GlyphClassDef_(self): - """Parses 'GlyphClassDef @BASE, @MARKS, @LIGATURES, @COMPONENTS;'""" + """Parses 'GlyphClassDef @BASE, @LIGATURES, @MARKS, @COMPONENTS;'""" assert self.is_cur_keyword_("GlyphClassDef") location = self.cur_token_location_ if self.next_token_ != ",": @@ -140,16 +140,16 @@ class Parser(object): else: baseGlyphs = None self.expect_symbol_(",") - if self.next_token_ != ",": - markGlyphs = self.parse_glyphclass_(accept_glyphname=False) - else: - markGlyphs = None - self.expect_symbol_(",") if self.next_token_ != ",": ligatureGlyphs = self.parse_glyphclass_(accept_glyphname=False) else: ligatureGlyphs = None self.expect_symbol_(",") + if self.next_token_ != ",": + markGlyphs = self.parse_glyphclass_(accept_glyphname=False) + else: + markGlyphs = None + self.expect_symbol_(",") if self.next_token_ != ";": componentGlyphs = self.parse_glyphclass_(accept_glyphname=False) else: diff --git a/Lib/fontTools/feaLib/parser_test.py b/Lib/fontTools/feaLib/parser_test.py index 7608622ee..7cce5a7be 100644 --- a/Lib/fontTools/feaLib/parser_test.py +++ b/Lib/fontTools/feaLib/parser_test.py @@ -232,20 +232,20 @@ class ParserTest(unittest.TestCase): self.assertEqual(smcp.statements[0].glyphs, {"a", "b", "s"}) def test_GlyphClassDef(self): - doc = self.parse("table GDEF {GlyphClassDef [b],[m],[l],[C c];} GDEF;") + doc = self.parse("table GDEF {GlyphClassDef [b],[l],[m],[C c];} GDEF;") s = doc.statements[0].statements[0] self.assertIsInstance(s, ast.GlyphClassDefStatement) self.assertEqual(glyphstr([s.baseGlyphs]), "b") - self.assertEqual(glyphstr([s.markGlyphs]), "m") self.assertEqual(glyphstr([s.ligatureGlyphs]), "l") + self.assertEqual(glyphstr([s.markGlyphs]), "m") self.assertEqual(glyphstr([s.componentGlyphs]), "[C c]") def test_GlyphClassDef_noCLassesSpecified(self): doc = self.parse("table GDEF {GlyphClassDef ,,,;} GDEF;") s = doc.statements[0].statements[0] self.assertIsNone(s.baseGlyphs) - self.assertIsNone(s.markGlyphs) self.assertIsNone(s.ligatureGlyphs) + self.assertIsNone(s.markGlyphs) self.assertIsNone(s.componentGlyphs) def test_ignore_sub(self): diff --git a/Lib/fontTools/feaLib/testdata/GlyphClassDef.fea b/Lib/fontTools/feaLib/testdata/GlyphClassDef.fea new file mode 100644 index 000000000..8e5353647 --- /dev/null +++ b/Lib/fontTools/feaLib/testdata/GlyphClassDef.fea @@ -0,0 +1,5 @@ +table GDEF { + GlyphClassDef [a], [b], [c], [d]; + GlyphClassDef [e], [f], [g], [h]; + GlyphClassDef [i], [j], [k], [l]; +} GDEF; diff --git a/Lib/fontTools/feaLib/testdata/GlyphClassDef.ttx b/Lib/fontTools/feaLib/testdata/GlyphClassDef.ttx new file mode 100644 index 000000000..0e22fe7d0 --- /dev/null +++ b/Lib/fontTools/feaLib/testdata/GlyphClassDef.ttx @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + +