Example #1
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 "
Example #2
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
Example #3
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
Example #4
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"
Example #5
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()
Example #6
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
Example #7
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"
    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)
Example #9
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
Example #10
0
 def test_deprecated_mtiFeatures_argument(self, FontClass):
     with pytest.warns(UserWarning, match="argument is ignored"):
         FeatureCompiler(FontClass(), mtiFeatures="whatever")
Example #11
0
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)
Example #12
0
    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