def test_arabic_numerals(self): ufo = Font() for name, code in [("four-ar", 0x664), ("seven-ar", 0x667)]: glyph = ufo.newGlyph(name) glyph.unicode = code ufo.kerning.update({ ('four-ar', 'seven-ar'): -30, }) ufo.features.text = dedent(""" languagesystem DFLT dflt; languagesystem arab dflt; """) writer = KernFeatureWriter() kern = writer.write(ufo) assert kern == dedent(""" lookup kern_ltr { lookupflag IgnoreMarks; pos four-ar seven-ar -30; } kern_ltr; feature kern { lookup kern_ltr; } kern;""")
def test_getKerningClasses(self, FontClass): font = FontClass() for i in range(65, 65 + 6): # A..F font.newGlyph(chr(i)) font.groups.update({ "public.kern1.A": ["A", "B"], "public.kern2.C": ["C", "D"] }) # simulate a name clash between pre-existing class definitions in # feature file, and those generated by the feature writer font.features.text = "@kern1.A = [E F];" writer = KernFeatureWriter() feaFile = parseLayoutFeatures(font) side1Classes, side2Classes = KernFeatureWriter.getKerningClasses( font, feaFile) assert "public.kern1.A" in side1Classes # the new class gets a unique name assert side1Classes["public.kern1.A"].name == "kern1.A_1" assert getGlyphs(side1Classes["public.kern1.A"]) == ["A", "B"] assert "public.kern2.C" in side2Classes assert side2Classes["public.kern2.C"].name == "kern2.C" assert getGlyphs(side2Classes["public.kern2.C"]) == ["C", "D"]
def test_mode(self): class MockTTFont: def getReverseGlyphMap(self): return {"one": 0, "four": 1, "six": 2, "seven": 3} outline = MockTTFont() ufo = Font() for name in ("one", "four", "six", "seven"): ufo.newGlyph(name) existing = dedent(""" feature kern { pos one four' -50 six; } kern; """) ufo.features.text = existing ufo.kerning.update({ ('seven', 'six'): 25.0, }) writer = KernFeatureWriter() # default mode="skip" compiler = FeatureCompiler(ufo, outline, featureWriters=[writer]) compiler.setupFile_features() assert compiler.features == existing writer = KernFeatureWriter(mode="append") compiler = FeatureCompiler(ufo, outline, featureWriters=[writer]) compiler.setupFile_features() assert compiler.features == existing + dedent(""" lookup kern_ltr { lookupflag IgnoreMarks; pos seven six 25; } kern_ltr; feature kern { lookup kern_ltr; } kern;""") writer = KernFeatureWriter(mode="prepend") compiler = FeatureCompiler(ufo, outline, featureWriters=[writer]) compiler.setupFile_features() assert compiler.features == dedent(""" lookup kern_ltr { lookupflag IgnoreMarks; pos seven six 25; } kern_ltr; feature kern { lookup kern_ltr; } kern; """) + existing
def test_insert_comment_after(self, FontClass): ufo = FontClass() for name in ("one", "four", "six", "seven"): ufo.newGlyph(name) existing = dedent("""\ feature kern { pos one four' -50 six; # # Automatic Code # } kern; """) ufo.features.text = existing ufo.kerning.update({("seven", "six"): 25.0}) writer = KernFeatureWriter() feaFile = parseLayoutFeatures(ufo) assert writer.write(ufo, feaFile) expected = dedent("""\ feature kern { pos one four' -50 six; # # } kern; lookup kern_ltr { lookupflag IgnoreMarks; pos seven six 25; } kern_ltr; feature kern { lookup kern_ltr; } kern; """) assert str(feaFile) == expected # test append mode ignores insert marker generated = self.writeFeatures(ufo, mode="append") assert str(generated) == dedent(""" lookup kern_ltr { lookupflag IgnoreMarks; pos seven six 25; } kern_ltr; feature kern { lookup kern_ltr; } kern; """)
def test_getKerningPairs(self, FontClass): font = FontClass() for i in range(65, 65 + 8): # A..H font.newGlyph(chr(i)) font.groups.update({ "public.kern1.foo": ["A", "B"], "public.kern2.bar": ["C", "D"], "public.kern1.baz": ["E", "F"], "public.kern2.nul": ["G", "H"], }) font.kerning.update({ ("public.kern1.foo", "public.kern2.bar"): 10, ("public.kern1.baz", "public.kern2.bar"): -10, ("public.kern1.foo", "D"): 15, ("A", "public.kern2.bar"): 5, ("G", "H"): -5, # class-class zero-value pairs are skipped ("public.kern1.foo", "public.kern2.nul"): 0, }) s1c, s2c = KernFeatureWriter.getKerningClasses(font) pairs = KernFeatureWriter.getKerningPairs(font, s1c, s2c) assert len(pairs) == 5 assert "G H -5" in repr(pairs[0]) assert (pairs[0].firstIsClass, pairs[0].secondIsClass) == (False, False) assert pairs[0].glyphs == {"G", "H"} assert "A @kern2.bar 5" in repr(pairs[1]) assert (pairs[1].firstIsClass, pairs[1].secondIsClass) == (False, True) assert pairs[1].glyphs == {"A", "C", "D"} assert "@kern1.foo D 15" in repr(pairs[2]) assert (pairs[2].firstIsClass, pairs[2].secondIsClass) == (True, False) assert pairs[2].glyphs == {"A", "B", "D"} assert "@kern1.baz @kern2.bar -10" in repr(pairs[3]) assert (pairs[3].firstIsClass, pairs[3].secondIsClass) == (True, True) assert pairs[3].glyphs == {"C", "D", "E", "F"} assert "@kern1.foo @kern2.bar 10" in repr(pairs[4]) assert (pairs[4].firstIsClass, pairs[4].secondIsClass) == (True, True) assert pairs[4].glyphs == {"A", "B", "C", "D"}
def test_correct_invalid_class_names(self, FontClass): font = FontClass() for i in range(65, 65 + 12): # A..L font.newGlyph(chr(i)) font.groups.update( { "public.kern1.foo$": ["A", "B", "C"], "public.kern1.foo@": ["D", "E", "F"], "@public.kern2.bar": ["G", "H", "I"], "public.kern2.bar&": ["J", "K", "L"], } ) font.kerning.update( { ("public.kern1.foo$", "@public.kern2.bar"): 10, ("public.kern1.foo@", "public.kern2.bar&"): -10, } ) side1Classes, side2Classes = KernFeatureWriter.getKerningClasses(font) assert side1Classes["public.kern1.foo$"].name == "kern1.foo" assert side1Classes["public.kern1.foo@"].name == "kern1.foo_1" # no valid 'public.kern{1,2}.' prefix, skipped assert "@public.kern2.bar" not in side2Classes assert side2Classes["public.kern2.bar&"].name == "kern2.bar"
def test__groupScriptsByTagAndDirection(self, FontClass): font = FontClass() font.features.text = dedent(""" languagesystem DFLT dflt; languagesystem latn dflt; languagesystem latn TRK; languagesystem arab dflt; languagesystem arab URD; languagesystem deva dflt; languagesystem dev2 dflt; """) feaFile = parseLayoutFeatures(font) scripts = ast.getScriptLanguageSystems(feaFile) scriptGroups = KernFeatureWriter._groupScriptsByTagAndDirection( scripts) assert "kern" in scriptGroups assert list(scriptGroups["kern"]["LTR"]) == [("latn", ["dflt", "TRK "])] assert list(scriptGroups["kern"]["RTL"]) == [("arab", ["dflt", "URD "])] assert "dist" in scriptGroups assert list(scriptGroups["dist"]["LTR"]) == [ ("deva", ["dflt"]), ("dev2", ["dflt"]), ]
def test_quantize(self, FontClass): font = FontClass() for name in ("one", "four", "six"): font.newGlyph(name) font.kerning.update({("four", "six"): -57.0, ("one", "six"): -24.0}) writer = KernFeatureWriter(quantization=5) feaFile = ast.FeatureFile() assert writer.write(font, feaFile) assert str(feaFile) == dedent("""\ lookup kern_ltr { lookupflag IgnoreMarks; pos four six -55; pos one six -25; } kern_ltr; feature kern { lookup kern_ltr; } kern; """)
def test_mode(self, FontClass): ufo = FontClass() for name in ("one", "four", "six", "seven"): ufo.newGlyph(name) existing = dedent( """\ feature kern { pos one four' -50 six; } kern; """ ) ufo.features.text = existing ufo.kerning.update({("seven", "six"): 25.0}) writer = KernFeatureWriter() # default mode="skip" feaFile = parseLayoutFeatures(ufo) assert not writer.write(ufo, feaFile) assert str(feaFile) == existing # pass optional "append" mode writer = KernFeatureWriter(mode="append") feaFile = parseLayoutFeatures(ufo) assert writer.write(ufo, feaFile) expected = existing + dedent( """ lookup kern_ltr { lookupflag IgnoreMarks; pos seven six 25; } kern_ltr; feature kern { lookup kern_ltr; } kern; """ ) assert str(feaFile) == expected # pass "skip" mode explicitly writer = KernFeatureWriter(mode="skip") feaFile = parseLayoutFeatures(ufo) assert not writer.write(ufo, feaFile) assert str(feaFile) == existing
def test_ignoreMarks(self): font = Font() for name in ("one", "four", "six"): font.newGlyph(name) font.kerning.update({ ('four', 'six'): -55.0, ('one', 'six'): -30.0, }) # default is ignoreMarks=True writer = KernFeatureWriter() kern = writer.write(font) assert kern == dedent(""" lookup kern_ltr { lookupflag IgnoreMarks; pos four six -55; pos one six -30; } kern_ltr; feature kern { lookup kern_ltr; } kern;""") writer = KernFeatureWriter(ignoreMarks=False) kern = writer.write(font) assert kern == dedent(""" lookup kern_ltr { pos four six -55; pos one six -30; } kern_ltr; feature kern { lookup kern_ltr; } kern;""")
def test_cleanup_missing_glyphs(self, FontClass): groups = { "public.kern1.A": ["A", "Aacute", "Abreve", "Acircumflex"], "public.kern2.B": ["B", "D", "E", "F"], "public.kern1.C": ["foobar"], } kerning = { ("public.kern1.A", "public.kern2.B"): 10, ("public.kern1.A", "baz"): -25, ("baz", "public.kern2.B"): -20, ("public.kern1.C", "public.kern2.B"): 20, } ufo = FontClass() exclude = {"Abreve", "D", "foobar"} for glyphs in groups.values(): for glyph in glyphs: if glyph in exclude: continue ufo.newGlyph(glyph) ufo.groups.update(groups) ufo.kerning.update(kerning) writer = KernFeatureWriter() feaFile = parseLayoutFeatures(ufo) writer.write(ufo, feaFile) classDefs = getClassDefs(feaFile) assert len(classDefs) == 2 assert classDefs[0].name == "kern1.A" assert classDefs[1].name == "kern2.B" assert getGlyphs(classDefs[0]) == ["A", "Aacute", "Acircumflex"] assert getGlyphs(classDefs[1]) == ["B", "E", "F"] lookups = getLookups(feaFile) assert len(lookups) == 1 kern_ltr = lookups[0] assert kern_ltr.name == "kern_ltr" rules = getPairPosRules(kern_ltr) assert len(rules) == 1 assert str(rules[0]) == "pos @kern1.A @kern2.B 10;"
def test_featureWriters_empty(self, FontClass): kernWriter = KernFeatureWriter(ignoreMarks=False) ufo = FontClass() ufo.newGlyph("a") ufo.newGlyph("v") ufo.kerning.update({("a", "v"): -40}) compiler = FeatureCompiler(ufo, featureWriters=[kernWriter]) ttFont1 = compiler.compile() assert "GPOS" in ttFont1 compiler = FeatureCompiler(ufo, featureWriters=[]) ttFont2 = compiler.compile() assert "GPOS" not in ttFont2
def designSpace2Var(self): ds = self.designSpace family = os.path.basename(self.familyPath) print("\n>>> Load the {} designspace".format(family)) print(" Load " + family + " files") ds.loadSourceFonts(Font) print(" Start to build Variable Tables") feature_Writers = [KernFeatureWriter(mode="append"), MarkFeatureWriter] font, _, _ = varLib.build(compileInterpolatableTTFsFromDS( ds, featureWriters=feature_Writers), optimize=False) font.save(os.path.join(self.destination, family + "-VF.ttf")) print(" " + family + " Variable Font generated\n")
def ufo2font(directory, ufolist, output, fromInstances=False): path = os.path.abspath(os.path.join(os.pardir, "src", directory)) for i in ufolist: ufoSource = os.path.join(path, i) destination = "" ufo = Font(ufoSource) folder = os.path.join(path, "fonts") if fromInstances is True: folder = os.path.abspath(os.path.join(path, os.pardir, "fonts")) featureWriters = [KernFeatureWriter(mode="append"), MarkFeatureWriter] if "otf" in output: destination = os.path.join(folder, "OTF") if not os.path.exists(destination): os.makedirs(destination) otf = compileOTF(ufo, removeOverlaps=True, useProductionNames=False, featureWriters=featureWriters) otf.save(os.path.join(destination, i[:-4] + ".otf")) print(destination + i[:-4] + ".otf saved") if "ttf" in output: destination = os.path.join(folder, "TTF") if not os.path.exists(destination): os.makedirs(destination) ttf = compileTTF(ufo, removeOverlaps=True, useProductionNames=False, featureWriters=featureWriters) ttf.save(os.path.join(destination, i[:-4] + ".ttf")) if "woff2" in output: destination = os.path.join(folder, "WOFF2") if not os.path.exists(destination): os.makedirs(destination) if "ttf" not in output: ttf = compileTTF(ufo, removeOverlaps=True, useProductionNames=False, featureWriters=featureWriters) ttf.flavor = "woff2" ttf.save(os.path.join(destination, i[:-4] + ".woff2")) if "woff" in output: destination = os.path.join(folder, "WOFF") if not os.path.exists(destination): os.makedirs(destination) if "ttf" not in output and "woff2" not in output: ttf = compileTTF(ufo, removeOverlaps=True, useProductionNames=False, featureWriters=featureWriters) ttf.flavor = "woff" ttf.save(os.path.join(destination, i[:-4] + ".woff"))
def test_insert_comment_middle(self, FontClass): ufo = FontClass() for name in ("one", "four", "six", "seven"): ufo.newGlyph(name) existing = dedent("""\ feature kern { pos one four' -50 six; # # Automatic Code # pos one six' -50 six; } kern; """) ufo.features.text = existing ufo.kerning.update({("seven", "six"): 25.0}) writer = KernFeatureWriter() feaFile = parseLayoutFeatures(ufo) with pytest.raises( InvalidFeaturesData, match="Insert marker has rules before and after, feature kern " "cannot be inserted.", ): writer.write(ufo, feaFile) # test append mode ignores insert marker generated = self.writeFeatures(ufo, mode="append") assert str(generated) == dedent(""" lookup kern_ltr { lookupflag IgnoreMarks; pos seven six 25; } kern_ltr; feature kern { lookup kern_ltr; } kern; """)
def makeOneMaster(self, ufoSource): ufo = Font(ufoSource) ttf = compileTTF(ufo, removeOverlaps=True, useProductionNames=False, featureWriters=[ KernFeatureWriter(mode="append"), MarkFeatureWriter ]) ttf.save( os.path.join(self.staticPath, os.path.basename(ufoSource)[:-4] + ".ttf")) print(" " + os.path.basename(ufoSource)[:-4] + " has been generated.\n")
def test_insert_comment_before_extended(self, FontClass): ufo = FontClass() for name in ("one", "four", "six", "seven"): ufo.newGlyph(name) existing = dedent("""\ feature kern { # # Automatic Code End # pos one four' -50 six; } kern; """) ufo.features.text = existing ufo.kerning.update({("seven", "six"): 25.0}) writer = KernFeatureWriter() feaFile = parseLayoutFeatures(ufo) assert writer.write(ufo, feaFile) expected = dedent("""\ lookup kern_ltr { lookupflag IgnoreMarks; pos seven six 25; } kern_ltr; feature kern { lookup kern_ltr; } kern; feature kern { # # pos one four' -50 six; } kern; """) assert str(feaFile).strip() == expected.strip()
def test_collect_fea_classes(self): text = '@MMK_L_v = [v w y];' expected = {'@MMK_L_v': ['v', 'w', 'y']} ufo = Font() ufo.features.text = text writer = KernFeatureWriter() writer.set_context(ufo) writer._collectFeaClasses() self.assertEquals(writer.context.leftFeaClasses, expected)
def makeVarFont(self): (minimum, maximum), tag2name = self.findRegularBoldDefaultLocation() # check if defautl exist otherTags = [ a.tag for a in self.designSpaceDocument.axes if a.tag != "wght" ] print("\tLoad {}.designspace".format(self.familyName)) self.designSpaceDocument.loadSourceFonts(defcon.Font) vfont, _, _ = varLib.build(compileInterpolatableTTFsFromDS( self.designSpaceDocument, featureWriters=[ KernFeatureWriter(mode="append"), MarkFeatureWriter() ]), optimize=False) fullfontsFolder = os.path.join(self.dirpath, "fonts/VAR") if not os.path.exists(fullfontsFolder): os.makedirs(fullfontsFolder) path = os.path.join(fullfontsFolder, self.familyName + "-VF.ttf") vfont.save(path) vfont = TTFont(path) tags = {"wght": (minimum, maximum)} if len(otherTags) == 0: slimft = instancer.instantiateVariableFont(vfont, tags) else: for t in otherTags: tags[t] = None slimft = instancer.instantiateVariableFont(vfont, tags) for namerecord in slimft['name'].names: namerecord.string = namerecord.toUnicode() if namerecord.nameID == 3: unicID = namerecord.string.split(";") newID = "4.444" + ";" + unicID[1] + ";" + unicID[2] print("\tTagging the font as a 'slim' one:", newID) namerecord.string = newID if namerecord.nameID == 5: namerecord.string = "Version 4.444" print("\tSaving " + self.familyName + "Slim-VF.ttf\n") slimFontFolder = os.path.join(fullfontsFolder + "/SlimVF") if not os.path.exists(slimFontFolder): os.makedirs(slimFontFolder) slimft.save( os.path.join(slimFontFolder, "%sSlim-VF.ttf" % self.familyName))
def designSpace2Var(family): print(">>> Load the {} designspace".format(family)) path, folder = getFile(".designspace", family) designSpace = openDesignSpace(path) print(" Load " + family + " files") designSpace.loadSourceFonts(Font) print(" Start to build Variable Tables") feature_Writers = [KernFeatureWriter(mode="append"), MarkFeatureWriter] font, _, _ = varLib.build(compileInterpolatableTTFsFromDS( designSpace, featureWriters=feature_Writers), optimize=False) destination = folder + "/fonts/VAR" if not os.path.exists(destination): os.makedirs(destination) font.save(os.path.join(destination, family + "-VF.ttf")) print(" " + family + " Variable Font generated\n")
def test_ignoreMarks(self, FontClass): font = FontClass() for name in ("one", "four", "six"): font.newGlyph(name) font.kerning.update({("four", "six"): -55.0, ("one", "six"): -30.0}) # default is ignoreMarks=True writer = KernFeatureWriter() feaFile = ast.FeatureFile() assert writer.write(font, feaFile) assert str(feaFile) == dedent( """\ lookup kern_ltr { lookupflag IgnoreMarks; pos four six -55; pos one six -30; } kern_ltr; feature kern { lookup kern_ltr; } kern; """ ) writer = KernFeatureWriter(ignoreMarks=False) feaFile = ast.FeatureFile() assert writer.write(font, feaFile) assert str(feaFile) == dedent( """\ lookup kern_ltr { pos four six -55; pos one six -30; } kern_ltr; feature kern { lookup kern_ltr; } kern; """ )
def test__cleanupMissingGlyphs(self): groups = { "public.kern1.A": ["A", "Aacute", "Abreve", "Acircumflex"], "public.kern2.B": ["B", "D", "E", "F"], } ufo = Font() for glyphs in groups.values(): for glyph in glyphs: ufo.newGlyph(glyph) ufo.groups.update(groups) del ufo["Abreve"] del ufo["D"] writer = KernFeatureWriter() writer.set_context(ufo) self.assertEquals(writer.context.groups, groups) writer._cleanupMissingGlyphs() self.assertEquals( writer.context.groups, { "public.kern1.A": ["A", "Aacute", "Acircumflex"], "public.kern2.B": ["B", "E", "F"] })