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)
This commit is contained in:
Rasmus Andersson 2017-09-03 23:03:17 -04:00
parent 31ae014e0c
commit 8234b62ab7
108 changed files with 26933 additions and 110 deletions

View file

@ -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.
"""

View file

@ -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

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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
"""

View file

@ -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

View file

@ -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))

View file

@ -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()

119
misc/pylib/robofab/tools/proof.py Executable file
View file

@ -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
#

View file

@ -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

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -0,0 +1,6 @@
"""
Module for rf specific tool like code.
"""