From 26c02e4d958d28f36686995b59e240dba34972a9 Mon Sep 17 00:00:00 2001 From: Sascha Brawer Date: Tue, 22 Dec 2015 14:42:13 +0100 Subject: [PATCH] [feaLib] Add helper for creating glyph class definition tables The helper will be used for building class-based kerning tables. --- Lib/fontTools/feaLib/builder.py | 40 ++++++++++++++++++++++++++++ Lib/fontTools/feaLib/builder_test.py | 36 ++++++++++++++++++++++--- 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/Lib/fontTools/feaLib/builder.py b/Lib/fontTools/feaLib/builder.py index c9b7ea621..12d4ac499 100644 --- a/Lib/fontTools/feaLib/builder.py +++ b/Lib/fontTools/feaLib/builder.py @@ -1121,3 +1121,43 @@ class SinglePosBuilder(LookupBuilder): st.Value, st.ValueFormat = value, valueFormat return self.buildLookup_(subtables) + + +class ClassDefBuilder(object): + """Helper for building ClassDef tables.""" + def __init__(self, otClass): + self.classes = set() + self.glyphs = {} + self.otClass = otClass + + def canAdd(self, glyphs): + glyphs = frozenset(glyphs) + if glyphs in self.classes: + return True + for glyph in glyphs: + if glyph in self.glyphs: + return False + return True + + def add(self, glyphs): + glyphs = frozenset(glyphs) + if glyphs in self.classes: + return + self.classes.add(glyphs) + for glyph in glyphs: + assert glyph not in self.glyphs + self.glyphs[glyph] = glyphs + + def build(self): + glyphClasses = {} + # Class id #0 does not need to be encoded because zero is the default + # when no class is specified. Therefore, we use id #0 for the glyph + # class that has the largest number of members. + classes = sorted(self.classes, key=len, reverse=True) + for classID, glyphs in enumerate(classes): + if classID != 0: + for glyph in glyphs: + glyphClasses[glyph] = classID + classDef = self.otClass() + classDef.classDefs = glyphClasses + return classDef diff --git a/Lib/fontTools/feaLib/builder_test.py b/Lib/fontTools/feaLib/builder_test.py index c67bac166..0e4d22653 100644 --- a/Lib/fontTools/feaLib/builder_test.py +++ b/Lib/fontTools/feaLib/builder_test.py @@ -1,9 +1,10 @@ from __future__ import print_function, division, absolute_import from __future__ import unicode_literals from fontTools.feaLib.builder import Builder, addOpenTypeFeatures -from fontTools.feaLib.builder import LigatureSubstBuilder +from fontTools.feaLib.builder import ClassDefBuilder, LigatureSubstBuilder from fontTools.feaLib.error import FeatureLibError from fontTools.ttLib import TTFont +from fontTools.ttLib.tables import otTables import codecs import difflib import os @@ -311,10 +312,39 @@ class BuilderTest(unittest.TestCase): "} foo;") +class ClassDefBuilderTest(unittest.TestCase): + def test_build(self): + builder = ClassDefBuilder(otTables.ClassDef2) + builder.add({"a", "b"}) + builder.add({"c"}) + builder.add({"e", "f", "g", "h"}) + cdef = builder.build() + self.assertIsInstance(cdef, otTables.ClassDef2) + # The largest class {"e", "f", "g", "h"} should become class ID #0. + # Zero is the default class ID, so it does not get encoded at all. + self.assertEqual(cdef.classDefs, { + "a": 1, + "b": 1, + "c": 2 + }) + + def test_canAdd(self): + b = ClassDefBuilder(otTables.ClassDef1) + b.add({"a", "b", "c", "d"}) + b.add({"e", "f"}) + self.assertTrue(b.canAdd({"a", "b", "c", "d"})) + self.assertTrue(b.canAdd({"e", "f"})) + self.assertTrue(b.canAdd({"g", "h", "i"})) + self.assertFalse(b.canAdd({"b", "c", "d"})) + self.assertFalse(b.canAdd({"a", "b", "c", "d", "e", "f"})) + self.assertFalse(b.canAdd({"d", "e", "f"})) + self.assertFalse(b.canAdd({"f"})) + + class LigatureSubstBuilderTest(unittest.TestCase): def test_make_key(self): - self.assertEqual(LigatureSubstBuilder.make_key(('f', 'f', 'i')), - (-3, ('f', 'f', 'i'))) + self.assertEqual(LigatureSubstBuilder.make_key(("f", "f", "i")), + (-3, ("f", "f", "i"))) if __name__ == "__main__":