Index: /packages/ufo2fdk/branches/ufo3/License.txt
===================================================================
--- /packages/ufo2fdk/branches/ufo3/License.txt	(revision 500)
+++ /packages/ufo2fdk/branches/ufo3/License.txt	(revision 500)
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2009 Type Supply LLC
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
Index: /packages/ufo2fdk/branches/ufo3/Install.txt
===================================================================
--- /packages/ufo2fdk/branches/ufo3/Install.txt	(revision 505)
+++ /packages/ufo2fdk/branches/ufo3/Install.txt	(revision 505)
@@ -0,0 +1,4 @@
+To install this package, type the following in the command line:
+
+  python setup.py install
+
Index: /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/kernFeatureWriter.py
===================================================================
--- /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/kernFeatureWriter.py	(revision 711)
+++ /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/kernFeatureWriter.py	(revision 711)
@@ -0,0 +1,453 @@
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+try:
+    sorted
+except NameError:
+    def sorted(l):
+        l = list(l)
+        l.sort()
+        return l
+
+inlineGroupInstance = (list, tuple, set)
+
+
+class KernFeatureWriter(object):
+
+    """
+    This object will create a kerning feature in FDK
+    syntax using the kerning in the given font. The
+    only external method is :meth:`ufo2fdk.tools.kernFeatureWriter.write`.
+
+    This object does what it can to create the best possible
+    kerning feature, but because it doesn't know anything
+    about how the raw kerning data was created, it has
+    to make some educated guesses about a few things. This
+    happens with regards to finding kerning groups that
+    are not referenced by any kerning pairs. This is only an
+    issue when attempting to decompose certain types of
+    exception pairs. The default implementation of this object
+    finds unreferenced groups in the ``getUnreferencedGroups``.
+    These groups will be studied when attempting to decompose
+    these special exceptions. This is as accurate as it can be,
+    but it is not foolproof. Passing a groupNamePrefix that
+    defines a prefix that all referenced kerning groups will
+    start with. If this is known, it will help remove the
+    ambiguities described above.
+    """
+
+    def __init__(self, font, groupNamePrefix=""):
+        self.font = font
+        self.groupNamePrefix = groupNamePrefix
+        self.leftGroups, self.rightGroups = self.getReferencedGroups()
+        self.leftUnreferencedGroups, self.rightUnreferencedGroups = self.getUnreferencedGroups()
+        self.pairs = self.getPairs()
+        self.flatLeftGroups, self.flatRightGroups, self.flatLeftUnreferencedGroups, self.flatRightUnreferencedGroups = self.getFlatGroups()
+
+    def write(self, headerText=None):
+        """
+        Write the feature text. If *headerText* is provided
+        it will inserted after the ``feature kern {`` line.
+        """
+        if not self.pairs:
+            return ""
+        glyphGlyph, glyphGroupDecomposed, groupGlyphDecomposed, glyphGroup, groupGlyph, groupGroup = self.getSeparatedPairs(self.pairs)
+        # write the classes
+        groups = dict(self.leftGroups)
+        groups.update(self.rightGroups)
+        for groupName, glyphList in groups.items():
+            if not glyphList:
+                del groups[groupName]
+        classes = self.getClassDefinitionsForGroups(groups)
+        # write the kerning rules
+        rules = []
+        order = [
+            ("# glyph, glyph", glyphGlyph),
+            ("# glyph, group exceptions", glyphGroupDecomposed),
+            ("# group exceptions, glyph", groupGlyphDecomposed),
+            ("# glyph, group", glyphGroup),
+            ("# group, glyph", groupGlyph),
+            ("# group, group", groupGroup),
+        ]
+        for note, pairs in order:
+            if pairs:
+                rules.append("")
+                rules.append(note)
+                rules += self.getFeatureRulesForPairs(pairs)
+        # compile
+        feature = ["feature kern {"]
+        if headerText:
+            for line in headerText.splitlines():
+                line = line.strip()
+                if not line.startswith("#"):
+                    line = "# " + line
+                line = "    " + line
+                feature.append(line)
+        for line in classes + rules:
+            if line:
+                line = "    " + line
+            feature.append(line)
+        feature.append("} kern;")
+        # done
+        return u"\n".join(feature)
+
+    # -------------
+    # Initial Setup
+    # -------------
+
+    def getReferencedGroups(self):
+        """
+        Get two dictionaries representing groups
+        referenced on the left and right of pairs.
+        You should not call this method directly.
+        """
+        leftReferencedGroups = set()
+        rightReferencedGroups = set()
+        groups = self.font.groups
+        for left, right in self.font.kerning.keys():
+            if left.startswith(self.groupNamePrefix) and left in groups:
+                leftReferencedGroups.add(left)
+            if right.startswith(self.groupNamePrefix) and right in groups:
+                rightReferencedGroups.add(right)
+        leftGroups = {}
+        for groupName in leftReferencedGroups:
+            glyphList = [glyphName for glyphName in groups[groupName] if glyphName in self.font]
+            glyphList = set(glyphList)
+            if not groupName.startswith("@"):
+                groupName = "@" + groupName
+            leftGroups[groupName] = glyphList
+        rightGroups = {}
+        for groupName in rightReferencedGroups:
+            glyphList = [glyphName for glyphName in groups[groupName] if glyphName in self.font]
+            glyphList = set(glyphList)
+            if not groupName.startswith("@"):
+                groupName = "@" + groupName
+            rightGroups[groupName] = glyphList
+        return leftGroups, rightGroups
+
+    def getUnreferencedGroups(self):
+        """
+        Get a dictionary representing kerning groups
+        that are not referenced in any kerning pairs.
+        You should not call this method directly.
+        """
+        # gather all glyphs that are already referenced
+        leftReferencedGlyphs = []
+        for glyphList in self.leftGroups.values():
+            leftReferencedGlyphs += glyphList
+        leftReferencedGlyphs = set(leftReferencedGlyphs)
+        rightReferencedGlyphs = []
+        for glyphList in self.rightGroups.values():
+            rightReferencedGlyphs += glyphList
+        rightReferencedGlyphs = set(rightReferencedGlyphs)
+        # find unreferenced groups
+        unreferencedLeftGroups = {}
+        unreferencedRightGroups = {}
+        for groupName, glyphList in sorted(self.font.groups.items()):
+            if not groupName.startswith("@") or not groupName.startswith(self.groupNamePrefix):
+                continue
+            if groupName in self.leftGroups:
+                continue
+            if groupName in self.rightGroups:
+                continue
+            glyphList = set(glyphList)
+            if not leftReferencedGlyphs & glyphList:
+                unreferencedLeftGroups[groupName] = glyphList
+                leftReferencedGlyphs = leftReferencedGlyphs | glyphList
+            if not rightReferencedGlyphs & glyphList:
+                unreferencedRightGroups[groupName] = glyphList
+                rightReferencedGlyphs = rightReferencedGlyphs | glyphList
+        return unreferencedLeftGroups, unreferencedRightGroups
+
+    def getPairs(self):
+        """
+        Get a dictionary containing all kerning pairs.
+        This should filter out pairs containing empty groups
+        and groups/glyphs that are not in the font.
+        You should not call this method directly.
+        """
+        pairs = {}
+        for (left, right), value in self.font.kerning.items():
+            # skip missing glyphs
+            if left not in self.font.groups and left not in self.font:
+                continue
+            if right not in self.font.groups and right not in self.font:
+                continue
+            # skip empty groups
+            if left.startswith(self.groupNamePrefix) and left in self.font.groups and not self.font.groups[left]:
+                continue
+            if right.startswith(self.groupNamePrefix) and right in self.font.groups and not self.font.groups[right]:
+                continue
+            # store pair
+            if left.startswith(self.groupNamePrefix) and left in self.font.groups:
+                if not left.startswith("@"):
+                    left = "@" + left
+            if right.startswith(self.groupNamePrefix) and right in self.font.groups:
+                if not right.startswith("@"):
+                    right = "@" + right
+            pairs[left, right] = value
+        return pairs
+
+    def getFlatGroups(self):
+        """
+        Get three dictionaries keyed by glyph names with
+        group names as values for left, right and
+        unreferenced groups. You should not call this
+        method directly.
+        """
+        flatLeftGroups = {}
+        flatRightGroups = {}
+        for groupName, glyphList in self.leftGroups.items():
+            for glyphName in glyphList:
+                # user has glyph in more than one group.
+                # this is not allowed.
+                if glyphName in flatLeftGroups:
+                    continue
+                flatLeftGroups[glyphName] = groupName
+        for groupName, glyphList in self.rightGroups.items():
+            for glyphName in glyphList:
+                # user has glyph in more than one group.
+                # this is not allowed.
+                if glyphName in flatRightGroups:
+                    continue
+                flatRightGroups[glyphName] = groupName
+        flatLeftUnreferencedGroups = {}
+        for groupName, glyphList in self.leftUnreferencedGroups.items():
+            for glyphName in glyphList:
+                flatLeftUnreferencedGroups[glyphName] = groupName
+        flatRightUnreferencedGroups = {}
+        for groupName, glyphList in self.rightUnreferencedGroups.items():
+            for glyphName in glyphList:
+                flatRightUnreferencedGroups[glyphName] = groupName
+        return flatLeftGroups, flatRightGroups, flatLeftUnreferencedGroups, flatRightUnreferencedGroups
+
+    # ------------
+    # Pair Support
+    # ------------
+
+    def isHigherLevelPairPossible(self, (left, right)):
+        """
+        Determine if there is a higher level pair possible.
+        This doesn't indicate that the pair exists, it simply
+        indicates that something higher than (left, right)
+        can exist.
+        You should not call this method directly.
+        """
+        leftInUnreferenced = False
+        rightInUnreferenced = False
+        if left.startswith("@"):
+            leftGroup = left
+            leftGlyph = None
+        else:
+            leftGroup = self.flatLeftGroups.get(left)
+            leftGlyph = left
+            if leftGroup is None and left in self.flatLeftUnreferencedGroups:
+                leftInUnreferenced= True
+        if right.startswith("@"):
+            rightGroup = right
+            rightGlyph = None
+        else:
+            rightGroup = self.flatRightGroups.get(right)
+            rightGlyph = right
+            if rightGroup is None and right in self.flatRightUnreferencedGroups:
+                rightInUnreferenced = True
+
+        havePotentialHigherLevelPair = False
+        if left.startswith("@") and right.startswith("@"):
+            pass
+        elif left.startswith("@"):
+            if rightGroup is not None or rightInUnreferenced:
+                if (left, right) in self.pairs:
+                    havePotentialHigherLevelPair = True
+        elif right.startswith("@"):
+            if leftGroup is not None or leftInUnreferenced:
+                if (left, right) in self.pairs:
+                    havePotentialHigherLevelPair = True
+        else:
+            if leftGroup is not None and rightGroup is not None:
+                if (leftGlyph, rightGlyph) in self.pairs:
+                    havePotentialHigherLevelPair = True
+                elif (leftGroup, rightGlyph) in self.pairs:
+                    havePotentialHigherLevelPair = True
+                elif (leftGlyph, rightGroup) in self.pairs:
+                    havePotentialHigherLevelPair = True
+            elif leftGroup is not None:
+                if (leftGlyph, rightGlyph) in self.pairs:
+                    havePotentialHigherLevelPair = True
+            elif rightGroup is not None:
+                if (leftGlyph, rightGlyph) in self.pairs:
+                    havePotentialHigherLevelPair = True
+        return havePotentialHigherLevelPair
+
+    def getSeparatedPairs(self, pairs):
+        """
+        Organize *pair* into the following groups:
+
+        * glyph, glyph
+        * glyph, group (decomposed)
+        * group, glyph (decomposed)
+        * glyph, group
+        * group, glyph
+        * group, group
+
+        You should not call this method directly.
+        """
+        ## seperate pairs
+        glyphGlyph = {}
+        glyphGroup = {}
+        glyphGroupDecomposed = {}
+        groupGlyph = {}
+        groupGlyphDecomposed = {}
+        groupGroup = {}
+        for (left, right), value in pairs.items():
+            if left.startswith("@") and right.startswith("@"):
+                groupGroup[left, right] = value
+            elif left.startswith("@"):
+                groupGlyph[left, right] = value
+            elif right.startswith("@"):
+                glyphGroup[left, right] = value
+            else:
+                glyphGlyph[left, right] = value
+        ## handle decomposition
+        allGlyphGlyph = set(glyphGlyph.keys())
+        # glyph to group
+        for (left, right), value in glyphGroup.items():
+            if self.isHigherLevelPairPossible((left, right)):
+                finalRight = tuple([r for r in sorted(self.rightGroups[right]) if (left, r) not in allGlyphGlyph])
+                for r in finalRight:
+                    allGlyphGlyph.add((left, r))
+                glyphGroupDecomposed[left, finalRight] = value
+                del glyphGroup[left, right]
+        # group to glyph
+        for (left, right), value in groupGlyph.items():
+            if self.isHigherLevelPairPossible((left, right)):
+                finalLeft = tuple([l for l in sorted(self.leftGroups[left]) if (l, right) not in glyphGlyph and (l, right) not in allGlyphGlyph])
+                for l in finalLeft:
+                    allGlyphGlyph.add((l, right))
+                groupGlyphDecomposed[finalLeft, right] = value
+                del groupGlyph[left, right]
+        ## return the result
+        return glyphGlyph, glyphGroupDecomposed, groupGlyphDecomposed, glyphGroup, groupGlyph, groupGroup
+
+    # -------------
+    # Write Support
+    # -------------
+
+    def getClassDefinitionsForGroups(self, groups):
+        """
+        Write class definitions to a list of strings.
+        You should not call this method directly.
+        """
+        classes = []
+        for groupName in sorted(groups.keys()):
+            group = groups[groupName]
+            l = "%s = [%s];" % (groupName, " ".join(sorted(group)))
+            classes.append(l)
+        return classes
+
+    def getFeatureRulesForPairs(self, pairs):
+        """
+        Write pair rules to a list of strings.
+        You should not call this method directly.
+        """
+        rules = []
+        for (left, right), value in sorted(pairs.items()):
+            if not left or not right:
+                continue
+            if isinstance(left, inlineGroupInstance) or isinstance(right, inlineGroupInstance):
+                line = "enum pos %s %s %d;"
+            else:
+                line = "pos %s %s %d;"
+            if isinstance(left, inlineGroupInstance):
+                left = "[%s]" % " ".join(sorted(left))
+            if isinstance(right, inlineGroupInstance):
+                right = "[%s]" % " ".join(sorted(right))
+            rules.append(line % (left, right, value))
+        return rules
+
+
+# ----
+# Test
+# ----
+
+
+def _test():
+    """
+    >>> from fontTools.agl import AGL2UV
+    >>> from defcon import Font
+    >>> font = Font()
+    >>> for glyphName in AGL2UV:
+    ...     font.newGlyph(glyphName)
+    >>> kerning = {
+    ...     # various pair types
+    ...     ("Agrave", "Agrave") : -100,
+    ...     ("@LEFT_A", "Agrave") : -75,
+    ...     ("@LEFT_A", "Aacute") : -74,
+    ...     ("eight", "@RIGHT_B") : -49,
+    ...     ("@LEFT_A", "@RIGHT_A") : -25,
+    ...     ("@LEFT_D", "X") : -25,
+    ...     ("X", "@RIGHT_D") : -25,
+    ...     # empty groups
+    ...     ("@LEFT_C", "@RIGHT_C") : 25,
+    ...     ("C", "@RIGHT_C") : 25,
+    ...     ("@LEFT_C", "C") : 25,
+    ...     # nonexistant glyphs
+    ...     ("NotInFont", "NotInFont") : 25,
+    ...     # nonexistant groups
+    ...     ("@LEFT_NotInFont", "@RIGHT_NotInFont") : 25,
+    ... }
+    >>> groups = {
+    ...     "@LEFT_A" : ["A", "Aacute", "Agrave"],
+    ...     "@RIGHT_A" : ["A", "Aacute", "Agrave"],
+    ...     "@LEFT_B" : ["B", "eight"],
+    ...     "@RIGHT_B" : ["B", "eight"],
+    ...     "@LEFT_C" : [],
+    ...     "@RIGHT_C" : [],
+    ...     "@LEFT_D" : ["D"],
+    ...     "@RIGHT_D" : ["D"],
+    ... }
+    >>> font.groups.update(groups)
+    >>> font.kerning.update(kerning)
+
+    >>> writer = KernFeatureWriter(font)
+    >>> text = writer.write()
+    >>> t1 = [line.strip() for line in text.strip().splitlines()]
+    >>> t2 = [line.strip() for line in _expectedFeatureText.strip().splitlines()]
+    >>> t1 == t2
+    True
+    """
+
+_expectedFeatureText = """
+feature kern {
+    @LEFT_A = [A Aacute Agrave];
+    @LEFT_D = [D];
+    @RIGHT_A = [A Aacute Agrave];
+    @RIGHT_B = [B eight];
+    @RIGHT_D = [D];
+
+    # glyph, glyph
+    pos Agrave Agrave -100;
+
+    # glyph, group exceptions
+    enum pos eight [B eight] -49;
+
+    # group exceptions, glyph
+    enum pos [A Aacute] Agrave -75;
+    enum pos [A Aacute Agrave] Aacute -74;
+
+    # glyph, group
+    pos X @RIGHT_D -25;
+
+    # group, glyph
+    pos @LEFT_D X -25;
+
+    # group, group
+    pos @LEFT_A @RIGHT_A -25;
+} kern;
+"""
+
+if __name__ == "__main__":
+    import doctest
+    doctest.testmod()
Index: /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/outlineOTF.py
===================================================================
--- /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/outlineOTF.py	(revision 1066)
+++ /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/outlineOTF.py	(revision 1066)
@@ -0,0 +1,705 @@
+from __future__ import division
+import time
+from fontTools.ttLib import TTFont, newTable
+from fontTools.cffLib import TopDictIndex, TopDict, CharStrings, SubrsIndex, GlobalSubrsIndex, PrivateDict, IndexedStrings
+from fontTools.ttLib.tables.O_S_2f_2 import Panose
+from fontTools.ttLib.tables._h_e_a_d import mac_epoch_diff
+from pens.t2CharStringPen import T2CharStringPen
+from fontInfoData import getFontBounds, getAttrWithFallback, dateStringToTimeValue, dateStringForNow, intListToNum, normalizeNameForPostscript
+try:
+    set
+except NameError:
+    from sets import Set as set
+try:
+    sorted
+except NameError:
+    def sorted(l):
+        l = list(l)
+        l.sort()
+        return l
+
+def _roundInt(v):
+    return int(round(v))
+
+
+class OutlineOTFCompiler(object):
+
+    """
+    This object will create a bare-bones OTF-CFF containing
+    outline data and not much else. The only external
+    method is :meth:`ufo2fdk.tools.outlineOTF.compile`.
+
+    When creating this object, you must provide a *font*
+    object and a *path* indicating where the OTF should
+    be saved. Optionally, you can provide a *glyphOrder*
+    list of glyph names indicating the order of the glyphs
+    in the font.
+    """
+
+    def __init__(self, font, path, glyphOrder=None):
+        self.ufo = font
+        self.path = path
+        # make any missing glyphs and store them locally
+        missingRequiredGlyphs = self.makeMissingRequiredGlyphs()
+        # make a dict of all glyphs
+        self.allGlyphs = {}
+        for glyph in font:
+            self.allGlyphs[glyph.name] = glyph
+        self.allGlyphs.update(missingRequiredGlyphs)
+        # store the glyph order
+        if glyphOrder is None:
+            glyphOrder = sorted(self.allGlyphs.keys())
+        self.glyphOrder = self.makeOfficialGlyphOrder(glyphOrder)
+        # make a reusable bounding box
+        self.fontBoundingBox = tuple([_roundInt(i) for i in self.makeFontBoundingBox()])
+        # make a reusable character mapping
+        self.unicodeToGlyphNameMapping = self.makeUnicodeToGlyphNameMapping()
+
+    # -----------
+    # Main Method
+    # -----------
+
+    def compile(self):
+        """
+        Compile the OTF.
+        """
+        self.otf = TTFont(sfntVersion="OTTO")
+        # populate basic tables
+        self.setupTable_head()
+        self.setupTable_hhea()
+        self.setupTable_hmtx()
+        self.setupTable_name()
+        self.setupTable_maxp()
+        self.setupTable_cmap()
+        self.setupTable_OS2()
+        self.setupTable_post()
+        self.setupTable_CFF()
+        self.setupOtherTables()
+        # write the file
+        self.otf.save(self.path)
+        # discard the object
+        self.otf.close()
+        del self.otf
+
+    # -----
+    # Tools
+    # -----
+
+    def makeFontBoundingBox(self):
+        """
+        Make a bounding box for the font.
+
+        **This should not be called externally.** Subclasses
+        may override this method to handle the bounds creation
+        in a different way if desired.
+        """
+        return getFontBounds(self.ufo)
+
+    def makeUnicodeToGlyphNameMapping(self):
+        """
+        Make a ``unicode : glyph name`` mapping for the font.
+
+        **This should not be called externally.** Subclasses
+        may override this method to handle the mapping creation
+        in a different way if desired.
+        """
+        mapping = {}
+        for glyphName, glyph in self.allGlyphs.items():
+            unicodes = glyph.unicodes
+            for uni in unicodes:
+                mapping[uni] = glyphName
+        return mapping
+
+    def makeMissingRequiredGlyphs(self):
+        """
+        Add space and .notdef to the font if they are not present.
+
+        **This should not be called externally.** Subclasses
+        may override this method to handle the glyph creation
+        in a different way if desired.
+        """
+        glyphs = {}
+        font = self.ufo
+        unitsPerEm = _roundInt(getAttrWithFallback(font.info, "unitsPerEm"))
+        ascender = _roundInt(getAttrWithFallback(font.info, "ascender"))
+        descender = _roundInt(getAttrWithFallback(font.info, "descender"))
+        defaultWidth = _roundInt(unitsPerEm * 0.5)
+        if ".notdef" not in self.ufo:
+            glyphs[".notdef"] = StubGlyph(name=".notdef", width=defaultWidth, unitsPerEm=unitsPerEm, ascender=ascender, descender=descender)
+        if "space" not in self.ufo:
+            glyphs["space"] = StubGlyph(name="space", width=defaultWidth, unitsPerEm=unitsPerEm, ascender=ascender, descender=descender, unicodes=[32])
+        return glyphs
+
+    def makeOfficialGlyphOrder(self, glyphOrder):
+        """
+        Make a the final glyph order.
+
+        **This should not be called externally.** Subclasses
+        may override this method to handle the order creation
+        in a different way if desired.
+        """
+        allGlyphs = self.allGlyphs
+        orderedGlyphs = [".notdef", "space"]
+        for glyphName in glyphOrder:
+            if glyphName in [".notdef", "space"]:
+                continue
+            orderedGlyphs.append(glyphName)
+        for glyphName in sorted(self.allGlyphs.keys()):
+            if glyphName not in orderedGlyphs:
+                orderedGlyphs.append(glyphName)
+        return orderedGlyphs
+
+    def getCharStringForGlyph(self, glyph, private, globalSubrs):
+        """
+        Get a Type2CharString for the *glyph*
+
+        **This should not be called externally.** Subclasses
+        may override this method to handle the charstring creation
+        in a different way if desired.
+        """
+        width = glyph.width
+        # subtract the nominal width
+        postscriptNominalWidthX = getAttrWithFallback(self.ufo.info, "postscriptNominalWidthX")
+        if postscriptNominalWidthX:
+            width = width - postscriptNominalWidthX
+        # round
+        width = _roundInt(width)
+        pen = T2CharStringPen(width, self.allGlyphs)
+        glyph.draw(pen)
+        charString = pen.getCharString(private, globalSubrs)
+        return charString
+
+    # --------------
+    # Table Builders
+    # --------------
+
+    def setupTable_head(self):
+        """
+        Make the head table.
+
+        **This should not be called externally.** Subclasses
+        may override or supplement this method to handle the
+        table creation in a different way if desired.
+        """
+        self.otf["head"] = head = newTable("head")
+        font = self.ufo
+        head.checkSumAdjustment = 0
+        head.tableVersion = 1.0
+        versionMajor = getAttrWithFallback(font.info, "versionMajor")
+        versionMinor = getAttrWithFallback(font.info, "versionMinor") * .001
+        head.fontRevision = versionMajor + versionMinor
+        head.magicNumber = 0x5F0F3CF5
+        # upm
+        head.unitsPerEm = getAttrWithFallback(font.info, "unitsPerEm")
+        # times
+        head.created = dateStringToTimeValue(getAttrWithFallback(font.info, "openTypeHeadCreated")) - mac_epoch_diff
+        head.modified = dateStringToTimeValue(dateStringForNow()) - mac_epoch_diff
+        # bounding box
+        xMin, yMin, xMax, yMax = self.fontBoundingBox
+        head.xMin = xMin
+        head.yMin = yMin
+        head.xMax = xMax
+        head.yMax = yMax
+        # style mapping
+        styleMapStyleName = getAttrWithFallback(font.info, "styleMapStyleName")
+        macStyle = []
+        if styleMapStyleName == "bold":
+            macStyle = [0]
+        elif styleMapStyleName == "bold italic":
+            macStyle = [0, 1]
+        elif styleMapStyleName == "italic":
+            macStyle = [1]
+        head.macStyle = intListToNum(macStyle, 0, 16)
+        # misc
+        head.flags = intListToNum(getAttrWithFallback(font.info, "openTypeHeadFlags"), 0, 16)
+        head.lowestRecPPEM = _roundInt(getAttrWithFallback(font.info, "openTypeHeadLowestRecPPEM"))
+        head.fontDirectionHint = 2
+        head.indexToLocFormat = 0
+        head.glyphDataFormat = 0
+
+    def setupTable_name(self):
+        """
+        Make the name table.
+
+        **This should not be called externally.** Subclasses
+        may override or supplement this method to handle the
+        table creation in a different way if desired.
+        """
+        self.otf["name"] = newTable("name")
+
+    def setupTable_maxp(self):
+        """
+        Make the maxp table.
+
+        **This should not be called externally.** Subclasses
+        may override or supplement this method to handle the
+        table creation in a different way if desired.
+        """
+        self.otf["maxp"] = maxp = newTable("maxp")
+        maxp.tableVersion = 0x00005000
+
+    def setupTable_cmap(self):
+        """
+        Make the cmap table.
+
+        **This should not be called externally.** Subclasses
+        may override or supplement this method to handle the
+        table creation in a different way if desired.
+        """
+        from fontTools.ttLib.tables._c_m_a_p import cmap_format_4
+        # mac
+        cmap4_0_3 = cmap_format_4(4)
+        cmap4_0_3.platformID = 0
+        cmap4_0_3.platEncID = 3
+        cmap4_0_3.language = 0
+        cmap4_0_3.cmap = dict(self.unicodeToGlyphNameMapping)
+        # windows
+        cmap4_3_1 = cmap_format_4(4)
+        cmap4_3_1.platformID = 3
+        cmap4_3_1.platEncID = 1
+        cmap4_3_1.language = 0
+        cmap4_3_1.cmap = dict(self.unicodeToGlyphNameMapping)
+        # store
+        self.otf["cmap"] = cmap = newTable("cmap")
+        cmap.tableVersion = 0
+        cmap.tables = [cmap4_0_3, cmap4_3_1]
+
+    def setupTable_OS2(self):
+        """
+        Make the OS/2 table.
+
+        **This should not be called externally.** Subclasses
+        may override or supplement this method to handle the
+        table creation in a different way if desired.
+        """
+        self.otf["OS/2"] = os2 = newTable("OS/2")
+        font = self.ufo
+        os2.version = 0x0004
+        # average glyph width
+        widths = [glyph.width for glyph in self.allGlyphs.values() if glyph.width > 0]
+        os2.xAvgCharWidth = _roundInt(sum(widths) / len(widths))
+        # weight and width classes
+        os2.usWeightClass = getAttrWithFallback(font.info, "openTypeOS2WeightClass")
+        os2.usWidthClass = getAttrWithFallback(font.info, "openTypeOS2WidthClass")
+        # embedding
+        os2.fsType = intListToNum(getAttrWithFallback(font.info, "openTypeOS2Type"), 0, 16)
+        # subscript
+        v = getAttrWithFallback(font.info, "openTypeOS2SubscriptXSize")
+        if v is None:
+            v = 0
+        os2.ySubscriptXSize = _roundInt(v)
+        v = getAttrWithFallback(font.info, "openTypeOS2SubscriptYSize")
+        if v is None:
+            v = 0
+        os2.ySubscriptYSize = _roundInt(v)
+        v = getAttrWithFallback(font.info, "openTypeOS2SubscriptXOffset")
+        if v is None:
+            v = 0
+        os2.ySubscriptXOffset = _roundInt(v)
+        v = getAttrWithFallback(font.info, "openTypeOS2SubscriptYOffset")
+        if v is None:
+            v = 0
+        os2.ySubscriptYOffset = _roundInt(v)
+        # superscript
+        v = getAttrWithFallback(font.info, "openTypeOS2SuperscriptXSize")
+        if v is None:
+            v = 0
+        os2.ySuperscriptXSize = _roundInt(v)
+        v = getAttrWithFallback(font.info, "openTypeOS2SuperscriptYSize")
+        if v is None:
+            v = 0
+        os2.ySuperscriptYSize = _roundInt(v)
+        v = getAttrWithFallback(font.info, "openTypeOS2SuperscriptXOffset")
+        if v is None:
+            v = 0
+        os2.ySuperscriptXOffset = _roundInt(v)
+        v = getAttrWithFallback(font.info, "openTypeOS2SuperscriptYOffset")
+        if v is None:
+            v = 0
+        os2.ySuperscriptYOffset = _roundInt(v)
+        # strikeout
+        v = getAttrWithFallback(font.info, "openTypeOS2StrikeoutSize")
+        if v is None:
+            v = 0
+        os2.yStrikeoutSize = _roundInt(v)
+        v = getAttrWithFallback(font.info, "openTypeOS2StrikeoutPosition")
+        if v is None:
+            v = 0
+        os2.yStrikeoutPosition = _roundInt(v)
+        # family class
+        os2.sFamilyClass = 0 # XXX not sure how to create the appropriate value
+        # panose
+        data = getAttrWithFallback(font.info, "openTypeOS2Panose")
+        panose = Panose()
+        panose.bFamilyType = data[0]
+        panose.bSerifStyle = data[1]
+        panose.bWeight = data[2]
+        panose.bProportion = data[3]
+        panose.bContrast = data[4]
+        panose.bStrokeVariation = data[5]
+        panose.bArmStyle = data[6]
+        panose.bLetterForm = data[7]
+        panose.bMidline = data[8]
+        panose.bXHeight = data[9]
+        os2.panose = panose
+        # Unicode ranges
+        uniRanges = getAttrWithFallback(font.info, "openTypeOS2UnicodeRanges")
+        os2.ulUnicodeRange1 = intListToNum(uniRanges, 0, 32)
+        os2.ulUnicodeRange2 = intListToNum(uniRanges, 32, 32)
+        os2.ulUnicodeRange3 = intListToNum(uniRanges, 64, 32)
+        os2.ulUnicodeRange4 = intListToNum(uniRanges, 96, 32)
+        # codepage ranges
+        codepageRanges = getAttrWithFallback(font.info, "openTypeOS2CodePageRanges")
+        os2.ulCodePageRange1 = intListToNum(codepageRanges, 0, 32)
+        os2.ulCodePageRange2 = intListToNum(codepageRanges, 32, 32)
+        # vendor id
+        os2.achVendID = getAttrWithFallback(font.info, "openTypeOS2VendorID")
+        # vertical metrics
+        os2.sxHeight = _roundInt(getAttrWithFallback(font.info, "xHeight"))
+        os2.sCapHeight = _roundInt(getAttrWithFallback(font.info, "capHeight"))
+        os2.sTypoAscender = _roundInt(getAttrWithFallback(font.info, "openTypeOS2TypoAscender"))
+        os2.sTypoDescender = _roundInt(getAttrWithFallback(font.info, "openTypeOS2TypoDescender"))
+        os2.sTypoLineGap = _roundInt(getAttrWithFallback(font.info, "openTypeOS2TypoLineGap"))
+        os2.usWinAscent = _roundInt(getAttrWithFallback(font.info, "openTypeOS2WinAscent"))
+        os2.usWinDescent = _roundInt(getAttrWithFallback(font.info, "openTypeOS2WinDescent"))
+        # style mapping
+        selection = list(getAttrWithFallback(font.info, "openTypeOS2Selection"))
+        styleMapStyleName = getAttrWithFallback(font.info, "styleMapStyleName")
+        if styleMapStyleName == "regular":
+            selection.append(6)
+        elif styleMapStyleName == "bold":
+            selection.append(5)
+        elif styleMapStyleName == "italic":
+            selection.append(0)
+        elif styleMapStyleName == "bold italic":
+            selection += [0, 5]
+        os2.fsSelection = intListToNum(selection, 0, 16)
+        # characetr indexes
+        unicodes = [i for i in self.unicodeToGlyphNameMapping.keys() if i is not None]
+        if unicodes:
+            minIndex = min(unicodes)
+            maxIndex = max(unicodes)
+        else:
+            # the font may have *no* unicode values
+            # (it really happens!) so there needs
+            # to be a fallback. use space for this.
+            minIndex = 0x0020
+            maxIndex = 0x0020
+        if maxIndex > 0xFFFF:
+            # the spec says that 0xFFFF should be used
+            # as the max if the max exceeds 0xFFFF
+            maxIndex = 0xFFFF
+        os2.fsFirstCharIndex = minIndex
+        os2.fsLastCharIndex = maxIndex
+        os2.usBreakChar = 32
+        os2.usDefaultChar = 0
+        # maximum contextual lookup length
+        os2.usMaxContex = 0
+
+    def setupTable_hmtx(self):
+        """
+        Make the hmtx table.
+
+        **This should not be called externally.** Subclasses
+        may override or supplement this method to handle the
+        table creation in a different way if desired.
+        """
+        self.otf["hmtx"] = hmtx = newTable("hmtx")
+        hmtx.metrics = {}
+        for glyphName, glyph in self.allGlyphs.items():
+            width = glyph.width
+            left = 0
+            if len(glyph) or len(glyph.components):
+                left = glyph.leftMargin
+            if left is None:
+                left = 0
+            hmtx[glyphName] = (_roundInt(width), _roundInt(left))
+
+    def setupTable_hhea(self):
+        """
+        Make the hhea table.
+
+        **This should not be called externally.** Subclasses
+        may override or supplement this method to handle the
+        table creation in a different way if desired.
+        """
+        self.otf["hhea"] = hhea = newTable("hhea")
+        font = self.ufo
+        hhea.tableVersion = 1.0
+        # vertical metrics
+        hhea.ascent = _roundInt(getAttrWithFallback(font.info, "openTypeHheaAscender"))
+        hhea.descent = _roundInt(getAttrWithFallback(font.info, "openTypeHheaDescender"))
+        hhea.lineGap = _roundInt(getAttrWithFallback(font.info, "openTypeHheaLineGap"))
+        # horizontal metrics
+        widths = []
+        lefts = []
+        rights = []
+        extents = []
+        for glyph in self.allGlyphs.values():
+            left = glyph.leftMargin
+            right = glyph.rightMargin
+            if left is None:
+                left = 0
+            if right is None:
+                right = 0
+            widths.append(glyph.width)
+            lefts.append(left)
+            rights.append(right)
+            # robofab
+            if hasattr(glyph, "box"):
+                bounds = glyph.box
+            # others
+            else:
+                bounds = glyph.bounds
+            if bounds is not None:
+                xMin, yMin, xMax, yMax = bounds
+            else:
+                xMin = 0
+                xMax = 0
+            extent = left + (xMax - xMin) # equation from spec for calculating xMaxExtent: Max(lsb + (xMax - xMin))
+            extents.append(extent)
+        hhea.advanceWidthMax = _roundInt(max(widths))
+        hhea.minLeftSideBearing = _roundInt(min(lefts))
+        hhea.minRightSideBearing = _roundInt(min(rights))
+        hhea.xMaxExtent = _roundInt(max(extents))
+        # misc
+        hhea.caretSlopeRise = getAttrWithFallback(font.info, "openTypeHheaCaretSlopeRise")
+        hhea.caretSlopeRun = getAttrWithFallback(font.info, "openTypeHheaCaretSlopeRun")
+        hhea.caretOffset = _roundInt(getAttrWithFallback(font.info, "openTypeHheaCaretOffset"))
+        hhea.reserved0 = 0
+        hhea.reserved1 = 0
+        hhea.reserved2 = 0
+        hhea.reserved3 = 0
+        hhea.metricDataFormat = 0
+        # glyph count
+        hhea.numberOfHMetrics = len(self.allGlyphs)
+
+    def setupTable_post(self):
+        """
+        Make the post table.
+
+        **This should not be called externally.** Subclasses
+        may override or supplement this method to handle the
+        table creation in a different way if desired.
+        """
+        self.otf["post"] = post = newTable("post")
+        font = self.ufo
+        post.formatType = 3.0
+        # italic angle
+        italicAngle = getAttrWithFallback(font.info, "italicAngle")
+        post.italicAngle = italicAngle
+        # underline
+        underlinePosition = getAttrWithFallback(font.info, "postscriptUnderlinePosition")
+        if underlinePosition is None:
+            underlinePosition = 0
+        post.underlinePosition = _roundInt(underlinePosition)
+        underlineThickness = getAttrWithFallback(font.info, "postscriptUnderlineThickness")
+        if underlineThickness is None:
+            underlineThickness = 0
+        post.underlineThickness = _roundInt(underlineThickness)
+        # determine if the font has a fixed width
+        widths = set([glyph.width for glyph in self.allGlyphs.values()])
+        post.isFixedPitch = getAttrWithFallback(font.info, "postscriptIsFixedPitch")
+        # misc
+        post.minMemType42 = 0
+        post.maxMemType42 = 0
+        post.minMemType1 = 0
+        post.maxMemType1 = 0
+
+    def setupTable_CFF(self):
+        """
+        Make the CFF table.
+
+        **This should not be called externally.** Subclasses
+        may override or supplement this method to handle the
+        table creation in a different way if desired.
+        """
+        self.otf["CFF "] = cff = newTable("CFF ")
+        cff = cff.cff
+        # set up the basics
+        cff.major = 1
+        cff.minor = 0
+        cff.hdrSize = 4
+        cff.offSize = 4
+        cff.fontNames = []
+        strings = IndexedStrings()
+        cff.strings = strings
+        private = PrivateDict(strings=strings)
+        private.rawDict.update(private.defaults)
+        globalSubrs = GlobalSubrsIndex(private=private)
+        topDict = TopDict(GlobalSubrs=globalSubrs, strings=strings)
+        topDict.Private = private
+        charStrings = topDict.CharStrings = CharStrings(file=None, charset=None,
+            globalSubrs=globalSubrs, private=private, fdSelect=None, fdArray=None)
+        charStrings.charStringsAreIndexed = True
+        topDict.charset = []
+        charStringsIndex = charStrings.charStringsIndex = SubrsIndex(private=private, globalSubrs=globalSubrs)
+        cff.topDictIndex = topDictIndex = TopDictIndex()
+        topDictIndex.append(topDict)
+        topDictIndex.strings = strings
+        cff.GlobalSubrs = globalSubrs
+        # populate naming data
+        info = self.ufo.info
+        psName = getAttrWithFallback(info, "postscriptFontName")
+        cff.fontNames.append(psName)
+        topDict = cff.topDictIndex[0]
+        topDict.FullName = getAttrWithFallback(info, "postscriptFullName")
+        topDict.FamilyName = normalizeNameForPostscript(getAttrWithFallback(info, "openTypeNamePreferredFamilyName"))
+        topDict.Weight = getAttrWithFallback(info, "postscriptWeightName")
+        topDict.FontName = getAttrWithFallback(info, "postscriptFontName")
+        # populate font matrix
+        unitsPerEm = _roundInt(getAttrWithFallback(info, "unitsPerEm"))
+        topDict.FontMatrix = [1.0 / unitsPerEm, 0, 0, 1.0 / unitsPerEm, 0, 0]
+        # populate the width values
+        topDict.defaultWidthX = _roundInt(getAttrWithFallback(info, "postscriptDefaultWidthX"))
+        topDict.nominalWidthX = _roundInt(getAttrWithFallback(info, "postscriptNominalWidthX"))
+        # populate hint data
+        blueFuzz = _roundInt(getAttrWithFallback(info, "postscriptBlueFuzz"))
+        blueShift = _roundInt(getAttrWithFallback(info, "postscriptBlueShift"))
+        blueScale = getAttrWithFallback(info, "postscriptBlueScale")
+        forceBold = getAttrWithFallback(info, "postscriptForceBold")
+        blueValues = getAttrWithFallback(info, "postscriptBlueValues")
+        if isinstance(blueValues, list):
+            blueValues = [_roundInt(i) for i in blueValues]
+        otherBlues = getAttrWithFallback(info, "postscriptOtherBlues")
+        if isinstance(otherBlues, list):
+            otherBlues = [_roundInt(i) for i in otherBlues]
+        familyBlues = getAttrWithFallback(info, "postscriptFamilyBlues")
+        if isinstance(familyBlues, list):
+            familyBlues = [_roundInt(i) for i in familyBlues]
+        familyOtherBlues = getAttrWithFallback(info, "postscriptFamilyOtherBlues")
+        if isinstance(familyOtherBlues, list):
+            familyOtherBlues = [_roundInt(i) for i in familyOtherBlues]
+        stemSnapH = getAttrWithFallback(info, "postscriptStemSnapH")
+        if isinstance(stemSnapH, list):
+            stemSnapH = [_roundInt(i) for i in stemSnapH]
+        stemSnapV = getAttrWithFallback(info, "postscriptStemSnapV")
+        if isinstance(stemSnapV, list):
+            stemSnapV = [_roundInt(i) for i in stemSnapV]
+        # only write the blues data if some blues are defined.
+        if (blueValues or otherBlues):
+            private.rawDict["BlueFuzz"] = blueFuzz
+            private.rawDict["BlueShift"] = blueShift
+            private.rawDict["BlueScale"] = blueScale
+            private.rawDict["ForceBold"] = forceBold
+            private.rawDict["BlueValues"] = blueValues
+            private.rawDict["OtherBlues"] = otherBlues
+            private.rawDict["FamilyBlues"] = familyBlues
+            private.rawDict["FamilyOtherBlues"] = familyOtherBlues
+        # only write the stems if both are defined.
+        if (stemSnapH and stemSnapV):
+            private.rawDict["StemSnapH"] = stemSnapH
+            private.rawDict["StdHW"] = stemSnapH[0]
+            private.rawDict["StemSnapV"] = stemSnapV
+            private.rawDict["StdVW"] = stemSnapV[0]
+        # populate glyphs
+        for glyphName in self.glyphOrder:
+            glyph = self.allGlyphs[glyphName]
+            unicodes = glyph.unicodes
+            charString = self.getCharStringForGlyph(glyph, private, globalSubrs)
+            # add to the font
+            exists = charStrings.has_key(glyphName)
+            if exists:
+                # XXX a glyph already has this name. should we choke?
+                glyphID = charStrings.charStrings[glyphName]
+                charStringsIndex.items[glyphID] = charString
+            else:
+                charStringsIndex.append(charString)
+                glyphID = len(topDict.charset)
+                charStrings.charStrings[glyphName] = glyphID
+                topDict.charset.append(glyphName)
+        topDict.FontBBox = self.fontBoundingBox
+        # write the glyph order
+        self.otf.setGlyphOrder(self.glyphOrder)
+
+    def setupOtherTables(self):
+        """
+        Make the other tables. The default implementation does nothing.
+
+        **This should not be called externally.** Subclasses
+        may override this method to add other tables to the
+        font if desired.
+        """
+        pass
+
+
+class StubGlyph(object):
+
+    """
+    This object will be used to create missing glyphs
+    (specifically the space and the .notdef) in the
+    provided UFO.
+    """
+
+    def __init__(self, name, width, unitsPerEm, ascender, descender, unicodes=[]):
+        self.name = name
+        self.width = width
+        self.unitsPerEm = unitsPerEm
+        self.ascender = ascender
+        self.descender = descender
+        self.unicodes = unicodes
+        self.components = []
+        if unicodes:
+            self.unicode = unicodes[0]
+        else:
+            self.unicode = None
+        if name == ".notdef":
+            self.draw = self._drawDefaultNotdef
+
+    def __len__(self):
+        if self.name == ".notdef":
+            return 1
+        return 0
+
+    def _get_leftMargin(self):
+        if self.bounds is None:
+            return 0
+        return self.bounds[0]
+
+    leftMargin = property(_get_leftMargin)
+
+    def _get_rightMargin(self):
+        bounds = self.bounds
+        if bounds is None:
+            return 0
+        xMin, yMin, xMax, yMax = bounds
+        return self.width - bounds[2]
+
+    rightMargin = property(_get_rightMargin)
+
+    def draw(self, pen):
+        pass
+
+    def _drawDefaultNotdef(self, pen):
+        width = int(round(self.unitsPerEm * 0.5))
+        stroke = int(round(self.unitsPerEm * 0.05))
+        ascender = self.ascender
+        descender = self.descender
+        xMin = stroke
+        xMax = width - stroke
+        yMax = ascender
+        yMin = descender
+        pen.moveTo((xMin, yMin))
+        pen.lineTo((xMax, yMin))
+        pen.lineTo((xMax, yMax))
+        pen.lineTo((xMin, yMax))
+        pen.lineTo((xMin, yMin))
+        pen.closePath()
+        xMin += stroke
+        xMax -= stroke
+        yMax -= stroke
+        yMin += stroke
+        pen.moveTo((xMin, yMin))
+        pen.lineTo((xMin, yMax))
+        pen.lineTo((xMax, yMax))
+        pen.lineTo((xMax, yMin))
+        pen.lineTo((xMin, yMin))
+        pen.closePath()
+
+    def _get_bounds(self):
+        from fontTools.pens.boundsPen import BoundsPen
+        pen = BoundsPen(None)
+        self.draw(pen)
+        return pen.bounds
+
+    bounds = property(_get_bounds)
Index: /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/fontInfoData.py
===================================================================
--- /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/fontInfoData.py	(revision 1066)
+++ /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/fontInfoData.py	(revision 1066)
@@ -0,0 +1,557 @@
+"""
+This file provides fallback data for info attributes
+that are required for building OTFs. There are two main
+functions that are important:
+
+* :func:`~getAttrWithFallback`
+* :func:`~preflightInfo`
+
+There are a set of other functions that are used internally
+for synthesizing values for specific attributes. These can be
+used externally as well.
+"""
+
+import time
+import unicodedata
+from fontTools.misc.textTools import binary2num
+from fontTools.misc.arrayTools import unionRect
+from robofab import ufoLib
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+# -----------------
+# Special Fallbacks
+# -----------------
+
+# generic
+
+def styleMapFamilyNameFallback(info):
+    """
+    Fallback to *openTypeNamePreferredFamilyName openTypeNamePreferredSubfamilyName*.
+    """
+    familyName = getAttrWithFallback(info, "openTypeNamePreferredFamilyName")
+    styleName = getAttrWithFallback(info, "openTypeNamePreferredSubfamilyName")
+    if styleName is None:
+        styleName = u""
+    return (familyName + u" " + styleName).strip()
+
+# head
+
+def dateStringForNow():
+    year, month, day, hour, minute, second, weekDay, yearDay, isDST = time.localtime()
+    year = str(year)
+    month = str(month).zfill(2)
+    day = str(day).zfill(2)
+    hour = str(hour).zfill(2)
+    minute = str(minute).zfill(2)
+    second = str(second).zfill(2)
+    return "%s/%s/%s %s:%s:%s" % (year, month, day, hour, minute, second)
+
+def openTypeHeadCreatedFallback(info):
+    """
+    Fallback to now.
+    """
+    return dateStringForNow()
+
+# hhea
+
+def openTypeHheaAscenderFallback(info):
+    """
+    Fallback to *unitsPerEm + descender*.
+    """
+    return info.unitsPerEm + info.descender
+
+def openTypeHheaDescenderFallback(info):
+    """
+    Fallback to *descender*.
+    """
+    return info.descender
+
+# name
+
+def openTypeNameVersionFallback(info):
+    """
+    Fallback to *versionMajor.versionMinor* in the form 0.000.
+    """
+    versionMajor = getAttrWithFallback(info, "versionMajor")
+    versionMinor = getAttrWithFallback(info, "versionMinor")
+    return "%d.%s" % (versionMajor, str(versionMinor).zfill(3))
+
+def openTypeNameUniqueIDFallback(info):
+    """
+    Fallback to *openTypeNameVersion;openTypeOS2VendorID;styleMapFamilyName styleMapStyleName*.
+    """
+    version = getAttrWithFallback(info, "openTypeNameVersion")
+    vendor = getAttrWithFallback(info, "openTypeOS2VendorID")
+    familyName = getAttrWithFallback(info, "styleMapFamilyName")
+    styleName = getAttrWithFallback(info, "styleMapStyleName").title()
+    return u"%s;%s;%s %s" % (version, vendor, familyName, styleName)
+
+def openTypeNamePreferredFamilyNameFallback(info):
+    """
+    Fallback to *familyName*.
+    """
+    return info.familyName
+
+def openTypeNamePreferredSubfamilyNameFallback(info):
+    """
+    Fallback to *styleName*.
+    """
+    return info.styleName
+
+def openTypeNameCompatibleFullNameFallback(info):
+    """
+    Fallback to *styleMapFamilyName styleMapStyleName*.
+    If *styleMapStyleName* is *regular* this will not add
+    the style name.
+    """
+    familyName = getAttrWithFallback(info, "styleMapFamilyName")
+    styleMapStyleName = getAttrWithFallback(info, "styleMapStyleName")
+    if styleMapStyleName != "regular":
+        familyName += " " + styleMapStyleName.title()
+    return familyName
+
+def openTypeNameWWSFamilyNameFallback(info):
+    # not yet supported
+    return None
+
+def openTypeNameWWSSubfamilyNameFallback(info):
+    # not yet supported
+    return None
+
+# OS/2
+
+def openTypeOS2TypoAscenderFallback(info):
+    """
+    Fallback to *unitsPerEm + descender*.
+    """
+    return info.unitsPerEm + info.descender
+
+def openTypeOS2TypoDescenderFallback(info):
+    """
+    Fallback to *descender*.
+    """
+    return info.descender
+
+def openTypeOS2WinAscentFallback(info):
+    """
+    Fallback to the maximum y value of the font's bounding box.
+    If that is not available, fallback to *ascender*.
+    """
+    font = info.getParent()
+    if font is None:
+        return getAttrWithFallback(info, "ascender")
+    bounds = getFontBounds(font)
+    xMin, yMin, xMax, yMax = bounds
+    return yMax
+
+def openTypeOS2WinDescentFallback(info):
+    """
+    Fallback to the minimum y value of the font's bounding box.
+    If that is not available, fallback to *descender*.
+    """
+    font = info.getParent()
+    if font is None:
+        return abs(getAttrWithFallback(info, "descender"))
+    bounds = getFontBounds(font)
+    if bounds is None:
+        return abs(getAttrWithFallback(info, "descender"))
+    xMin, yMin, xMax, yMax = bounds
+    return abs(yMin)
+
+# postscript
+
+_postscriptFontNameExceptions = set(" [](){}<>/%")
+_postscriptFontNameAllowed = set([unichr(i) for i in xrange(33, 137)])
+
+def normalizeNameForPostscript(name):
+    normalized = []
+    for c in name:
+        if c in _postscriptFontNameExceptions:
+            continue
+        if c not in _postscriptFontNameAllowed:
+            c = unicodedata.normalize("NFKD", c).encode("ascii", "ignore")
+        normalized.append(c)
+    return "".join(normalized)
+
+def postscriptFontNameFallback(info):
+    """
+    Fallback to a string containing only valid characters
+    as defined in the specification. This will draw from
+    *openTypeNamePreferredFamilyName* and *openTypeNamePreferredSubfamilyName*.
+    """
+    name = u"%s-%s" % (getAttrWithFallback(info, "openTypeNamePreferredFamilyName"), getAttrWithFallback(info, "openTypeNamePreferredSubfamilyName"))
+    return normalizeNameForPostscript(name)
+
+def postscriptFullNameFallback(info):
+    """
+    Fallback to *openTypeNamePreferredFamilyName openTypeNamePreferredSubfamilyName*.
+    """
+    return u"%s %s" % (getAttrWithFallback(info, "openTypeNamePreferredFamilyName"), getAttrWithFallback(info, "openTypeNamePreferredSubfamilyName"))
+
+def postscriptSlantAngleFallback(info):
+    """
+    Fallback to *italicAngle*.
+    """
+    return getAttrWithFallback(info, "italicAngle")
+
+_postscriptWeightNameOptions = {
+    100 : "Thin",
+    200 : "Extra-light",
+    300 : "Light",
+    400 : "Normal",
+    500 : "Medium",
+    600 : "Semi-bold",
+    700 : "Bold",
+    800 : "Extra-bold",
+    900 : "Black"
+}
+
+def postscriptWeightNameFallback(info):
+    """
+    Fallback to the closest match of the *openTypeOS2WeightClass*
+    in this table:
+
+    ===  ===========
+    100  Thin
+    200  Extra-light
+    300  Light
+    400  Normal
+    500  Medium
+    600  Semi-bold
+    700  Bold
+    800  Extra-bold
+    900  Black
+    ===  ===========
+    """
+    value = getAttrWithFallback(info, "openTypeOS2WeightClass")
+    value = int(round(value * .01) * 100)
+    if value < 100:
+        value = 100
+    elif value > 900:
+        value = 900
+    name = _postscriptWeightNameOptions[value]
+    return name
+
+# --------------
+# Attribute Maps
+# --------------
+
+staticFallbackData = dict(
+    styleMapStyleName="regular",
+    versionMajor=0,
+    versionMinor=0,
+    copyright=None,
+    trademark=None,
+    italicAngle=0,
+    # not needed
+    year=None,
+    note=None,
+
+    openTypeHeadLowestRecPPEM=6,
+    openTypeHeadFlags=[0, 1],
+
+    openTypeHheaLineGap=200,
+    openTypeHheaCaretSlopeRise=1,
+    openTypeHheaCaretSlopeRun=0,
+    openTypeHheaCaretOffset=0,
+
+    openTypeNameDesigner=None,
+    openTypeNameDesignerURL=None,
+    openTypeNameManufacturer=None,
+    openTypeNameManufacturerURL=None,
+    openTypeNameLicense=None,
+    openTypeNameLicenseURL=None,
+    openTypeNameDescription=None,
+    openTypeNameSampleText=None,
+
+    openTypeOS2WidthClass=5,
+    openTypeOS2WeightClass=400,
+    openTypeOS2Selection=[],
+    openTypeOS2VendorID="NONE",
+    openTypeOS2Panose=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+    openTypeOS2FamilyClass=[0, 0],
+    openTypeOS2UnicodeRanges=[],
+    openTypeOS2CodePageRanges=[],
+    openTypeOS2TypoLineGap=200,
+    openTypeOS2Type=[2],
+    # let the FDK fallback on these
+    openTypeOS2SubscriptXSize=None,
+    openTypeOS2SubscriptYSize=None,
+    openTypeOS2SubscriptXOffset=None,
+    openTypeOS2SubscriptYOffset=None,
+    openTypeOS2SuperscriptXSize=None,
+    openTypeOS2SuperscriptYSize=None,
+    openTypeOS2SuperscriptXOffset=None,
+    openTypeOS2SuperscriptYOffset=None,
+    openTypeOS2StrikeoutSize=None,
+    openTypeOS2StrikeoutPosition=None,
+
+    # fallback to None on these
+    # as the user should be in
+    # complete control
+    openTypeVheaVertTypoAscender=None,
+    openTypeVheaVertTypoDescender=None,
+    openTypeVheaVertTypoLineGap=None,
+    openTypeVheaCaretSlopeRise=None,
+    openTypeVheaCaretSlopeRun=None,
+    openTypeVheaCaretOffset=None,
+
+    postscriptUniqueID=None,
+    postscriptUnderlineThickness=None,
+    postscriptUnderlinePosition=None,
+    postscriptIsFixedPitch=False,
+    postscriptBlueValues=[],
+    postscriptOtherBlues=[],
+    postscriptFamilyBlues=[],
+    postscriptFamilyOtherBlues=[],
+    postscriptStemSnapH=[],
+    postscriptStemSnapV=[],
+    postscriptBlueFuzz=1,
+    postscriptBlueShift=7,
+    postscriptBlueScale=.039625,
+    postscriptForceBold=False,
+    postscriptDefaultWidthX=200,
+    postscriptNominalWidthX=0,
+
+    # not used in OTF
+    postscriptDefaultCharacter=None,
+    postscriptWindowsCharacterSet=None,
+
+    # not used in OTF
+    macintoshFONDFamilyID=None,
+    macintoshFONDName=None
+)
+
+specialFallbacks = dict(
+    styleMapFamilyName=styleMapFamilyNameFallback,
+    openTypeHeadCreated=openTypeHeadCreatedFallback,
+    openTypeHheaAscender=openTypeHheaAscenderFallback,
+    openTypeHheaDescender=openTypeHheaDescenderFallback,
+    openTypeNameVersion=openTypeNameVersionFallback,
+    openTypeNameUniqueID=openTypeNameUniqueIDFallback,
+    openTypeNamePreferredFamilyName=openTypeNamePreferredFamilyNameFallback,
+    openTypeNamePreferredSubfamilyName=openTypeNamePreferredSubfamilyNameFallback,
+    openTypeNameCompatibleFullName=openTypeNameCompatibleFullNameFallback,
+    openTypeNameWWSFamilyName=openTypeNameWWSFamilyNameFallback,
+    openTypeNameWWSSubfamilyName=openTypeNameWWSSubfamilyNameFallback,
+    openTypeOS2TypoAscender=openTypeOS2TypoAscenderFallback,
+    openTypeOS2TypoDescender=openTypeOS2TypoDescenderFallback,
+    openTypeOS2WinAscent=openTypeOS2WinAscentFallback,
+    openTypeOS2WinDescent=openTypeOS2WinDescentFallback,
+    postscriptFontName=postscriptFontNameFallback,
+    postscriptFullName=postscriptFullNameFallback,
+    postscriptSlantAngle=postscriptSlantAngleFallback,
+    postscriptWeightName=postscriptWeightNameFallback
+)
+
+requiredAttributes = set(ufoLib.fontInfoAttributesVersion2) - (set(staticFallbackData.keys()) | set(specialFallbacks.keys()))
+
+recommendedAttributes = set([
+    "styleMapFamilyName",
+    "versionMajor",
+    "versionMinor",
+    "copyright",
+    "trademark",
+    "openTypeHeadCreated",
+    "openTypeNameDesigner",
+    "openTypeNameDesignerURL",
+    "openTypeNameManufacturer",
+    "openTypeNameManufacturerURL",
+    "openTypeNameLicense",
+    "openTypeNameLicenseURL",
+    "openTypeNameDescription",
+    "openTypeNameSampleText",
+    "openTypeOS2WidthClass",
+    "openTypeOS2WeightClass",
+    "openTypeOS2VendorID",
+    "openTypeOS2Panose",
+    "openTypeOS2FamilyClass",
+    "openTypeOS2UnicodeRanges",
+    "openTypeOS2CodePageRanges",
+    "openTypeOS2TypoLineGap",
+    "openTypeOS2Type",
+    "postscriptBlueValues",
+    "postscriptOtherBlues",
+    "postscriptFamilyBlues",
+    "postscriptFamilyOtherBlues",
+    "postscriptStemSnapH",
+    "postscriptStemSnapV"
+])
+
+# ------------
+# Main Methods
+# ------------
+
+def getAttrWithFallback(info, attr):
+    """
+    Get the value for *attr* from the *info* object.
+    If the object does not have the attribute or the value
+    for the atribute is None, this will either get a
+    value from a predefined set of attributes or it
+    will synthesize a value from the available data.
+    """
+    if hasattr(info, attr) and getattr(info, attr) is not None:
+        value = getattr(info, attr)
+    else:
+        if attr in specialFallbacks:
+            value = specialFallbacks[attr](info)
+        else:
+            value = staticFallbackData[attr]
+    return value
+
+def preflightInfo(info):
+    """
+    Returns a dict containing two items. The value for each
+    item will be a list of info attribute names.
+
+    ==================  ===
+    missingRequired     Required data that is missing.
+    missingRecommended  Recommended data that is missing.
+    ==================  ===
+    """
+    missingRequired = set()
+    missingRecommended = set()
+    for attr in requiredAttributes:
+        if not hasattr(info, attr) or getattr(info, attr) is None:
+            missingRequired.add(attr)
+    for attr in recommendedAttributes:
+        if not hasattr(info, attr) or getattr(info, attr) is None:
+            missingRecommended.add(attr)
+    return dict(missingRequired=missingRequired, missingRecommended=missingRecommended)
+
+def getFontBounds(font):
+    """
+    Get a tuple of (xMin, yMin, xMax, yMax) for all
+    glyphs in the given *font*.
+    """
+    rect = None
+    # defcon
+    if hasattr(font, "bounds"):
+        rect = font.bounds
+    # others
+    else:
+        for glyph in font:
+            # robofab
+            if hasattr(glyph,"box"):
+                bounds = glyph.box
+            # others
+            else:
+                bounds = glyph.bounds
+            if rect is None:
+                rect = bounds
+                continue
+            if rect is not None and bounds is not None:
+                rect = unionRect(rect, bounds)
+    if rect is None:
+        rect = (0, 0, 0, 0)
+    return rect
+
+# -----------------
+# Low Level Support
+# -----------------
+
+# these should not be used outside of this package
+
+def intListToNum(intList, start, length):
+    all = []
+    bin = ""
+    for i in range(start, start+length):
+        if i in intList:
+            b = "1"
+        else:
+            b = "0"
+        bin = b + bin
+        if not (i + 1) % 8:
+            all.append(bin)
+            bin = ""
+    if bin:
+        all.append(bin)
+    all.reverse()
+    all = " ".join(all)
+    return binary2num(all)
+
+def dateStringToTimeValue(date):
+    try:
+        t = time.strptime(date, "%Y/%m/%d %H:%M:%S")
+        return long(time.mktime(t))
+    except OverflowError:
+        return 0L
+
+# ----
+# Test
+# ----
+
+class _TestInfoObject(object):
+
+    def __init__(self):
+        self.familyName = "Family Name"
+        self.styleName = "Style Name"
+        self.unitsPerEm = 1000
+        self.descender = -250
+        self.xHeight = 450
+        self.capHeight = 600
+        self.ascender = 650
+        self.bounds = (0, -225, 100, 755)
+
+    def getParent(self):
+        return self
+
+
+def _test():
+    """
+    >>> info = _TestInfoObject()
+
+    >>> getAttrWithFallback(info, "familyName")
+    'Family Name'
+    >>> getAttrWithFallback(info, "styleName")
+    'Style Name'
+
+    >>> getAttrWithFallback(info, "styleMapFamilyName")
+    u'Family Name Style Name'
+    >>> info.styleMapFamilyName = "Style Map Family Name"
+    >>> getAttrWithFallback(info, "styleMapFamilyName")
+    'Style Map Family Name'
+
+    >>> getAttrWithFallback(info, "openTypeNamePreferredFamilyName")
+    'Family Name'
+    >>> getAttrWithFallback(info, "openTypeNamePreferredSubfamilyName")
+    'Style Name'
+    >>> getAttrWithFallback(info, "openTypeNameCompatibleFullName")
+    'Style Map Family Name'
+
+    >>> getAttrWithFallback(info, "openTypeHheaAscender")
+    750
+    >>> getAttrWithFallback(info, "openTypeHheaDescender")
+    -250
+
+    >>> getAttrWithFallback(info, "openTypeNameVersion")
+    '0.000'
+    >>> info.versionMinor = 1
+    >>> info.versionMajor = 1
+    >>> getAttrWithFallback(info, "openTypeNameVersion")
+    '1.001'
+
+    >>> getAttrWithFallback(info, "openTypeNameUniqueID")
+    u'1.001;NONE;Style Map Family Name Regular'
+
+    >>> getAttrWithFallback(info, "openTypeOS2TypoAscender")
+    750
+    >>> getAttrWithFallback(info, "openTypeOS2TypoDescender")
+    -250
+    >>> getAttrWithFallback(info, "openTypeOS2WinAscent")
+    755
+    >>> getAttrWithFallback(info, "openTypeOS2WinDescent")
+    225
+
+    >>> getAttrWithFallback(info, "postscriptSlantAngle")
+    0
+    >>> getAttrWithFallback(info, "postscriptWeightName")
+    'Normal'
+    """
+
+if __name__ == "__main__":
+    import doctest
+    doctest.testmod()
Index: /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/pens/t2CharStringPen.py
===================================================================
--- /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/pens/t2CharStringPen.py	(revision 691)
+++ /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/pens/t2CharStringPen.py	(revision 691)
@@ -0,0 +1,41 @@
+from fontTools.misc.psCharStrings import T2CharString
+from ufo2fdk.pens import RelativeCoordinatePen, roundInt, roundIntPoint
+
+
+class T2CharStringPen(RelativeCoordinatePen):
+
+    def __init__(self, width, glyphSet):
+        RelativeCoordinatePen.__init__(self, glyphSet)
+        self._program = []
+        if width is not None:
+            self._program.append(roundInt(width))
+
+    def _relativeMoveTo(self, pt):
+        pt = roundIntPoint(pt)
+        x, y = pt
+        self._program.extend([x, y, "rmoveto"])
+
+    def _relativeLineTo(self, pt):
+        pt = roundIntPoint(pt)
+        x, y = pt
+        self._program.extend([x, y, "rlineto"])
+
+    def _relativeCurveToOne(self, pt1, pt2, pt3):
+        pt1 = roundIntPoint(pt1)
+        pt2 = roundIntPoint(pt2)
+        pt3 = roundIntPoint(pt3)
+        x1, y1 = pt1
+        x2, y2 = pt2
+        x3, y3 = pt3
+        self._program.extend([x1, y1, x2, y2, x3, y3, "rrcurveto"])
+
+    def _closePath(self):
+        pass
+
+    def _endPath(self):
+        pass
+
+    def getCharString(self, private=None, globalSubrs=None):
+        program = self._program + ["endchar"]
+        charString = T2CharString(program=program, private=private, globalSubrs=globalSubrs)
+        return charString
Index: /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/pens/__init__.py
===================================================================
--- /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/pens/__init__.py	(revision 688)
+++ /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/pens/__init__.py	(revision 688)
@@ -0,0 +1,56 @@
+from fontTools.pens.basePen import BasePen
+
+def roundInt(v):
+    return int(round(v))
+
+def roundIntPoint((x, y)):
+    return roundInt(x), roundInt(y)
+
+
+class RelativeCoordinatePen(BasePen):
+
+    def __init__(self, glyphSet):
+        BasePen.__init__(self, glyphSet)
+        self._lastX = None
+        self._lastY = None
+
+    def _makePointRelative(self, pt):
+        absX, absY = pt
+        absX = absX
+        absY = absY
+        # no points have been added
+        # so no conversion is needed
+        if self._lastX is None:
+            relX, relY = absX, absY
+        # otherwise calculate the relative coordinates
+        else:
+            relX = absX - self._lastX
+            relY = absY - self._lastY
+        # store the absolute coordinates
+        self._lastX = absX
+        self._lastY = absY
+        # now return the relative coordinates
+        return relX, relY
+
+    def _moveTo(self, pt):
+        pt = self._makePointRelative(pt)
+        self._relativeMoveTo(pt)
+
+    def _relativeMoveTo(self, pt):
+        raise NotImplementedError
+
+    def _lineTo(self, pt):
+        pt = self._makePointRelative(pt)
+        self._relativeLineTo(pt)
+
+    def _relativeLineTo(self, pt):
+        raise NotImplementedError
+
+    def _curveToOne(self, pt1, pt2, pt3):
+        pt1 = self._makePointRelative(pt1)
+        pt2 = self._makePointRelative(pt2)
+        pt3 = self._makePointRelative(pt3)
+        self._relativeCurveToOne(pt1, pt2, pt3)
+
+    def _relativeCurveToOne(self, pt1, pt2, pt3):
+        raise NotImplementedError
Index: /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/pens/bezPen.py
===================================================================
--- /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/pens/bezPen.py	(revision 688)
+++ /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/pens/bezPen.py	(revision 688)
@@ -0,0 +1,284 @@
+"""
+Tools for transforming glyph data to Bez and back.
+
+BezPen
+    fontTools pen for drawing Bez instructions
+
+drawBez
+    function that draws Bez data with a fontTools pen
+
+To do:
+-   need to support hint reading from Bez. I'm not sure
+    what the hint instructions look like, so they will
+    probably cause an error now.
+-   need to support div token. i see that it divides
+    one value by another, but what should be done with
+    the resulting value? should it be used for calculating
+    the absolute position of the next coordinate? i need
+    to see some test cases before i can impliment this...
+
+Experimentatal:
+- flex, preflx1, preflx2 (need test cases)
+"""
+
+"""
+---------------------
+- Bez Specification -
+---------------------
+
+(taken from FDK/Tools/FontLab/Macros/System/Modules/BezChar.py)
+
+Type    Action
+Number  Convert from ascii to number, and push value on argList. Numbers may be int or fractions.
+div     divide n-2 token by n-1 token in place in the stack, and pop off n-1 token.
+sc      Start path command. Start new contour. Set CurX,Y to  (0,0)
+cp      Close path. Do a Contour.close(). Set pt index to zero. Note: any drawing command
+        for Pt 0 will also start a new path.
+rmt     Relative moveto. Do move to CurX + n-1, CurY + n. 
+hmt     Relative h moveto. Do move to CurX + n. 
+vmt     Relative v moveto. Do move to  CurY + n. 
+
+vdt     Relative v line to. Do line-to CurY + n. Set start/end points as a corner pt.
+hdt     Relative h line to. Do line-to CurX + n  Set start/end points as a corner pt.
+rdt     Relative line to.  Do line-to CurX + n-1, CurY + n.  Set start/end points as a corner pt.
+
+rct     Relative curve to. Calculate:
+                                dx1 = n-5, dy1 = n-4
+                                dx2 = dx1 + n-3, dy2 = dy1 + n-2
+                                dx3 = dx2 + n-1, dy3 = dx2 + n
+                                Do CurveTo (CurX + dx1, CurY + dy1),
+                                         (CurX +dx2, CurY + dy2)
+                                         (CurX + x3, CurY + dx3)
+                                         Update CurX += dx3, CurY += dx3.
+                                         Set new pt to be a curve pt.
+vhct    Relative vertical horizontal curve to. Calculate:
+                                dx1 = 0, dy1 = n-3
+                                dx2 = n-2, dy2 = dy1 + n-1
+                                dx3 = dx2 + n, dy3 = dy2
+                                Do Curve to as above.
+hvct    Relative horizontal vertical curve to.
+                                dx1 = n-3, dy1 = 0
+                                dx2 = dx1 + n-2, dy2 = n-1
+                                dx3 = dx2, dy3 = dy2 +n
+                                Do Curve to as above.
+
+prflx1  Start of Type1 style flex ommands. Discard all
+flx     Flex coommand. Push back on stack as to rct's.
+
+rb, ry, rm, rv Stem hint, stem3 hint commands. Discard args and command.
+
+sol/eol Dot Sections. Discard these and arg list.
+beginsubr/endsubr Hint replacement subroutine block. Discard
+snc/enc  hint replacement block. Discarded.
+newcolors   end of hint replacement block. Discard.
+id      Discard
+"""
+
+from fontTools.pens.basePen import BasePen
+from ufo2fdk.pens import RelativeCoordinatePen, roundInt, roundIntPoint
+
+
+class BezPen(RelativeCoordinatePen):
+
+    def __init__(self, glyphSet=None):
+        RelativeCoordinatePen.__init__(self, glyphSet)
+        self._output = []
+        self._lastX = None
+        self._lastY = None
+        self._lastPointType = None
+
+    def _relativeMoveTo(self, pt):
+        self._lastPointType = "move"
+        x, y = pt
+        if x == 0 and y != 0:
+            self._output.append("%d vmt" % y)
+        elif y == 0 and x != 0:
+            self._output.append("%d hmt" % x)
+        else:
+            self._output.append("%d %d rmt" % (x, y))
+
+    def _relativeLineTo(self, pt):
+        self._lastPointType = "line"
+        x, y = pt
+        if x == 0:
+            self._output.append("%d vdt" % y)
+        elif y == 0:
+            self._output.append("%d hdt" % x)
+        else:
+            self._output.append("%d %d rdt" % (x, y))
+
+    def _relativeCurveToOne(self, pt1, pt2, pt3):
+        self._lastPointType = "curve"
+        x1, y1 = pt1
+        x2, y2 = pt2
+        x3, y3 = pt3
+        if x1 == 0 and y3 == 0:
+            self._output.append("%d %d %d %d vhct" % (y1, x2, y2, x3))
+        elif y1 == 0 and x3 == 0:
+            self._output.append("%d %d %d %d hvct" % (x1, x2, y2, y3))
+        else:
+            self._output.append("%d %d %d %d %d %d rct" % (x1, y1, x2, y2, x3, y3))
+
+    def _closePath(self):
+        if self._lastPointType != "move":
+            self._output.append("cp")
+
+    def _endPath(self):
+        self._closePath()
+
+    def getBez(self):
+        bez = list(self._output)
+        # add the start path command if outline data is present
+        if bez:
+            bez.insert(0, "sc")
+        # add the final drawing command
+        bez.append("ed\n")
+        # the bez data must be joined with \n
+        # not os.linesep, because ACLib requires \n
+        bez = "\n".join(bez)
+        return bez
+
+
+def _absolutePoint(pt, last):
+    if last is None:
+        return pt
+    relX, relY = pt
+    absX, absY = last
+    absX += relX
+    absY += relY
+    return absX, absY
+
+# tokens that should be ignored during drawing
+ignoredTokens = set([
+    "sc", "ed",
+    "rb", "ry", "rm", "rv",
+    "sol", "eol",
+    "beginsubr", "endsubr",
+    "snc", "enc",
+    "newcolors",
+    "id"
+])
+
+def intPoint(pt):
+    x, y = pt
+    if int(x) == x:
+        x = int(x)
+    if int(y) == y:
+        y = int(y)
+    return x, y
+
+def drawBez(bez, pen):
+    """
+    A function for drawing Bez data with a standard pen.
+    Hint data in the Bez is ignored.
+    """
+    #
+    lastPoint = None
+    lastToken = None
+    for line in bez.splitlines():
+        # empty line
+        if not line:
+            continue
+        # misplaced log entry
+        if line.startswith("Wrote"):
+            continue
+        # character name
+        if line.startswith("%"):
+            continue
+        token = line.split(" ")[-1]
+        # test for ignore tokens
+        if token in ignoredTokens:
+            continue
+        ## Flex Tokens
+        # flex token. convert to rct.
+        if token == "flex":
+            token = "rct"
+        # preflx tokens. remove 
+        elif token == "preflx1" or token == "preflx2":
+            lastPoint = None
+            lastToken = None
+            continue
+        ## Drawing Tokens
+        # closePath
+        if token == "cp":
+            pen.closePath()
+            lastToken = "close"
+            continue
+        coordinates = [float(i) for i in line.split(" ")[:-1]]
+        # moveTo
+        if token == "rmt":
+            if lastToken == "move":
+                pen.closePath()
+            lastPoint = _absolutePoint(coordinates, lastPoint)
+            lastPoint = intPoint(lastPoint)
+            pen.moveTo(lastPoint)
+            lastToken = "move"
+        elif token == "vmt":
+            if lastToken == "move":
+                pen.closePath()
+            lastPoint = _absolutePoint((0, coordinates[0]), lastPoint)
+            lastPoint = intPoint(lastPoint)
+            pen.moveTo(lastPoint)
+            lastToken = "move"
+        elif token == "hmt":
+            if lastToken == "move":
+                pen.closePath()
+            lastPoint = _absolutePoint((coordinates[0], 0), lastPoint)
+            lastPoint = intPoint(lastPoint)
+            pen.moveTo(lastPoint)
+            lastToken = "move"
+        # lineTo
+        elif token == "rdt":
+            lastPoint = _absolutePoint((coordinates), lastPoint)
+            lastPoint = intPoint(lastPoint)
+            pen.lineTo(lastPoint)
+            lastToken = "line"
+        elif token == "vdt":
+            lastPoint = _absolutePoint((0, coordinates[0]), lastPoint)
+            lastPoint = intPoint(lastPoint)
+            pen.lineTo(lastPoint)
+            lastToken = "line"
+        elif token == "hdt":
+            lastPoint = _absolutePoint((coordinates[0], 0), lastPoint)
+            lastPoint = intPoint(lastPoint)
+            pen.lineTo(lastPoint)
+            lastToken = "line"
+        # curveTo
+        elif token == "rct":
+            p1 = (coordinates[0], coordinates[1])
+            p1 = lastPoint = _absolutePoint(p1, lastPoint)
+            p2 = (coordinates[2], coordinates[3])
+            p2 = lastPoint = _absolutePoint(p2, lastPoint)
+            p3 = (coordinates[4], coordinates[5])
+            p3 = lastPoint = _absolutePoint(p3, lastPoint)
+            p1 = intPoint(p1)
+            p2 = intPoint(p2)
+            p3 = intPoint(p3)
+            pen.curveTo(p1, p2, p3)
+            lastToken = "curve"
+        elif token == "vhct":
+            p1 = (0, coordinates[0])
+            p1 = lastPoint = _absolutePoint(p1, lastPoint)
+            p2 = (coordinates[1], coordinates[2])
+            p2 = lastPoint = _absolutePoint(p2, lastPoint)
+            p3 = (coordinates[3], 0)
+            p3 = lastPoint = _absolutePoint(p3, lastPoint)
+            p1 = intPoint(p1)
+            p2 = intPoint(p2)
+            p3 = intPoint(p3)
+            pen.curveTo(p1, p2, p3)
+            lastToken = "curve"
+        elif token == "hvct":
+            p1 = (coordinates[0], 0)
+            p1 = lastPoint = _absolutePoint(p1, lastPoint)
+            p2 = (coordinates[1], coordinates[2])
+            p2 = lastPoint = _absolutePoint(p2, lastPoint)
+            p3 = (0, coordinates[3])
+            p3 = lastPoint = _absolutePoint(p3, lastPoint)
+            p1 = intPoint(p1)
+            p2 = intPoint(p2)
+            p3 = intPoint(p3)
+            pen.curveTo(p1, p2, p3)
+            lastToken = "curve"
+        else:
+            raise NotImplementedError, line
Index: /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/__init__.py
===================================================================
--- /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/__init__.py	(revision 807)
+++ /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/__init__.py	(revision 807)
@@ -0,0 +1,152 @@
+import os
+import shutil
+import tempfile
+import fdkBridge
+from fdkBridge import haveFDK
+from makeotfParts import MakeOTFPartsCompiler
+from outlineOTF import OutlineOTFCompiler
+
+
+__all__ = [
+    "haveFDK",
+    "OTFCompiler",
+    "version"
+]
+
+version = "0.1"
+
+
+#def preflightFont(font):
+#    missingGlyphs = []
+#    if ".notdef" not in font:
+#        missingGlyphs.append(".notdef")
+#    if space not in font and ord(" ") not in font.unicodedata:
+#        missingGlyphs.append("space")
+#    missingInfo, suggestedInfo = preflightInfo(font.info)
+#    # if maxIndex >= 0xFFFF: from outlineOTF
+
+
+class OTFCompiler(object):
+
+    """
+    This object will create an OTF from a UFO. When creating this object,
+    there are three optional arguments:
+
+    +------------------------+------------------------------------------------------------+
+    | *savePartsNextToUFO*   | This will cause the compilation of parts for the           |
+    |                        | FDK to occur at *yourUFOName.fdk*. Use this with           |
+    |                        | caution, as an existing file at that location will         |
+    |                        | be overwritten.                                            |
+    +------------------------+------------------------------------------------------------+
+    | *partsCompilerClass*   | This will override the default parts compiler,             |
+    |                        | :class:`ufo2fdk.tools.makeotfParts.MakeOTFPartsCompiler`.  |
+    +------------------------+------------------------------------------------------------+
+    | *outlineCompilerClass* | This will override the default parts compiler,             |
+    |                        | :class:`ufo2fdk.tools.outlineOTF.OutlineOTFCompiler`.      |
+    +------------------------+------------------------------------------------------------+
+    """
+
+    def __init__(self, savePartsNextToUFO=False, partsCompilerClass=MakeOTFPartsCompiler, outlineCompilerClass=OutlineOTFCompiler):
+        self.savePartsNextToUFO = savePartsNextToUFO
+        self.partsCompilerClass = partsCompilerClass
+        self.outlineCompilerClass = outlineCompilerClass
+
+    def compile(self, font, path, checkOutlines=False, autohint=False, releaseMode=False, glyphOrder=None, progressBar=None):
+        """
+        This method will write *font* into an OTF-CFF at *path*.
+        If *checkOutlines* is True, the checkOutlines program
+        will be run on the font. If *autohint* is True, the
+        autohint program will be run on the font. If *releaseMode*
+        is True, makeotf will be told to compile the font in
+        release mode. An optional list of glyph names in *glyphOrder*
+        will specifiy the order of glyphs inthe font. If provided,
+        *progressBar* should be an object that has an *update* method.
+
+        When this method is finished, it will return a dictionary
+        containing reports from the run programs. The keys
+        are as follows:
+
+        * makeotf
+        * checkOutlines
+        * autohint
+        """
+        # get the path for the parts
+        if self.savePartsNextToUFO:
+            partsPath = os.path.splitext(font.path)[0] + ".fdk"
+        else:
+            partsPath = tempfile.mkdtemp()
+        # make report storage
+        report = dict(makeotf=None, checkOutlines=None, autohint=None)
+        # do the compile
+        try:
+            # make the parts
+            if progressBar is not None:
+                progressBar.update("Preparing...")
+            partsCompiler = self.partsCompilerClass(font, partsPath, glyphOrder=glyphOrder, outlineCompilerClass=self.outlineCompilerClass)
+            partsCompiler.compile()
+            # checkOutlines
+            if checkOutlines:
+                if progressBar is not None:
+                    progressBar.update("Removing overlap...")
+                stderr, stdout = fdkBridge.checkOutlines(partsCompiler.paths["outlineSource"])
+                ## replace the temp names in the report.
+                if not self.savePartsNextToUFO:
+                    stderr = stderr.replace(partsPath, "")
+                    stderr = stderr.replace(partsPath + "/", "")
+                    stdout = stdout.replace(partsPath, "")
+                    stdout = stdout.replace(partsPath + "/", "")
+                report["checkOutlines"] = "\n".join((stdout, stderr))
+            # autohint
+            if autohint:
+                if progressBar is not None:
+                    progressBar.update("Autohinting...")
+                stderr, stdout = fdkBridge.autohint(partsCompiler.paths["outlineSource"])
+                ## replace the temp names in the report.
+                if not self.savePartsNextToUFO:
+                    stderr = stderr.replace(partsPath, "")
+                    stderr = stderr.replace(partsPath + "/", "")
+                    stdout = stdout.replace(partsPath, "")
+                    stdout = stdout.replace(partsPath + "/", "")
+                report["autohint"] = "\n".join((stdout, stderr))
+            # makeotf
+            if progressBar is not None:
+                progressBar.update("Compiling...")
+            ## make a temp location for makeotf to compile to.
+            ## it gets confused by the various directories,
+            ## so compile into the parts directory. it will
+            ## be moved later.
+            tempFontPath = os.path.join(partsPath, "compile.otf")
+            stderr, stdout = fdkBridge.makeotf(
+                outputPath=tempFontPath,
+                outlineSourcePath=partsCompiler.paths["outlineSource"],
+                featuresPath=partsCompiler.paths["features"],
+                glyphOrderPath=partsCompiler.paths["glyphOrder"],
+                menuNamePath=partsCompiler.paths["menuName"],
+                fontInfoPath=partsCompiler.paths["fontInfo"],
+                releaseMode=releaseMode
+                )
+            ## replace the temp names in the report.
+            stderr = stderr.replace("compile.otf", os.path.basename(path))
+            stdout = stdout.replace("compile.otf", os.path.basename(path))
+            stderr = stderr.replace(tempFontPath, path)
+            stdout = stdout.replace(tempFontPath, path)
+            stderr = stderr.replace(os.path.basename(tempFontPath), os.path.basename(path))
+            stdout = stdout.replace(os.path.basename(tempFontPath), os.path.basename(path))
+            if not self.savePartsNextToUFO:
+                stderr = stderr.replace(partsPath, "")
+                stderr = stderr.replace(partsPath + "/", "")
+                stdout = stdout.replace(partsPath, "")
+                stdout = stdout.replace(partsPath + "/", "")
+            ## copy the result from the temp location
+            if os.path.exists(tempFontPath):
+                shutil.copy(tempFontPath, path)
+            report["makeotf"] = "\n".join((stdout, stderr))
+            if progressBar is not None:
+                progressBar.update("Finishing...")
+        # destroy the temp directory
+        finally:
+            if not self.savePartsNextToUFO and os.path.exists(partsPath):
+                shutil.rmtree(partsPath)
+        # return the report
+        return report
+
Index: /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/featureTableWriter.py
===================================================================
--- /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/featureTableWriter.py	(revision 452)
+++ /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/featureTableWriter.py	(revision 452)
@@ -0,0 +1,101 @@
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+
+class FeatureTableWriter(object):
+
+    """
+    A very simple feature file syntax table writer.
+    """
+
+    def __init__(self, name, indentation="    "):
+        self._name = name
+        self._lineSep = "\n"
+        self._indentation = indentation
+        self._lines = ["table %s {" % name]
+
+    def addLineWithKeyValue(self, key, value):
+        """
+        Adds a line with form::
+
+            key value;
+        """
+        line = "%s %s;" % (key, str(value))
+        self.addLine(line)
+
+    def addLine(self, line):
+        """
+        Adds a raw line.
+        """
+        line = self._indentation + line
+        self._lines.append(line)
+
+    def write(self):
+        """
+        Returns the text of the table.
+        """
+        lines = self._lines + ["} %s;" % self._name]
+        return self._lineSep.join(lines)
+
+
+# --------------
+# Text Utilities
+# --------------
+
+# The comments were taken from the feature file syntax spec.
+
+def winCharEncode(char):
+    exceptions = set("\\\"\t\n\r")
+    # Strings are converted to Unicode for the Windows platform
+    # by adding a high byte of 0. 2-byte Unicode values for the
+    # Windows platform may be specified using a special character
+    # sequence of a backslash character (\) followed by exactly
+    # four hexadecimal numbers (of either case) which may not all
+    # be zero, e.g. \4e2d.
+    # The ASCII backslash character must be represented as the
+    # sequence \005c or \005C and the ASCII double quote character
+    # must be represented as the sequence \0022.
+    value = ord(char)
+    if value > 128 or char in exceptions:
+        v = hex(value)[2:].upper()
+        v = "0" * (4 - len(v)) + v
+        return "\\" + v
+    return char
+
+def macCharEncode(char):
+    exceptions = set("\\\"\t\n\r")
+    # character codes in the range 128-255 may be specified
+    # using a special character sequence of a backslash
+    # character (\) followed by exactly two hexadecimal numbers
+    # (of either case) which may not both be zero, e.g. \83.
+    # The ASCII blackslash character must be represented as the
+    # sequence \5c or \5C and the ASCII double quote character
+    # must be represented as the sequence \22.
+    try:
+        value = ord(char.encode("macroman"))
+        if (128 < value and value < 256) or char in exceptions:
+            v = hex(value)[2:].upper()
+            v = "0" * (2 - len(v)) + v
+            return "\\" + v
+    except UnicodeEncodeError:
+        pass
+    value = ord(char)
+    if value >= 256:
+        v = hex(value)[2:].upper()
+        v = "0" * (4 - len(v)) + v
+        return "\\" + v
+    return char
+
+def winStr(text):
+    """
+    Convert string to FDK encoding for Windows.
+    """
+    return "".join([winCharEncode(c) for c in unicode(text)])
+
+def macStr(text):
+    """
+    Convert string to FDK encoding for Mac.
+    """
+    return "".join([macCharEncode(c) for c in unicode(text)])
Index: /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/makeotfParts.py
===================================================================
--- /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/makeotfParts.py	(revision 805)
+++ /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/makeotfParts.py	(revision 805)
@@ -0,0 +1,665 @@
+import os
+import shutil
+import re
+from fontInfoData import getAttrWithFallback, intListToNum
+from outlineOTF import OutlineOTFCompiler
+from featureTableWriter import FeatureTableWriter, winStr, macStr
+from kernFeatureWriter import KernFeatureWriter
+try:
+    sorted
+except NameError:
+    def sorted(l):
+        l = list(l)
+        l.sort()
+        return l
+
+
+class MakeOTFPartsCompiler(object):
+
+    """
+    This object will create the "parts" needed by the FDK.
+    The only external method is :meth:`ufo2fdk.tools.makeotfParts.compile`.
+    There is one attribute, :attr:`ufo2fdk.tools.makeotfParts.path`
+    that may be referenced externally. That is a dictionary of
+    paths to the various parts.
+
+    When creating this object, you must provide a *font*
+    object and a *path* indicating where the parts should
+    be saved. Optionally, you can provide a *glyphOrder*
+    list of glyph names indicating the order of the glyphs
+    in the font. You may also provide an *outlineCompilerClass*
+    argument that will serve as the outline source compiler.
+    The class passed for this argument must be a subclass of
+    :class:`ufo2fdk.tools.outlineOTF.OutlineOTFCompiler`.
+    """
+
+    def __init__(self, font, path, glyphOrder=None, outlineCompilerClass=OutlineOTFCompiler):
+        self.font = font
+        self.path = path
+        self.outlineCompilerClass = outlineCompilerClass
+        # store the glyph order
+        if glyphOrder is None:
+            glyphOrder = sorted(font.keys())
+        self.glyphOrder = glyphOrder
+        # make the paths for all files
+        self.paths = dict(
+            outlineSource=os.path.join(path, "font.otf"),
+            menuName=os.path.join(path, "menuname"),
+            glyphOrder=os.path.join(path, "glyphOrder"),
+            fontInfo=os.path.join(path, "fontinfo"),
+            features=os.path.join(path, "features")
+        )
+
+    def compile(self):
+        """
+        Compile the parts.
+        """
+        # set up the parts directory removing
+        # an existing directory if necessary.
+        if os.path.exists(self.path):
+            shutil.rmtree(self.path)
+        os.mkdir(self.path)
+        # build the parts
+        self.setupFile_outlineSource(self.paths["outlineSource"])
+        self.setupFile_menuName(self.paths["menuName"])
+        self.setupFile_glyphOrder(self.paths["glyphOrder"])
+        self.setupFile_fontInfo(self.paths["fontInfo"])
+        self.setupFile_features(self.paths["features"])
+
+    def setupFile_outlineSource(self, path):
+        """
+        Make the outline source file.
+
+        **This should not be called externally.** Subclasses
+        may override this method to handle the file creation
+        in a different way if desired.
+        """
+        c = self.outlineCompilerClass(self.font, path, self.glyphOrder)
+        c.compile()
+
+    def setupFile_menuName(self, path):
+        """
+        Make the menu name source file. This gets the values for
+        the file using the fallback system as described below:
+
+        ====  ===
+        [PS]  postscriptFontName
+        f=    openTypeNamePreferredFamilyName
+        s=    openTypeNamePreferredSubfamilyName
+        l=    styleMapFamilyName
+        m=1,  openTypeNameCompatibleFullName
+        ====  ===
+
+        **This should not be called externally.** Subclasses
+        may override this method to handle the file creation
+        in a different way if desired.
+        """
+        psName = getAttrWithFallback(self.font.info,"postscriptFontName")
+        lines = [
+            "[%s]" % psName
+        ]
+        # family name
+        familyName = getAttrWithFallback(self.font.info,"openTypeNamePreferredFamilyName")
+        encodedFamilyName = winStr(familyName)
+        lines.append("f=%s" % encodedFamilyName)
+        if encodedFamilyName != familyName:
+            lines.append("f=1,%s" % macStr(familyName))
+        # style name
+        styleName = getAttrWithFallback(self.font.info,"openTypeNamePreferredSubfamilyName")
+        encodedStyleName = winStr(styleName)
+        lines.append("s=%s" % encodedStyleName)
+        if encodedStyleName != styleName:
+            lines.append("s=1,%s" % macStr(styleName))
+        # compatible name
+        winCompatible = getAttrWithFallback(self.font.info,"styleMapFamilyName")
+        ## the second qualification here is in place for Mac Office <= 2004.
+        ## in that app the menu name is pulled from name ID 18. the font
+        ## may have standard naming data that combines to a length longer
+        ## than the app can handle (see Adobe Tech Note #5088). the designer
+        ## may have created a specific openTypeNameCompatibleFullName to
+        ## get around this problem. sigh, old app bugs live long lives.
+        if winCompatible != familyName or self.font.info.openTypeNameCompatibleFullName is not None:
+            # windows
+            l = "l=%s" % winCompatible
+            lines.append(l)
+            # mac
+            macCompatible = getAttrWithFallback(self.font.info,"openTypeNameCompatibleFullName")
+            l = "m=1,%s" % macCompatible
+            lines.append(l)
+        text = "\n".join(lines) + "\n"
+        f = open(path, "wb")
+        f.write(text)
+        f.close()
+
+    def setupFile_glyphOrder(self, path):
+        """
+        Make the glyph order source file.
+
+        **This should not be called externally.** Subclasses
+        may override this method to handle the file creation
+        in a different way if desired.
+        """
+        lines = []
+        for glyphName in self.glyphOrder:
+            if glyphName in self.font and self.font[glyphName].unicode is not None:
+                code = self.font[glyphName].unicode
+                code = hex(code)[2:].upper()
+                if len(code) < 4:
+                    code = code.zfill(4)
+                line = "%s %s uni%s" % (glyphName, glyphName, code)
+            else:
+                line = "%s %s" % (glyphName, glyphName)
+            lines.append(line)
+        text = "\n".join(lines) + "\n"
+        f = open(path, "wb")
+        f.write(text)
+        f.close()
+
+    def setupFile_fontInfo(self, path):
+        """
+        Make the font info source file. This gets the values for
+        the file using the fallback system as described below:
+
+        ==========================  ===
+        IsItalicStyle               styleMapStyleName
+        IsBoldStyle                 styleMapStyleName
+        PreferOS/2TypoMetrics       openTypeOS2Selection
+        IsOS/2WidthWeigthSlopeOnly  openTypeOS2Selection
+        IsOS/2OBLIQUE               openTypeOS2Selection
+        ==========================  ===
+
+        **This should not be called externally.** Subclasses
+        may override this method to handle the file creation
+        in a different way if desired.
+        """
+        lines = []
+        # style mapping
+        styleMapStyleName = getAttrWithFallback(self.font.info,"styleMapStyleName")
+        if styleMapStyleName in ("italic", "bold italic"):
+            lines.append("IsItalicStyle true")
+        else:
+            lines.append("IsItalicStyle false")
+        if styleMapStyleName in ("bold", "bold italic"):
+            lines.append("IsBoldStyle true")
+        else:
+            lines.append("IsBoldStyle false")
+        # fsSelection bits
+        selection = getAttrWithFallback(self.font.info,"openTypeOS2Selection")
+        if 7 in selection:
+            lines.append("PreferOS/2TypoMetrics true")
+        else:
+            lines.append("PreferOS/2TypoMetrics false")
+        if 8 in selection:
+            lines.append("IsOS/2WidthWeigthSlopeOnly true")
+        else:
+            lines.append("IsOS/2WidthWeigthSlopeOnly false")
+        if 9 in selection:
+            lines.append("IsOS/2OBLIQUE true")
+        else:
+            lines.append("IsOS/2OBLIQUE false")
+        # write the file
+        if lines:
+            f = open(path, "wb")
+            f.write("\n".join(lines))
+            f.close()
+
+    def setupFile_features(self, path):
+        """
+        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.
+        """
+        # force absolute includes into the features
+        if self.font.path is None:
+            existingFeaturePath = None
+            existing = self.font.features.text
+            if existing is None:
+                existing = ""
+        else:
+            existingFeaturePath = os.path.join(self.font.path, "features.fea")
+            existing = forceAbsoluteIncludesInFeatures(self.font.features.text, os.path.dirname(self.font.path))
+        # break the features into parts
+        features, tables = extractFeaturesAndTables(existing, scannedFiles=[existingFeaturePath])
+        # build tables that are not in the existing features
+        autoTables = {}
+        if "head" not in tables:
+            autoTables["head"] = self.writeFeatures_head()
+        if "hhea" not in tables:
+            autoTables["hhea"] = self.writeFeatures_hhea()
+        if "OS/2" not in tables:
+            autoTables["OS/2"] = self.writeFeatures_OS2()
+        if "name" not in tables:
+            autoTables["name"] = self.writeFeatures_name()
+        # build the kern feature if necessary
+        autoFeatures = {}
+        if "kern" not in features and len(self.font.kerning):
+            autoFeatures["kern"] = self.writeFeatures_kern()
+        # write the features
+        features = [existing]
+        for name, text in sorted(autoFeatures.items()):
+            features.append(text)
+        for name, text in sorted(autoTables.items()):
+            features.append(text)
+        features = "\n\n".join(features)
+        # write the result
+        f = open(path, "wb")
+        f.write(features)
+        f.close()
+
+    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 = KernFeatureWriter(self.font)
+        return writer.write()
+
+    def writeFeatures_head(self):
+        """
+        Write the head to a string and return it.
+
+        This gets the values for the file using the fallback
+        system as described below:
+
+        =====  ===
+        X.XXX  versionMajor.versionMinor
+        =====  ===
+
+        **This should not be called externally.** Subclasses
+        may override this method to handle the string creation
+        in a different way if desired.
+        """
+        versionMajor = getAttrWithFallback(self.font.info, "versionMajor")
+        versionMinor = getAttrWithFallback(self.font.info, "versionMinor")
+        value = "%d.%s" % (versionMajor, str(versionMinor).zfill(3))
+        writer = FeatureTableWriter("head")
+        writer.addLineWithKeyValue("FontRevision", value)
+        return writer.write()
+
+    def writeFeatures_hhea(self):
+        """
+        Write the hhea to a string and return it.
+
+        This gets the values for the file using the fallback
+        system as described below:
+
+        ===========  ===
+        Ascender     openTypeHheaAscender
+        Descender    openTypeHheaDescender
+        LineGap      openTypeHheaLineGap
+        CaretOffset  openTypeHheaCaretOffset
+        ===========  ===
+
+        **This should not be called externally.** Subclasses
+        may override this method to handle the string creation
+        in a different way if desired.
+        """
+        ascender = getAttrWithFallback(self.font.info, "openTypeHheaAscender")
+        descender = getAttrWithFallback(self.font.info, "openTypeHheaDescender")
+        lineGap = getAttrWithFallback(self.font.info, "openTypeHheaLineGap")
+        caret = getAttrWithFallback(self.font.info, "openTypeHheaCaretOffset")
+        writer = FeatureTableWriter("hhea")
+        writer.addLineWithKeyValue("Ascender", _roundInt(ascender))
+        writer.addLineWithKeyValue("Descender", _roundInt(descender))
+        writer.addLineWithKeyValue("LineGap", _roundInt(lineGap))
+        writer.addLineWithKeyValue("CaretOffset", _roundInt(caret))
+        return writer.write()
+
+    def writeFeatures_name(self):
+        """
+        Write the name to a string and return it.
+
+        This gets the values for the file using the fallback
+        system as described below:
+
+        =========  ===
+        nameid 0   copyright
+        nameid 7   trademark
+        nameid 8   openTypeNameManufacturer
+        nameid 9   openTypeNameDesigner
+        nameid 10  openTypeNameDescription
+        nameid 11  openTypeNameManufacturerURL
+        nameid 12  openTypeNameDesignerURL
+        nameid 13  openTypeNameLicense
+        nameid 14  openTypeNameLicenseURL
+        nameid 19  openTypeNameSampleText
+        =========  ===
+
+        **This should not be called externally.** Subclasses
+        may override this method to handle the string creation
+        in a different way if desired.
+        """
+        idToAttr = [
+            (0  , "copyright"),
+            (7  , "trademark"),
+            (8  , "openTypeNameManufacturer"),
+            (9  , "openTypeNameDesigner"),
+            (10 , "openTypeNameDescription"),
+            (11 , "openTypeNameManufacturerURL"),
+            (12 , "openTypeNameDesignerURL"),
+            (13 , "openTypeNameLicense"),
+            (14 , "openTypeNameLicenseURL"),
+            (19 , "openTypeNameSampleText")
+        ]
+        multilineNameTableEntries = {}
+        lines = []
+        for id, attr in idToAttr:
+            value = getAttrWithFallback(self.font.info, attr)
+            if value is None:
+                continue
+            s = 'nameid %d "%s";' % (id, winStr(value))
+            lines.append(s)
+            s = 'nameid %d 1 "%s";' % (id, macStr(value))
+            lines.append(s)
+        if not lines:
+            return ""
+        writer = FeatureTableWriter("name")
+        for line in lines:
+            writer.addLine(line)
+        return writer.write()
+
+    def writeFeatures_OS2(self):
+        """
+        Write the OS/2 to a string and return it.
+
+        This gets the values for the file using the fallback
+        system as described below:
+
+        =============  ===
+        FSType         openTypeOS2Type
+        Panose         openTypeOS2Panose
+        UnicodeRange   openTypeOS2UnicodeRanges
+        CodePageRange  openTypeOS2CodePageRanges
+        TypoAscender   openTypeOS2TypoAscender
+        TypoDescender  openTypeOS2TypoDescender
+        TypoLineGap    openTypeOS2TypoLineGap
+        winAscent      openTypeOS2WinAscent
+        winDescent     openTypeOS2WinDescent
+        XHeight        xHeight
+        CapHeight      capHeight
+        WeightClass    openTypeOS2WeightClass
+        WidthClass     openTypeOS2WidthClass
+        Vendor         openTypeOS2VendorID
+        =============  ===
+
+        **This should not be called externally.** Subclasses
+        may override this method to handle the string creation
+        in a different way if desired.
+        """
+        codePageBitTranslation = {
+            0  : "1252",
+            1  : "1250",
+            2  : "1251",
+            3  : "1253",
+            4  : "1254",
+            5  : "1255",
+            6  : "1256",
+            7  : "1257",
+            8  : "1258",
+            16 : "874",
+            17 : "932",
+            18 : "936",
+            19 : "949",
+            20 : "950",
+            21 : "1361",
+            48 : "869",
+            49 : "866",
+            50 : "865",
+            51 : "864",
+            52 : "863",
+            53 : "862",
+            54 : "861",
+            55 : "860",
+            56 : "857",
+            57 : "855",
+            58 : "852",
+            59 : "775",
+            60 : "737",
+            61 : "708",
+            62 : "850",
+            63 : "437"
+        }
+        # writer
+        writer = FeatureTableWriter("OS/2")
+        # type
+        writer.addLineWithKeyValue("FSType", intListToNum(getAttrWithFallback(self.font.info, "openTypeOS2Type"), 0, 16))
+        # panose
+        panose = [str(i) for i in getAttrWithFallback(self.font.info, "openTypeOS2Panose")]
+        writer.addLineWithKeyValue("Panose", " ".join(panose))
+        # unicode ranges
+        unicodeRange = [str(i) for i in getAttrWithFallback(self.font.info, "openTypeOS2UnicodeRanges")]
+        if unicodeRange:
+            writer.addLineWithKeyValue("UnicodeRange", " ".join(unicodeRange))
+        # code page ranges
+        codePageRange = [codePageBitTranslation[i] for i in getAttrWithFallback(self.font.info, "openTypeOS2CodePageRanges") if i in codePageBitTranslation]
+        if codePageRange:
+            writer.addLineWithKeyValue("CodePageRange", " ".join(codePageRange))
+        # vertical metrics
+        writer.addLineWithKeyValue("TypoAscender", _roundInt(getAttrWithFallback(self.font.info, "openTypeOS2TypoAscender")))
+        writer.addLineWithKeyValue("TypoDescender", _roundInt(getAttrWithFallback(self.font.info, "openTypeOS2TypoDescender")))
+        writer.addLineWithKeyValue("TypoLineGap", _roundInt(getAttrWithFallback(self.font.info, "openTypeOS2TypoLineGap")))
+        writer.addLineWithKeyValue("winAscent", _roundInt(getAttrWithFallback(self.font.info, "openTypeOS2WinAscent")))
+        writer.addLineWithKeyValue("winDescent", abs(_roundInt(getAttrWithFallback(self.font.info, "openTypeOS2WinDescent"))))
+        writer.addLineWithKeyValue("XHeight", _roundInt(getAttrWithFallback(self.font.info, "xHeight")))
+        writer.addLineWithKeyValue("CapHeight", _roundInt(getAttrWithFallback(self.font.info, "capHeight")))
+        writer.addLineWithKeyValue("WeightClass", getAttrWithFallback(self.font.info, "openTypeOS2WeightClass"))
+        writer.addLineWithKeyValue("WidthClass", getAttrWithFallback(self.font.info, "openTypeOS2WidthClass"))
+        writer.addLineWithKeyValue("Vendor", '"%s"' % getAttrWithFallback(self.font.info, "openTypeOS2VendorID"))
+        return writer.write()
+
+
+includeRE = re.compile(
+    "include"
+    "\s*"
+    "\("
+    "([^\)]+)"
+    "\)"
+    )
+
+def forceAbsoluteIncludesInFeatures(text, directory):
+    """
+    Convert relative includes in the *text*
+    to absolute includes.
+    """
+    for includePath in includeRE.findall(text):
+        currentDirectory = directory
+        parts = includePath.split("/")
+        for index, part in enumerate(parts):
+            part = part.strip()
+            if not part:
+                continue
+            if part == "..":
+                currentDirectory = os.path.dirname(currentDirectory)
+            elif part == ".":
+                continue
+            else:
+                break
+        subPath = "/".join(parts[index:])
+        srcPath = os.path.join(currentDirectory, subPath)
+        text = text.replace(includePath, srcPath)
+    return text
+
+def _roundInt(value):
+    return int(round(value))
+
+# ----------------------
+# Basic Feature Splitter
+# ----------------------
+
+stringRE = re.compile(
+    "(\"[^$\"]*\")"
+)
+featureTableStartRE = re.compile(
+    "("
+    "feature"
+    "\s+"
+    "\S{4}"
+    "\s*"
+    "\{"
+    "|"
+    "table"
+    "\s+"
+    "\S{4}"
+    "\s*"
+    "\{"
+    ")",
+    re.MULTILINE
+)
+featureNameRE = re.compile(
+    "feature"
+    "\s+"
+    "(\S{4})"
+    "\s*"
+    "\{"
+)
+tableNameRE = re.compile(
+    "table"
+    "\s+"
+    "(\S{4})"
+    "\s*"
+    "\{"
+)
+
+def extractFeaturesAndTables(text, scannedFiles=[]):
+    # strip all comments
+    decommentedLines = [line.split("#")[0] for line in text.splitlines()]
+    text = "\n".join(decommentedLines)
+    # replace all strings with temporary placeholders.
+    destringedLines = []
+    stringReplacements = {}
+    for line in text.splitlines():
+        if "\"" in line:
+            line = line.replace("\\\"", "__ufo2fdk_temp_escaped_quote__")
+            for found in stringRE.findall(line):
+                temp = "__ufo2fdk_temp_string_%d__" % len(stringReplacements)
+                line = line.replace(found, temp, 1)
+                stringReplacements[temp] = found.replace("__ufo2fdk_temp_escaped_quote__", "\\\"")
+            line = line.replace("__ufo2fdk_temp_escaped_quote__", "\\\"")
+        destringedLines.append(line)
+    text = "\n".join(destringedLines)
+    # extract all includes
+    includes = includeRE.findall(text)
+    # slice off the text that comes before
+    # the first feature/table definition
+    precedingText = ""
+    startMatch = featureTableStartRE.search(text)
+    if startMatch is not None:
+        start, end = startMatch.span()
+        precedingText = text[:start].strip()
+        text = text[start:]
+    else:
+        precedingText = text
+        text = ""
+    # break the features
+    broken = _textBreakRecurse(text)
+    # organize into tables and features
+    features = {}
+    tables = {}
+    for text in broken:
+        text = text.strip()
+        if not text:
+            continue
+        # replace the strings
+        finalText = text
+        for temp, original in stringReplacements.items():
+            if temp in finalText:
+                del stringReplacements[temp]
+                finalText = finalText.replace(temp, original, 1)
+        finalText = finalText.strip()
+        # grab feature or table names and store
+        featureMatch = featureNameRE.search(text)
+        if featureMatch is not None:
+            features[featureMatch.group(1)] = finalText
+        else:
+            tableMatch = tableNameRE.search(text)
+            tables[tableMatch.group(1)] = finalText
+    # scan all includes
+    for path in includes:
+        if path in scannedFiles:
+            continue
+        scannedFiles.append(path)
+        if os.path.exists(path):
+            f = open(path, "r")
+            text = f.read()
+            f.close()
+            f, t = extractFeaturesAndTables(text, scannedFiles)
+            features.update(f)
+            tables.update(t)
+    return features, tables
+
+def _textBreakRecurse(text):
+    matched = []
+    match = featureTableStartRE.search(text)
+    if match is None:
+        matched.append(text)
+    else:
+        start, end = match.span()
+        # add any preceding text to the previous item
+        if start != 0:
+            precedingText = matched.pop(0)
+            precedingText += text[:start]
+            matched.insert(0, precedingText)
+        # look ahead to see if there is another feature
+        next = text[end:]
+        nextMatch = featureTableStartRE.search(next)
+        if nextMatch is None:
+            # if nothing has been found, add
+            # the remaining text to the feature
+            matchedText = text[start:]
+            matched.append(matchedText)
+        else:
+            # if one has been found, grab all text
+            # from before the feature start and add
+            # it to the current feature.
+            nextStart, nextEnd = nextMatch.span()
+            matchedText = text[:end + nextStart]
+            matched.append(matchedText)
+            # recurse through the remaining text
+            matched += _textBreakRecurse(next[nextStart:])
+    return matched
+
+
+extractFeaturesAndTablesTestText = """
+@foo = [bar];
+
+# test commented item
+#feature fts1 {
+#    sub foo by bar;
+#} fts1;
+
+feature fts2 {
+    sub foo by bar;
+} fts2;
+
+table tts1 {
+    nameid 1 "feature this { is not really a \\\"feature that { other thing is";
+} tts1;feature fts3 { sub a by b;} fts3;
+"""
+
+extractFeaturesAndTablesTestResult = (
+    {
+        'fts2': 'feature fts2 {\n    sub foo by bar;\n} fts2;',
+        'fts3': 'feature fts3 { sub a by b;} fts3;'
+    },
+    {
+        'tts1': 'table tts1 {\n    nameid 1 "feature this { is not really a \\"feature that { other thing is";\n} tts1;'
+    }
+)
+
+def testBreakFeaturesAndTables():
+    """
+    >>> r = extractFeaturesAndTables(extractFeaturesAndTablesTestText)
+    >>> r == extractFeaturesAndTablesTestResult
+    True
+    """
+
+if __name__ == "__main__":
+    import doctest
+    doctest.testmod()
Index: /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/fdkBridge.py
===================================================================
--- /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/fdkBridge.py	(revision 1066)
+++ /packages/ufo2fdk/branches/ufo3/Lib/ufo2fdk/fdkBridge.py	(revision 1066)
@@ -0,0 +1,279 @@
+"""
+This module is the bridge between Python and the FDK.
+It uses subprocess.Popen to create a process that
+executes an FDK program.
+"""
+
+
+import sys
+import os
+import re
+import tempfile
+
+# ----------------
+# Public Functions
+# ----------------
+
+
+minMakeOTFVersion = (2, 0, 39)
+minMakeOTFVersionRE = re.compile(
+    "\s*"
+    "makeotf"
+    "\s+"
+    "v"
+    "(\d+)"
+    "."
+    "(\d+)"
+    "."
+    "(\d+)"
+)
+
+
+def haveFDK():
+    """
+    This will return a bool indicating if the FDK
+    can be found. It searches for the FDK by using
+    *which* to find the commandline *makeotf*,
+    *checkoutlines* and *autohint* programs. If one
+    of those cannot be found, this FDK is considered
+    to be unavailable.
+    """
+    import subprocess
+    if _fdkToolDirectory is None:
+        return False
+    env = _makeEnviron()
+    for tool in ["makeotf", "checkoutlines", "autohint"]:
+        cmds = "which %s" % tool
+        popen = subprocess.Popen(cmds, stderr=subprocess.PIPE, stdout=subprocess.PIPE, env=env, shell=True)
+        popen.wait()
+        text = popen.stderr.read()
+        text += popen.stdout.read()
+        if not text:
+            return False
+    # now test to make sure that makeotf is new enough
+    help = _execute(["makeotf", "-h"])[1]
+    m = minMakeOTFVersionRE.match(help)
+    if m is None:
+        return False
+    v1 = int(m.group(1))
+    v2 = int(m.group(2))
+    v3 = int(m.group(3))
+    if (v1, v2, v3) < minMakeOTFVersion:
+        return False
+    return True
+
+def makeotf(outputPath, outlineSourcePath=None, featuresPath=None, glyphOrderPath=None, menuNamePath=None, fontInfoPath=None, releaseMode=False):
+    """
+    Run makeotf.
+    The arguments will be converted into arguments
+    for makeotf as follows:
+
+    =================  ===
+    outputPath         -o
+    outlineSourcePath  -f
+    featuresPath       -ff
+    glyphOrderPath     -gf
+    menuNamePath       -mf
+    fontInfoPath       -fi
+    releaseMode        -r
+    =================  ===
+    """
+    cmds = ["makeotf", "-o", outputPath]
+    if outlineSourcePath:
+        cmds.extend(["-f", outlineSourcePath])
+    if featuresPath:
+        cmds.extend(["-ff", featuresPath])
+    if glyphOrderPath:
+        cmds.extend(["-gf", glyphOrderPath])
+    if menuNamePath:
+        cmds.extend(["-mf", menuNamePath])
+    if fontInfoPath:
+        cmds.extend(["-fi", fontInfoPath])
+    if releaseMode:
+        cmds.append("-r")
+    stderr, stdout = _execute(cmds)
+    return stderr, stdout
+
+def autohint(fontPath):
+    """
+    Run autohint.
+    The following arguments will be passed to autohint.
+
+    * -nb
+    * -a
+    * -r
+    * -q
+    """
+    cmds = ["autohint", "-nb", "-a", "-r", "-q", fontPath]
+    stderr, stdout = _execute(cmds)
+    return stderr, stdout
+
+def checkOutlines(fontPath, removeOverlap=True, correctContourDirection=True):
+    """
+    Run checkOutlines.
+    The arguments will be converted into arguments
+    for checkOutlines as follows:
+
+    The following arguments will be passed to autohint.
+
+    =============================  ===
+    removeOverlap=False            -V
+    correctContourDirection=False  -O
+    =============================  ===
+
+    Additionally, the following arguments will be passed to checkOutlines.
+
+    * -e
+    * -k
+    """
+    cmds = ["checkoutlines", "-e", "-k"]
+    # checkoutlines seems to apply the contour direction correction
+    # before the overlap removal. that may alter the design of the
+    # glyph when self-intersecting contours are present. to get
+    # around this, do these separately. this is inefficient, but...
+    allStderr = []
+    allStdout = []
+    if removeOverlap:
+        c = cmds + ["-O", fontPath]
+        stderr, stdout = _execute(c)
+        allStderr.append(stderr)
+        allStdout.append(stdout)
+    if correctContourDirection:
+        c = cmds + ["-V", fontPath]
+        stderr, stdout = _execute(c)
+        allStderr.append(stderr)
+        allStdout.append(stdout)
+    return "\n".join(allStderr), "\n".join(allStdout)
+
+outlineCheckFirstLineRE = re.compile(
+    "Wrote fixed file"
+    ".+"
+)
+
+def checkOutlinesGlyph(glyph, contours, removeOverlap=True, correctContourDirection=True):
+    """
+    Run outlineCheck on one or more contours.
+    This will remove the original contours from
+    the given glyph, if a change was made. This
+    returns a boolean indicating if a change
+    was made or not.
+
+    The arguments will be converted into arguments
+    for outlineCheck as follows:
+
+    The following arguments will be passed to autohint.
+
+    =============================  ===
+    removeOverlap=False            -V
+    correctContourDirection=False  -O
+    =============================  ===
+
+    Additionally, the following arguments will be passed to outlineCheck.
+
+    * -e
+    * -k
+    """
+    from ufo2fdk.pens.bezPen import BezPen, drawBez
+    # write the bez
+    pen = BezPen(glyphSet=None)
+    for contour in contours:
+        contour.draw(pen)
+    inBez = pen.getBez()
+    # write the bez to a temp file
+    bezPathIn = tempfile.mkstemp()[1]
+    f = open(bezPathIn, "w")
+    f.write(inBez)
+    f.close()
+    # call the outlinecheck tool
+    cmds = ["outlinecheck", "-n", "-k"]
+    if not removeOverlap:
+        cmds.append("-V")
+    if not correctContourDirection:
+        cmds.append("-O")
+    cmds.append(bezPathIn)
+    _execute(cmds)
+    # load the new bez
+    bezPathOut = bezPathIn + ".new"
+    madeChange = False
+    if os.path.exists(bezPathOut):
+        f = open(bezPathOut, "r")
+        outBez = f.read()
+        f.close()
+        # remove irrelevant log at the top of the file
+        outBez = outBez.splitlines()
+        if outlineCheckFirstLineRE.match(outBez[0]):
+            outBez = outBez[1:]
+        outBez = "\n".join(outBez)
+        # apply only if the new bez is different than the old bez
+        if inBez != outBez:
+            # remove the old contours
+            for contour in contours:
+                glyph.removeContour(contour)
+            # write the bez back into the glyph
+            pen = glyph.getPen()
+            drawBez(outBez, pen)
+            madeChange = True
+    # remove files
+    for path in [bezPathIn, bezPathOut]:
+        if os.path.exists(path):
+            os.remove(path)
+    # return the change status
+    return madeChange
+
+def removeOverlap(glyph, contours):
+    from warnings import warn
+    warn(DeprecationWarning("Use checkOutlinesGlyph!"))
+    return checkOutlinesGlyph(glyph, contours, removeOverlap=True, correctContourDirection=False)
+
+
+# --------------
+# Internal Tools
+# --------------
+
+if sys.platform == "darwin":
+    _fdkToolDirectory = os.path.join(os.environ["HOME"], "bin/FDK/Tools/osx")
+else:
+    _fdkToolDirectory = None
+
+def _makeEnviron():
+    env = dict(os.environ)
+    if _fdkToolDirectory not in env["PATH"].split(":"):
+        env["PATH"] += (":%s" % _fdkToolDirectory)
+    kill = ["ARGVZERO", "EXECUTABLEPATH", "PYTHONHOME", "PYTHONPATH", "RESOURCEPATH"]
+    for key in kill:
+        if key in env:
+            del env[key]
+    return env
+
+def _execute(cmds):
+    import subprocess
+    # for some reason, autohint and/or checkoutlines
+    # locks up when subprocess.PIPE is given. subprocess
+    # requires a real file so StringIO is not acceptable
+    # here. thus, make a temporary file.
+    stderrPath = tempfile.mkstemp()[1]
+    stdoutPath = tempfile.mkstemp()[1]
+    stderrFile = open(stderrPath, "w")
+    stdoutFile = open(stdoutPath, "w")
+    # get the os.environ
+    env = _makeEnviron()
+    # make a string of escaped commands
+    cmds = subprocess.list2cmdline(cmds)
+    # go
+    popen = subprocess.Popen(cmds, stderr=stderrFile, stdout=stdoutFile, env=env, shell=True)
+    popen.wait()
+    # get the output
+    stderrFile.close()
+    stdoutFile.close()
+    stderrFile = open(stderrPath, "r")
+    stdoutFile = open(stdoutPath, "r")
+    stderr = stderrFile.read()
+    stdout = stdoutFile.read()
+    stderrFile.close()
+    stdoutFile.close()
+    # trash the temp files
+    os.remove(stderrPath)
+    os.remove(stdoutPath)
+    # done
+    return stderr, stdout
+
Index: /packages/ufo2fdk/branches/ufo3/setup.py
===================================================================
--- /packages/ufo2fdk/branches/ufo3/setup.py	(revision 501)
+++ /packages/ufo2fdk/branches/ufo3/setup.py	(revision 501)
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+import sys
+from distutils.core import setup
+
+try:
+    import fontTools
+except:
+    print "*** Warning: ufo2fdk requires FontTools, see:"
+    print "    fonttools.sf.net"
+
+try:
+    import robofab
+except:
+    print "*** Warning: ufo2fdk requires RoboFab, see:"
+    print "    robofab.com"
+
+if "sdist" in sys.argv:
+    import os
+    import subprocess
+    import shutil
+    docFolder = os.path.join(os.getcwd(), "documentation")
+    # remove existing
+    doctrees = os.path.join(docFolder, "build", "doctrees")
+    if os.path.exists(doctrees):
+        shutil.rmtree(doctrees)
+    # compile
+    p = subprocess.Popen(["make", "html"], cwd=docFolder)
+    p.wait()
+    # remove doctrees
+    shutil.rmtree(doctrees)
+
+
+
+setup(name="ufo2fdk",
+    version="0.1",
+    description="A bridge between UFOs and the AFKDO",
+    author="Tal Leming",
+    author_email="tal@typesupply.com",
+    url="http://code.typesupply.com",
+    license="MIT",
+    packages=["ufo2fdk"],
+    package_dir={"":"Lib"}
+)
Index: /packages/ufo2fdk/branches/ufo3/documentation/source/conf.py
===================================================================
--- /packages/ufo2fdk/branches/ufo3/documentation/source/conf.py	(revision 500)
+++ /packages/ufo2fdk/branches/ufo3/documentation/source/conf.py	(revision 500)
@@ -0,0 +1,190 @@
+# -*- coding: utf-8 -*-
+#
+# ufo2fdk documentation build configuration file, created by
+# sphinx-quickstart on Sun Jan 25 09:05:02 2009.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# The contents of this file are pickled, so don't put values in the namespace
+# that aren't pickleable (module imports are okay, they're removed automatically).
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If your extensions are in another directory, add it here. If the directory
+# is relative to the documentation root, use os.path.abspath to make it
+# absolute, like shown here.
+#sys.path.append(os.path.abspath('.'))
+
+# General configuration
+# ---------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'ufo2fdk'
+copyright = u'2009, Type Supply LLC'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0.1'
+# The full version, including alpha/beta/rc tags.
+release = '0.1'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# List of directories, relative to source directory, that shouldn't be searched
+# for source files.
+exclude_trees = []
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+
+# Options for HTML output
+# -----------------------
+
+# The style sheet to use for HTML and HTML Help pages. A file of that name
+# must exist either in Sphinx' static/ path, or in one of the custom paths
+# given in html_static_path.
+html_style = 'default.css'
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_use_modindex = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, the reST sources are included in the HTML build as _sources/<name>.
+#html_copy_source = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'ufo2fdkdoc'
+
+
+# Options for LaTeX output
+# ------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, document class [howto/manual]).
+latex_documents = [
+  ('index', 'ufo2fdk.tex', ur'ufo2fdk Documentation',
+   ur'Tal Leming', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_use_modindex = True
Index: /packages/ufo2fdk/branches/ufo3/documentation/source/autodoc/kernFeatureWriter.rst
===================================================================
--- /packages/ufo2fdk/branches/ufo3/documentation/source/autodoc/kernFeatureWriter.rst	(revision 424)
+++ /packages/ufo2fdk/branches/ufo3/documentation/source/autodoc/kernFeatureWriter.rst	(revision 424)
@@ -0,0 +1,10 @@
+.. highlight:: python
+.. module:: ufo2fdk
+
+=================
+kernFeatureWriter
+=================
+
+.. automodule:: ufo2fdk.kernFeatureWriter
+.. autoclass:: ufo2fdk.kernFeatureWriter.KernFeatureWriter
+   :members:
Index: /packages/ufo2fdk/branches/ufo3/documentation/source/autodoc/outlineOTF.rst
===================================================================
--- /packages/ufo2fdk/branches/ufo3/documentation/source/autodoc/outlineOTF.rst	(revision 368)
+++ /packages/ufo2fdk/branches/ufo3/documentation/source/autodoc/outlineOTF.rst	(revision 368)
@@ -0,0 +1,10 @@
+.. highlight:: python
+.. module:: ufo2fdk
+
+==========
+outlineOTF
+==========
+
+.. automodule:: ufo2fdk.outlineOTF
+.. autoclass:: ufo2fdk.outlineOTF.OutlineOTFCompiler
+   :members:
Index: /packages/ufo2fdk/branches/ufo3/documentation/source/autodoc/fontInfoData.rst
===================================================================
--- /packages/ufo2fdk/branches/ufo3/documentation/source/autodoc/fontInfoData.rst	(revision 368)
+++ /packages/ufo2fdk/branches/ufo3/documentation/source/autodoc/fontInfoData.rst	(revision 368)
@@ -0,0 +1,42 @@
+.. highlight:: python
+.. module:: ufo2fdk
+
+============
+fontInfoData
+============
+
+.. automodule:: ufo2fdk.fontInfoData
+
+Main Functions
+^^^^^^^^^^^^^^
+
+.. autofunction:: ufo2fdk.fontInfoData.getAttrWithFallback
+.. autofunction:: ufo2fdk.fontInfoData.preflightInfo
+
+Static Fallbacks
+^^^^^^^^^^^^^^^^
+
+Most of the fallbacks have static values. To see what is set for these, look at the source code's *staticFallbackData* definition.
+
+Special Fallbacks
+^^^^^^^^^^^^^^^^^
+
+In some cases, the fallback values are dynamically generated from other data in the info object. These are handled internally with functions.
+
+.. autofunction:: ufo2fdk.fontInfoData.styleMapFamilyNameFallback
+.. autofunction:: ufo2fdk.fontInfoData.openTypeHeadCreatedFallback
+.. autofunction:: ufo2fdk.fontInfoData.openTypeHheaAscenderFallback
+.. autofunction:: ufo2fdk.fontInfoData.openTypeHheaDescenderFallback
+.. autofunction:: ufo2fdk.fontInfoData.openTypeNameVersionFallback
+.. autofunction:: ufo2fdk.fontInfoData.openTypeNameUniqueIDFallback
+.. autofunction:: ufo2fdk.fontInfoData.openTypeNamePreferredFamilyNameFallback
+.. autofunction:: ufo2fdk.fontInfoData.openTypeNamePreferredSubfamilyNameFallback
+.. autofunction:: ufo2fdk.fontInfoData.openTypeNameCompatibleFullNameFallback
+.. autofunction:: ufo2fdk.fontInfoData.openTypeOS2TypoAscenderFallback
+.. autofunction:: ufo2fdk.fontInfoData.openTypeOS2TypoDescenderFallback
+.. autofunction:: ufo2fdk.fontInfoData.openTypeOS2WinAscentFallback
+.. autofunction:: ufo2fdk.fontInfoData.openTypeOS2WinDescentFallback
+.. autofunction:: ufo2fdk.fontInfoData.postscriptFontNameFallback
+.. autofunction:: ufo2fdk.fontInfoData.postscriptFullNameFallback
+.. autofunction:: ufo2fdk.fontInfoData.postscriptSlantAngleFallback
+.. autofunction:: ufo2fdk.fontInfoData.postscriptWeightNameFallback
Index: /packages/ufo2fdk/branches/ufo3/documentation/source/autodoc/makeotfParts.rst
===================================================================
--- /packages/ufo2fdk/branches/ufo3/documentation/source/autodoc/makeotfParts.rst	(revision 368)
+++ /packages/ufo2fdk/branches/ufo3/documentation/source/autodoc/makeotfParts.rst	(revision 368)
@@ -0,0 +1,10 @@
+.. highlight:: python
+.. module:: ufo2fdk
+
+============
+makeotfParts
+============
+
+.. automodule:: ufo2fdk.makeotfParts
+.. autoclass:: ufo2fdk.makeotfParts.MakeOTFPartsCompiler
+   :members:
Index: /packages/ufo2fdk/branches/ufo3/documentation/source/autodoc/fdkBridge.rst
===================================================================
--- /packages/ufo2fdk/branches/ufo3/documentation/source/autodoc/fdkBridge.rst	(revision 368)
+++ /packages/ufo2fdk/branches/ufo3/documentation/source/autodoc/fdkBridge.rst	(revision 368)
@@ -0,0 +1,12 @@
+.. highlight:: python
+.. module:: ufo2fdk
+
+=========
+fdkBridge
+=========
+
+.. automodule:: ufo2fdk.fdkBridge
+.. autofunction:: ufo2fdk.fdkBridge.haveFDK
+.. autofunction:: ufo2fdk.fdkBridge.makeotf
+.. autofunction:: ufo2fdk.fdkBridge.checkOutlines
+.. autofunction:: ufo2fdk.fdkBridge.autohint
Index: /packages/ufo2fdk/branches/ufo3/documentation/source/index.rst
===================================================================
--- /packages/ufo2fdk/branches/ufo3/documentation/source/index.rst	(revision 482)
+++ /packages/ufo2fdk/branches/ufo3/documentation/source/index.rst	(revision 482)
@@ -0,0 +1,116 @@
+.. _index:
+.. highlight:: python
+.. module:: ufo2fdk
+
+ufo2fdk
+=======
+
+ufo2fdk is a package that handles the creation of OpenType-CFF fonts from UFO source files with the aid of the `Adobe Font Development Kit for OpenType (AFDKO aka FDK) <http://www.adobe.com/devnet/opentype/afdko/>`_. This package does not embedd the FDK, rather it :mod:`bridges <ufo2fdk.fdkBridge>` to it on the user's system with the expectation that the user has installed the FDK in the normal way.
+
+The fonts created by the package should be considered only for beta usage and should be used with care.
+
+Basic Usage
+===========
+
+Two main things are exposed for public use: *haveFDK* and *OTFCompiler*.
+
+haveFDK
+^^^^^^^
+
+This function tests the availability of the FDK and returns a boolean indicating what was found. Example::
+
+  from ufo2fdk import haveFDK
+
+  if haveFDK():
+    print "I found the FDK!"
+  else:
+    print "I'm sorry, I could not find the FDK."
+
+OTFCompiler
+^^^^^^^^^^^
+
+.. autoclass:: OTFCompiler
+   :members:
+
+Example::
+
+  from ufo2FDK import OTFCompiler
+
+  compiler = OTFCompiler()
+  reports = compiler.compile(font, "/MyDirectory/MyFont.otf", checkOutlines=True, autohint=True)
+  print reports["checkOutlines"]
+  print reports["autohint"]
+  print reports["makeotf"]
+
+That's all there is to it.
+
+Naming Data
+^^^^^^^^^^^
+
+As with any OpenType compiler, you have to set the font naming data to a particular standard for your naming to be set correctly. In ufo2fdk, you can get away with setting *two* naming attributes in your font.info object for simple fonts:
+
+- familyName: The name for your family. For example, "My Garamond".
+- styleName: The style name for this particular font. For example, "Display Light Italic"
+
+ufo2fdk will create all of the other naming data based on thse two fields. If you want to use the fully automatic naming system, all of the other name attributes should be set to ``None`` in your font. However, if you want to override the automated system at any level, you can specify particular naming attributes and ufo2fdk will honor your settings. You don't have to set *all* of the attributes, just the ones you don't want to be automated. For example, in the family "My Garamond" you have eight weights. It would be nice to style map the italics to the romans for each weight. To do this, in the individual romans and italics, you need to set the style mapping data. This is done through the ``styleMapFamilyName`` and ``styleMapStyleName`` attributes. In each of your roman and italic pairs you would do this:
+
+**My Garamond-Light.ufo**
+
+- familyName = "My Garamond"
+- styleName = "Light"
+- styleMapFamilyName = "My Garamond Display Light"
+- styleMapStyleName = "regular"
+
+**My Garamond-Light Italic.ufo**
+
+- familyName = "My Garamond"
+- styleName = "Display Light Italic"
+- styleMapFamilyName = "My Garamond Display Light"
+- styleMapStyleName = "italic"
+
+**My Garamond-Book.ufo**
+
+- familyName = "My Garamond"
+- styleName = "Book"
+- styleMapFamilyName = "My Garamond Display Book"
+- styleMapStyleName = "regular"
+
+**My Garamond-Book Italic.ufo**
+
+- familyName = "My Garamond"
+- styleName = "Display Book Italic"
+- styleMapFamilyName = "My Garamond Display Book"
+- styleMapStyleName = "italic"
+
+**etc.**
+
+The full details of how the names are created is available in the :mod:`fontInfoData documentation <ufo2fdk.fontInfoData>`. The usage of the names is detailed in the :mod:`makeotfParts documentation <ufo2fdk.makeotfParts>`.
+
+Additionally, if you have defined any naming data, or any data for that matter, in table definitions within your font's features that data will be honored.
+
+Kerning Data
+^^^^^^^^^^^^
+
+If your font's features, and any files included in your font's features, do not contain a kerning feature, ufo2fdk will create one based on your font's kerning data. Do to the complexities in how raw kerning data is translated into a kerning feature, it is safest to use your preferred kerning editor to create the kerning feature if possible.
+
+Internals
+^^^^^^^^^
+
+If you want to know how the bridging works, what information is needed in the source UFO, how to modify the internal behavior to fit your needs, etc. read on:
+
+.. toctree::
+   :maxdepth: 1
+
+   autodoc/fdkBridge
+   autodoc/makeotfParts
+   autodoc/outlineOTF
+   autodoc/kernFeatureWriter
+   autodoc/fontInfoData
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
Index: /packages/ufo2fdk/branches/ufo3/documentation/readme.txt
===================================================================
--- /packages/ufo2fdk/branches/ufo3/documentation/readme.txt	(revision 426)
+++ /packages/ufo2fdk/branches/ufo3/documentation/readme.txt	(revision 426)
@@ -0,0 +1,7 @@
+To compile this documentation, use Sphinx. In the shell, move to the documentation folder and enter the following:
+
+  make html
+
+You may already have Sphinx installed. If not, you can get it here:
+
+  http://sphinx.pocoo.org
Index: /packages/ufo2fdk/branches/ufo3/documentation/Makefile
===================================================================
--- /packages/ufo2fdk/branches/ufo3/documentation/Makefile	(revision 368)
+++ /packages/ufo2fdk/branches/ufo3/documentation/Makefile	(revision 368)
@@ -0,0 +1,75 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+
+.PHONY: help clean html web pickle htmlhelp latex changes linkcheck
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html      to make standalone HTML files"
+	@echo "  pickle    to make pickle files"
+	@echo "  json      to make JSON files"
+	@echo "  htmlhelp  to make HTML files and a HTML help project"
+	@echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  changes   to make an overview over all changed/added/deprecated items"
+	@echo "  linkcheck to check all external links for integrity"
+
+clean:
+	-rm -rf build/*
+
+html:
+	mkdir -p build/html build/doctrees
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) build/html
+	@echo
+	@echo "Build finished. The HTML pages are in build/html."
+
+pickle:
+	mkdir -p build/pickle build/doctrees
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) build/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+web: pickle
+
+json:
+	mkdir -p build/json build/doctrees
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) build/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	mkdir -p build/htmlhelp build/doctrees
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) build/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in build/htmlhelp."
+
+latex:
+	mkdir -p build/latex build/doctrees
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) build/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in build/latex."
+	@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+	      "run these through (pdf)latex."
+
+changes:
+	mkdir -p build/changes build/doctrees
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) build/changes
+	@echo
+	@echo "The overview file is in build/changes."
+
+linkcheck:
+	mkdir -p build/linkcheck build/doctrees
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) build/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in build/linkcheck/output.txt."
Index: /packages/ufo2fdk/branches/ufo3/MANIFEST.in
===================================================================
--- /packages/ufo2fdk/branches/ufo3/MANIFEST.in	(revision 500)
+++ /packages/ufo2fdk/branches/ufo3/MANIFEST.in	(revision 500)
@@ -0,0 +1,3 @@
+include License.txt
+include Install.txt
+recursive-include documentation *
