예제 #1
0
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
예제 #2
0
    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
예제 #3
0
    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 "
예제 #4
0
    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()
예제 #5
0
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
예제 #6
0
    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"
예제 #7
0
    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
예제 #8
0
    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)
예제 #9
0
    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"