def test_removeGlyphData(self): path = getTestFontPath() font = Font(path) font.newGlyph("XXX") font.unicodeData.addGlyphData("XXX", [65]) font.unicodeData.removeGlyphData("A", [65]) self.assertEqual(font.unicodeData[65], ['XXX'])
def test_categoryForGlyphName(self): path = getTestFontPath() font = Font(path) font.newGlyph("A.alt") self.assertEqual(font.unicodeData.categoryForGlyphName("A"), 'Lu') self.assertEqual(font.unicodeData.categoryForGlyphName("A.alt"), 'Lu') self.assertEqual(font.unicodeData.categoryForGlyphName("A.alt", False), 'Cn')
def setUp(self): self.font = Font() self.glyph = self.font.newGlyph("A") self.anchor = Anchor( self.glyph, {"name": "anchor1", "x": 300, "y": 700} ) self.notificationObject = NotificationTestObserver()
def test_setitem(self): path = getTestFontPath() font = Font(path) font.newGlyph("XXX") font.unicodeData[1000] = ["XXX"] self.assertEqual(font.unicodeData[1000], ['XXX']) font.unicodeData[65] = ["YYY"] self.assertEqual(font.unicodeData[65], ['A', 'YYY'])
def test_pseudoUnicodeForGlyphName(self): path = getTestFontPath() font = Font(path) self.assertEqual(font.unicodeData.pseudoUnicodeForGlyphName("A"), 65) font.newGlyph("A.foo") self.assertEqual(font.unicodeData.pseudoUnicodeForGlyphName("A.foo"), 65) font.newGlyph("B_A") self.assertEqual(font.unicodeData.pseudoUnicodeForGlyphName("B_A"), 66)
def test_forcedUnicodeForGlyphName(self): path = getTestFontPath() font = Font(path) self.assertEqual(font.unicodeData.forcedUnicodeForGlyphName("A"), 65) font.newGlyph("B_A") self.assertEqual(font.unicodeData.forcedUnicodeForGlyphName("B_A"), 0xE000) font.newGlyph("B_B") self.assertEqual(font.unicodeData.forcedUnicodeForGlyphName("B_B"), 0xE001)
def test_scriptForGlyphName(self): path = getTestFontPath() font = Font(path) font.newGlyph("A.alt") self.assertEqual(font.unicodeData.scriptForGlyphName("A"), 'Latin') self.assertEqual(font.unicodeData.scriptForGlyphName("A.alt"), 'Latin') self.assertEqual(font.unicodeData.scriptForGlyphName("A.alt", False), 'Unknown') font.newGlyph("Alpha") font["Alpha"].unicode = 0x0391 self.assertEqual(font.unicodeData.scriptForGlyphName("Alpha"), 'Greek')
def test_glyphNameForForcedUnicode(self): path = getTestFontPath() font = Font(path) self.assertEqual(font.unicodeData.glyphNameForForcedUnicode(65), "A") font.newGlyph("B_A") self.assertIsNone(font.unicodeData.glyphNameForForcedUnicode(0xE000)) font.unicodeData.forcedUnicodeForGlyphName("B_A") self.assertEqual(font.unicodeData.glyphNameForForcedUnicode(0xE000), "B_A") font.newGlyph("B_B") font.unicodeData.forcedUnicodeForGlyphName("B_B") self.assertEqual(font.unicodeData.glyphNameForForcedUnicode(0xE001), "B_B")
def makeTestFonts(rootPath): """ Make some test fonts that have the kerning problem.""" path1 = os.path.join(rootPath, "geometryMaster1.ufo") path2 = os.path.join(rootPath, "geometryMaster2.ufo") path3 = os.path.join(rootPath, "my_test_instance_dir_one", "geometryInstance%3.3f.ufo") path4 = os.path.join(rootPath, "my_test_instance_dir_two", "geometryInstanceAnisotropic1.ufo") path5 = os.path.join(rootPath, "my_test_instance_dir_two", "geometryInstanceAnisotropic2.ufo") f1 = Font() fillInfo(f1) addGlyphs(f1, 100) f1.features.text = u"# features text from master 1" f2 = Font() fillInfo(f2) addGlyphs(f2, 500) f2.features.text = u"# features text from master 2" f1.info.ascender = 400 f1.info.descender = -200 f2.info.ascender = 600 f2.info.descender = -100 f1.info.copyright = u"This is the copyright notice from master 1" f2.info.copyright = u"This is the copyright notice from master 2" f1.lib['ufoProcessor.test.lib.entry'] = "Lib entry for master 1" f2.lib['ufoProcessor.test.lib.entry'] = "Lib entry for master 2" f1.save(path1, 2) f2.save(path2, 2) return path1, path2, path3, path4, path5
def test_blockForGlyphName(self): path = getTestFontPath() font = Font(path) font.newGlyph("A.alt") self.assertEqual(font.unicodeData.blockForGlyphName("A"), 'Basic Latin') self.assertEqual(font.unicodeData.blockForGlyphName("A.alt"), 'Basic Latin') self.assertEqual(font.unicodeData.blockForGlyphName("A.alt", False), 'No_Block') font.newGlyph("schwa") font["schwa"].unicode = 0x0259 self.assertEqual(font.unicodeData.blockForGlyphName("schwa"), 'IPA Extensions')
def test_update(self): font = Font(getTestFontPath()) other = {("X", "X"): 500} font.kerning.update(other) self.assertEqual(sorted(font.kerning.keys()), [("A", "A"), ("A", "B"), ("X", "X")]) self.assertTrue(font.kerning.dirty)
def makeTestFonts(rootPath): """ Make some test fonts that have the kerning problem.""" path1 = os.path.join(rootPath, "mutingMaster1.ufo") path2 = os.path.join(rootPath, "mutingMaster2.ufo") path3 = os.path.join(rootPath, "mutedGlyphInstance.ufo") # Two masters f1 = Font() addGlyphs(f1, 0) f1.info.unitsPerEm = 1000 f1.kerning[('glyphOne', 'glyphOne')] = -100 f2 = Font() addGlyphs(f2, 33) f2.info.unitsPerEm = 2000 f2.kerning[('glyphOne', 'glyphOne')] = -200 # save f1.save(path1, 3) f2.save(path2, 3) return path1, path2, path3
def test_openRelativeForGlyphName(self): font = Font() font.newGlyph("parenleft") font["parenleft"].unicode = int("0028", 16) font.newGlyph("parenright") font["parenright"].unicode = int("0029", 16) font.newGlyph("parenleft.alt") font.newGlyph("parenright.alt") self.assertEqual( font.unicodeData.openRelativeForGlyphName("parenright", True), 'parenleft') self.assertEqual( font.unicodeData.openRelativeForGlyphName("parenright.alt", True), 'parenleft.alt') del font["parenleft.alt"] self.assertEqual( font.unicodeData.openRelativeForGlyphName("parenright.alt", True), 'parenleft')
def test_delitem(self): path = getTestFontPath() font = Font(path) del font.unicodeData[65] self.assertNotIn(65, font.unicodeData) font.unicodeData.glyphNameForUnicode(65) self.assertNotIn(0xBEAF, font.unicodeData) del font.unicodeData[0xBEAF] self.assertNotIn(0xBEAF, font.unicodeData)
def test_find(self): font = Font() font.groups["public.kern1.A"] = ["A", "A.alt"] font.groups["public.kern2.C"] = ["C", "C.alt"] font.kerning["public.kern1.A", "public.kern2.C"] = 1 font.kerning["public.kern1.A", "C.alt"] = 2 font.kerning["A.alt", "C.alt"] = 3 self.assertEqual(font.kerning.find(("A", "C")), 1) self.assertEqual(font.kerning.find(("A", "C.alt")), 2) self.assertEqual(font.kerning.find(("A.alt", "C.alt")), 3)
def test_decompositionBaseForGlyphName(self): path = getTestFontPath() font = Font(path) font.newGlyph("Aacute") font["Aacute"].unicode = int("00C1", 16) self.assertEqual( font.unicodeData.decompositionBaseForGlyphName("Aacute", True), 'A') font.newGlyph("Aringacute") font["Aringacute"].unicode = int("01FA", 16) self.assertEqual( font.unicodeData.decompositionBaseForGlyphName("Aringacute", True), 'A') font.newGlyph("Aacute.alt") self.assertEqual( font.unicodeData.decompositionBaseForGlyphName("Aacute.alt", True), 'A') font.newGlyph("A.alt") self.assertEqual( font.unicodeData.decompositionBaseForGlyphName("Aacute.alt", True), 'A.alt')
def test_endSelfNotificationObservation(self): font = Font() self.assertIsNotNone(font.unicodeData.dispatcher) self.assertIsNotNone(font.unicodeData.font) self.assertIsNotNone(font.unicodeData.layerSet) self.assertIsNotNone(font.unicodeData.layer) font.unicodeData.endSelfNotificationObservation() self.assertIsNone(font.unicodeData.dispatcher) self.assertIsNone(font.unicodeData.font) self.assertIsNone(font.unicodeData.layerSet) self.assertIsNone(font.unicodeData.layer)
def testUnicodes(docPath): # after executing testSwap there should be some test fonts # let's check if the unicode values for glyph "narrow" arrive at the right place. d = DesignSpaceProcessor() d.read(docPath) for instance in d.instances: if os.path.exists(instance.path): f = Font(instance.path) if instance.name == "TestFamily-TestStyle_pop1000.000": assert f['narrow'].unicodes == [291, 292, 293] else: assert f['narrow'].unicodes == [207] else: print("Missing test font at %s" % instance.path)
def makeTestFonts(rootPath): """ Make some test fonts that have the kerning problem.""" path1 = os.path.join(rootPath, "validMaster1.ufo") path2 = os.path.join(rootPath, "validMaster2.ufo") path3 = os.path.join(rootPath, "invalidInstance.ufo") # Two masters f1 = Font() f1.groups['public.kern1.@MMK_L_one'] = ['glyphOne', 'glyphTwo'] f1.groups['public.kern2.@MMK_R_two'] = ['glyphThree', 'glyphFour'] addGlyphs(f1) f2 = Font() f2.groups.update(f1.groups) # both masters have the same groups addGlyphs(f2) assert f1.groups == f2.groups # a normal group / group pair in each master f1.kerning[('public.kern1.@MMK_L_one', 'public.kern2.@MMK_R_two')] = 1000 f1.kerning[('a', 'b')] = 10 f2.kerning[('public.kern1.@MMK_L_one', 'public.kern2.@MMK_R_two')] = 2000 f2.kerning[('a', 'b')] = 10 # a valid exception to this pair in each master f1.kerning[('public.kern1.@MMK_L_one', 'glyphThree')] = -500 f2.kerning[('glyphOne', 'public.kern2.@MMK_R_two')] = -800 # make sure the kerning and groups in each master validate. #assert kerningValidatorReportPairs(f1.kerning, f1.groups) == (True, [], []) #assert kerningValidatorReportPairs(f2.kerning, f2.groups) == (True, [], []) # save f1.save(path1, 3) f2.save(path2, 3) return path1, path2, path3
def setUp(self): self.font = font = Font() font.newGlyph("a") font["a"].unicode = 0x0061 font.newGlyph("b") font["b"].unicode = 0x0062 font.newGlyph("c") font["c"].unicode = 0x0063 font.newGlyph("alpha") font["alpha"].unicode = 0x03B1 font.newGlyph("aacute") font["aacute"].unicode = 0x00E1 font.newGlyph("comma") font["comma"].unicode = 0x002C font.newGlyph("schwa") font["schwa"].unicode = 0x0259 font.newGlyph("undefined")
def makeSwapFonts(rootPath): """ Make some test fonts that have the kerning problem.""" path1 = os.path.join(rootPath, "Swap.ufo") path2 = os.path.join(rootPath, "Swapped.ufo") f1 = Font() fillInfo(f1) addGlyphs(f1, 100) f1.features.text = u"# features text from master 1" f1.info.ascender = 800 f1.info.descender = -200 f1.kerning[('glyphOne', 'glyphOne')] = -10 f1.kerning[('glyphTwo', 'glyphTwo')] = 10 f1.save(path1, 2) return path1, path2
def makeTestFonts(rootPath): """ Make some test fonts that have the kerning problem.""" path1 = os.path.join(rootPath, "geometryMaster1.ufo") path2 = os.path.join(rootPath, "geometryMaster2.ufo") path3 = os.path.join(rootPath, "geometryInstance.ufo") path4 = os.path.join(rootPath, "geometryInstanceAnisotropic1.ufo") path5 = os.path.join(rootPath, "geometryInstanceAnisotropic2.ufo") # Two masters f1 = Font() addGlyphs(f1, 100) f2 = Font() addGlyphs(f2, 500) fillInfo(f1) fillInfo(f2) # save f1.save(path1, 2) f2.save(path2, 2) return path1, path2, path3, path4, path5
def testSwap(docPath): srcPath, dstPath = _makeSwapFonts(os.path.dirname(docPath)) f = Font(srcPath) swapGlyphNames(f, "narrow", "wide") f.info.styleName = "Swapped" f.save(dstPath) # test the results in newly opened fonts old = Font(srcPath) new = Font(dstPath) assert new.kerning.get(("narrow", "narrow")) == old.kerning.get( ("wide", "wide")) assert new.kerning.get(("wide", "wide")) == old.kerning.get( ("narrow", "narrow")) # after the swap these widths should be the same assert old['narrow'].width == new['wide'].width assert old['wide'].width == new['narrow'].width # The following test may be a bit counterintuitive: # the rule swaps the glyphs, but we do not want glyphs that are not # specifically affected by the rule to *appear* any different. # So, components have to be remapped. assert new['wide.component'].components[0].baseGlyph == "narrow" assert new['narrow.component'].components[0].baseGlyph == "wide"
class AnchorTest(unittest.TestCase): def __init__(self, methodName): unittest.TestCase.__init__(self, methodName) def setUp(self): self.font = Font() self.glyph = self.font.newGlyph("A") self.anchor = Anchor() def tearDown(self): del self.anchor del self.glyph del self.font def test_dirty(self): self.assertFalse(self.anchor.dirty) notdirty = not self.anchor.dirty self.anchor.dirty = notdirty self.assertEqual(self.anchor.dirty, notdirty) self.anchor.dirty = not notdirty self.assertNotEqual(self.anchor.dirty, notdirty) def test_getParent(self): self.assertIsNone(self.anchor.getParent()) self.anchor = Anchor(self.glyph) self.assertEqual(self.anchor.getParent(), self.glyph) def test_font(self): self.assertIsNone(self.anchor.font) self.anchor = Anchor(self.glyph) self.assertEqual(self.anchor.font, self.font) with self.assertRaises(AttributeError): self.anchor.font = "foo" def test_layerSet(self): self.assertIsNone(self.anchor.layerSet) self.anchor = Anchor(self.glyph) self.assertEqual(self.anchor.layerSet, self.glyph.layerSet) self.assertIsNotNone(self.anchor.layerSet) with self.assertRaises(AttributeError): self.anchor.layerSet = "foo" def test_layer(self): self.assertIsNone(self.anchor.layer) self.anchor = Anchor(self.glyph) self.assertEqual(self.anchor.layer, self.glyph.layer) self.assertIsNotNone(self.anchor.layer) with self.assertRaises(AttributeError): self.anchor.layer = "foo" def test_x(self): self.anchor.x = 100 self.assertEqual(self.anchor.x, 100) self.assertTrue(self.anchor.dirty) def test_y(self): self.anchor.y = 100 self.assertEqual(self.anchor.y, 100) self.assertTrue(self.anchor.dirty) def test_name(self): self.anchor.name = "foo" self.assertEqual(self.anchor.name, "foo") self.assertTrue(self.anchor.dirty) self.anchor.name = None self.assertIsNone(self.anchor.name) self.assertTrue(self.anchor.dirty) def test_color(self): self.anchor.color = "1,1,1,1" self.assertEqual(self.anchor.color, "1,1,1,1") self.assertTrue(self.anchor.dirty) def test_identifiers(self): anchor = Anchor(self.glyph) self.assertEqual(anchor.identifiers, self.glyph.identifiers) def test_identifier(self): self.assertIsNone(self.anchor.identifier) identifier = self.anchor.generateIdentifier() self.assertEqual(identifier, self.anchor.identifier) self.assertIsNotNone(self.anchor.identifier) def test_identifier_set(self): self.assertIsNone(self.anchor.identifier) self.anchor.identifier = "foo" self.assertEqual(self.anchor.identifier, "foo") self.anchor.identifier = "bar" self.assertEqual(self.anchor.identifier, "foo") self.anchor.identifier = None self.assertEqual(self.anchor.identifier, "foo") def test_instance(self): a = Anchor(anchorDict=dict(x=1, y=2, name="3", identifier="4", color="1,1,1,1")) self.assertEqual((a.x, a.y, a.name, a.identifier, a.color), (1, 2, "3", "4", "1,1,1,1")) def test_move(self): a = Anchor(anchorDict=dict(x=1, y=2, name="3")) a.dirty = False self.assertEqual((a.x, a.y), (1, 2)) a.move((10, 0)) self.assertTrue(a.dirty) a.dirty = False self.assertEqual((a.x, a.y), (11, 2)) a.move((0, -123)) self.assertTrue(a.dirty) a.dirty = False self.assertEqual((a.x, a.y), (11, -121)) a.move((-11, 121)) self.assertEqual((a.x, a.y), (0, 0)) self.assertTrue(a.dirty)
from defcon.objects.font import Font import os glyphOrder = [] with open("glyphorder.txt", "r") as f: glyphOrder = f.read().splitlines() DIR = "master_ufo" for fn in os.listdir(DIR): if fn.endswith(".ufo"): font = Font(os.path.join(DIR, fn)) font.glyphOrder = glyphOrder font.save() print fn print "Done"
class AnchorNotificationTest(unittest.TestCase): def __init__(self, methodName): unittest.TestCase.__init__(self, methodName) def setUp(self): self.font = Font() self.glyph = self.font.newGlyph("A") self.anchor = Anchor( self.glyph, {"name": "anchor1", "x": 300, "y": 700} ) self.notificationObject = NotificationTestObserver() def tearDown(self): del self.anchor del self.glyph del self.font del self.notificationObject def test_x_changed_notification(self): self.anchor.dispatcher.addObserver( observer=self.notificationObject, methodName="notificationCallback", notification="Anchor.XChanged", observable=self.anchor ) self.assertEqual(self.notificationObject.stack, []) self.anchor.x += 10 self.assertEqual( self.notificationObject.stack[-1], ("Anchor.XChanged", "anchor1") ) def test_y_changed_notification(self): self.anchor.dispatcher.addObserver( observer=self.notificationObject, methodName="notificationCallback", notification="Anchor.YChanged", observable=self.anchor ) self.assertEqual(self.notificationObject.stack, []) self.anchor.y += 10 self.assertEqual( self.notificationObject.stack[-1], ("Anchor.YChanged", "anchor1") ) def test_name_notification(self): self.anchor.dispatcher.addObserver( observer=self.notificationObject, methodName="notificationCallback", notification="Anchor.NameChanged", observable=self.anchor ) self.assertEqual(self.notificationObject.stack, []) self.anchor.name += "_suffix" self.assertEqual( self.notificationObject.stack[-1], ("Anchor.NameChanged", "anchor1_suffix") ) def test_color_notification(self): self.anchor.dispatcher.addObserver( observer=self.notificationObject, methodName="notificationCallback", notification="Anchor.ColorChanged", observable=self.anchor ) self.assertEqual(self.notificationObject.stack, []) self.anchor.color = "1,1,1,1" self.assertEqual( self.notificationObject.stack[-1], ("Anchor.ColorChanged", "anchor1") ) def test_identifier_notification(self): self.anchor.dispatcher.addObserver( observer=self.notificationObject, methodName="notificationCallback", notification="Anchor.IdentifierChanged", observable=self.anchor ) self.assertEqual(self.notificationObject.stack, []) self.anchor.identifier = "anchor1_identifier" self.assertEqual( self.notificationObject.stack[-1], ("Anchor.IdentifierChanged", "anchor1") ) def test_endSelfNotificationObservation(self): self.assertIsNotNone(self.anchor.dispatcher) self.assertIsNotNone(self.anchor.font) self.assertIsNotNone(self.anchor.layerSet) self.assertIsNotNone(self.anchor.layer) self.assertIsNotNone(self.anchor.glyph) self.anchor.endSelfNotificationObservation() self.assertIsNone(self.anchor.dispatcher) self.assertIsNone(self.anchor.font) self.assertIsNone(self.anchor.layerSet) self.assertIsNone(self.anchor.layer) self.assertIsNone(self.anchor.glyph)
def setUp(self): self.font = Font() self.glyph = self.font.newGlyph("A") self.anchor = Anchor()
def testMutingOptions(rootPath, cleanUp=True): # that works, let's do it via MutatorMath # path1 and path2 are masters. path3 is the instance path1, path2, path3 = makeTestFonts(rootPath) documentPath = os.path.join(rootPath, 'mutingTest.designspace') doc = DesignSpaceDocumentWriter(documentPath, verbose=True) doc.addSource(path1, name="master_1", location=dict(width=0), copyLib=True, copyGroups=True, copyInfo=True, copyFeatures=True, muteKerning=True) doc.addSource( path2, name="master_2", location=dict(width=1000), copyLib=False, copyGroups=False, copyInfo=False, copyFeatures=False, muteInfo=True, mutedGlyphNames=['glyphThree'] # mute glyphThree in master 1 ) doc.startInstance(fileName=path3, familyName="TestInstance", styleName="Regular", location=dict(width=500)) doc.writeGlyph('glyphFour', mute=True) # mute glyphFour in the instance doc.writeKerning() doc.writeInfo() doc.endInstance() doc.save() # execute the designspace. doc = DesignSpaceDocumentReader(documentPath, 2, roundGeometry=True, verbose=True, progressFunc=testingProgressFunc) doc.process(makeGlyphs=True, makeKerning=True, makeInfo=True) # look at the results m1 = Font(path1) m2 = Font(path2) r = Font(path3) # # the glyphThree master was muted in the second master # so the instance glyphThree should be the same as the first master: assert r['glyphThree'].bounds == m1['glyphThree'].bounds # we muted glyphFour in the instance. # so it should not be part of the instance UFO: assert "glyphFour" not in r # font.info is muted for master2, so the instance has to have the values from master 1 assert r.info.unitsPerEm == m1.info.unitsPerEm # kerning is muted for master1, so the instance has to have the kerning from master 2 assert r.kerning[('glyphOne', 'glyphOne')] == m2.kerning[('glyphOne', 'glyphOne')] if cleanUp: # remove the mess try: shutil.rmtree(path1) shutil.rmtree(path2) shutil.rmtree(path3) except: pass return True
def testGeometry(rootPath, cleanUp=True): # that works, let's do it via MutatorMath path1, path2, path3, path4, path5 = makeTestFonts(rootPath) documentPath = os.path.join(rootPath, 'geometryTest.designspace') doc = DesignSpaceDocumentWriter(documentPath, verbose=True) doc.addSource( path1, name="master_1", location=dict(width=0), copyLib=True, copyGroups=True, copyInfo=True, copyFeatures=True, ) doc.addSource( path2, name="master_2", location=dict(width=1000), copyLib=False, copyGroups=False, copyInfo=False, copyFeatures=False, ) doc.startInstance(fileName=path3, familyName="TestInstance", styleName="Regular", location=dict(width=500) ) doc.endInstance() doc.startInstance(fileName=path4, familyName="TestInstance", styleName="Anisotropic1", location=dict(width=(0, 1000)) ) doc.endInstance() doc.startInstance(fileName=path5, familyName="TestInstance", styleName="Anisotropic2", location=dict(width=(1000, 0)) ) doc.endInstance() doc.save() # execute the designspace. doc = DesignSpaceDocumentReader(documentPath, 2, roundGeometry=True, verbose=True, progressFunc=testingProgressFunc) doc.process(makeGlyphs=True, makeKerning=False, makeInfo=True) r1 = Font(path3) assert r1['glyphOne'].bounds == (0, 0, 300, 300) r2 = Font(path4) assert r2['glyphOne'].bounds == (0, 0, 100, 500) r3 = Font(path5) assert r3['glyphOne'].bounds == (0, 0, 500, 100) if cleanUp: # remove the mess shutil.rmtree(path1) shutil.rmtree(path2) shutil.rmtree(path3) return True
from fontMath.mathKerning import MathKerning from defcon.objects.font import Font f = Font() f.groups["public.kern1.groupA"] = ['one', 'Bee'] f.groups["public.kern2.groupB"] = ['two', 'Three'] f.kerning[('public.kern1.groupA', 'public.kern2.groupB')] = -100 f.kerning[('one', 'two')] = 0 m = MathKerning(f.kerning, f.groups) print(m.items()) print((m*1.0).items())
def test___setitem__(self): font = Font(getTestFontPath()) font.kerning["NotInFont", "NotInFont"] = 100 self.assertEqual(sorted(font.kerning.keys()), [("A", "A"), ("A", "B"), ("NotInFont", "NotInFont")]) self.assertTrue(font.kerning.dirty)