def test_GSUB_writers_run_first(self, FontClass): class FooFeatureWriter(BaseFeatureWriter): tableTag = "GSUB" def write(self, font, feaFile, compiler=None): foo = ast.FeatureBlock("FOO ") foo.statements.append( ast.SingleSubstStatement("a", "v", prefix="", suffix="", forceChain=None)) feaFile.statements.append(foo) featureWriters = [KernFeatureWriter, FooFeatureWriter] ufo = FontClass() ufo.newGlyph("a") ufo.newGlyph("v") ufo.kerning.update({("a", "v"): -40}) compiler = FeatureCompiler(ufo, featureWriters=featureWriters) assert len(compiler.featureWriters) == 2 assert compiler.featureWriters[0].tableTag == "GSUB" assert compiler.featureWriters[1].tableTag == "GPOS" ttFont = compiler.compile() assert "GSUB" in ttFont gsub = ttFont["GSUB"].table assert gsub.FeatureList.FeatureCount == 1 assert gsub.FeatureList.FeatureRecord[0].FeatureTag == "FOO "
def _layoutEngineOTLTablesRepresentationFactory(layoutEngine): font = layoutEngine.font ret = dict() glyphOrder = sorted(font.keys()) if font.features.text: otf = TTFont() otf.setGlyphOrder(glyphOrder) # compile with feaLib + markWriter/kernWriter try: compiler = FeatureCompiler(font, otf) compiler.postProcess = lambda: None compiler.compile() for name in ("GDEF", "GSUB", "GPOS"): if name in otf: table = otf[name].compile(otf) value = hb.Blob.create_for_array(table, HB.MEMORY_MODE_READONLY) ret[name] = value except Exception: # TODO: handle this in the UI import traceback print(traceback.format_exc(5)) # discard tables from incompletely parsed feature text ret = dict() return ret, glyphOrder
def test_loadFeatureWriters_from_UFO_lib(self, FontClass): ufo = FontClass() ufo.newGlyph("a") ufo.newGlyph("v") ufo.kerning.update({("a", "v"): -40}) ufo.lib[FEATURE_WRITERS_KEY] = [{"class": "KernFeatureWriter"}] compiler = FeatureCompiler(ufo) ttFont = compiler.compile() assert len(compiler.featureWriters) == 1 assert isinstance(compiler.featureWriters[0], KernFeatureWriter) assert "GPOS" in ttFont
def test_ttFont_None(self, FontClass): ufo = FontClass() ufo.newGlyph("f") ufo.newGlyph("f_f") ufo.features.text = dedent("""\ feature liga { sub f f by f_f; } liga; """) compiler = FeatureCompiler(ufo) ttFont = compiler.compile() assert "GSUB" in ttFont gsub = ttFont["GSUB"].table assert gsub.FeatureList.FeatureCount == 1 assert gsub.FeatureList.FeatureRecord[0].FeatureTag == "liga"
def test_deprecated_methods(self, FontClass): compiler = FeatureCompiler(FontClass()) with pytest.warns(UserWarning, match="method is deprecated"): compiler.setupFile_features() compiler.features = "" with pytest.warns(UserWarning, match="method is deprecated"): compiler.setupFile_featureTables() class UserCompiler(FeatureCompiler): def setupFile_features(self): self.features = "# hello world" def setupFile_featureTables(self): self.ttFont = ttLib.TTFont() compiler = UserCompiler(FontClass()) with pytest.warns(UserWarning, match="method is deprecated"): compiler.compile()
def compileUFOToFont(ufoPath): """Compile the source UFO to a TTF with the smallest amount of tables needed to let HarfBuzz do its work. That would be 'cmap', 'post' and whatever OTL tables are needed for the features. Return the compiled font data. This function may do some redundant work (eg. we need an UFOReader elsewhere, too), but having a picklable argument and return value allows us to run it in a separate process, enabling parallelism. """ reader = UFOReader(ufoPath, validate=False) glyphSet = reader.getGlyphSet() info = SimpleNamespace() reader.readInfo(info) glyphOrder = sorted(glyphSet.keys()) # no need for the "real" glyph order if ".notdef" not in glyphOrder: # We need a .notdef glyph, so let's make one. glyphOrder.insert(0, ".notdef") cmap, revCmap, anchors = fetchCharacterMappingAndAnchors(glyphSet, ufoPath) fb = FontBuilder(round(info.unitsPerEm)) fb.setupGlyphOrder(glyphOrder) fb.setupCharacterMap(cmap) fb.setupPost() # This makes sure we store the glyph names ttFont = fb.font # Store anchors in the font as a private table: this is valuable # data that our parent process can use to do faster reloading upon # changes. ttFont["FGAx"] = newTable("FGAx") ttFont["FGAx"].data = pickle.dumps(anchors) ufo = MinimalFontObject(ufoPath, reader, revCmap, anchors) feaComp = FeatureCompiler(ufo, ttFont) try: feaComp.compile() except FeatureLibError as e: error = f"{e.__class__.__name__}: {e}" except Exception: # This is most likely a bug, and not an input error, so perhaps # we shouldn't even catch it here. error = traceback.format_exc() else: error = None return ttFont, error
def test_ttFont(self, FontClass): ufo = FontClass() ufo.newGlyph("f") ufo.newGlyph("f_f") ufo.features.text = dedent("""\ feature liga { sub f f by f_f; } liga; """) ttFont = ttLib.TTFont() ttFont.setGlyphOrder(["f", "f_f"]) compiler = FeatureCompiler(ufo, ttFont) compiler.compile() assert "GSUB" in ttFont gsub = ttFont["GSUB"].table assert gsub.FeatureList.FeatureCount == 1 assert gsub.FeatureList.FeatureRecord[0].FeatureTag == "liga"
def test_buildTables_FeatureLibError(self, FontClass, caplog): caplog.set_level(logging.CRITICAL) ufo = FontClass() ufo.newGlyph("f") ufo.newGlyph("f.alt01") ufo.newGlyph("f_f") features = dedent("""\ feature BUGS { # invalid lookup MIXED_TYPE { sub f by f.alt01; sub f f by f_f; } MIXED_TYPE; } BUGS; """) ufo.features.text = features compiler = FeatureCompiler(ufo) tmpfile = None try: with caplog.at_level(logging.ERROR, logger=logger.name): with pytest.raises(FeatureLibError): compiler.compile() assert len(caplog.records) == 1 assert "Compilation failed! Inspect temporary file" in caplog.text tmpfile = py.path.local(re.findall(".*: '(.*)'$", caplog.text)[0]) assert tmpfile.exists() assert tmpfile.read_text("utf-8") == features finally: if tmpfile is not None: tmpfile.remove(ignore_errors=True)
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 test_deprecated_mtiFeatures_argument(self, FontClass): with pytest.warns(UserWarning, match="argument is ignored"): FeatureCompiler(FontClass(), mtiFeatures="whatever")
logging.basicConfig(level=logging.INFO) parser = argparse.ArgumentParser( description="Apply feature writers to a UFO file") parser.add_argument("--output", "-o", metavar="OUTPUT", help="output file name") parser.add_argument("ufo", metavar="UFO", help="UFO file") parser.add_argument( "writers", metavar="WRITER", nargs="*", help="list of feature writers to enable", ) args = parser.parse_args() if not args.output: args.output = makeOutputFileName(args.ufo) ufo = loader(args.ufo) writers = [loadFeatureWriterFromString(w) for w in args.writers] compiler = FeatureCompiler(ufo, featureWriters=writers or None) compiler.setupFeatures() buf = StringIO() compiler.writeFeatures(buf) ufo.features.text = buf.getvalue() logger.info("Written on %s" % args.output) ufo.save(args.output)
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