summaryrefslogtreecommitdiff
path: root/misc/pylib/robofab/tools
diff options
context:
space:
mode:
authorRasmus Andersson <rasmus@notion.se>2017-09-04 06:03:17 +0300
committerRasmus Andersson <rasmus@notion.se>2017-09-04 18:12:34 +0300
commit8234b62ab762637ef24c3398b4204a8ce8db31a7 (patch)
tree1c8df547021cdb58951630a015e4101ede46dbf1 /misc/pylib/robofab/tools
parent31ae014e0c827dd76696fdab7e4ca3fed9f6402b (diff)
downloadinter-8234b62ab762637ef24c3398b4204a8ce8db31a7.tar.xz
Speeds up font compilation by around 200%
Cython is used to compile some hot paths into native Python extensions. These hot paths were identified through running ufocompile with the hotshot profiler and then converting file by file to Cython, starting with the "hottest" paths and continuing until returns were deminishing. This means that only a few Python files were converted to Cython. Closes #23 Closes #20 (really this time)
Diffstat (limited to 'misc/pylib/robofab/tools')
-rwxr-xr-xmisc/pylib/robofab/tools/__init__.py12
-rwxr-xr-xmisc/pylib/robofab/tools/accentBuilder.py348
-rw-r--r--misc/pylib/robofab/tools/fontlabFeatureSplitter.py85
-rwxr-xr-xmisc/pylib/robofab/tools/glifExport.py95
-rwxr-xr-xmisc/pylib/robofab/tools/glifImport.py74
-rw-r--r--misc/pylib/robofab/tools/glyphConstruction.py565
-rwxr-xr-xmisc/pylib/robofab/tools/glyphNameSchemes.py41
-rwxr-xr-xmisc/pylib/robofab/tools/objectDumper.py55
-rwxr-xr-xmisc/pylib/robofab/tools/otFeatures.py190
-rwxr-xr-xmisc/pylib/robofab/tools/proof.py119
-rwxr-xr-xmisc/pylib/robofab/tools/remote.py175
-rwxr-xr-xmisc/pylib/robofab/tools/rfPrefs.py122
-rwxr-xr-xmisc/pylib/robofab/tools/toolsAll.py145
-rwxr-xr-xmisc/pylib/robofab/tools/toolsFL.py339
-rwxr-xr-xmisc/pylib/robofab/tools/toolsRF.py6
15 files changed, 2371 insertions, 0 deletions
diff --git a/misc/pylib/robofab/tools/__init__.py b/misc/pylib/robofab/tools/__init__.py
new file mode 100755
index 000000000..e97bd43c2
--- /dev/null
+++ b/misc/pylib/robofab/tools/__init__.py
@@ -0,0 +1,12 @@
+"""
+
+Directory for all tool like code.
+Stuff that doesn't really belong to objects, pens, compilers etc.
+The code is split up into sections.
+
+
+"""
+
+
+
+
diff --git a/misc/pylib/robofab/tools/accentBuilder.py b/misc/pylib/robofab/tools/accentBuilder.py
new file mode 100755
index 000000000..d122222fd
--- /dev/null
+++ b/misc/pylib/robofab/tools/accentBuilder.py
@@ -0,0 +1,348 @@
+"""A simple set of tools for building accented glyphs.
+# Hey look! A demonstration:
+from robofab.accentBuilder import AccentTools, buildRelatedAccentList
+font = CurrentFont
+# a list of accented glyphs that you want to build
+myList=['Aacute', 'aacute']
+# search for glyphs related to glyphs in myList and add them to myList
+myList=buildRelatedAccentList(font, myList)+myList
+# start the class
+at=AccentTools(font, myList)
+# clear away any anchors that exist (this is optional)
+at.clearAnchors()
+# add necessary anchors if you want to
+at.buildAnchors(ucXOffset=20, ucYOffset=40, lcXOffset=15, lcYOffset=30)
+# print a report of any errors that occured
+at.printAnchorErrors()
+# build the accented glyphs if you want to
+at.buildAccents()
+# print a report of any errors that occured
+at.printAccentErrors()
+"""
+#XXX! This is *very* experimental! I think it works, but you never know.
+
+from robofab.gString import lowercase_plain, accents, uppercase_plain, splitAccent, findAccentBase
+from robofab.tools.toolsAll import readGlyphConstructions
+import robofab
+from robofab.interface.all.dialogs import ProgressBar
+from robofab.world import RFWorld
+inFontLab = RFWorld().inFontLab
+
+anchorColor=125
+accentColor=75
+
+def stripSuffix(glyphName):
+ """strip away unnecessary suffixes from a glyph name"""
+ if glyphName.find('.') != -1:
+ baseName = glyphName.split('.')[0]
+ if glyphName.find('.sc') != -1:
+ baseName = '.'.join([baseName, 'sc'])
+ return baseName
+ else:
+ return glyphName
+
+def buildRelatedAccentList(font, list):
+ """build a list of related glyphs suitable for use with AccentTools"""
+ searchList = []
+ baseGlyphs = {}
+ foundList = []
+ for glyphName in list:
+ splitNames = splitAccent(glyphName)
+ baseName = splitNames[0]
+ accentNames = splitNames[1]
+ if baseName not in searchList:
+ searchList.append(baseName)
+ if baseName not in baseGlyphs.keys():
+ baseGlyphs[baseName] = [accentNames]
+ else:
+ baseGlyphs[baseName].append(accentNames)
+ foundGlyphs = findRelatedGlyphs(font, searchList, doAccents=0)
+ for baseGlyph in foundGlyphs.keys():
+ for foundGlyph in foundGlyphs[baseGlyph]:
+ for accentNames in baseGlyphs[baseGlyph]:
+ foundList.append(makeAccentName(foundGlyph, accentNames))
+ return foundList
+
+def findRelatedGlyphs(font, searchItem, doAccents=True):
+ """Gather up a bunch of related glyph names. Send it either a
+ single glyph name 'a', or a list of glyph names ['a', 'x'] and it
+ returns a dict like: {'a': ['atilde', 'a.alt', 'a.swash']}. if doAccents
+ is False it will skip accented glyph names.
+ This is a relatively slow operation!"""
+ relatedGlyphs = {}
+ for name in font.keys():
+ base = name.split('.')[0]
+ if name not in relatedGlyphs.keys():
+ relatedGlyphs[name] = []
+ if base not in relatedGlyphs.keys():
+ relatedGlyphs[base] = []
+ if doAccents:
+ accentBase = findAccentBase(name)
+ if accentBase not in relatedGlyphs.keys():
+ relatedGlyphs[accentBase] = []
+ baseAccentBase = findAccentBase(base)
+ if baseAccentBase not in relatedGlyphs.keys():
+ relatedGlyphs[baseAccentBase] = []
+ if base != name and name not in relatedGlyphs[base]:
+ relatedGlyphs[base].append(name)
+ if doAccents:
+ if accentBase != name and name not in relatedGlyphs[accentBase]:
+ relatedGlyphs[accentBase].append(name)
+ if baseAccentBase != name and name not in relatedGlyphs[baseAccentBase]:
+ relatedGlyphs[baseAccentBase].append(name)
+ foundGlyphs = {}
+ if isinstance(searchItem, str):
+ searchList = [searchItem]
+ else:
+ searchList = searchItem
+ for glyph in searchList:
+ foundGlyphs[glyph] = relatedGlyphs[glyph]
+ return foundGlyphs
+
+def makeAccentName(baseName, accentNames):
+ """make an accented glyph name"""
+ if isinstance(accentNames, str):
+ accentNames = [accentNames]
+ build = []
+ if baseName.find('.') != -1:
+ base = baseName.split('.')[0]
+ suffix = baseName.split('.')[1]
+ else:
+ base = baseName
+ suffix = ''
+ build.append(base)
+ for accent in accentNames:
+ build.append(accent)
+ buildJoin = ''.join(build)
+ name = '.'.join([buildJoin, suffix])
+ return name
+
+def nameBuster(glyphName, glyphConstruct):
+ stripedSuffixName = stripSuffix(glyphName)
+ suffix = None
+ errors = []
+ accentNames = []
+ baseName = glyphName
+ if glyphName.find('.') != -1:
+ suffix = glyphName.split('.')[1]
+ if glyphName.find('.sc') != -1:
+ suffix = glyphName.split('.sc')[1]
+ if stripedSuffixName not in glyphConstruct.keys():
+ errors.append('%s: %s not in glyph construction database'%(glyphName, stripedSuffixName))
+ else:
+ if suffix is None:
+ baseName = glyphConstruct[stripedSuffixName][0]
+ else:
+ if glyphName.find('.sc') != -1:
+ baseName = ''.join([glyphConstruct[stripedSuffixName][0], suffix])
+ else:
+ baseName = '.'.join([glyphConstruct[stripedSuffixName][0], suffix])
+ accentNames = glyphConstruct[stripedSuffixName][1:]
+ return (baseName, stripedSuffixName, accentNames, errors)
+
+class AccentTools:
+ def __init__(self, font, accentList):
+ """several tools for working with anchors and building accents"""
+ self.glyphConstructions = readGlyphConstructions()
+ self.accentList = accentList
+ self.anchorErrors = ['ANCHOR ERRORS:']
+ self.accentErrors = ['ACCENT ERRORS:']
+ self.font = font
+
+ def clearAnchors(self, doProgress=True):
+ """clear all anchors in the font"""
+ tickCount = len(self.font)
+ if doProgress:
+ bar = ProgressBar("Cleaning all anchors...", tickCount)
+ tick = 0
+ for glyphName in self.accentList:
+ if doProgress:
+ bar.label(glyphName)
+ baseName, stripedSuffixName, accentNames, errors = nameBuster(glyphName, self.glyphConstructions)
+ existError = False
+ if len(errors) > 0:
+ existError = True
+ if not existError:
+ toClear = [baseName]
+ for accent, position in accentNames:
+ toClear.append(accent)
+ for glyphName in toClear:
+ try:
+ self.font[glyphName].clearAnchors()
+ except IndexError: pass
+ if doProgress:
+ bar.tick(tick)
+ tick = tick+1
+ if doProgress:
+ bar.close()
+
+ def buildAnchors(self, ucXOffset=0, ucYOffset=0, lcXOffset=0, lcYOffset=0, markGlyph=True, doProgress=True):
+ """add the necessary anchors to the glyphs if they don't exist
+ some flag definitions:
+ uc/lc/X/YOffset=20 offset values for the anchors
+ markGlyph=1 mark the glyph that is created
+ doProgress=1 show a progress bar"""
+ accentOffset = 10
+ tickCount = len(self.accentList)
+ if doProgress:
+ bar = ProgressBar('Adding anchors...', tickCount)
+ tick = 0
+ for glyphName in self.accentList:
+ if doProgress:
+ bar.label(glyphName)
+ previousPositions = {}
+ baseName, stripedSuffixName, accentNames, errors = nameBuster(glyphName, self.glyphConstructions)
+ existError = False
+ if len(errors) > 0:
+ existError = True
+ for anchorError in errors:
+ self.anchorErrors.append(anchorError)
+ if not existError:
+ existError = False
+ try:
+ self.font[baseName]
+ except IndexError:
+ self.anchorErrors.append(' '.join([glyphName, ':', baseName, 'does not exist.']))
+ existError = True
+ for accentName, accentPosition in accentNames:
+ try:
+ self.font[accentName]
+ except IndexError:
+ self.anchorErrors.append(' '.join([glyphName, ':', accentName, 'does not exist.']))
+ existError = True
+ if not existError:
+ #glyph = self.font.newGlyph(glyphName, clear=True)
+ for accentName, accentPosition in accentNames:
+ if baseName.split('.')[0] in lowercase_plain:
+ xOffset = lcXOffset-accentOffset
+ yOffset = lcYOffset-accentOffset
+ else:
+ xOffset = ucXOffset-accentOffset
+ yOffset = ucYOffset-accentOffset
+ # should I add a cedilla and ogonek yoffset override here?
+ if accentPosition not in previousPositions.keys():
+ self._dropAnchor(self.font[baseName], accentPosition, xOffset, yOffset)
+ if markGlyph:
+ self.font[baseName].mark = anchorColor
+ if inFontLab:
+ self.font[baseName].update()
+ else:
+ self._dropAnchor(self.font[previousPositions[accentPosition]], accentPosition, xOffset, yOffset)
+ self._dropAnchor(self.font[accentName], accentPosition, accentOffset, accentOffset, doAccentPosition=1)
+ previousPositions[accentPosition] = accentName
+ if markGlyph:
+ self.font[accentName].mark = anchorColor
+ if inFontLab:
+ self.font[accentName].update()
+ if inFontLab:
+ self.font.update()
+ if doProgress:
+ bar.tick(tick)
+ tick = tick+1
+ if doProgress:
+ bar.close()
+
+ def printAnchorErrors(self):
+ """print errors encounted during buildAnchors"""
+ if len(self.anchorErrors) == 1:
+ print 'No anchor errors encountered'
+ else:
+ for i in self.anchorErrors:
+ print i
+
+ def _dropAnchor(self, glyph, positionName, xOffset=0, yOffset=0, doAccentPosition=False):
+ """anchor adding method. for internal use only."""
+ existingAnchorNames = []
+ for anchor in glyph.getAnchors():
+ existingAnchorNames.append(anchor.name)
+ if doAccentPosition:
+ positionName = ''.join(['_', positionName])
+ if positionName not in existingAnchorNames:
+ glyphLeft, glyphBottom, glyphRight, glyphTop = glyph.box
+ glyphXCenter = glyph.width/2
+ if positionName == 'top':
+ glyph.appendAnchor(positionName, (glyphXCenter, glyphTop+yOffset))
+ elif positionName == 'bottom':
+ glyph.appendAnchor(positionName, (glyphXCenter, glyphBottom-yOffset))
+ elif positionName == 'left':
+ glyph.appendAnchor(positionName, (glyphLeft-xOffset, glyphTop))
+ elif positionName == 'right':
+ glyph.appendAnchor(positionName, (glyphRight+xOffset, glyphTop))
+ elif positionName == '_top':
+ glyph.appendAnchor(positionName, (glyphXCenter, glyphBottom-yOffset))
+ elif positionName == '_bottom':
+ glyph.appendAnchor(positionName, (glyphXCenter, glyphTop+yOffset))
+ elif positionName == '_left':
+ glyph.appendAnchor(positionName, (glyphRight+xOffset, glyphTop))
+ elif positionName == '_right':
+ glyph.appendAnchor(positionName, (glyphLeft-xOffset, glyphTop))
+ if inFontLab:
+ glyph.update()
+
+ def buildAccents(self, clear=True, adjustWidths=True, markGlyph=True, doProgress=True):
+ """build accented glyphs. some flag definitions:
+ clear=1 clear the glyphs if they already exist
+ markGlyph=1 mark the glyph that is created
+ doProgress=1 show a progress bar
+ adjustWidths=1 will fix right and left margins when left or right accents are added"""
+ tickCount = len(self.accentList)
+ if doProgress:
+ bar = ProgressBar('Building accented glyphs...', tickCount)
+ tick = 0
+ for glyphName in self.accentList:
+ if doProgress:
+ bar.label(glyphName)
+ existError = False
+ anchorError = False
+
+ baseName, stripedSuffixName, accentNames, errors = nameBuster(glyphName, self.glyphConstructions)
+ if len(errors) > 0:
+ existError = True
+ for accentError in errors:
+ self.accentErrors.append(accentError)
+
+ if not existError:
+ baseAnchors = []
+ try:
+ self.font[baseName]
+ except IndexError:
+ self.accentErrors.append('%s: %s does not exist.'%(glyphName, baseName))
+ existError = True
+ else:
+ for anchor in self.font[baseName].anchors:
+ baseAnchors.append(anchor.name)
+ for accentName, accentPosition in accentNames:
+ accentAnchors = []
+ try:
+ self.font[accentName]
+ except IndexError:
+ self.accentErrors.append('%s: %s does not exist.'%(glyphName, accentName))
+ existError = True
+ else:
+ for anchor in self.font[accentName].getAnchors():
+ accentAnchors.append(anchor.name)
+ if accentPosition not in baseAnchors:
+ self.accentErrors.append('%s: %s not in %s anchors.'%(glyphName, accentPosition, baseName))
+ anchorError = True
+ if ''.join(['_', accentPosition]) not in accentAnchors:
+ self.accentErrors.append('%s: %s not in %s anchors.'%(glyphName, ''.join(['_', accentPosition]), accentName))
+ anchorError = True
+ if not existError and not anchorError:
+ destination = self.font.compileGlyph(glyphName, baseName, self.glyphConstructions[stripedSuffixName][1:], adjustWidths)
+ if markGlyph:
+ destination.mark = accentColor
+ if doProgress:
+ bar.tick(tick)
+ tick = tick+1
+ if doProgress:
+ bar.close()
+
+ def printAccentErrors(self):
+ """print errors encounted during buildAccents"""
+ if len(self.accentErrors) == 1:
+ print 'No accent errors encountered'
+ else:
+ for i in self.accentErrors:
+ print i
+
+
diff --git a/misc/pylib/robofab/tools/fontlabFeatureSplitter.py b/misc/pylib/robofab/tools/fontlabFeatureSplitter.py
new file mode 100644
index 000000000..3e0173dfc
--- /dev/null
+++ b/misc/pylib/robofab/tools/fontlabFeatureSplitter.py
@@ -0,0 +1,85 @@
+import re
+
+featureRE = re.compile(
+ "^" # start of line
+ "\s*" #
+ "feature" # feature
+ "\s+" #
+ "(\w{4})" # four alphanumeric characters
+ "\s*" #
+ "\{" # {
+ , re.MULTILINE # run in multiline to preserve line seps
+)
+
+def splitFeaturesForFontLab(text):
+ """
+ >>> result = splitFeaturesForFontLab(testText)
+ >>> result == expectedTestResult
+ True
+ """
+ classes = ""
+ features = []
+ while text:
+ m = featureRE.search(text)
+ if m is None:
+ classes = text
+ text = ""
+ else:
+ start, end = m.span()
+ # if start is not zero, this is the first match
+ # and all previous lines are part of the "classes"
+ if start > 0:
+ assert not classes
+ classes = text[:start]
+ # extract the current feature
+ featureName = m.group(1)
+ featureText = text[start:end]
+ text = text[end:]
+ # grab all text before the next feature definition
+ # and add it to the current definition
+ if text:
+ m = featureRE.search(text)
+ if m is not None:
+ start, end = m.span()
+ featureText += text[:start]
+ text = text[start:]
+ else:
+ featureText += text
+ text = ""
+ # store the feature
+ features.append((featureName, featureText))
+ return classes, features
+
+testText = """
+@class1 = [a b c d];
+
+feature liga {
+ sub f i by fi;
+} liga;
+
+@class2 = [x y z];
+
+feature salt {
+ sub a by a.alt;
+} salt; feature ss01 {sub x by x.alt} ss01;
+
+feature ss02 {sub y by y.alt} ss02;
+
+# feature calt {
+# sub a b' by b.alt;
+# } calt;
+"""
+
+expectedTestResult = (
+ "\n@class1 = [a b c d];\n",
+ [
+ ("liga", "\nfeature liga {\n sub f i by fi;\n} liga;\n\n@class2 = [x y z];\n"),
+ ("salt", "\nfeature salt {\n sub a by a.alt;\n} salt; feature ss01 {sub x by x.alt} ss01;\n"),
+ ("ss02", "\nfeature ss02 {sub y by y.alt} ss02;\n\n# feature calt {\n# sub a b' by b.alt;\n# } calt;\n")
+ ]
+)
+
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
diff --git a/misc/pylib/robofab/tools/glifExport.py b/misc/pylib/robofab/tools/glifExport.py
new file mode 100755
index 000000000..bacb9190a
--- /dev/null
+++ b/misc/pylib/robofab/tools/glifExport.py
@@ -0,0 +1,95 @@
+"""Tool for exporting GLIFs from FontLab"""
+
+import FL
+import os
+from robofab.interface.all.dialogs import ProgressBar
+from robofab.glifLib import GlyphSet
+from robofab.tools.glifImport import GlyphPlaceholder
+from robofab.pens.flPen import drawFLGlyphOntoPointPen
+
+
+def exportGlyph(glyphName, flGlyph, glyphSet):
+ """Export a FontLab glyph."""
+
+ glyph = GlyphPlaceholder()
+ glyph.width = flGlyph.width
+ glyph.unicodes = flGlyph.unicodes
+ if flGlyph.note:
+ glyph.note = flGlyph.note
+ customdata = flGlyph.customdata
+ if customdata:
+ from cStringIO import StringIO
+ from robofab.plistlib import readPlist, Data
+ f = StringIO(customdata)
+ try:
+ glyph.lib = readPlist(f)
+ except: # XXX ugh, plistlib can raise lots of things
+ # Anyway, customdata does not contain valid plist data,
+ # but we don't need to toss it!
+ glyph.lib = {"org.robofab.fontlab.customdata": Data(customdata)}
+
+ def drawPoints(pen):
+ # whoohoo, nested scopes are cool.
+ drawFLGlyphOntoPointPen(flGlyph, pen)
+
+ glyphSet.writeGlyph(glyphName, glyph, drawPoints)
+
+
+def exportGlyphs(font, glyphs=None, dest=None, doProgress=True, bar=None):
+ """Export all glyphs in a FontLab font"""
+ if dest is None:
+ dir, base = os.path.split(font.file_name)
+ base = base.split(".")[0] + ".glyphs"
+ dest = os.path.join(dir, base)
+
+ if not os.path.exists(dest):
+ os.makedirs(dest)
+
+ glyphSet = GlyphSet(dest)
+
+ if glyphs is None:
+ indices = range(len(font))
+ else:
+ indices = []
+ for glyphName in glyphs:
+ indices.append(font.FindGlyph(glyphName))
+ barStart = 0
+ closeBar = False
+ if doProgress:
+ if not bar:
+ bar = ProgressBar("Exporting Glyphs", len(indices))
+ closeBar = True
+ else:
+ barStart = bar.getCurrentTick()
+ else:
+ bar = None
+ try:
+ done = {}
+ for i in range(len(indices)):
+ #if not (i % 10) and not bar.tick(i + barStart):
+ # raise KeyboardInterrupt
+ index = indices[i]
+ flGlyph = font[index]
+ if flGlyph is None:
+ continue
+ glyphName = flGlyph.name
+ if not glyphName:
+ print "can't dump glyph #%s, it has no glyph name" % i
+ else:
+ if glyphName in done:
+ n = 1
+ while ("%s#%s" % (glyphName, n)) in done:
+ n += 1
+ glyphName = "%s#%s" % (glyphName, n)
+ done[glyphName] = None
+ exportGlyph(glyphName, flGlyph, glyphSet)
+ if bar and not i % 10:
+ bar.tick(barStart + i)
+ # Write out contents.plist
+ glyphSet.writeContents()
+ except KeyboardInterrupt:
+ if bar:
+ bar.close()
+ bar = None
+ if bar and closeBar:
+ bar.close()
diff --git a/misc/pylib/robofab/tools/glifImport.py b/misc/pylib/robofab/tools/glifImport.py
new file mode 100755
index 000000000..68d14460a
--- /dev/null
+++ b/misc/pylib/robofab/tools/glifImport.py
@@ -0,0 +1,74 @@
+"""Tools for importing GLIFs into FontLab"""
+
+import os
+from FL import fl
+from robofab.tools.toolsFL import NewGlyph, FontIndex
+from robofab.pens.flPen import FLPointPen
+from robofab.glifLib import GlyphSet
+from robofab.interface.all.dialogs import ProgressBar, GetFolder
+
+
+class GlyphPlaceholder:
+ pass
+
+
+def importAllGlifFiles(font, dirName=None, doProgress=True, bar=None):
+ """import all GLIFs into a FontLab font"""
+ if dirName is None:
+ if font.file_name:
+ dir, base = os.path.split(font.file_name)
+ base = base.split(".")[0] + ".glyphs"
+ dirName = os.path.join(dir, base)
+ else:
+ dirName = GetFolder("Please select a folder with .glif files")
+ glyphSet = GlyphSet(dirName)
+ glyphNames = glyphSet.keys()
+ glyphNames.sort()
+ barStart = 0
+ closeBar = False
+ if doProgress:
+ if not bar:
+ bar = ProgressBar("Importing Glyphs", len(glyphNames))
+ closeBar = True
+ else:
+ barStart = bar.getCurrentTick()
+ else:
+ bar = None
+ try:
+ for i in range(len(glyphNames)):
+ #if not (i % 10) and not bar.tick(barStart + i):
+ # raise KeyboardInterrupt
+ glyphName = glyphNames[i]
+ flGlyph = NewGlyph(font, glyphName, clear=True)
+ pen = FLPointPen(flGlyph)
+ glyph = GlyphPlaceholder()
+ glyphSet.readGlyph(glyphName, glyph, pen)
+ if hasattr(glyph, "width"):
+ flGlyph.width = int(round(glyph.width))
+ if hasattr(glyph, "unicodes"):
+ flGlyph.unicodes = glyph.unicodes
+ if hasattr(glyph, "note"):
+ flGlyph.note = glyph.note # XXX must encode
+ if hasattr(glyph, "lib"):
+ from cStringIO import StringIO
+ from robofab.plistlib import writePlist
+ lib = glyph.lib
+ if lib:
+ if len(lib) == 1 and "org.robofab.fontlab.customdata" in lib:
+ data = lib["org.robofab.fontlab.customdata"].data
+ else:
+ f = StringIO()
+ writePlist(glyph.lib, f)
+ data = f.getvalue()
+ flGlyph.customdata = data
+ # XXX the next bit is only correct when font is the current font :-(
+ fl.UpdateGlyph(font.FindGlyph(glyphName))
+ if bar and not i % 10:
+ bar.tick(barStart + i)
+ except KeyboardInterrupt:
+ if bar:
+ bar.close()
+ bar = None
+ fl.UpdateFont(FontIndex(font))
+ if bar and closeBar:
+ bar.close()
diff --git a/misc/pylib/robofab/tools/glyphConstruction.py b/misc/pylib/robofab/tools/glyphConstruction.py
new file mode 100644
index 000000000..afe098423
--- /dev/null
+++ b/misc/pylib/robofab/tools/glyphConstruction.py
@@ -0,0 +1,565 @@
+
+_glyphConstruction = """\
+#
+# RoboFab Glyph Construction Database
+#
+# format:
+# Glyphname: BaseGlyph Accent.RelativePosition* Accent.RelativePosition*
+# *RelativePosition can be top, bottom, left, right
+#
+# NOTE: this is not a comprehensive, or even accurate, glyph list.
+# It was built by Python robots and, in many cases, by tired human hands.
+# Please report any omissions, errors or praise to the local RoboFab authorities.
+#
+##: Uppercase
+AEacute: AE acute.top
+AEmacron: AE macron.top
+Aacute: A acute.top
+Abreve: A breve.top
+Abreveacute: A breve.top acute.top
+Abrevedotaccent: A breve.top dotaccent.bottom
+Abrevegrave: A breve.top grave.top
+Abrevetilde: A breve.top tilde.top
+Acaron: A caron.top
+Acircumflex: A circumflex.top
+Acircumflexacute: A circumflex.top acute.top
+Acircumflexdotaccent: A circumflex.top dotaccent.bottom
+Acircumflexgrave: A circumflex.top grave.top
+Acircumflextilde: A circumflex.top tilde.top
+Adblgrave: A dblgrave.top
+Adieresis: A dieresis.top
+Adieresismacron: A dieresis.top macron.top
+Adotaccent: A dotaccent.top
+Adotaccentmacron: A dotaccent.top macron.top
+Agrave: A grave.top
+Amacron: A macron.top
+Aogonek: A ogonek.bottom
+Aring: A ring.top
+Aringacute: A ring.top acute.top
+Atilde: A tilde.top
+Bdotaccent: B dotaccent.top
+Cacute: C acute.top
+Ccaron: C caron.top
+Ccedilla: C cedilla.bottom
+Ccedillaacute: C cedilla.bottom acute.top
+Ccircumflex: C circumflex.top
+Cdotaccent: C dotaccent.top
+Dcaron: D caron.top
+Dcedilla: D cedilla.bottom
+Ddotaccent: D dotaccent.top
+Eacute: E acute.top
+Ebreve: E breve.top
+Ecaron: E caron.top
+Ecedilla: E cedilla.bottom
+Ecedillabreve: E cedilla.bottom breve.top
+Ecircumflex: E circumflex.top
+Ecircumflexacute: E circumflex.top acute.top
+Ecircumflexdotaccent: E circumflex.top dotaccent.bottom
+Ecircumflexgrave: E circumflex.top grave.top
+Ecircumflextilde: E circumflex.top tilde.top
+Edblgrave: E dblgrave.top
+Edieresis: E dieresis.top
+Edotaccent: E dotaccent.top
+Egrave: E grave.top
+Emacron: E macron.top
+Emacronacute: E macron.top acute.top
+Emacrongrave: E macron.top grave.top
+Eogonek: E ogonek.bottom
+Etilde: E tilde.top
+Fdotaccent: F dotaccent.top
+Gacute: G acute.top
+Gbreve: G breve.top
+Gcaron: G caron.top
+Gcedilla: G cedilla.bottom
+Gcircumflex: G circumflex.top
+Gcommaaccent: G commaaccent.bottom
+Gdotaccent: G dotaccent.top
+Gmacron: G macron.top
+Hcaron: H caron.top
+Hcedilla: H cedilla.top
+Hcircumflex: H circumflex.top
+Hdieresis: H dieresis.top
+Hdotaccent: H dotaccent.top
+Iacute: I acute.top
+Ibreve: I breve.top
+Icaron: I caron.top
+Icircumflex: I circumflex.top
+Idblgrave: I dblgrave.top
+Idieresis: I dieresis.top
+Idieresisacute: I dieresis.top acute.top
+Idotaccent: I dotaccent.top
+Igrave: I grave.top
+Imacron: I macron.top
+Iogonek: I ogonek.bottom
+Itilde: I tilde.top
+Jcircumflex: J circumflex.top
+Kacute: K acute.top
+Kcaron: K caron.top
+Kcedilla: K cedilla.bottom
+Kcommaaccent: K commaaccent.bottom
+Lacute: L acute.top
+Lcaron: L commaaccent.right
+Lcedilla: L cedilla.bottom
+Lcommaaccent: L commaaccent.bottom
+Ldot: L dot.right
+Ldotaccent: L dotaccent.bottom
+Ldotaccentmacron: L dotaccent.bottom macron.top
+Macute: M acute.top
+Mdotaccent: M dotaccent.top
+Nacute: N acute.top
+Ncaron: N caron.top
+Ncedilla: N cedilla.bottom
+Ncommaaccent: N commaaccent.bottom
+Ndotaccent: N dotaccent.top
+Ngrave: N grave.top
+Ntilde: N tilde.top
+Oacute: O acute.top
+Obreve: O breve.top
+Ocaron: O caron.top
+Ocircumflex: O circumflex.top
+Ocircumflexacute: O circumflex.top acute.top
+Ocircumflexdotaccent: O circumflex.top dotaccent.bottom
+Ocircumflexgrave: O circumflex.top grave.top
+Ocircumflextilde: O circumflex.top tilde.top
+Odblgrave: O dblgrave.top
+Odieresis: O dieresis.top
+Odieresismacron: O dieresis.top macron.top
+Ograve: O grave.top
+Ohungarumlaut: O hungarumlaut.top
+Omacron: O macron.top
+Omacronacute: O macron.top acute.top
+Omacrongrave: O macron.top grave.top
+Oogonek: O ogonek.bottom
+Oogonekmacron: O ogonek.bottom macron.top
+Oslashacute: Oslash acute.top
+Otilde: O tilde.top
+Otildeacute: O tilde.top acute.top
+Otildedieresis: O tilde.top dieresis.top
+Otildemacron: O tilde.top macron.top
+Pacute: P acute.top
+Pdotaccent: P dotaccent.top
+Racute: R acute.top
+Rcaron: R caron.top
+Rcedilla: R cedilla.bottom
+Rcommaaccent: R commaaccent.bottom
+Rdblgrave: R dblgrave.top
+Rdotaccent: R dotaccent.top
+Rdotaccentmacron: R dotaccent.top macron.top
+Sacute: S acute.top
+Sacutedotaccent: S acute.top dotaccent.top
+Scaron: S caron.top
+Scarondotaccent: S caron.top dotaccent.top
+Scedilla: S cedilla.bottom
+Scircumflex: S circumflex.top
+Scommaaccent: S commaaccent.bottom
+Sdotaccent: S dotaccent.top
+Tcaron: T caron.top
+Tcedilla: T cedilla.bottom
+Tcommaaccent: T commaaccent.bottom
+Tdotaccent: T dotaccent.top
+Uacute: U acute.top
+Ubreve: U breve.top
+Ucaron: U caron.top
+Ucircumflex: U circumflex.top
+Udblgrave: U dblgrave.top
+Udieresis: U dieresis.top
+Udieresisacute: U dieresis.top acute.top
+Udieresiscaron: U dieresis.top caron.top
+Udieresisgrave: U dieresis.top grave.top
+Udieresismacron: U dieresis.top macron.top
+Ugrave: U grave.top
+Uhungarumlaut: U hungarumlaut.top
+Umacron: U macron.top
+Umacrondieresis: U macron.top dieresis.top
+Uogonek: U ogonek.bottom
+Uring: U ring.top
+Utilde: U tilde.top
+Utildeacute: U tilde.top acute.top
+Vtilde: V tilde.top
+Wacute: W acute.top
+Wcircumflex: W circumflex.top
+Wdieresis: W dieresis.top
+Wdotaccent: W dotaccent.top
+Wgrave: W grave.top
+Xdieresis: X dieresis.top
+Xdotaccent: X dotaccent.top
+Yacute: Y acute.top
+Ycircumflex: Y circumflex.top
+Ydieresis: Y dieresis.top
+Ydotaccent: Y dotaccent.top
+Ygrave: Y grave.top
+Ytilde: Y tilde.top
+Zacute: Z acute.top
+Zcaron: Z caron.top
+Zcircumflex: Z circumflex.top
+Zdotaccent: Z dotaccent.top
+##: Lowercase
+aacute: a acute.top
+abreve: a breve.top
+abreveacute: a breve.top acute.top
+abrevedotaccent: a breve.top dotaccent.bottom
+abrevegrave: a breve.top grave.top
+abrevetilde: a breve.top tilde.top
+acaron: a caron.top
+acircumflex: a circumflex.top
+acircumflexacute: a circumflex.top acute.top
+acircumflexdotaccent: a circumflex.top dotaccent.bottom
+acircumflexgrave: a circumflex.top grave.top
+acircumflextilde: a circumflex.top tilde.top
+adblgrave: a dblgrave.top
+adieresis: a dieresis.top
+adieresismacron: a dieresis.top macron.top
+adotaccent: a dotaccent.top
+adotaccentmacron: a dotaccent.top macron.top
+aeacute: ae acute.top
+aemacron: ae macron.top
+agrave: a grave.top
+amacron: a macron.top
+aogonek: a ogonek.bottom
+aring: a ring.top
+aringacute: a ring.top acute.top
+atilde: a tilde.top
+bdotaccent: b dotaccent.top
+cacute: c acute.top
+ccaron: c caron.top
+ccedilla: c cedilla.bottom
+ccedillaacute: c cedilla.bottom acute.top
+ccircumflex: c circumflex.top
+cdotaccent: c dotaccent.top
+dcaron: d commaaccent.right
+dcedilla: d cedilla.bottom
+ddotaccent: d dotaccent.top
+dmacron: d macron.top
+eacute: e acute.top
+ebreve: e breve.top
+ecaron: e caron.top
+ecedilla: e cedilla.bottom
+ecedillabreve: e cedilla.bottom breve.top
+ecircumflex: e circumflex.top
+ecircumflexacute: e circumflex.top acute.top
+ecircumflexdotaccent: e circumflex.top dotaccent.bottom
+ecircumflexgrave: e circumflex.top grave.top
+ecircumflextilde: e circumflex.top tilde.top
+edblgrave: e dblgrave.top
+edieresis: e dieresis.top
+edotaccent: e dotaccent.top
+egrave: e grave.top
+emacron: e macron.top
+emacronacute: e macron.top acute.top
+emacrongrave: e macron.top grave.top
+eogonek: e ogonek.bottom
+etilde: e tilde.top
+fdotaccent: f dotaccent.top
+gacute: g acute.top
+gbreve: g breve.top
+gcaron: g caron.top
+gcedilla: g cedilla.top
+gcircumflex: g circumflex.top
+gcommaaccent: g commaaccent.top
+gdotaccent: g dotaccent.top
+gmacron: g macron.top
+hcaron: h caron.top
+hcedilla: h cedilla.bottom
+hcircumflex: h circumflex.top
+hdieresis: h dieresis.top
+hdotaccent: h dotaccent.top
+iacute: dotlessi acute.top
+ibreve: dotlessi breve.top
+icaron: dotlessi caron.top
+icircumflex: dotlessi circumflex.top
+idblgrave: dotlessi dblgrave.top
+idieresis: dotlessi dieresis.top
+idieresisacute: dotlessi dieresis.top acute.top
+igrave: dotlessi grave.top
+imacron: dotlessi macron.top
+iogonek: i ogonek.bottom
+itilde: dotlessi tilde.top
+jcaron: dotlessj caron.top
+jcircumflex: dotlessj circumflex.top
+jacute: dotlessj acute.top
+kacute: k acute.top
+kcaron: k caron.top
+kcedilla: k cedilla.bottom
+kcommaaccent: k commaaccent.bottom
+lacute: l acute.top
+lcaron: l commaaccent.right
+lcedilla: l cedilla.bottom
+lcommaaccent: l commaaccent.bottom
+ldot: l dot.right
+ldotaccent: l dotaccent.bottom
+ldotaccentmacron: l dotaccent.bottom macron.top
+macute: m acute.top
+mdotaccent: m dotaccent.top
+nacute: n acute.top
+ncaron: n caron.top
+ncedilla: n cedilla.bottom
+ncommaaccent: n commaaccent.bottom
+ndotaccent: n dotaccent.top
+ngrave: n grave.top
+ntilde: n tilde.top
+oacute: o acute.top
+obreve: o breve.top
+ocaron: o caron.top
+ocircumflex: o circumflex.top
+ocircumflexacute: o circumflex.top acute.top
+ocircumflexdotaccent: o circumflex.top dotaccent.bottom
+ocircumflexgrave: o circumflex.top grave.top
+ocircumflextilde: o circumflex.top tilde.top
+odblgrave: o dblgrave.top
+odieresis: o dieresis.top
+odieresismacron: o dieresis.top macron.top
+ograve: o grave.top
+ohungarumlaut: o hungarumlaut.top
+omacron: o macron.top
+omacronacute: o macron.top acute.top
+omacrongrave: o macron.top grave.top
+oogonek: o ogonek.bottom
+oogonekmacron: o ogonek.bottom macron.top
+oslashacute: oslash acute.top
+otilde: o tilde.top
+otildeacute: o tilde.top acute.top
+otildedieresis: o tilde.top dieresis.top
+otildemacron: o tilde.top macron.top
+pacute: p acute.top
+pdotaccent: p dotaccent.top
+racute: r acute.top
+rcaron: r caron.top
+rcedilla: r cedilla.bottom
+rcommaaccent: r commaaccent.bottom
+rdblgrave: r dblgrave.top
+rdotaccent: r dotaccent.top
+rdotaccentmacron: r dotaccent.top macron.top
+sacute: s acute.top
+sacutedotaccent: s acute.top dotaccent.top
+scaron: s caron.top
+scarondotaccent: s caron.top dotaccent.top
+scedilla: s cedilla.bottom
+scircumflex: s circumflex.top
+scommaaccent: s commaaccent.bottom
+sdotaccent: s dotaccent.top
+tcaron: t commaaccent.right
+tcedilla: t cedilla.bottom
+tcommaaccent: t commaaccent.bottom
+tdieresis: t dieresis.top
+tdotaccent: t dotaccent.top
+uacute: u acute.top
+ubreve: u breve.top
+ucaron: u caron.top
+ucircumflex: u circumflex.top
+udblgrave: u dblgrave.top
+udieresis: u dieresis.top
+udieresisacute: u dieresis.top acute.top
+udieresiscaron: u dieresis.top caron.top
+udieresisgrave: u dieresis.top grave.top
+udieresismacron: u dieresis.top macron.top
+ugrave: u grave.top
+uhungarumlaut: u hungarumlaut.top
+umacron: u macron.top
+umacrondieresis: u macron.top dieresis.top
+uogonek: u ogonek.bottom
+uring: u ring.top
+utilde: u tilde.top
+utildeacute: u tilde.top acute.top
+vtilde: v tilde.top
+wacute: w acute.top
+wcircumflex: w circumflex.top
+wdieresis: w dieresis.top
+wdotaccent: w dotaccent.top
+wgrave: w grave.top
+wring: w ring.top
+xdieresis: x dieresis.top
+xdotaccent: x dotaccent.top
+yacute: y acute.top
+ycircumflex: y circumflex.top
+ydieresis: y dieresis.top
+ydotaccent: y dotaccent.top
+ygrave: y grave.top
+yring: y ring.top
+ytilde: y tilde.top
+zacute: z acute.top
+zcaron: z caron.top
+zcircumflex: z circumflex.top
+zdotaccent: z dotaccent.top
+##: Small: Caps
+AEacute.sc: AE.sc acute.top
+AEmacron.sc: AE.sc macron.top
+Aacute.sc: A.sc acute.top
+Abreve.sc: A.sc breve.top
+Abreveacute.sc: A.sc breve.top acute.top
+Abrevedotaccent.sc: A.sc breve.top dotaccent.bottom
+Abrevegrave.sc: A.sc breve.top grave.top
+Abrevetilde.sc: A.sc breve.top tilde.top
+Acaron.sc: A.sc caron.top
+Acircumflex.sc: A.sc circumflex.top
+Acircumflexacute.sc: A.sc circumflex.top acute.top
+Acircumflexdotaccent.sc: A.sc circumflex.top dotaccent.bottom
+Acircumflexgrave.sc: A.sc circumflex.top grave.top
+Acircumflextilde.sc: A.sc circumflex.top tilde.top
+Adblgrave.sc: A.sc dblgrave.top
+Adieresis.sc: A.sc dieresis.top
+Adieresismacron.sc: A.sc dieresis.top macron.top
+Adotaccent.sc: A.sc dotaccent.top
+Adotaccentmacron.sc: A.sc dotaccent.top macron.top
+Agrave.sc: A.sc grave.top
+Amacron.sc: A.sc macron.top
+Aogonek.sc: A.sc ogonek.bottom
+Aring.sc: A.sc ring.top
+Aringacute.sc: A.sc ring.top acute.top
+Atilde.sc: A.sc tilde.top
+Bdotaccent.sc: B.sc dotaccent.top
+Cacute.sc: C.sc acute.top
+Ccaron.sc: C.sc caron.top
+Ccedilla.sc: C.sc cedilla.bottom
+Ccedillaacute.sc: C.sc cedilla.bottom acute.top
+Ccircumflex.sc: C.sc circumflex.top
+Cdotaccent.sc: C.sc dotaccent.top
+Dcaron.sc: D.sc caron.top
+Dcedilla.sc: D.sc cedilla.bottom
+Ddotaccent.sc: D.sc dotaccent.top
+Eacute.sc: E.sc acute.top
+Ebreve.sc: E.sc breve.top
+Ecaron.sc: E.sc caron.top
+Ecedilla.sc: E.sc cedilla.bottom
+Ecedillabreve.sc: E.sc cedilla.bottom breve.top
+Ecircumflex.sc: E.sc circumflex.top
+Ecircumflexacute.sc: E.sc circumflex.top acute.top
+Ecircumflexdotaccent.sc: E.sc circumflex.top dotaccent.bottom
+Ecircumflexgrave.sc: E.sc circumflex.top grave.top
+Ecircumflextilde.sc: E.sc circumflex.top tilde.top
+Edblgrave.sc: E.sc dblgrave.top
+Edieresis.sc: E.sc dieresis.top
+Edotaccent.sc: E.sc dotaccent.top
+Egrave.sc: E.sc grave.top
+Emacron.sc: E.sc macron.top
+Emacronacute.sc: E.sc macron.top acute.top
+Emacrongrave.sc: E.sc macron.top grave.top
+Eogonek.sc: E.sc ogonek.bottom
+Etilde.sc: E.sc tilde.top
+Fdotaccent.sc: F.sc dotaccent.top
+Gacute.sc: G.sc acute.top
+Gbreve.sc: G.sc breve.top
+Gcaron.sc: G.sc caron.top
+Gcedilla.sc: G.sc cedilla.bottom
+Gcircumflex.sc: G.sc circumflex.top
+Gcommaaccent.sc: G.sc commaaccent.bottom
+Gdotaccent.sc: G.sc dotaccent.top
+Gmacron.sc: G.sc macron.top
+Hcaron.sc: H.sc caron.top
+Hcedilla.sc: H.sc cedilla.top
+Hcircumflex.sc: H.sc circumflex.top
+Hdieresis.sc: H.sc dieresis.top
+Hdotaccent.sc: H.sc dotaccent.top
+Iacute.sc: I.sc acute.top
+Ibreve.sc: I.sc breve.top
+Icaron.sc: I.sc caron.top
+Icircumflex.sc: I.sc circumflex.top
+Idblgrave.sc: I.sc dblgrave.top
+Idieresis.sc: I.sc dieresis.top
+Idieresisacute.sc: I.sc dieresis.top acute.top
+Idotaccent.sc: I.sc dotaccent.top
+Igrave.sc: I.sc grave.top
+Imacron.sc: I.sc macron.top
+Iogonek.sc: I.sc ogonek.bottom
+Itilde.sc: I.sc tilde.top
+Jcircumflex.sc: J.sc circumflex.top
+Kacute.sc: K.sc acute.top
+Kcaron.sc: K.sc caron.top
+Kcedilla.sc: K.sc cedilla.bottom
+Kcommaaccent.sc: K.sc commaaccent.bottom
+Lacute.sc: L.sc acute.top
+Lcaron.sc: L.sc commaaccent.right
+Lcedilla.sc: L.sc cedilla.bottom
+Lcommaaccent.sc: L.sc commaaccent.bottom
+Ldot.sc: L.sc dot.right
+Ldotaccent.sc: L.sc dotaccent.bottom
+Ldotaccentmacron.sc: L.sc dotaccent.bottom macron.top
+Macute.sc: M.sc acute.top
+Mdotaccent.sc: M.sc dotaccent.top
+Nacute.sc: N.sc acute.top
+Ncaron.sc: N.sc caron.top
+Ncedilla.sc: N.sc cedilla.bottom
+Ncommaaccent.sc: N.sc commaaccent.bottom
+Ndotaccent.sc: N.sc dotaccent.top
+Ngrave.sc: N.sc grave.top
+Ntilde.sc: N.sc tilde.top
+Oacute.sc: O.sc acute.top
+Obreve.sc: O.sc breve.top
+Ocaron.sc: O.sc caron.top
+Ocircumflex.sc: O.sc circumflex.top
+Ocircumflexacute.sc: O.sc circumflex.top acute.top
+Ocircumflexdotaccent.sc: O.sc circumflex.top dotaccent.bottom
+Ocircumflexgrave.sc: O.sc circumflex.top grave.top
+Ocircumflextilde.sc: O.sc circumflex.top tilde.top
+Odblgrave.sc: O.sc dblgrave.top
+Odieresis.sc: O.sc dieresis.top
+Odieresismacron.sc: O.sc dieresis.top macron.top
+Ograve.sc: O.sc grave.top
+Ohungarumlaut.sc: O.sc hungarumlaut.top
+Omacron.sc: O.sc macron.top
+Omacronacute.sc: O.sc macron.top acute.top
+Omacrongrave.sc: O.sc macron.top grave.top
+Oogonek.sc: O.sc ogonek.bottom
+Oogonekmacron.sc: O.sc ogonek.bottom macron.top
+Oslashacute.sc: Oslash.sc acute.top
+Otilde.sc: O.sc tilde.top
+Otildeacute.sc: O.sc tilde.top acute.top
+Otildedieresis.sc: O.sc tilde.top dieresis.top
+Otildemacron.sc: O.sc tilde.top macron.top
+Pacute.sc: P.sc acute.top
+Pdotaccent.sc: P.sc dotaccent.top
+Racute.sc: R.sc acute.top
+Rcaron.sc: R.sc caron.top
+Rcedilla.sc: R.sc cedilla.bottom
+Rcommaaccent.sc: R.sc commaaccent.bottom
+Rdblgrave.sc: R.sc dblgrave.top
+Rdotaccent.sc: R.sc dotaccent.top
+Rdotaccentmacron.sc: R.sc dotaccent.top macron.top
+Sacute.sc: S.sc acute.top
+Sacutedotaccent.sc: S.sc acute.top dotaccent.top
+Scaron.sc: S.sc caron.top
+Scarondotaccent.sc: S.sc caron.top dotaccent.top
+Scedilla.sc: S.sc cedilla.bottom
+Scircumflex.sc: S.sc circumflex.top
+Scommaaccent.sc: S.sc commaaccent.bottom
+Sdotaccent.sc: S.sc dotaccent.top
+Tcaron.sc: T.sc caron.top
+Tcedilla.sc: T.sc cedilla.bottom
+Tcommaaccent.sc: T.sc commaaccent.bottom
+Tdotaccent.sc: T.sc dotaccent.top
+Uacute.sc: U.sc acute.top
+Ubreve.sc: U.sc breve.top
+Ucaron.sc: U.sc caron.top
+Ucircumflex.sc: U.sc circumflex.top
+Udblgrave.sc: U.sc dblgrave.top
+Udieresis.sc: U.sc dieresis.top
+Udieresisacute.sc: U.sc dieresis.top acute.top
+Udieresiscaron.sc: U.sc dieresis.top caron.top
+Udieresisgrave.sc: U.sc dieresis.top grave.top
+Udieresismacron.sc: U.sc dieresis.top macron.top
+Ugrave.sc: U.sc grave.top
+Uhungarumlaut.sc: U.sc hungarumlaut.top
+Umacron.sc: U.sc macron.top
+Umacrondieresis.sc: U.sc macron.top dieresis.top
+Uogonek.sc: U.sc ogonek.bottom
+Uring.sc: U.sc ring.top
+Utilde.sc: U.sc tilde.top
+Utildeacute.sc: U.sc tilde.top acute.top
+Vtilde.sc: V.sc tilde.top
+Wacute.sc: W.sc acute.top
+Wcircumflex.sc: W.sc circumflex.top
+Wdieresis.sc: W.sc dieresis.top
+Wdotaccent.sc: W.sc dotaccent.top
+Wgrave.sc: W.sc grave.top
+Xdieresis.sc: X.sc dieresis.top
+Xdotaccent.sc: X.sc dotaccent.top
+Yacute.sc: Y.sc acute.top
+Ycircumflex.sc: Y.sc circumflex.top
+Ydieresis.sc: Y.sc dieresis.top
+Ydotaccent.sc: Y.sc dotaccent.top
+Ygrave.sc: Y.sc grave.top
+Ytilde.sc: Y.sc tilde.top
+Zacute.sc: Z.sc acute.top
+Zcaron.sc: Z.sc caron.top
+Zcircumflex.sc: Z.sc circumflex.top
+Zdotaccent.sc: Z.sc dotaccent.top
+""" \ No newline at end of file
diff --git a/misc/pylib/robofab/tools/glyphNameSchemes.py b/misc/pylib/robofab/tools/glyphNameSchemes.py
new file mode 100755
index 000000000..cb6172b9a
--- /dev/null
+++ b/misc/pylib/robofab/tools/glyphNameSchemes.py
@@ -0,0 +1,41 @@
+"""A separate module for glyphname to filename functions.
+
+glyphNameToShortFileName() generates a non-clashing filename for systems with
+filename-length limitations.
+"""
+
+MAXLEN = 31
+
+def glyphNameToShortFileName(glyphName, glyphSet):
+ """Alternative glyphname to filename function.
+
+ Features a garuanteed maximum filename for really long glyphnames, and clash testing.
+ - all non-ascii characters are converted to "_" (underscore), including "."
+ - all glyphnames which are too long are truncated and a hash is added at the end
+ - the hash is generated from the whole glyphname
+ - finally, the candidate glyphname is checked against the contents.plist
+ and a incrementing number is added at the end if there is a clash.
+ """
+ import binascii, struct, string
+ ext = ".glif"
+ ok = string.ascii_letters + string.digits + " _"
+ h = binascii.hexlify(struct.pack(">l", binascii.crc32(glyphName)))
+ n = ''
+ for c in glyphName:
+ if c in ok:
+ if c != c.lower():
+ n += c + "_"
+ else:
+ n += c
+ else:
+ n += "_"
+ if len(n + ext) < MAXLEN:
+ return n + ext
+ count = 0
+ candidate = n[:MAXLEN - len(h + ext)] + h + ext
+ if glyphSet is not None:
+ names = glyphSet.getReverseContents()
+ while candidate.lower() in names:
+ candidate = n[:MAXLEN - len(h + ext + str(count))] + h + str(count) + ext
+ count += 1
+ return candidate
diff --git a/misc/pylib/robofab/tools/objectDumper.py b/misc/pylib/robofab/tools/objectDumper.py
new file mode 100755
index 000000000..29110ca53
--- /dev/null
+++ b/misc/pylib/robofab/tools/objectDumper.py
@@ -0,0 +1,55 @@
+"""Simple and ugly way to print some attributes and properties of an object to stdout.
+FontLab doesn't have an object browser and sometimes we do need to look inside"""
+
+from pprint import pprint
+
+def classname(object, modname):
+ """Get a class name and qualify it with a module name if necessary."""
+ name = object.__name__
+ if object.__module__ != modname:
+ name = object.__module__ + '.' + name
+ return name
+
+def _objectDumper(object, indent=0, private=False):
+ """Collect a dict with the contents of the __dict__ as a quick means of peeking inside
+ an instance. Some RoboFab locations do not support PyBrowser and still need debugging."""
+ data = {}
+ data['__class__'] = "%s at %d"%(classname(object.__class__, object.__module__), id(object))
+ for k in object.__class__.__dict__.keys():
+ if private and k[0] == "_":
+ continue
+ x = object.__class__.__dict__[k]
+ if hasattr(x, "fget"): #other means of recognising a property?
+ try:
+ try:
+ value = _objectDumper(x.fget(self), 1)
+ except:
+ value = x.fget(self)
+ data[k] = "[property, %s] %s"%(type(x.fget(self)).__name__, value)
+ except:
+ data[k] = "[property] (Error getting property value)"
+ for k in object.__dict__.keys():
+ if private and k[0] == "_":
+ continue
+ try:
+ data[k] = "[attribute, %s] %s"%(type(object.__dict__[k]).__name__, `object.__dict__[k]`)
+ except:
+ data[k] = "[attribute] (Error getting attribute value)"
+ return data
+
+def flattenDict(dict, indent=0):
+ t = []
+ k = dict.keys()
+ k.sort()
+ print
+ print '---RoboFab Object Dump---'
+ for key in k:
+ value = dict[key]
+ t.append(indent*"\t"+"%s: %s"%(key, value))
+ t.append('')
+ return "\r".join(t)
+
+def dumpObject(object, private=False):
+ print pprint(_objectDumper(object, private=private))
+
+
diff --git a/misc/pylib/robofab/tools/otFeatures.py b/misc/pylib/robofab/tools/otFeatures.py
new file mode 100755
index 000000000..8138a2fe7
--- /dev/null
+++ b/misc/pylib/robofab/tools/otFeatures.py
@@ -0,0 +1,190 @@
+"""Simple module to write features to font"""
+
+
+import string
+
+
+from types import StringType, ListType, TupleType
+
+from robofab.world import world
+if world.inFontLab:
+ from FL import *
+ from fl_cmd import *
+from robofab.tools.toolsFL import FontIndex
+
+ #feat = []
+ #feat.append('feature smcp {')
+ #feat.append('\tlookup SMALLCAPS {')
+ #feat.append('\t\tsub @LETTERS_LC by @LETTERS_LC;')
+ #feat.append('\t} SMALLCAPS;')
+ #feat.append('} smcp;')
+
+
+class FeatureWriter:
+ """Make properly formatted feature code"""
+ def __init__(self, type):
+ self.type = type
+ self.data = []
+
+ def add(self, src, dst):
+ """Add a substitution: change src to dst."""
+ self.data.append((src, dst))
+
+ def write(self, group=0):
+ """Write the whole thing to string"""
+ t = []
+ if len(self.data) == 0:
+ return None
+ t.append('feature %s {' % self.type)
+ for src, dst in self.data:
+ if isinstance(src, (list, tuple)):
+ if group:
+ src = "[%s]" % string.join(src, ' ')
+ else:
+ src = string.join(src, ' ')
+ if isinstance(dst, (list, tuple)):
+ if group:
+ dst = "[%s]" % string.join(dst, ' ')
+ else:
+ dst = string.join(dst, ' ')
+ src = string.strip(src)
+ dst = string.strip(dst)
+ t.append("\tsub %s by %s;" % (src, dst))
+ t.append('}%s;' % self.type)
+ return string.join(t, '\n')
+
+
+class GlyphName:
+ """Simple class that splits a glyphname in handy parts,
+ access the parts as attributes of the name."""
+ def __init__(self, name):
+ self.suffix = []
+ self.ligs = []
+ self.name = self.base = name
+ if '.' in name:
+ self.bits = name.split('.')
+ self.base = self.bits[0]
+ self.suffix = self.bits[1:]
+ if '_' in name:
+ self.ligs = self.base.split('_')
+
+
+def GetAlternates(font, flavor="alt", match=0):
+ """Sort the glyphs of this font by the parts of the name.
+ flavor is the bit to look for, i.e. 'alt' in a.alt
+ match = 1 if you want a exact match: alt1 != alt
+ match = 0 if the flavor is a partial match: alt == alt1
+ """
+ names = {}
+ for c in font.glyphs:
+ name = GlyphName(c.name)
+ if not names.has_key(name.base):
+ names[name.base] = []
+ if match:
+ # only include if there is an exact match
+ if flavor in name.suffix:
+ names[name.base].append(c.name)
+ else:
+ # include if there is a partial match
+ for a in name.suffix:
+ if a.find(flavor) != -1:
+ names[name.base].append(c.name)
+ return names
+
+
+# XXX there should be a more generic glyph finder.
+
+def MakeCapsFeature(font):
+ """Build a feature for smallcaps based on .sc glyphnames"""
+ names = GetAlternates(font, 'sc', match=1)
+ fw = FeatureWriter('smcp')
+ k = names.keys()
+ k.sort()
+ for p in k:
+ if names[p]:
+ fw.add(p, names[p])
+ feat = fw.write()
+ if feat:
+ font.features.append(Feature('smcp', feat))
+ return feat
+
+
+def MakeAlternatesFeature(font):
+ """Build a aalt feature based on glyphnames"""
+ names = GetAlternates(font, 'alt', match=0)
+ fw = FeatureWriter('aalt')
+ k = names.keys()
+ k.sort()
+ for p in k:
+ if names[p]:
+ fw.add(p, names[p])
+ feat = fw.write(group=1)
+ if feat:
+ font.features.append(Feature('aalt', feat))
+ return feat
+
+
+def MakeSwashFeature(font):
+ """Build a swash feature based on glyphnames"""
+ names = GetAlternates(font, 'swash', match=0)
+ fw = FeatureWriter('swsh')
+ k=names.keys()
+ k.sort()
+ for p in k:
+ if names[p]:
+ l=names[p]
+ l.sort()
+ fw.add(p, l[0])
+ feat=fw.write()
+ if feat:
+ font.features.append(Feature('swsh', feat))
+ return feat
+
+
+def MakeLigaturesFeature(font):
+ """Build a liga feature based on glyphnames"""
+ from robofab.gString import ligatures
+ ligCountDict = {}
+ for glyph in font.glyphs:
+ if glyph.name in ligatures:
+ if len(glyph.name) not in ligCountDict.keys():
+ ligCountDict[len(glyph.name)] = [glyph.name]
+ else:
+ ligCountDict[len(glyph.name)].append(glyph.name)
+ elif glyph.name.find('_') != -1:
+ usCounter=1
+ for i in glyph.name:
+ if i =='_':
+ usCounter=usCounter+1
+ if usCounter not in ligCountDict.keys():
+ ligCountDict[usCounter] = [glyph.name]
+ else:
+ ligCountDict[usCounter].append(glyph.name)
+ ligCount=ligCountDict.keys()
+ ligCount.sort()
+ foundLigs=[]
+ for i in ligCount:
+ l = ligCountDict[i]
+ l.sort()
+ foundLigs=foundLigs+l
+ fw=FeatureWriter('liga')
+ for i in foundLigs:
+ if i.find('_') != -1:
+ sub=i.split('_')
+ else:
+ sub=[]
+ for c in i:
+ sub.append(c)
+ fw.add(sub, i)
+ feat=fw.write()
+ if feat:
+ font.features.append(Feature('liga', feat))
+ return feat
+
+
+if __name__ == "__main__":
+ fw = FeatureWriter('liga')
+ fw.add(['f', 'f', 'i'], ['f_f_i'])
+ fw.add('f f ', 'f_f')
+ fw.add(['f', 'i'], 'f_i')
+ print fw.write()
diff --git a/misc/pylib/robofab/tools/proof.py b/misc/pylib/robofab/tools/proof.py
new file mode 100755
index 000000000..bb3b9d100
--- /dev/null
+++ b/misc/pylib/robofab/tools/proof.py
@@ -0,0 +1,119 @@
+"""This is the place for stuff that makes proofs and test text settings etc"""
+
+import string
+
+
+
+
+idHeader = """<ASCII-MAC>
+<Version:2.000000><FeatureSet:InDesign-Roman><ColorTable:=<Black:COLOR:CMYK:Process:0.000000,0.000000,0.000000,1.000000>>"""
+
+idColor = """<cColor:COLOR\:%(model)s\:Process\:%(c)f\,%(m)f\,%(y)f\,%(k)f>"""
+
+idParaStyle = """<ParaStyle:><cTypeface:%(weight)s><cSize:%(size)f><cLeading:%(leading)f><cFont:%(family)s>"""
+idGlyphStyle = """<cTypeface:%(weight)s><cSize:%(size)f><cLeading:%(leading)f><cFont:%(family)s>"""
+
+seperator = ''
+
+autoLinespaceFactor = 1.2
+
+
+class IDTaggedText:
+
+ """Export a text as a XML tagged text file for InDesign (2.0?).
+ The tags can contain information about
+ - family: font family i.e. "Times"
+ - weight: font weight "Bold"
+ - size: typesize in points
+ - leading: leading in points
+ - color: a CMYK color, as a 4 tuple of floats between 0 and 1
+ - insert special glyphs based on glyphindex
+ (which is why it only makes sense if you use this in FontLab,
+ otherwise there is no other way to get the indices)
+ """
+
+ def __init__(self, family, weight, size=36, leading=None):
+ self.family = family
+ self.weight = weight
+ self.size = size
+ if not leading:
+ self.leading = autoLinespaceFactor*size
+ self.text = []
+ self.data = []
+ self.addHeader()
+
+ def add(self, text):
+ """Method to add text to the file."""
+ t = self.charToGlyph(text)
+ self.data.append(t)
+
+ def charToGlyph(self, text):
+ return text
+
+ def addHeader(self):
+ """Add the standard header."""
+ # set colors too?
+ self.data.append(idHeader)
+
+ def replace(self, old, new):
+ """Replace occurances of 'old' with 'new' in all content."""
+ d = []
+ for i in self.data:
+ d.append(i.replace(old, new))
+ self.data = d
+
+ def save(self, path):
+ """Save the tagged text here."""
+ f = open(path, 'w')
+ f.write(string.join(self.data, seperator))
+ f.close()
+
+ def addGlyph(self, index):
+ """Add a special glyph, index is the glyphIndex in an OpenType font."""
+ self.addStyle()
+ self.data.append("<cSpecialGlyph:%d><0xFFFD>"%index)
+
+ def addStyle(self, family=None, weight=None, size=None, leading=None, color=None):
+ """Set the paragraph style for the following text."""
+ if not family:
+ family = self.family
+ if not weight:
+ weight = self.weight
+ if not size:
+ size = self.size
+ if not leading:
+ leading = autoLinespaceFactor*self.size
+ self.data.append(idGlyphStyle%({'weight': weight, 'size': size, 'family': family, 'leading':leading}))
+ if color:
+ self.data.append(idColor%({'model': 'CMYK', 'c': color[0], 'm': color[1], 'y': color[2], 'k': color[3]}))
+
+
+
+if __name__ == "__main__":
+ from random import randint
+ id = IDTaggedText("Minion", "Regular", size=40, leading=50)
+
+ id.addStyle(color=(0,0,0,1))
+ id.add("Hello")
+
+ id.addStyle(weight="Bold", color=(0,0.5,1,0))
+ id.add(" Everybody")
+ id.addStyle(weight="Regular", size=100, color=(0,1,1,0))
+ id.addGlyph(102)
+ id.addGlyph(202)
+
+ from robofab.interface.all.dialogs import PutFile
+ path = PutFile("Save the tagged file:", "TaggedText.txt")
+ if path:
+ id.save(path)
+
+ # then: open a document in Adobe InDesign
+ # select "Place" (cmd-D on Mac)
+ # select the text file you just generated
+ # place the text
+
+
+
+
+
+ # \ No newline at end of file
diff --git a/misc/pylib/robofab/tools/remote.py b/misc/pylib/robofab/tools/remote.py
new file mode 100755
index 000000000..288afcfe7
--- /dev/null
+++ b/misc/pylib/robofab/tools/remote.py
@@ -0,0 +1,175 @@
+"""Remote control for MacOS FontLab.
+initFontLabRemote() registers a callback for appleevents and
+runFontLabRemote() sends the code from a different application,
+such as a Mac Python IDE or Python interpreter.
+"""
+
+from robofab.world import world
+
+if world.inFontLab and world.mac is not None:
+ from Carbon import AE as _AE
+
+else:
+ import sys
+ from aetools import TalkTo
+
+ class FontLab(TalkTo):
+ pass
+
+__all__ = ['initFontLabRemote', 'runFontLabRemote']
+
+def _executePython(theAppleEvent, theReply):
+ import aetools
+ import cStringIO
+ import traceback
+ import sys
+ parms, attrs = aetools.unpackevent(theAppleEvent)
+ source = parms.get("----")
+ if source is None:
+ return
+ stdout = cStringIO.StringIO()
+ #print "<executing remote command>"
+ save = sys.stdout, sys.stderr
+ sys.stdout = sys.stderr = stdout
+ namespace = {}
+ try:
+ try:
+ exec source in namespace
+ except:
+ traceback.print_exc()
+ finally:
+ sys.stdout, sys.stderr = save
+ output = stdout.getvalue()
+ aetools.packevent(theReply, {"----": output})
+
+_imported = False
+
+def initFontLabRemote():
+ """Call this in FontLab at startup of the application to switch on the remote."""
+ print "FontLabRemote is on."
+ _AE.AEInstallEventHandler("Rfab", "exec", _executePython)
+
+if world.inFontLab and world.mac is not None:
+ initFontLabRemote()
+
+def runFontLabRemote(code):
+ """Call this in the MacOS Python IDE to make FontLab execute the code."""
+ fl = FontLab("FLab", start=1)
+ ae, parms, attrs = fl.send("Rfab", "exec", {"----": code})
+ output = parms.get("----")
+ return output
+
+
+
+# GlyphTransmit
+# Convert a glyph to a string using digestPen, transmit string, unpack string with pointpen.
+#
+
+
+def Glyph2String(glyph):
+ from robofab.pens.digestPen import DigestPointPen
+ import pickle
+ p = DigestPointPen(glyph)
+ glyph.drawPoints(p)
+ info = {}
+ info['name'] = glyph.name
+ info['width'] = glyph.width
+ info['points'] = p.getDigest()
+ return str(pickle.dumps(info))
+
+def String2Glyph(gString, penClass, font):
+ import pickle
+ if gString is None:
+ return None
+ info = pickle.loads(gString)
+ name = info['name']
+ if not name in font.keys():
+ glyph = font.newGlyph(name)
+ else:
+ glyph = font[name]
+ pen = penClass(glyph)
+ for p in info['points']:
+ if p == "beginPath":
+ pen.beginPath()
+ elif p == "endPath":
+ pen.endPath()
+ else:
+ pt, type = p
+ pen.addPoint(pt, type)
+ glyph.width = info['width']
+ glyph.update()
+ return glyph
+
+_makeFLGlyph = """
+from robofab.world import CurrentFont
+from robofab.tools.remote import receiveGlyph
+code = '''%s'''
+receiveGlyph(code, CurrentFont())
+"""
+
+def transmitGlyph(glyph):
+ from robofab.world import world
+ if world.inFontLab and world.mac is not None:
+ # we're in fontlab, on a mac
+ print Glyph2String(glyph)
+ pass
+ else:
+ remoteProgram = _makeFLGlyph%Glyph2String(glyph)
+ print "remoteProgram", remoteProgram
+ return runFontLabRemote(remoteProgram)
+
+def receiveGlyph(glyphString, font=None):
+ from robofab.world import world
+ if world.inFontLab and world.mac is not None:
+ # we're in fontlab, on a mac
+ from robofab.pens.flPen import FLPointPen
+ print String2Glyph(glyphString, FLPointPen, font)
+ pass
+ else:
+ from robofab.pens.rfUFOPen import RFUFOPointPen
+ print String2Glyph(glyphString, RFUFOPointPen, font)
+
+
+#
+# command to tell FontLab to open a UFO and save it as a vfb
+
+def os9PathConvert(path):
+ """Attempt to convert a unix style path to a Mac OS9 style path.
+ No support for relative paths!
+ """
+ if path.find("/Volumes") == 0:
+ # it's on the volumes list, some sort of external volume
+ path = path[len("/Volumes")+1:]
+ elif path[0] == "/":
+ # a dir on the root volume
+ path = path[1:]
+ new = path.replace("/", ":")
+ return new
+
+
+_remoteUFOImportProgram = """
+from robofab.objects.objectsFL import NewFont
+import os.path
+destinationPathVFB = "%(destinationPathVFB)s"
+font = NewFont()
+font.readUFO("%(sourcePathUFO)s", doProgress=True)
+font.update()
+font.save(destinationPathVFB)
+print font, "done"
+font.close()
+"""
+
+def makeVFB(sourcePathUFO, destinationPathVFB=None):
+ """FontLab convenience function to import a UFO and save it as a VFB"""
+ import os
+ fl = FontLab("FLab", start=1)
+ if destinationPathVFB is None:
+ destinationPathVFB = os.path.splitext(sourcePathUFO)[0]+".vfb"
+ src9 = os9PathConvert(sourcePathUFO)
+ dst9 = os9PathConvert(destinationPathVFB)
+ code = _remoteUFOImportProgram%{'sourcePathUFO': src9, 'destinationPathVFB':dst9}
+ ae, parms, attrs = fl.send("Rfab", "exec", {"----": code})
+ output = parms.get("----")
+ return output
+
+ \ No newline at end of file
diff --git a/misc/pylib/robofab/tools/rfPrefs.py b/misc/pylib/robofab/tools/rfPrefs.py
new file mode 100755
index 000000000..440984d0d
--- /dev/null
+++ b/misc/pylib/robofab/tools/rfPrefs.py
@@ -0,0 +1,122 @@
+"""A simple module for dealing with preferences that are used by scripts. Based almost entirely on MacPrefs.
+
+To save some preferences:
+myPrefs = RFPrefs(drive/directory/directory/myPrefs.plist)
+myPrefs.myString = 'xyz'
+myPrefs.myInteger = 1234
+myPrefs.myList = ['a', 'b', 'c']
+myPrefs.myDict = {'a':1, 'b':2}
+myPrefs.save()
+
+To retrieve some preferences:
+myPrefs = RFPrefs(drive/directory/directory/myPrefs.plist)
+myString = myPrefs.myString
+myInteger = myPrefs.myInteger
+myList = myPrefs.myList
+myDict = myPrefs.myDict
+
+When using this module within FontLab, it is not necessary to
+provide the RFPrefs class with a path. If a path is not given,
+it will look for a file in FontLab/RoboFab Data/RFPrefs.plist.
+If that file does not exist, it will make it.
+"""
+
+from robofab import RoboFabError
+from robofab.plistlib import Plist
+from cStringIO import StringIO
+import os
+
+class _PrefObject:
+
+ def __init__(self, dict=None):
+ if not dict:
+ self._prefs = {}
+ else:
+ self._prefs = dict
+
+ def __len__(self):
+ return len(self._prefs)
+
+ def __delattr__(self, attr):
+ if self._prefs.has_key(attr):
+ del self._prefs[attr]
+ else:
+ raise AttributeError, 'delete non-existing instance attribute'
+
+ def __getattr__(self, attr):
+ if attr == '__members__':
+ keys = self._prefs.keys()
+ keys.sort()
+ return keys
+ try:
+ return self._prefs[attr]
+ except KeyError:
+ raise AttributeError, attr
+
+ def __setattr__(self, attr, value):
+ if attr[0] != '_':
+ self._prefs[attr] = value
+ else:
+ self.__dict__[attr] = value
+
+ def asDict(self):
+ return self._prefs
+
+class RFPrefs(_PrefObject):
+
+ """The main preferences object to call"""
+
+ def __init__(self, path=None):
+ from robofab.world import world
+ self.__path = path
+ self._prefs = {}
+ if world.inFontLab:
+ #we don't have a path, but we know where we can put it
+ if not path:
+ from robofab.tools.toolsFL import makeDataFolder
+ settingsPath = makeDataFolder()
+ path = os.path.join(settingsPath, 'RFPrefs.plist')
+ self.__path = path
+ self._makePrefsFile()
+ #we do have a path, make sure it exists and load it
+ else:
+ self._makePrefsFile()
+ else:
+ #no path, raise error
+ if not path:
+ raise RoboFabError, "no preferences path defined"
+ #we do have a path, make sure it exists and load it
+ else:
+ self._makePrefsFile()
+ self._prefs = Plist.fromFile(path)
+
+ def _makePrefsFile(self):
+ if not os.path.exists(self.__path):
+ self.save()
+
+ def __getattr__(self, attr):
+ if attr[0] == '__members__':
+ keys = self._prefs.keys()
+ keys.sort()
+ return keys
+ try:
+ return self._prefs[attr]
+ except KeyError:
+ raise AttributeError, attr
+ #if attr[0] != '_':
+ # self._prefs[attr] = _PrefObject()
+ # return self._prefs[attr]
+ #else:
+ # raise AttributeError, attr
+
+ def save(self):
+ """save the plist file"""
+ f = StringIO()
+ pl = Plist()
+ for i in self._prefs.keys():
+ pl[i] = self._prefs[i]
+ pl.write(f)
+ data = f.getvalue()
+ f = open(self.__path, 'wb')
+ f.write(data)
+ f.close()
diff --git a/misc/pylib/robofab/tools/toolsAll.py b/misc/pylib/robofab/tools/toolsAll.py
new file mode 100755
index 000000000..d729fe770
--- /dev/null
+++ b/misc/pylib/robofab/tools/toolsAll.py
@@ -0,0 +1,145 @@
+"""A collection of non-environment specific tools"""
+
+
+import sys
+import os
+from robofab.objects.objectsRF import RInfo
+
+if sys.platform == "darwin" and sys.version_info[:3] == (2, 2, 0):
+ # the Mac support of Jaguar's Python 2.2 is broken
+ have_broken_macsupport = 1
+else:
+ have_broken_macsupport = 0
+
+
+def readGlyphConstructions():
+ """read GlyphConstruction and turn it into a dict"""
+ from robofab.tools.glyphConstruction import _glyphConstruction
+ data = _glyphConstruction.split("\n")
+ glyphConstructions = {}
+ for i in data:
+ if len(i) == 0: continue
+ if i[0] != '#':
+ name = i.split(': ')[0]
+ construction = i.split(': ')[1].split(' ')
+ build = [construction[0]]
+ for c in construction[1:]:
+ accent = c.split('.')[0]
+ position = c.split('.')[1]
+ build.append((accent, position))
+ glyphConstructions[name] = build
+ return glyphConstructions
+
+#
+#
+# glyph.unicode: ttFont["cmap"].getcmap(3, 1)
+#
+#
+
+def guessFileType(fileName):
+ if not os.path.exists(fileName):
+ return None
+ base, ext = os.path.splitext(fileName)
+ ext = ext.lower()
+ if not have_broken_macsupport:
+ try:
+ import MacOS
+ except ImportError:
+ pass
+ else:
+ cr, tp = MacOS.GetCreatorAndType(fileName)
+ if tp in ("sfnt", "FFIL"):
+ return "TTF"
+ if tp == "LWFN":
+ return "Type 1"
+ if ext == ".dfont":
+ return "TTF"
+ if ext in (".otf", ".ttf"):
+ return "TTF"
+ if ext in (".pfb", ".pfa"):
+ return "Type 1"
+ return None
+
+def extractTTFFontInfo(font):
+ # UFO.info attribute name / index.
+ # name table entries index according to http://www.microsoft.com/typography/otspec/name.htm
+ attrs = [
+ ('copyright', 0),
+ ('familyName', 1),
+ ('styleMapStyleName', 2),
+ ('postscriptFullName', 4),
+ ('trademark', 7),
+ ('openTypeNameDesigner', 9),
+ ('openTypeNameLicenseURL', 14),
+ ('openTypeNameDesignerURL', 12),
+ ]
+ info = RInfo()
+ names = font['name']
+ info.ascender = font['hhea'].ascent
+ info.descender = font['hhea'].descent
+ info.unitsPerEm = font['head'].unitsPerEm
+ for name, index in attrs:
+ entry = font["name"].getName(index, 3, 1, 0x409)
+ if entry is not None:
+ try:
+ value = unicode(entry.string, "utf_16_be")
+ if name == "styleMapStyleName":
+ value = value.lower()
+ setattr(info, name, value)
+ except Exception, e:
+ print "Error importing value %s: %s: %s"%(str(name), value, e.message)
+ return info
+
+def extractT1FontInfo(font):
+ info = RInfo()
+ src = font.font['FontInfo']
+ factor = font.font['FontMatrix'][0]
+ assert factor > 0
+ info.unitsPerEm = int(round(1/factor, 0))
+ # assume something for ascender descender
+ info.ascender = (info.unitsPerEm / 5) * 4
+ info.descender = info.ascender - info.unitsPerEm
+ info.versionMajor = font.font['FontInfo']['version']
+ info.fullName = font.font['FontInfo']['FullName']
+ info.familyName = font.font['FontInfo']['FullName'].split("-")[0]
+ info.notice = unicode(font.font['FontInfo']['Notice'], "macroman")
+ info.italicAngle = font.font['FontInfo']['ItalicAngle']
+ info.uniqueID = font['UniqueID']
+ return info
+
+def fontToUFO(src, dst, fileType=None):
+ from robofab.ufoLib import UFOWriter
+ from robofab.pens.adapterPens import SegmentToPointPen
+ if fileType is None:
+ fileType = guessFileType(src)
+ if fileType is None:
+ raise ValueError, "Can't determine input file type"
+ ufoWriter = UFOWriter(dst)
+ if fileType == "TTF":
+ from fontTools.ttLib import TTFont
+ font = TTFont(src, 0)
+ elif fileType == "Type 1":
+ from fontTools.t1Lib import T1Font
+ font = T1Font(src)
+ else:
+ assert 0, "unknown file type: %r" % fileType
+ inGlyphSet = font.getGlyphSet()
+ outGlyphSet = ufoWriter.getGlyphSet()
+ for glyphName in inGlyphSet.keys():
+ print "-", glyphName
+ glyph = inGlyphSet[glyphName]
+ def drawPoints(pen):
+ pen = SegmentToPointPen(pen)
+ glyph.draw(pen)
+ outGlyphSet.writeGlyph(glyphName, glyph, drawPoints)
+ outGlyphSet.writeContents()
+ if fileType == "TTF":
+ info = extractTTFFontInfo(font)
+ elif fileType == "Type 1":
+ info = extractT1FontInfo(font)
+ ufoWriter.writeInfo(info)
+
+if __name__ == "__main__":
+ print readGlyphConstructions()
+
+ \ No newline at end of file
diff --git a/misc/pylib/robofab/tools/toolsFL.py b/misc/pylib/robofab/tools/toolsFL.py
new file mode 100755
index 000000000..c8aff19a1
--- /dev/null
+++ b/misc/pylib/robofab/tools/toolsFL.py
@@ -0,0 +1,339 @@
+"""
+T.O.O.L.S.: Things Other Objects Lack (Sometimes)
+-assorted raw tools.
+
+This is an assorted colection of raw tools that do
+things inside of FontLab. Many of these functions
+form the bedrock of objectsFL. In short, use these
+tools only if you need the raw functions and they are
+not supported by the objects.
+
+Object model:
+Most of these tools were written before
+objectsFL. Some of these tools are used by
+objectsFL. That means that if you want to
+use functions from robofab.tools you can always
+feed them FontLab objects (like Font, Glyps,
+etc.). If the functions also accept Robjects from
+robofab.objects it is usually mentioned in the
+doc string.
+
+This is a simple way to convert a robofab Font
+object back to a FL Font object. Even if you don't
+know which particular faith an object belongs to
+you can use this:
+
+font = unwrapFont(font)
+"""
+
+
+from FL import *
+from warnings import warn
+
+try:
+ from fl_cmd import *
+except ImportError:
+ print "The fl_cmd module is not available here. toolsFL.py"
+
+import os
+
+from robofab import RoboFabError
+
+# local encoding
+if os.name == "mac":
+ LOCAL_ENCODING = "macroman"
+else:
+ LOCAL_ENCODING = "latin-1"
+
+
+#
+#
+#
+# stuff for fontlab app
+#
+#
+#
+
+def AppFolderRenamer():
+ """This function will rename the folder that contains the
+ FontLab application to a more specific name that includes
+ the version of the application
+ Warning: it messes with the paths of your app, if you have
+ items that hardwired to this path you'd be in trouble.
+ """
+ if fl.count > 0:
+ warn("Close all fonts before running AppFolderRenamer")
+ return
+ old = fl.path[:-1]
+ root = os.path.dirname(old)
+ new = "FontLab " + fl.version.replace('/', '_')
+ path = os.path.join(root, new)
+ if path != old:
+ try:
+ os.rename(old, path)
+ except OSError:
+ pass
+ warn("Please quit and restart FontLab")
+
+#
+#
+#
+# stuff for fonts
+#
+#
+#
+
+def GetFont(full_name):
+ """Return fontobjects which match full_name.
+ Note: result is a list.
+ Returns: a list of FL Font objects
+ """
+ found = []
+ for f in AllFonts():
+ if f.full_name == full_name:
+ found.append(f)
+ return found
+
+def AllFonts():
+ """Collect a list of all open fonts.
+ Returns: a list of FL Font objects.
+ """
+ fontcount = len(fl)
+ af = []
+ for i in range(fontcount):
+ af.append(fl[i])
+ return af
+
+def FontIndex(font):
+ """return the index of a specified FL Font"""
+ font = unwrapFont(font)
+ a = AllFonts()
+ p = []
+ for f in a:
+ p.append(f.file_name)
+ if font.file_name in p:
+ return p.index(font.file_name)
+ else:
+ return None
+
+def unwrapFont(font):
+ """Unwrap the font if it happens to be a RoboFab Font"""
+ if hasattr(font, 'isRobofab'):
+ return font.naked()
+ return font
+
+def MakeTempFont(font, dupemark=None, removeOverlap=True, decompose=True):
+ """Save the current FL Font,
+ - close the file,
+ - duplicate the file in the finder (icon looks weird, but it works)
+ - open the duplicate
+ - decompose the glyphs
+ - remove overlaps
+ - return the fontobject
+
+ font is either a FL Font or RF RFont object.
+
+ Problems: doesn't check if the filename is getting too long.
+ Note: it will overwrite older files with the same name.
+ """
+ import string
+ f = unwrapFont(font)
+ if not dupemark or dupemark == "":
+ dupemark = "_tmp_"
+ path = f.file_name
+ a = f.file_name.split('.')
+ a.insert(len(a)-1, dupemark)
+ newpath = string.join(a, '.')
+ f.Save(path)
+ fl.Close(FontIndex(f))
+ file = open(path, 'rb')
+ data = file.read()
+ file.close()
+ file = open(newpath, 'wb')
+ file.write(data)
+ file.close()
+ fl.Open(newpath, 1)
+ nf = fl.font
+ if nf is None:
+ print 'uh oh, sup?'
+ return None
+ else:
+ for g in nf.glyphs:
+ if decompose:
+ g.Decompose()
+ if removeOverlap:
+ g.RemoveOverlap()
+ return nf
+
+def makePSFontName(name):
+ """Create a postscript filename out of a regular postscript fontname,
+ using the old fashioned macintosh 5:3:3 convention.
+ """
+ import string
+ parts = []
+ current = []
+ final = []
+ notAllowed = '-_+=,-'
+ index = 0
+ for c in name:
+ if c in notAllowed:
+ continue
+ if c in string.uppercase or index == 0:
+ c = string.upper(c)
+ if current:
+ parts.append("".join(current))
+ current = [c]
+ else:
+ current.append(c)
+ index = index + 1
+ if current:
+ parts.append("".join(current))
+ final.append(parts[0][:5])
+ for p in parts[1:]:
+ final.append(p[:3])
+ return "".join(final)
+
+#
+#
+#
+# stuff for glyphs
+#
+#
+#
+
+def NewGlyph(font, glyphName, clear=False, updateFont=True):
+ """Make a new glyph if it doesn't already exist, return the glyph.
+ font is either a FL Font or RF RFont object. If updateFont is True
+ the (very slow) fl.UpdateFont function will be called.
+ """
+ font = unwrapFont(font)
+ if isinstance(glyphName, unicode):
+ glyphName = glyphName.encode(LOCAL_ENCODING)
+ glyph = font[glyphName]
+ if glyph is None:
+ new = Glyph()
+ new.name = glyphName
+ font.glyphs.append(new)
+ if updateFont:
+ fl.UpdateFont(FontIndex(font))
+ glyph = font[glyphName]
+ elif clear:
+ glyph.Clear()
+ glyph.anchors.clean()
+ glyph.components.clean()
+ glyph.note = ""
+ return glyph
+
+
+def AddToAlias(additions, sep='+'):
+ """additions is a dict with glyphnames as keys
+ and glyphConstruction as values. In order to make
+ a bunch of additions in one go rather than open
+ and close the file for each name. Add a glyph
+ to the alias.dat file if it doesn't already exist.
+ additions = {'Gcircumflex': ['G','circumflex'], }
+ Returns a list of only the added glyphnames."""
+ import string
+ glyphs = {}
+ data = []
+ new = []
+ path = os.path.join(fl.path, 'Mapping', 'alias.dat')
+ if os.path.exists(path):
+ file = open(path, 'r')
+ data = file.read().split('\n')
+ file.close()
+ for i in data:
+ if len(i) == 0: continue
+ if i[0] != '%':
+ glyphs[i.split(' ')[0]] = i.split(' ')[1]
+ for glyphName, glyphConstruction in additions.items():
+ if glyphName not in glyphs.keys():
+ new.append(glyphName)
+ glyphs[glyphName] = string.join(glyphConstruction, sep)
+ newNames = ['%%FONTLAB ALIASES']
+ l = glyphs.keys()
+ l.sort()
+ for i in l:
+ newNames.append(string.join([i, glyphs[i]], ' '))
+ file = open(path, 'w')
+ file.write(string.join(newNames, '\n'))
+ file.close()
+ return new
+
+
+def GlyphIndexTable(font):
+ """Make a glyph index table for font"""
+ font = unwrapFont(font)
+ idx = {}
+ for i in range(len(font)):
+ g = font.glyphs[i]
+ idx[g.name] = i
+ return idx
+
+def MakeReverseCompoMapping(font):
+ """Return a dict that maps glyph names to lists containing tuples
+ of the form:
+ (clientGlyphName, componentIndex)
+ """
+ font = unwrapFont(font)
+ reverseCompoMapping = {}
+ for g in font.glyphs:
+ for i, c in zip(range(len(g.components)), g.components):
+ base = font[c.index].name
+ if not base in reverseCompoMapping:
+ reverseCompoMapping[base] = []
+ reverseCompoMapping[base].append((g.name, i))
+ return reverseCompoMapping
+
+
+#
+#
+#
+# stuff for text files
+#
+#
+#
+
+def textPrinter(text, name=None, path=None):
+ """Write a string to a text file. If no name is given it becomes
+ Untitled_hour_minute_second.txt . If no path is given it goes
+ into the FontLab/RoboFab Data directory."""
+ if not name:
+ import time
+ tm_year,tm_mon,tm_day,tm_hour,tm_min,tm_sec,tm_wday,tm_yday,tm_isdst = time.localtime()
+ now = '_'.join((`tm_hour`, `tm_min`, `tm_sec`))
+ name = 'Untitled_%s.txt'%now
+ if not path:
+ path = os.path.join(makeDataFolder(), name)
+ f = open(path, 'wb')
+ f.write(text)
+ f.close()
+
+def makeDataFolder():
+ """Make the RoboFab data folder"""
+ folderPath = os.path.join(fl.path, "RoboFab Data")
+ if not os.path.exists(folderPath):
+ try:
+ os.makedirs(folderPath)
+ except:
+ pass
+ return folderPath
+
+
+def Log(text=None):
+ """Make an entry in the default log file."""
+ now = str(time.asctime(time.localtime(time.time())))
+ if not text:
+ text = "-"
+ entry = "%s: %s\r"%(now, text)
+ path = os.path.join(os.getcwd(), "Logs")
+ new = 0
+ if not os.path.exists(path):
+ os.makedirs(path)
+ new = 1
+ log = os.path.join(path, "log.txt")
+ f = open(log, 'a')
+ if new:
+ f.write("# log file for FL\r")
+ f.write(entry)
+ f.close()
diff --git a/misc/pylib/robofab/tools/toolsRF.py b/misc/pylib/robofab/tools/toolsRF.py
new file mode 100755
index 000000000..01624c5ad
--- /dev/null
+++ b/misc/pylib/robofab/tools/toolsRF.py
@@ -0,0 +1,6 @@
+"""
+
+Module for rf specific tool like code.
+
+"""
+