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_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_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 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_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_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_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"