<repo_name>folengo/ufo2ft<file_sep>/Lib/ufo2ft/__init__.py
from __future__ import print_function, division, absolute_import

from ufo2ft.kernFeatureWriter import KernFeatureWriter
from ufo2ft.makeotfParts import FeatureOTFCompiler
from ufo2ft.markFeatureWriter import MarkFeatureWriter
from ufo2ft.otfPostProcessor import OTFPostProcessor
from ufo2ft.outlineOTF import OutlineOTFCompiler, OutlineTTFCompiler


__version__ = "0.3.5.dev0"


def compileOTF(ufo, outlineCompilerClass=OutlineOTFCompiler,
               featureCompilerClass=FeatureOTFCompiler, mtiFeaFiles=None,
               kernWriter=KernFeatureWriter, markWriter=MarkFeatureWriter,
               glyphOrder=None, convertCubics=True, cubicConversionError=2,
               useProductionNames=True, optimizeCff=True):
    """Create FontTools CFF font from a UFO.

    Some arguments are only used when generating CFF or TrueType outlines:
    `convertCubics` and `cubicConversionError` only apply to TrueType, and
    `optimizeCff` only applies to CFF.
    """

    outlineCompiler = outlineCompilerClass(
        ufo, glyphOrder, convertCubics, cubicConversionError)
    otf = outlineCompiler.compile()

    featureCompiler = featureCompilerClass(
        ufo, otf, kernWriter, markWriter, mtiFeaFiles=mtiFeaFiles)
    featureCompiler.compile()

    postProcessor = OTFPostProcessor(otf, ufo)
    otf = postProcessor.process(useProductionNames, optimizeCff)

    return otf


def compileTTF(ufo, outlineCompilerClass=OutlineTTFCompiler, **kwargs):
    """Create FontTools TrueType font from a UFO."""

    return compileOTF(ufo, outlineCompilerClass=outlineCompilerClass, **kwargs)
<file_sep>/tests/outlineOTF_test.py
from fontTools.ttLib import TTFont
from defcon import Font
from ufo2ft.outlineOTF import OutlineTTFCompiler
from ufo2ft import compileTTF
import unittest
import os


def getTestUFO():
    dirname = os.path.dirname(__file__)
    return Font(os.path.join(dirname, 'data', 'TestFont.ufo'))


class TestOutlineTTCompiler(unittest.TestCase):

    def setUp(self):
        self.otf = TTFont()
        self.ufo = getTestUFO()

    def test_setupTable_gasp(self):
        compiler = OutlineTTFCompiler(self.ufo)
        compiler.otf = self.otf
        compiler.setupTable_gasp()
        self.assertTrue('gasp' in compiler.otf)
        self.assertEqual(compiler.otf['gasp'].gaspRange,
                         {7: 10, 65535: 15})

    def test_compile_with_gasp(self):
        compiler = OutlineTTFCompiler(self.ufo)
        compiler.compile()
        self.assertTrue('gasp' in compiler.otf)
        self.assertEqual(compiler.otf['gasp'].gaspRange,
                         {7: 10, 65535: 15})

    def test_compile_without_gasp(self):
        self.ufo.info.openTypeGaspRangeRecords = None
        compiler = OutlineTTFCompiler(self.ufo)
        compiler.compile()
        self.assertTrue('gasp' not in compiler.otf)

    def test_compile_empty_gasp(self):
        # ignore empty gasp
        self.ufo.info.openTypeGaspRangeRecords = []
        compiler = OutlineTTFCompiler(self.ufo)
        compiler.compile()
        self.assertTrue('gasp' not in compiler.otf)


class TestGlyphOrder(unittest.TestCase):

    def setUp(self):
        self.ufo = getTestUFO()

    def test_compile_original_glyph_order(self):
        DEFAULT_ORDER = ['.notdef', 'space', 'a', 'b', 'c']
        compiler = OutlineTTFCompiler(self.ufo)
        compiler.compile()
        self.assertEqual(compiler.otf.getGlyphOrder(), DEFAULT_ORDER)

    def test_compile_tweaked_glyph_order(self):
        NEW_ORDER = ['.notdef', 'space', 'b', 'a', 'c']
        self.ufo.lib['public.glyphOrder'] = NEW_ORDER
        compiler = OutlineTTFCompiler(self.ufo)
        compiler.compile()
        self.assertEqual(compiler.otf.getGlyphOrder(), NEW_ORDER)

    def test_compile_strange_glyph_order(self):
        """Move space and .notdef to end of glyph ids
        ufo2ft always puts .notdef first.
        """
        NEW_ORDER = ['b', 'a', 'c', 'space', '.notdef']
        EXPECTED_ORDER = ['.notdef', 'b', 'a', 'c', 'space']
        self.ufo.lib['public.glyphOrder'] = NEW_ORDER
        compiler = OutlineTTFCompiler(self.ufo)
        compiler.compile()
        self.assertEqual(compiler.otf.getGlyphOrder(), EXPECTED_ORDER)


class TestNames(unittest.TestCase):

    def setUp(self):
        self.ufo = getTestUFO()

    def test_compile_without_production_names(self):
        result = compileTTF(self.ufo, useProductionNames=False)
        self.assertEqual(result.getGlyphOrder(), ['.notdef', 'space', 'a', 'b', 'c'])

    def test_compile_with_production_names(self):
        result = compileTTF(self.ufo, useProductionNames=True)
        self.assertEqual(result.getGlyphOrder(), ['.notdef', 'uni0020', 'uni0061', 'uni0062', 'uni0063'])

    CUSTOM_POSTSCRIPT_NAMES = {
            '.notdef': '.notdef',
            'space': 'foo',
            'a': 'bar',
            'b': 'baz',
            'c': 'meh'
        }

    def test_compile_with_custom_postscript_names(self):
        self.ufo.lib['public.postscriptNames'] = self.CUSTOM_POSTSCRIPT_NAMES
        result = compileTTF(self.ufo, useProductionNames=True)
        self.assertEqual(sorted(result.getGlyphOrder()), sorted(self.CUSTOM_POSTSCRIPT_NAMES.values()))

    def test_compile_with_custom_postscript_names_notdef_preserved(self):
        custom_names = dict(self.CUSTOM_POSTSCRIPT_NAMES)
        custom_names['.notdef'] = 'defnot'
        self.ufo.lib['public.postscriptNames'] = custom_names
        result = compileTTF(self.ufo, useProductionNames=True)
        order = sorted(result.getGlyphOrder())
        self.assertEqual(result.getGlyphOrder(), ['.notdef', 'foo', 'bar', 'baz', 'meh'])


if __name__ == "__main__":
    import sys
    sys.exit(unittest.main())
<file_sep>/requirements.txt
fonttools==3.7.2
ufoLib==2.0.0
defcon==0.2.1
cu2qu==1.1.1
compreffor==0.4.3
<file_sep>/Lib/ufo2ft/makeotfParts.py
from __future__ import \
    print_function, division, absolute_import, unicode_literals
import logging
import os
import re
from fontTools.misc.py23 import *
from fontTools import feaLib
from fontTools.feaLib.builder import addOpenTypeFeaturesFromString
from fontTools import mtiLib

from ufo2ft.maxContextCalc import maxCtxFont

logger = logging.getLogger(__name__)


class FeatureOTFCompiler(object):
    """Generates OpenType feature tables for a UFO.

    If mtiFeaFiles is passed to the constructor, it should be a dictionary
    mapping feature table tags to source files which should be compiled by
    mtiLib into that respective table.
    """

    def __init__(self, font, outline, kernWriter, markWriter, mtiFeaFiles=None):
        self.font = font
        self.outline = outline
        self.kernWriter = kernWriter
        self.markWriter = markWriter
        self.mtiFeaFiles = mtiFeaFiles
        self.setupAnchorPairs()

    def compile(self):
        """Compile the features.

        Starts by generating feature syntax for the kern, mark, and mkmk
        features. If they already exist, they will not be overwritten.
        """

        self.precompile()
        self.setupFile_features()
        self.setupFile_featureTables()

        # only after compiling features can usMaxContext be calculated
        self.outline['OS/2'].usMaxContext = maxCtxFont(self.outline)

    def precompile(self):
        """Set any attributes needed before compilation.

        **This should not be called externally.** Subclasses
        may override this method if desired.
        """
        pass

    def setupFile_features(self):
        """
        Make the features source file. If any tables
        or the kern feature are defined in the font's
        features, they will not be overwritten.

        **This should not be called externally.** Subclasses
        may override this method to handle the file creation
        in a different way if desired.
        """

        if self.mtiFeaFiles is not None:
            return

        features = self._findLayoutFeatures()

        kernRE = r"feature\s+kern\s+{.*?}\s+kern\s*;"
        markRE = re.compile(kernRE.replace("kern", "mark"), re.DOTALL)
        mkmkRE = re.compile(kernRE.replace("kern", "mkmk"), re.DOTALL)
        kernRE = re.compile(kernRE, re.DOTALL)

        existing = self.font.features.text or ""

        # build the GPOS features as necessary
        autoFeatures = {}
        if "kern" not in features:
            autoFeatures["kern"] = self.writeFeatures_kern()
        writeMark = "mark" not in features
        writeMkmk = "mkmk" not in features
        if writeMark or writeMkmk:
            autoFeatures["mark"] = self.writeFeatures_mark(
                doMark=writeMark, doMkmk=writeMkmk)

        # write the features
        features = [existing]
        for name, text in sorted(autoFeatures.items()):
            features.append(text)
        self.features = "\n\n".join(features)

    def writeFeatures_kern(self):
        """
        Write the kern feature to a string and return it.

        **This should not be called externally.** Subclasses
        may override this method to handle the string creation
        in a different way if desired.
        """
        writer = self.kernWriter(self.font)
        return writer.write()

    def writeFeatures_mark(self, doMark=True, doMkmk=True):
        """
        Write the mark and mkmk features to a string and return it.

        **This should not be called externally.** Subclasses
        may override this method to handle the string creation
        in a different way if desired.
        """

        writer = self.markWriter(
            self.font, self.anchorPairs, self.mkmkAnchorPairs,
            self.ligaAnchorPairs)
        return writer.write(doMark, doMkmk)

    def setupAnchorPairs(self):
        """
        Try to determine the base-accent anchor pairs to use in building the
        mark and mkmk features.

        **This should not be called externally.** Subclasses
        may override this method to set up the anchor pairs
        in a different way if desired.
        """

        self.anchorPairs = []
        self.ligaAnchorPairs = []

        anchorNames = set()
        for glyph in self.font:
            for anchor in glyph.anchors:
                if anchor.name is None:
                    logger.warning("Unnamed anchor discarded in %s", glyph.name)
                    continue
                anchorNames.add(anchor.name)

        for baseName in sorted(anchorNames):
            accentName = "_" + baseName
            if accentName in anchorNames:
                self.anchorPairs.append((baseName, accentName))

                ligaNames = []
                i = 1
                while True:
                    ligaName = "%s_%d" % (baseName, i)
                    if ligaName not in anchorNames:
                        break
                    ligaNames.append(ligaName)
                    i += 1
                if ligaNames:
                    self.ligaAnchorPairs.append((tuple(ligaNames), accentName))

        self.mkmkAnchorPairs = self.anchorPairs

    def _findLayoutFeatures(self):
        """Returns what OpenType layout feature tags are present in the UFO."""
        if self.font.path is None:
            return set()
        feapath = os.path.join(self.font.path, "features.fea")
        if not os.path.exists(feapath):
            return set()
        glyphMap = self.outline.getReverseGlyphMap()
        parser = feaLib.parser.Parser(feapath, glyphMap=glyphMap)
        doc = parser.parse()
        return {f.name for f in doc.statements
                if isinstance(f, feaLib.ast.FeatureBlock)}

    def setupFile_featureTables(self):
        """
        Compile and return OpenType feature tables from the source.
        Raises a FeaLibError if the feature compilation was unsuccessful.

        **This should not be called externally.** Subclasses
        may override this method to handle the table compilation
        in a different way if desired.
        """

        if self.mtiFeaFiles is not None:
            for tag, feapath in self.mtiFeaFiles.items():
                with open(feapath) as feafile:
                    table = mtiLib.build(feafile, self.outline)
                    assert table.tableTag == tag
                    self.outline[tag] = table

        elif self.features.strip():
            feapath = os.path.join(self.font.path, "features.fea") if self.font.path is not None else None
            addOpenTypeFeaturesFromString(self.outline, self.features,
                                          filename=feapath)
