Merged ufo2 branch r95:170 into the trunk.

git-svn-id: http://svn.robofab.com/trunk@171 b5fa9d6c-a76f-4ffd-b3cb-f825fc41095c
This commit is contained in:
Tal Leming 2009-02-28 15:47:24 +00:00
parent 1eb53b404e
commit ea39f12120
44 changed files with 5678 additions and 5325 deletions

File diff suppressed because it is too large Load Diff

View File

@ -75,5 +75,5 @@ class RoboFabError(Exception): pass
class RoboFabWarning(Warning): pass
numberVersion = (1, 1, "develop", 3)
version = "1.1.3"
numberVersion = (1, 2, "develop", 0)
version = "1.2.0d"

View File

@ -1,358 +0,0 @@
"""This module has been deprecated."""
from warnings import warn
warn("family.py is deprecated.", DeprecationWarning)
"""
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
W A R N I N G
This is work in progress, a fast moving target.
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
"""
import os
from robofab import RoboFabError
from robofab.plistlib import readPlist, writePlist
from robofab.objects.objectsRF import RInfo, RFont, RLib, RGlyph, OpenFont
from robofab.ufoLib import UFOReader, writePlistAtomically, fontInfoAttrs
from robofab.objects.objectsBase import RBaseObject, BaseGroups
from robofab.glifLib import GlyphSet
import weakref
"""
RoboFab family planning
A font family is a group of UFO fonts that are related when
- they share a common source
- they are masters in various interpolations
- they are weights in a section of design space
Some family infrastructure is needed because there is some
information that transcends font or glyph. It is also a tool
to track fonts which are part of a larger structure, make sure
all resources needed for a project are present, up to date etc.
Structure:
MyFamilyProjectName.uff/
# the file and folder structure of the extended UFO family.
lib.plist
# a lib for the family
fonts/
# a folder with ufo's
masterA.ufo/
masterB.ufo/
# any number of fonts
...
contents.plist
# a contents.plist to track resources
shared/
# a .ufo containing capable of containing all
# data that a .ufo can contain. this data is
# shared with all members of the family
metainfo.plist
fontinfo.plist
lib.plist
groups.plist
kerning.plist
glyphs/
# any number of common or sharable glifs.
foundryLogo.glif
genericStuff.glif
# location for inbetween masters, interpolation exceptions:
# glyphs that don't fit in any specific font
a_lightBold_028.glif
a_lightBold_102.glif
a_lightBold_103.glif
...
contents.plist
"""
FONTS_DIRNAME = 'fonts'
FONTSCONTENTS_FILENAME = "contents.plist"
METAINFO_FILENAME = 'metainfo.plist'
LIB_FILENAME = 'lib.plist'
FAMILY_EXTENSION = ".uff"
FONT_EXTENSION = ".ufo"
SHARED_DIRNAME = "shared"
def makeUFFName(familyName):
return ''.join([familyName, FAMILY_EXTENSION])
def _scanContentsDirectory(path, forceRebuild=False):
contentsPath = os.path.join(path, FONTSCONTENTS_FILENAME)
if forceRebuild or not os.path.exists(contentsPath):
ext = FONT_EXTENSION
fileNames = os.listdir(path)
fileNames = [n for n in fileNames if n.endswith(ext)]
contents = {}
for n in fileNames:
contents[n[:-len(ext)]] = n
else:
contents = readPlist(contentsPath)
return contents
class FamilyReader(object):
"""A reader that reads all info from a .uff"""
def __init__(self, path):
self._path = path
def _checkForFile(self, path):
if not os.path.exists(path):
return False
else:
return True
def readMetaInfo(self):
path = os.path.join(self._path, METAINFO_FILENAME)
if not self._checkForFile(path):
return
def readLib(self):
path = os.path.join(self._path, LIB_FILENAME)
if not self._checkForFile(path):
return {}
return readPlist(path)
def readFontsContents(self):
contentsPath = os.path.join(self._path, FONTS_DIRNAME)
contents = _scanContentsDirectory(contentsPath)
return contents
def getSharedPath(self):
"""Return the path of all shared values in the family,
rather then create a new instance for it."""
return os.path.join(self._path, SHARED_DIRNAME)
class FamilyWriter(object):
"""a writer that builds all the necessary family stuff."""
fileCreator = 'org.robofab.uffLib'
formatVersion = 1
def __init__(self, path):
self._path = path
def _makeDirectory(self, subDirectory=None):
path = self._path
if subDirectory:
path = os.path.join(self._path, subDirectory)
if not os.path.exists(path):
os.makedirs(path)
if not os.path.exists(os.path.join(path, METAINFO_FILENAME)):
self._writeMetaInfo()
return path
def _writeMetaInfo(self):
path = os.path.join(self._path, METAINFO_FILENAME)
metaInfo = {
'creator': self.fileCreator,
'formatVersion': self.formatVersion,
}
writePlistAtomically(metaInfo, path)
def writeLib(self, libDict):
self._makeDirectory()
path = os.path.join(self._path, LIB_FILENAME)
if libDict:
writePlistAtomically(libDict, path)
elif os.path.exists(path):
os.remove(path)
def writeFontsContents(self):
path = self.makeFontsPath()
contents = _scanContentsDirectory(path)
contentsPath = os.path.join(path, FONTSCONTENTS_FILENAME)
writePlistAtomically(contents, contentsPath)
def makeFontsPath(self):
fontDir = self._makeDirectory(FONTS_DIRNAME)
return fontDir
def makeSharedPath(self):
sharedDir = self._makeDirectory(SHARED_DIRNAME)
return sharedDir
def getSharedGlyphSet(self):
path = self.makeSharedGlyphsPath()
return GlyphSet(path)
class RFamily(RBaseObject):
"""
Sketch for Family, the font superstructure.
This should ultimately move to objectsRF
The shared fontinfo and glyphset is just another font, named 'shared',
this avoids duplication of a lot of functionality in maintaining
the shared glyphset, reading, writing etc.
"""
def __init__(self, path=None):
self._path = path
self.info = RInfo() # this should go away. it is part of shared.info.
self.lib = RLib()
self.shared = RFont()
self._fontsContents = {} # font name: path
self._fonts = {} # fontName: object
self.lib.setParent(self)
if self._path:
self._loadData()
def __repr__(self):
if self.info.familyName:
name = self.info.familyName
else:
name = 'UnnamedFamily'
return "<RFont family for %s>" %(name)
def __len__(self):
return len(self._fontsContents.keys())
def __getitem__(self, fontKey):
if self._fontsContents.has_key(fontKey):
if not self._fonts.has_key(fontKey):
fontPath = os.path.join(self._path, FONTS_DIRNAME, self._fontsContents[fontKey])
font = RFont(fontPath)
font.setParent(self)
self._fonts[fontKey] = font
# uh, is returning a proxy the right thing to do here?
return weakref.proxy(self._fonts[fontKey])
raise IndexError
def __setitem__(self, key, fontObject):
if not key:
key = 'None'
key = self._makeKey(key)
self._fontsContents[key] = None
self._fonts[key] = fontObject
fontObject._path = None
def keys(self):
return self._fontsContents.keys()
def has_key(self, key):
return self._fontsContents.has_key(key)
__contains__ = has_key
def _loadData(self):
fr = FamilyReader(self._path)
self._fontsContents = fr.readFontsContents()
self.shared = RFont(fr.getSharedPath())
self.lib.update(fr.readLib())
def _hasChanged(self):
#mark the object as changed
self.setChanged(True)
def _makeKey(self, key):
# add a numerical extension to the key if it already exists
if self._fontsContents.has_key(key):
if key[-2] == '.':
try:
key = key[:-1] + `int(key[-1]) + 1`
except ValueError:
key = key + '.1'
else:
key = key + 1
self_makeKey(key)
return key
def save(self, destDir=None, doProgress=False):
if not destDir:
saveAs = False
destDir = self._path
else:
saveAs = True
fw = FamilyWriter(destDir)
for fontName, fontPath in self._fontsContents.items():
if saveAs and not self._fonts.has_key(fontName):
font = self[fontName]
if self._fonts.has_key(fontName):
if not fontPath or saveAs:
fontPath = os.path.join(path, fw.makeFontsPath(), ''.join([fontName, FONT_EXTENSION]))
self._fontsContents[fontName] = fontPath
self._fonts[fontName].save(fontPath, doProgress=False)
fw.writeFontsContents()
fw.writeLib(self.lib)
sharedPath = fw.makeSharedPath()
self.shared.save(sharedPath, doProgress=False)
self._path = destDir
#def sharedGlyphNames(self):
# """a list of all shared glyphs"""
# keys = self.sharedGlyphs.keys()
# if self.sharedGlyphSet is not None:
# keys.extend(self.sharedGlyphSet.keys())
# d = dict.fromkeys(keys)
# return d.keys()
#
#def getGlyph(self, glyphName, fontName=None):
# """retrieve a glyph from fontName, or from shared if no font is given."""
# if fontName is None or fontName =="shared":
# # ask for a shared glyph
# return self.shared[glyphName]
# if self.has_key(fontName):
# return self[fontName].getGlyph(glyphName)
# return None
#
#def newGlyph(self, glyphName):
# """add a new shared glyph"""
# return self.shared.newGlyph(glyphName)
#
#def removeGlyph(self, glyphName):
# """remove a shared glyph"""
# self.shared.removeGlyph(glyphName)
if __name__ == "__main__":
from robofab.world import OpenFont
from robofab.interface.all.dialogs import GetFolder
from PyBrowser import Browser
#open and test
font = OpenFont()
family = RFamily()
family['aFont'] = font
family.lib['org.robofab.uffLibTest'] = 'TestOneTwo!'
family.shared.info.familyName = 'ThisIsAFamilyName'
family.shared.newGlyph('xxx')
path = GetFolder('where do you want to store this new .uff?')
path = os.path.join(path, makeUFFName('MyBigFamily'))
family.save(path)
#family = RFamily(path)
#Browser(family.getGlyph('ASharedGlyph_Yay'))
## save as test
#path = GetFolder('select a .uff directory')
#family = RFamily(path)
#family.newGlyph('xxx')
#family.name = 'YeOldFamily'
#newPath = os.path.join(os.path.split(path)[0], 'xxx'+os.path.split(path)[1])
#family.save(newPath)

View File

@ -1,542 +0,0 @@
from warnings import warn
warn("featureLib.py is deprecated.", DeprecationWarning)
"""
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
W A R N I N G
This is work in progress, a fast moving target.
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
"""
"""FeatureLib
An attempt to get OpenType features written like Adobe's FDK
Feature descriptions to export into UFO and back. FontLab has
an interface for writing the features. FeatureLib offers some
tools to store the feature text, or to try interpretating it.
There seem to be no clever ways to make an interpreter for
feature speak based on abstract descriptions of the language:
No Backus Naur Form description is available.
No python interpreter
No C source code available to the public
So rather than go for the complete image, this implementation
is incomplete and probably difficult to extend. But it can
interpret the following features and write them back into the
right format and order:
feature xxxx {
<something>
} xxxx;
# lines with comment
@classname = [name1 name2 etc];
sub x x x by y y;
sub x from [a b c];
pos xx yy 100;
When interpret = False is passed as parameter it won't attempt to
interpret the feature text and just store it. Uninterpreted feature
text is exported as an url encoded string to ensure roundtripping
when the data is stored in a plist:
%09feature%20smcp%20%7B
This makes the feature text safe for storage in plist form without
breaking anything.
Also, if interpretation fails for any reason, the feature text is stored
so data should be lost.
To do:
- make it possible to read a .fea file and spit into seperate items
- test with more and different features from fontlab.
"""
DEBUG = True
DEFAULTNAME = "xxxx"
__all__ = ["Feature", "FeatureSet", "many_to_many", "one_from_many",
"simple_pair", "extractFLFeatures", "putFeaturesLib", "getFeaturesLib"]
# substition types
# are there official opentype substitution titles for this?
many_to_many = 0
one_from_many = 1
# kern types
simple_pair = 0
# lib key for features
featureLibKey = "org.robofab.features"
class Feature:
"""Feature contains one single feature, of any flavor.
Read from feature script
Write to feature script
Read from simple dict
Write to simple dict
Parse some of the lines
Accept edits and additions
"""
def __init__(self, name=None, text=None, data=None, interpret=True):
if name is not None:
self.name = name
else:
self.name = DEFAULTNAME
self._sub = []
self._pos = []
self._comment = []
self._feature = []
self._classes = []
self._tab = " "*4
self._text = None
if text is not None:
self.readFeatureText(text, interpret)
elif data is not None:
self.fromDict(data)
def __repr__(self):
return "<Robofab Feature object '%s'>"%(self.name)
def addSub(self, itemsIn, itemsOut, subType=many_to_many):
"""Add a substitution statement"""
self._sub.append((subType, itemsIn, itemsOut))
def addPos(self, itemOne, itemTwo, offset):
"""Add a positioning statement"""
self._pos.append((itemOne, itemTwo, offset))
def hasSubs(self):
"""Return True if this feature has substitutions defined."""
return len(self._sub) > 0
def hasPos(self):
"""Return True if this feature has positioning defined."""
return len(self._pos) > 0
def readFeatureText(self, featureText, interpret=True):
"""Read the feature text and try to make sense of it.
Note: Should you want to preserve the actual featuretext
rather than the intrepreted data, set interpret = False
In case the feature text isn't properly interpreted
(possible) or because the feature text is hand edited
and you just want it to round trip to UFO.
"""
if interpret:
if featureText is not None:
self.parse(featureText)
else:
self._text = featureText
def parse(self, featureText):
"""bluntly split the lines of feature code as they come from fontlab
This doesn't by any means parse all of the possible combinations
in a .fea file. It parses the pos and sub lines defines within a feature.
Something higher up should parse the seperate features from the .fea.
It doesn't check for validity of the lines.
"""
lines = featureText.split("\n")
count = 0
featureOpened = False
interpretOK = True
for l in lines:
#
# run through all lines
#
p = l.strip()
if len(p)==0:continue
if p[-1] == ";":
p = p[:-1]
p = p.split(" ")
count += 1
#
# plain substitutions
# example:
# sub @class496 by @class497;
# sub aring from [amacron aringacute adieresis aacute];
# sub s s by s_s;
#
if p[0] == "sub":
if "by" in p:
# sub xx by xx;
self.addSub(p[1:p.index("by")], p[p.index("by")+1:], many_to_many)
elif "from" in p:
# sub x from [zzz];
theList = " ".join(p[p.index("from")+1:])[1:-1].split(" ")
self.addSub(p[1:p.index("from")], theList, one_from_many)
#
# plain kerning
# example:
# pos Yacute A -215;
#
elif p[0] == "pos":
items = p[1:-1]
value = int(p[-1])
self._pos.append((simple_pair, items, value))
#
# comments
#
elif p[0] == "#":
# comment?
self._comment.append(" ".join(p[1:]))
#
# features beginning or feature within feature
#
elif p[0] == "feature":
# comment?
if not featureOpened:
# ah, it's a fully wrapped description
if len(p[1]) == 4 and p[2] == "{":
self.name = p[1]
else:
print 'uh oh xxxxx', p
featureOpened = True
else:
# it's an unwrapped (from fontlab) description
self._feature.append(p[1:])
#
# feature ending
#
elif p[0] == "}" and p[1] == self.name:
featureOpened = False
#
# special cases (humph)
#
#
# special case: class definitions
#
elif "=" in p:
# check for class defenitions
# example:
# @MMK_L_A = [A Aacute];
# @S = [S Sacute Scedille]
equalOperatorIndex = p.index("=")
classNames = p[:equalOperatorIndex]
# get the seperate names from the list:
classMembers = " ".join(p[equalOperatorIndex+1:])[1:-1].split(" ")
self._classes.append((classNames, classMembers))
#
# we can't make sense of it, store the feature text instead then..
#
else:
print "Feature interpreter error:", p
interpretOK = False
if not interpretOK:
if DEBUG:
"Couldn't interpret all feature lines, storing the text as well."
self._text = featureText
def writeFeatureText(self, wrapped=True):
"""return the feature as an OpenType feature string
wrapped = True: wrapped with featurename { feature items; }
wrapped = False: similar to that produced by FontLab
"""
text = []
if self._text:
# if literal feature text is stored: use that
# XXXX how to handle is there are new, manually added feature items?
# XXXX should the caller clear the text first?
from urllib import unquote
return unquote(self._text)
if wrapped:
text.append("feature %s {"%self.name)
if self._comment:
text.append(" # %s"%(" ".join(self._comment)))
if self._feature:
for f in self._feature:
text.append(" feature %s;"%(" ".join(f)))
if self._classes:
#
# first dump any in-feature class definitions
#
for classNames, classMembers in self._classes:
text.append(self._tab+"%s = [%s];"%(" ".join(classNames), " ".join(classMembers)))
if self._pos:
#
# run through the list twice to get the class kerns first
#
for posType, names, value in self._pos:
text.append(self._tab+"pos %s %d;"%(" ".join(names), value))
if self._sub:
for (subType, stuffIn, stuffOut) in self._sub:
if subType == many_to_many:
text.append(self._tab+"sub %s by %s;"%((" ".join(stuffIn), " ".join(stuffOut))))
elif subType == one_from_many:
text.append(self._tab+"sub %s from [%s];"%((" ".join(stuffIn), " ".join(stuffOut))))
if wrapped:
text.append("} %s;"%self.name)
final = "\n".join(text)+"\n"
return final
def asDict(self):
"""Return the data of this feature as a plist ready dictionary"""
data = {}
data['name'] = self.name
if self._comment:
data['comment'] = self._comment
if self._sub:
data["sub"] = self._sub
if self._pos:
data["pos"] = self._pos
if self._feature:
data["feature"] = self._feature
if self._text:
from urllib import quote
data['text'] = quote(self._text)
return data
def fromDict(self, aDict):
"""Read the data from a dict."""
self.name = aDict.get("name", DEFAULTNAME)
self._sub = aDict.get("sub", [])
self._pos = aDict.get("pos", [])
self._feature = aDict.get("feature", [])
self._comment = aDict.get("comment", [])
text = aDict.get('text', None)
if text is not None:
from urllib import unquote
self._text = unquote(text)
class FeatureSet(dict):
"""A dict to combine all features, and write them to various places"""
def __init__(self, interpret=True):
self.interpret = interpret
def readFL(self, aFont):
"""Read the feature stuff from a RFont in FL context.
This can be structured better I think, but let's get
something working first.
"""
for name in aFont.getOTFeatures():
if DEBUG:
print 'reading %s from %s'%(name, aFont.info.fullName)
self[name] = Feature(name, aFont.getOTFeature(name), interpret = self.interpret)
self.changed = True
def writeFL(self, aFont, featureName=None):
"""Write one or all features back"""
if featureName == None:
names = self.keys()
else:
names = [featureName]
for n in names:
text = self[n].writeFeatureText(wrapped=False)
if DEBUG:
print "writing feature %s"%n
print '- '*30
print `text`
print `self[n]._text`
print '- '*30
aFont.setOTFeature(n, text)
def writeLib(self, aFont):
aFont.lib[featureLibKey] = self.asDict()
def readLib(self, aFont):
"""Read the feature stuff from the font lib.
Rather than add all this to yet another file in the UFO,
just store it in the lib. UFO users will be able to read
the data anyway.
"""
stuff = aFont.lib.get(featureLibKey, None)
if stuff is None:
if DEBUG:
print "No features found in this lib.."
return
self.clear()
self.update(stuff)
def append(self, aFeature):
"""Append a feature object to this set"""
self[aFeature.name] = aFeature
if DEBUG:
print "..added %s to FeatureSet"%aFeature.name
def newFeature(self, name):
"""Add a new feature and return it"""
self[name] = Feature(name)
return self[name]
def update(self, aDict):
"""Accept a dictionary with all features written out as dicts.
Ready for data read from plist
"""
for name, feature in aDict.items():
self[name] = Feature(data=feature, interpret=self.interpret)
def asDict(self):
"""Return a dict with all features also written out as dicts. Not the same as self.
Data is ready for writing to plist
"""
data = {}
for name, feature in self.items():
data[name] = feature.asDict()
return data
# convenience functions
def extractFLFeatures(aFont, interpret=True):
"""FontLab specific: copy features from the font to the font.lib"""
fs = FeatureSet(interpret = interpret)
fs.readFL(aFont)
fs.writeLib(aFont)
def putFeaturesLib(aFont, featureSet):
"""Put the features in the appropriate place in the font.lib"""
featureSet.writeLib(aFont)
def getFeaturesLib(aFont, interpret=True):
"""Get the featureset from a lib."""
fs = FeatureSet(interpret = interpret)
fs.readLib(aFont)
return fs
if __name__ == "__main__":
# examples
print "-"*10, "sub many by many"
# a regular ligature feature
dligtext = """feature dlig {
# Latin
@MMK_L_A = [A Aacute];
sub I J by IJ;
sub i j by ij;
sub s s by s_s;
} dlig;
"""
feat1 = Feature(text=dligtext)
print feat1.asDict()
print
print feat1.writeFeatureText()
print "-"*10, "sub one from many"
# aalt one from many substitution
aalttext = """feature aalt {
sub aring from [amacron acircumflex adblgrave a agrave abreve acaron atilde aogonek aringacute adieresis aacute];
sub utilde from [umacron uring uacute udieresisacute ucircumflex uhorn udblgrave udieresis uhungarumlaut udieresisgrave ugrave ubreve uogonek ucaron u];
sub Hcircumflex from [H.sc Hcedilla.sc Hcircumflex.sc Hdotaccent H Hcedilla Hdieresis Hdieresis.sc Hdotaccent.sc];
sub pdotaccent from [p pacute];
} aalt;
"""
feat2 = Feature(text=aalttext)
print feat2.asDict()
print
print feat2.writeFeatureText()
print "-"*10, "kerning"
# kern and positioning
kerntext = """ feature kern {
# Latin
pos Yacute A -215;
pos Yacute B -30;
pos Yacute C -100;
pos Yacute D -50;
pos Yacute E -35;
pos Yacute F -35;
pos Yacute G -80;
pos Yacute H -25;
# -- kerning classes
@MMK_L_A = [A Aacute];
@MMK_R_C = [C Ccedilla];
} kern;
"""
feat3 = Feature(text=kerntext)
print feat3.asDict()
print
print feat3.writeFeatureText()
print "-"*10, "something with groups in it"
# references to groups are treated like any other
grouptext = """ feature smcp {
# Latin
sub @class496 by @class497;
} smcp;
"""
# Feature doesn't interpret the text in this example
# when interpret = False is given as parameter,
# the feature code is stored and reproduced exactly.
# But then you have to specify the name of the feature
# otherwise it will default and overwrite other features
# with the same default name.
feat4 = Feature(name="smcp", text=grouptext, interpret = False)
print feat4.asDict()
print
print feat4.writeFeatureText()
print "-"*10, "store the feature set in the lib"
# now create a feature set to dump all features in the lib
set = FeatureSet()
set.append(feat1)
set.append(feat2)
set.append(feat3)
set.append(feat4)
from robofab.world import NewFont
testFont = NewFont()
set.writeLib(testFont)
print testFont.lib[featureLibKey]
print "-"*10, "read the feature set from the lib again"
notherSet = FeatureSet()
notherSet.readLib(testFont)
for name, feat in notherSet.items():
print name, feat

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
"""UFO for GlifLib"""
from robofab import RoboFabError, RoboFabWarning
from robofab.objects.objectsBase import BaseFont, BaseKerning, BaseGroups, BaseInfo, BaseLib,\
from robofab.objects.objectsBase import BaseFont, BaseKerning, BaseGroups, BaseInfo, BaseFeatures, BaseLib,\
BaseGlyph, BaseContour, BaseSegment, BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, \
relativeBCPIn, relativeBCPOut, absoluteBCPIn, absoluteBCPOut, _box,\
_interpolate, _interpolatePt, roundPt, addPt,\
@ -45,8 +45,10 @@ def OpenFont(path=None, note=None):
def NewFont(familyName=None, styleName=None):
"""Make a new font"""
new = RFont()
new.info.familyName = familyName
new.info.styleName = styleName
if familyName is not None:
new.info.familyName = familyName
if styleName is not None:
new.info.styleName = styleName
return new
def AllFonts():
@ -67,13 +69,16 @@ class PostScriptFontHintValues(BasePostScriptFontHintValues):
"""
def __init__(self, aFont=None, data=None):
# read the data from the font.lib, it won't be anywhere else
self.setParent(aFont)
BasePostScriptFontHintValues.__init__(self)
if aFont is not None:
self.setParent(aFont)
# in version 1, this data was stored in the lib
# if it is still there, guess that it is correct
# move it to font info and remove it from the lib.
libData = aFont.lib.get(postScriptHintDataLibKey)
if libData is not None:
self.fromDict(libData)
del libData[postScriptHintDataLibKey]
if data is not None:
self.fromDict(data)
@ -123,15 +128,17 @@ class RFont(BaseFont):
self.kerning.setParent(self)
self.info = RInfo()
self.info.setParent(self)
self.features = RFeatures()
self.features.setParent(self)
self.groups = RGroups()
self.groups.setParent(self)
self.lib = RLib()
self.lib.setParent(self)
self.psHints = PostScriptFontHintValues(self)
self.psHints.setParent(self)
if path:
self._loadData(path)
else:
self.psHints = PostScriptFontHintValues(self)
self.psHints.setParent(self)
def __setitem__(self, glyphName, glyph):
"""Set a glyph at key."""
@ -152,19 +159,51 @@ class RFont(BaseFont):
return len(self._glyphSet)
def _loadData(self, path):
#Load the data into the font
from robofab.ufoLib import UFOReader
u = UFOReader(path)
u.readInfo(self.info)
self.kerning.update(u.readKerning())
reader = UFOReader(path)
fontLib = reader.readLib()
# info
reader.readInfo(self.info)
# kerning
self.kerning.update(reader.readKerning())
self.kerning.setChanged(False)
self.groups.update(u.readGroups())
self.lib.update(u.readLib())
# after reading the lib, read hinting data from the lib
# groups
self.groups.update(reader.readGroups())
# features
if reader.formatVersion == 1:
# migrate features from the lib
features = []
classes = fontLib.get("org.robofab.opentype.classes")
if classes is not None:
del fontLib["org.robofab.opentype.classes"]
features.append(classes)
splitFeatures = fontLib.get("org.robofab.opentype.features")
if splitFeatures is not None:
order = fontLib.get("org.robofab.opentype.featureorder")
if order is None:
order = splitFeatures.keys()
order.sort()
else:
del fontLib["org.robofab.opentype.featureorder"]
del fontLib["org.robofab.opentype.features"]
for tag in order:
oneFeature = splitFeatures.get(tag)
if oneFeature is not None:
features.append(oneFeature)
features = "\n".join(features)
else:
features = reader.readFeatures()
self.features.text = features
# hint data
self.psHints = PostScriptFontHintValues(self)
self._glyphSet = u.getGlyphSet()
if postScriptHintDataLibKey in fontLib:
del fontLib[postScriptHintDataLibKey]
# lib
self.lib.update(fontLib)
# glyphs
self._glyphSet = reader.getGlyphSet()
self._hasNotChanged(doGlyphs=False)
def _loadGlyph(self, glyphName):
"""Load a single glyph from the glyphSet, on request."""
from robofab.pens.rfUFOPen import RFUFOPointPen
@ -329,7 +368,7 @@ class RFont(BaseFont):
return reverseMap
def save(self, destDir=None, doProgress=False, saveNow=False):
def save(self, destDir=None, doProgress=False, formatVersion=2):
"""Save the Font in UFO format."""
# XXX note that when doing "save as" by specifying the destDir argument
# _all_ glyphs get loaded into memory. This could be optimized by either
@ -337,6 +376,7 @@ class RFont(BaseFont):
# well that would work) by simply clearing out self._objects after the
# save.
from robofab.ufoLib import UFOWriter
from robofab.tools.fontlabFeatureSplitter import splitFeaturesForFontLab
# if no destination is given, or if
# the given destination is the current
# path, this is not a save as operation
@ -345,54 +385,78 @@ class RFont(BaseFont):
destDir = self._path
else:
saveAs = True
u = UFOWriter(destDir)
# start a progress bar
nonGlyphCount = 5
bar = None
if doProgress:
from robofab.interface.all.dialogs import ProgressBar
bar = ProgressBar('Exporting UFO', nonGlyphCount+len(self._object.keys()))
bar = ProgressBar("Exporting UFO", nonGlyphCount + len(self._object.keys()))
# write
writer = UFOWriter(destDir, formatVersion=formatVersion)
try:
#if self.info.changed:
# make a shallow copy of the lib. stuff may be added to it.
fontLib = dict(self.lib)
# info
if bar:
bar.label('Saving info...')
u.writeInfo(self.info)
bar.label("Saving info...")
writer.writeInfo(self.info)
if bar:
bar.tick()
# kerning
if self.kerning.changed or saveAs:
if bar:
bar.label('Saving kerning...')
u.writeKerning(self.kerning.asDict())
self.kerning.setChanged(False)
bar.label("Saving kerning...")
writer.writeKerning(self.kerning.asDict())
if bar:
bar.tick()
# groups
if bar:
bar.label("Saving groups...")
writer.writeGroups(self.groups)
if bar:
bar.tick()
#if self.groups.changed:
# features
if bar:
bar.label('Saving groups...')
u.writeGroups(self.groups)
bar.label("Saving features...")
features = self.features.text
if features is None:
features = ""
if formatVersion == 2:
writer.writeFeatures(features)
elif formatVersion == 1:
classes, features = splitFeaturesForFontLab(features)
if classes:
fontLib["org.robofab.opentype.classes"] = classes.strip() + "\n"
if features:
featureDict = {}
for featureName, featureText in features:
featureDict[featureName] = featureText.strip() + "\n"
fontLib["org.robofab.opentype.features"] = featureDict
fontLib["org.robofab.opentype.featureorder"] = [featureName for featureName, featureText in features]
if bar:
bar.tick()
# save postscript hint data
self.lib[postScriptHintDataLibKey] = self.psHints.asDict()
#if self.lib.changed:
# lib
if formatVersion == 1:
fontLib[postScriptHintDataLibKey] = self.psHints.asDict()
if bar:
bar.label('Saving lib...')
u.writeLib(self.lib)
bar.label("Saving lib...")
writer.writeLib(fontLib)
if bar:
bar.tick()
# glyphs
glyphNameToFileNameFunc = self.getGlyphNameToFileNameFunc()
glyphSet = u.getGlyphSet(glyphNameToFileNameFunc)
glyphSet = writer.getGlyphSet(glyphNameToFileNameFunc)
if len(self._scheduledForDeletion) != 0:
if bar:
bar.label('Removing deleted glyphs......')
bar.label("Removing deleted glyphs...")
for glyphName in self._scheduledForDeletion:
if glyphSet.has_key(glyphName):
glyphSet.deleteGlyph(glyphName)
if bar:
bar.tick()
if bar:
bar.label('Saving glyphs...')
bar.label("Saving glyphs...")
count = nonGlyphCount
if saveAs:
glyphNames = self.keys()
@ -407,15 +471,18 @@ class RFont(BaseFont):
count = count + 1
glyphSet.writeContents()
self._glyphSet = glyphSet
# only blindly stop if the user says to
except KeyboardInterrupt:
bar.close()
bar = None
# kill the progress bar
if bar:
bar.close()
# reset internal stuff
self._path = destDir
self._scheduledForDeletion = []
self.setChanged(False)
def newGlyph(self, glyphName, clear=True):
"""Make a new glyph with glyphName
if the glyph exists and clear=True clear the glyph"""
@ -1126,370 +1193,9 @@ class RLib(BaseLib):
class RInfo(BaseInfo):
_title = "RoboFabFonInfo"
def __init__(self):
BaseInfo.__init__(self)
self.selected = False
self._familyName = None
self._styleName = None
self._fullName = None
self._fontName = None
self._menuName = None
self._fondName = None
self._otFamilyName = None
self._otStyleName = None
self._otMacName = None
self._weightValue = None
self._weightName = None
self._widthName = None
self._fontStyle = None
self._msCharSet = None
self._note = None
self._fondID = None
self._uniqueID = None
self._versionMajor = None
self._versionMinor = None
self._year = None
self._copyright = None
self._notice = None
self._trademark = None
self._license = None
self._licenseURL = None
self._createdBy = None
self._designer = None
self._designerURL = None
self._vendorURL = None
self._ttVendor = None
self._ttUniqueID = None
self._ttVersion = None
self._unitsPerEm = None
self._ascender = None
self._descender = None
self._capHeight = None
self._xHeight = None
self._defaultWidth = None
self._italicAngle = None
self._slantAngle = None
def _get_familyName(self):
return self._familyName
def _set_familyName(self, value):
self._familyName = value
familyName = property(_get_familyName, _set_familyName, doc="family_name")
def _get_styleName(self):
return self._styleName
def _set_styleName(self, value):
self._styleName = value
styleName = property(_get_styleName, _set_styleName, doc="style_name")
def _get_fullName(self):
return self._fullName
def _set_fullName(self, value):
self._fullName = value
fullName = property(_get_fullName, _set_fullName, doc="full_name")
def _get_fontName(self):
return self._fontName
def _set_fontName(self, value):
self._fontName = value
fontName = property(_get_fontName, _set_fontName, doc="font_name")
def _get_menuName(self):
return self._menuName
def _set_menuName(self, value):
self._menuName = value
menuName = property(_get_menuName, _set_menuName, doc="menu_name")
def _get_fondName(self):
return self._fondName
def _set_fondName(self, value):
self._fondName = value
fondName = property(_get_fondName, _set_fondName, doc="apple_name")
def _get_otFamilyName(self):
return self._otFamilyName
def _set_otFamilyName(self, value):
self._otFamilyName = value
otFamilyName = property(_get_otFamilyName, _set_otFamilyName, doc="pref_family_name")
def _get_otStyleName(self):
return self._otStyleName
def _set_otStyleName(self, value):
self._otStyleName = value
otStyleName = property(_get_otStyleName, _set_otStyleName, doc="pref_style_name")
def _get_otMacName(self):
return self._otMacName
def _set_otMacName(self, value):
self._otMacName = value
otMacName = property(_get_otMacName, _set_otMacName, doc="mac_compatible")
def _get_weightValue(self):
return self._weightValue
def _set_weightValue(self, value):
self._weightValue = value
weightValue = property(_get_weightValue, _set_weightValue, doc="weight value")
def _get_weightName(self):
return self._weightName
def _set_weightName(self, value):
self._weightName = value
weightName = property(_get_weightName, _set_weightName, doc="weight name")
def _get_widthName(self):
return self._widthName
def _set_widthName(self, value):
self._widthName = value
widthName = property(_get_widthName, _set_widthName, doc="width name")
_title = "RoboFabFontInfo"
def _get_fontStyle(self):
return self._fontStyle
def _set_fontStyle(self, value):
self._fontStyle = value
fontStyle = property(_get_fontStyle, _set_fontStyle, doc="font_style")
def _get_msCharSet(self):
return self._msCharSet
def _set_msCharSet(self, value):
self._msCharSet = value
msCharSet = property(_get_msCharSet, _set_msCharSet, doc="ms_charset")
def _get_note(self):
return self._note
def _set_note(self, value):
self._note = value
note = property(_get_note, _set_note, doc="note")
def _get_fondID(self):
return self._fondID
def _set_fondID(self, value):
self._fondID = value
fondID = property(_get_fondID, _set_fondID, doc="fond_id")
def _get_uniqueID(self):
return self._uniqueID
def _set_uniqueID(self, value):
self._uniqueID = value
uniqueID = property(_get_uniqueID, _set_uniqueID, doc="unique_id")
def _get_versionMajor(self):
return self._versionMajor
def _set_versionMajor(self, value):
self._versionMajor = value
versionMajor = property(_get_versionMajor, _set_versionMajor, doc="version_major")
def _get_versionMinor(self):
return self._versionMinor
def _set_versionMinor(self, value):
self._versionMinor = value
versionMinor = property(_get_versionMinor, _set_versionMinor, doc="version_minor")
def _get_year(self):
return self._year
def _set_year(self, value):
self._year = value
year = property(_get_year, _set_year, doc="year")
def _get_copyright(self):
return self._copyright
def _set_copyright(self, value):
self._copyright = value
copyright = property(_get_copyright, _set_copyright, doc="copyright")
def _get_notice(self):
return self._notice
def _set_notice(self, value):
self._notice = value
notice = property(_get_notice, _set_notice, doc="notice")
def _get_trademark(self):
return self._trademark
def _set_trademark(self, value):
self._trademark = value
trademark = property(_get_trademark, _set_trademark, doc="trademark")
def _get_license(self):
return self._license
def _set_license(self, value):
self._license = value
license = property(_get_license, _set_license, doc="license")
def _get_licenseURL(self):
return self._licenseURL
def _set_licenseURL(self, value):
self._licenseURL = value
licenseURL = property(_get_licenseURL, _set_licenseURL, doc="license_url")
def _get_designer(self):
return self._designer
def _set_designer(self, value):
self._designer = value
designer = property(_get_designer, _set_designer, doc="designer")
def _get_createdBy(self):
return self._createdBy
def _set_createdBy(self, value):
self._createdBy = value
createdBy = property(_get_createdBy, _set_createdBy, doc="source")
def _get_designerURL(self):
return self._designerURL
def _set_designerURL(self, value):
self._designerURL = value
designerURL = property(_get_designerURL, _set_designerURL, doc="designer_url")
def _get_vendorURL(self):
return self._vendorURL
def _set_vendorURL(self, value):
self._vendorURL = value
vendorURL = property(_get_vendorURL, _set_vendorURL, doc="vendor_url")
def _get_ttVendor(self):
return self._ttVendor
def _set_ttVendor(self, value):
self._ttVendor = value
ttVendor = property(_get_ttVendor, _set_ttVendor, doc="vendor")
def _get_ttUniqueID(self):
return self._ttUniqueID
def _set_ttUniqueID(self, value):
self._ttUniqueID = value
ttUniqueID = property(_get_ttUniqueID, _set_ttUniqueID, doc="tt_u_id")
def _get_ttVersion(self):
return self._ttVersion
def _set_ttVersion(self, value):
self._ttVersion = value
ttVersion = property(_get_ttVersion, _set_ttVersion, doc="tt_version")
def _get_unitsPerEm(self):
return self._unitsPerEm
def _set_unitsPerEm(self, value):
self._unitsPerEm = value
unitsPerEm = property(_get_unitsPerEm, _set_unitsPerEm, doc="")
def _get_ascender(self):
return self._ascender
def _set_ascender(self, value):
self._ascender = value
ascender = property(_get_ascender, _set_ascender, doc="ascender value")
def _get_descender(self):
return self._descender
def _set_descender(self, value):
self._descender = value
descender = property(_get_descender, _set_descender, doc="descender value")
def _get_capHeight(self):
return self._capHeight
def _set_capHeight(self, value):
self._capHeight = value
capHeight = property(_get_capHeight, _set_capHeight, doc="cap height value")
def _get_xHeight(self):
return self._xHeight
def _set_xHeight(self, value):
self._xHeight = value
xHeight = property(_get_xHeight, _set_xHeight, doc="x height value")
def _get_defaultWidth(self):
return self._defaultWidth
def _set_defaultWidth(self, value):
self._defaultWidth = value
defaultWidth = property(_get_defaultWidth, _set_defaultWidth, doc="default width value")
def _get_italicAngle(self):
return self._italicAngle
def _set_italicAngle(self, value):
self._italicAngle = value
italicAngle = property(_get_italicAngle, _set_italicAngle, doc="italic_angle")
def _get_slantAngle(self):
return self._slantAngle
def _set_slantAngle(self, value):
self._slantAngle = value
slantAngle = property(_get_slantAngle, _set_slantAngle, doc="slant_angle")
class RFeatures(BaseFeatures):
_title = "RoboFabFeatures"

View File

@ -17,6 +17,7 @@ if __name__ == "__main__":
mod = __import__(modName, {}, {}, ["*"])
except ImportError:
print "*** skipped", fileName
continue
suites.append(loader.loadTestsFromModule(mod))

View File

@ -47,3 +47,232 @@ def runTests(testCases=None, verbosity=1):
testSuite = unittest.TestSuite(suites)
testRunner.run(testSuite)
# font info values used by several tests
fontInfoVersion1 = {
"familyName" : "Some Font (Family Name)",
"styleName" : "Regular (Style Name)",
"fullName" : "Some Font-Regular (Postscript Full Name)",
"fontName" : "SomeFont-Regular (Postscript Font Name)",
"menuName" : "Some Font Regular (Style Map Family Name)",
"fontStyle" : 64,
"note" : "A note.",
"versionMajor" : 1,
"versionMinor" : 0,
"year" : 2008,
"copyright" : "Copyright Some Foundry.",
"notice" : "Some Font by Some Designer for Some Foundry.",
"trademark" : "Trademark Some Foundry",
"license" : "License info for Some Foundry.",
"licenseURL" : "http://somefoundry.com/license",
"createdBy" : "Some Foundry",
"designer" : "Some Designer",
"designerURL" : "http://somedesigner.com",
"vendorURL" : "http://somefoundry.com",
"unitsPerEm" : 1000,
"ascender" : 750,
"descender" : -250,
"capHeight" : 750,
"xHeight" : 500,
"defaultWidth" : 400,
"slantAngle" : -12.5,
"italicAngle" : -12.5,
"widthName" : "Medium (normal)",
"weightName" : "Medium",
"weightValue" : 500,
"fondName" : "SomeFont Regular (FOND Name)",
"otFamilyName" : "Some Font (Preferred Family Name)",
"otStyleName" : "Regular (Preferred Subfamily Name)",
"otMacName" : "Some Font Regular (Compatible Full Name)",
"msCharSet" : 0,
"fondID" : 15000,
"uniqueID" : 4000000,
"ttVendor" : "SOME",
"ttUniqueID" : "OpenType name Table Unique ID",
"ttVersion" : "OpenType name Table Version",
}
fontInfoVersion2 = {
"familyName" : "Some Font (Family Name)",
"styleName" : "Regular (Style Name)",
"styleMapFamilyName" : "Some Font Regular (Style Map Family Name)",
"styleMapStyleName" : "regular",
"versionMajor" : 1,
"versionMinor" : 0,
"year" : 2008,
"copyright" : "Copyright Some Foundry.",
"trademark" : "Trademark Some Foundry",
"unitsPerEm" : 1000,
"descender" : -250,
"xHeight" : 500,
"capHeight" : 750,
"ascender" : 750,
"italicAngle" : -12.5,
"note" : "A note.",
"openTypeHeadCreated" : "2000/01/01 00:00:00",
"openTypeHeadLowestRecPPEM" : 10,
"openTypeHeadFlags" : [0, 1],
"openTypeHheaAscender" : 750,
"openTypeHheaDescender" : -250,
"openTypeHheaLineGap" : 200,
"openTypeHheaCaretSlopeRise" : 1,
"openTypeHheaCaretSlopeRun" : 0,
"openTypeHheaCaretOffset" : 0,
"openTypeNameDesigner" : "Some Designer",
"openTypeNameDesignerURL" : "http://somedesigner.com",
"openTypeNameManufacturer" : "Some Foundry",
"openTypeNameManufacturerURL" : "http://somefoundry.com",
"openTypeNameLicense" : "License info for Some Foundry.",
"openTypeNameLicenseURL" : "http://somefoundry.com/license",
"openTypeNameVersion" : "OpenType name Table Version",
"openTypeNameUniqueID" : "OpenType name Table Unique ID",
"openTypeNameDescription" : "Some Font by Some Designer for Some Foundry.",
"openTypeNamePreferredFamilyName" : "Some Font (Preferred Family Name)",
"openTypeNamePreferredSubfamilyName" : "Regular (Preferred Subfamily Name)",
"openTypeNameCompatibleFullName" : "Some Font Regular (Compatible Full Name)",
"openTypeNameSampleText" : "Sample Text for Some Font.",
"openTypeNameWWSFamilyName" : "Some Font (WWS Family Name)",
"openTypeNameWWSSubfamilyName" : "Regular (WWS Subfamily Name)",
"openTypeOS2WidthClass" : 5,
"openTypeOS2WeightClass" : 500,
"openTypeOS2Selection" : [3],
"openTypeOS2VendorID" : "SOME",
"openTypeOS2Panose" : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
"openTypeOS2FamilyClass" : [1, 1],
"openTypeOS2UnicodeRanges" : [0, 1],
"openTypeOS2CodePageRanges" : [0, 1],
"openTypeOS2TypoAscender" : 750,
"openTypeOS2TypoDescender" : -250,
"openTypeOS2TypoLineGap" : 200,
"openTypeOS2WinAscent" : 750,
"openTypeOS2WinDescent" : -250,
"openTypeOS2Type" : [],
"openTypeOS2SubscriptXSize" : 200,
"openTypeOS2SubscriptYSize" : 400,
"openTypeOS2SubscriptXOffset" : 0,
"openTypeOS2SubscriptYOffset" : -100,
"openTypeOS2SuperscriptXSize" : 200,
"openTypeOS2SuperscriptYSize" : 400,
"openTypeOS2SuperscriptXOffset" : 0,
"openTypeOS2SuperscriptYOffset" : 200,
"openTypeOS2StrikeoutSize" : 20,
"openTypeOS2StrikeoutPosition" : 300,
"openTypeVheaVertTypoAscender" : 750,
"openTypeVheaVertTypoDescender" : -250,
"openTypeVheaVertTypoLineGap" : 200,
"openTypeVheaCaretSlopeRise" : 0,
"openTypeVheaCaretSlopeRun" : 1,
"openTypeVheaCaretOffset" : 0,
"postscriptFontName" : "SomeFont-Regular (Postscript Font Name)",
"postscriptFullName" : "Some Font-Regular (Postscript Full Name)",
"postscriptSlantAngle" : -12.5,
"postscriptUniqueID" : 4000000,
"postscriptUnderlineThickness" : 20,
"postscriptUnderlinePosition" : -200,
"postscriptIsFixedPitch" : False,
"postscriptBlueValues" : [500, 510],
"postscriptOtherBlues" : [-250, -260],
"postscriptFamilyBlues" : [500, 510],
"postscriptFamilyOtherBlues" : [-250, -260],
"postscriptStemSnapH" : [100, 120],
"postscriptStemSnapV" : [80, 90],
"postscriptBlueFuzz" : 1,
"postscriptBlueShift" : 7,
"postscriptBlueScale" : 0.039625,
"postscriptForceBold" : True,
"postscriptDefaultWidthX" : 400,
"postscriptNominalWidthX" : 400,
"postscriptWeightName" : "Medium",
"postscriptDefaultCharacter" : ".notdef",
"postscriptWindowsCharacterSet" : 1,
"macintoshFONDFamilyID" : 15000,
"macintoshFONDName" : "SomeFont Regular (FOND Name)",
}
expectedFontInfo1To2Conversion = {
"familyName" : "Some Font (Family Name)",
"styleMapFamilyName" : "Some Font Regular (Style Map Family Name)",
"styleMapStyleName" : "regular",
"styleName" : "Regular (Style Name)",
"unitsPerEm" : 1000,
"ascender" : 750,
"capHeight" : 750,
"xHeight" : 500,
"descender" : -250,
"italicAngle" : -12.5,
"versionMajor" : 1,
"versionMinor" : 0,
"year" : 2008,
"copyright" : "Copyright Some Foundry.",
"trademark" : "Trademark Some Foundry",
"note" : "A note.",
"macintoshFONDFamilyID" : 15000,
"macintoshFONDName" : "SomeFont Regular (FOND Name)",
"openTypeNameCompatibleFullName" : "Some Font Regular (Compatible Full Name)",
"openTypeNameDescription" : "Some Font by Some Designer for Some Foundry.",
"openTypeNameDesigner" : "Some Designer",
"openTypeNameDesignerURL" : "http://somedesigner.com",
"openTypeNameLicense" : "License info for Some Foundry.",
"openTypeNameLicenseURL" : "http://somefoundry.com/license",
"openTypeNameManufacturer" : "Some Foundry",
"openTypeNameManufacturerURL" : "http://somefoundry.com",
"openTypeNamePreferredFamilyName" : "Some Font (Preferred Family Name)",
"openTypeNamePreferredSubfamilyName": "Regular (Preferred Subfamily Name)",
"openTypeNameCompatibleFullName" : "Some Font Regular (Compatible Full Name)",
"openTypeNameUniqueID" : "OpenType name Table Unique ID",
"openTypeNameVersion" : "OpenType name Table Version",
"openTypeOS2VendorID" : "SOME",
"openTypeOS2WeightClass" : 500,
"openTypeOS2WidthClass" : 5,
"postscriptDefaultWidthX" : 400,
"postscriptFontName" : "SomeFont-Regular (Postscript Font Name)",
"postscriptFullName" : "Some Font-Regular (Postscript Full Name)",
"postscriptSlantAngle" : -12.5,
"postscriptUniqueID" : 4000000,
"postscriptWeightName" : "Medium",
"postscriptWindowsCharacterSet" : 1
}
expectedFontInfo2To1Conversion = {
"familyName" : "Some Font (Family Name)",
"menuName" : "Some Font Regular (Style Map Family Name)",
"fontStyle" : 64,
"styleName" : "Regular (Style Name)",
"unitsPerEm" : 1000,
"ascender" : 750,
"capHeight" : 750,
"xHeight" : 500,
"descender" : -250,
"italicAngle" : -12.5,
"versionMajor" : 1,
"versionMinor" : 0,
"copyright" : "Copyright Some Foundry.",
"trademark" : "Trademark Some Foundry",
"note" : "A note.",
"fondID" : 15000,
"fondName" : "SomeFont Regular (FOND Name)",
"fullName" : "Some Font Regular (Compatible Full Name)",
"notice" : "Some Font by Some Designer for Some Foundry.",
"designer" : "Some Designer",
"designerURL" : "http://somedesigner.com",
"license" : "License info for Some Foundry.",
"licenseURL" : "http://somefoundry.com/license",
"createdBy" : "Some Foundry",
"vendorURL" : "http://somefoundry.com",
"otFamilyName" : "Some Font (Preferred Family Name)",
"otStyleName" : "Regular (Preferred Subfamily Name)",
"otMacName" : "Some Font Regular (Compatible Full Name)",
"ttUniqueID" : "OpenType name Table Unique ID",
"ttVersion" : "OpenType name Table Version",
"ttVendor" : "SOME",
"weightValue" : 500,
"widthName" : "Medium (normal)",
"defaultWidth" : 400,
"fontName" : "SomeFont-Regular (Postscript Font Name)",
"fullName" : "Some Font-Regular (Postscript Full Name)",
"slantAngle" : -12.5,
"uniqueID" : 4000000,
"weightName" : "Medium",
"msCharSet" : 0,
"year" : 2008
}

View File

@ -0,0 +1,111 @@
import unittest
from cStringIO import StringIO
import sys
from robofab import ufoLib
from robofab.objects.objectsFL import NewFont
from robofab.test.testSupport import fontInfoVersion1, fontInfoVersion2
class RInfoRFTestCase(unittest.TestCase):
def testRoundTripVersion2(self):
font = NewFont()
infoObject = font.info
for attr, value in fontInfoVersion2.items():
if attr in infoObject._ufoToFLAttrMapping and infoObject._ufoToFLAttrMapping[attr]["nakedAttribute"] is None:
continue
setattr(infoObject, attr, value)
newValue = getattr(infoObject, attr)
self.assertEqual((attr, newValue), (attr, value))
font.close()
def testVersion2UnsupportedSet(self):
saveStderr = sys.stderr
saveStdout = sys.stdout
tempStderr = StringIO()
sys.stderr = tempStderr
sys.stdout = tempStderr
font = NewFont()
infoObject = font.info
requiredWarnings = []
try:
for attr, value in fontInfoVersion2.items():
if attr in infoObject._ufoToFLAttrMapping and infoObject._ufoToFLAttrMapping[attr]["nakedAttribute"] is not None:
continue
setattr(infoObject, attr, value)
s = "The attribute %s is not supported by FontLab." % attr
requiredWarnings.append((attr, s))
finally:
sys.stderr = saveStderr
sys.stdout = saveStdout
tempStderr = tempStderr.getvalue()
for attr, line in requiredWarnings:
self.assertEquals((attr, line in tempStderr), (attr, True))
font.close()
def testVersion2UnsupportedGet(self):
saveStderr = sys.stderr
saveStdout = sys.stdout
tempStderr = StringIO()
sys.stderr = tempStderr
sys.stdout = tempStderr
font = NewFont()
infoObject = font.info
requiredWarnings = []
try:
for attr, value in fontInfoVersion2.items():
if attr in infoObject._ufoToFLAttrMapping and infoObject._ufoToFLAttrMapping[attr]["nakedAttribute"] is not None:
continue
getattr(infoObject, attr, value)
s = "The attribute %s is not supported by FontLab." % attr
requiredWarnings.append((attr, s))
finally:
sys.stderr = saveStderr
sys.stdout = saveStdout
tempStderr = tempStderr.getvalue()
for attr, line in requiredWarnings:
self.assertEquals((attr, line in tempStderr), (attr, True))
font.close()
def testRoundTripVersion1(self):
font = NewFont()
infoObject = font.info
for attr, value in fontInfoVersion1.items():
if attr not in ufoLib.deprecatedFontInfoAttributesVersion2:
setattr(infoObject, attr, value)
for attr, expectedValue in fontInfoVersion1.items():
if attr not in ufoLib.deprecatedFontInfoAttributesVersion2:
value = getattr(infoObject, attr)
self.assertEqual((attr, expectedValue), (attr, value))
font.close()
def testVersion1DeprecationRoundTrip(self):
saveStderr = sys.stderr
saveStdout = sys.stdout
tempStderr = StringIO()
sys.stderr = tempStderr
sys.stdout = tempStderr
font = NewFont()
infoObject = font.info
requiredWarnings = []
try:
for attr, value in fontInfoVersion1.items():
if attr in ufoLib.deprecatedFontInfoAttributesVersion2:
setattr(infoObject, attr, value)
v = getattr(infoObject, attr)
self.assertEquals((attr, value), (attr, v))
s = "DeprecationWarning: The %s attribute has been deprecated." % attr
requiredWarnings.append((attr, s))
finally:
sys.stderr = saveStderr
sys.stdout = saveStdout
tempStderr = tempStderr.getvalue()
for attr, line in requiredWarnings:
self.assertEquals((attr, line in tempStderr), (attr, True))
font.close()
if __name__ == "__main__":
from robofab.test.testSupport import runTests
runTests()

View File

@ -0,0 +1,56 @@
import unittest
from cStringIO import StringIO
import sys
from robofab import ufoLib
from robofab.objects.objectsRF import RInfo
from robofab.test.testSupport import fontInfoVersion1, fontInfoVersion2
class RInfoRFTestCase(unittest.TestCase):
def testRoundTripVersion2(self):
infoObject = RInfo()
for attr, value in fontInfoVersion2.items():
setattr(infoObject, attr, value)
newValue = getattr(infoObject, attr)
self.assertEqual((attr, newValue), (attr, value))
def testRoundTripVersion1(self):
infoObject = RInfo()
for attr, value in fontInfoVersion1.items():
if attr not in ufoLib.deprecatedFontInfoAttributesVersion2:
setattr(infoObject, attr, value)
for attr, expectedValue in fontInfoVersion1.items():
if attr not in ufoLib.deprecatedFontInfoAttributesVersion2:
value = getattr(infoObject, attr)
self.assertEqual((attr, expectedValue), (attr, value))
def testVersion1DeprecationRoundTrip(self):
"""
unittest doesn't catch warnings in self.assertRaises,
so some hackery is required to catch the warnings
that are raised when setting deprecated attributes.
"""
saveStderr = sys.stderr
tempStderr = StringIO()
sys.stderr = tempStderr
infoObject = RInfo()
requiredWarnings = []
try:
for attr, value in fontInfoVersion1.items():
if attr in ufoLib.deprecatedFontInfoAttributesVersion2:
setattr(infoObject, attr, value)
v = getattr(infoObject, attr)
self.assertEquals((attr, value), (attr, v))
s = "DeprecationWarning: The %s attribute has been deprecated." % attr
requiredWarnings.append((attr, s))
finally:
sys.stderr = saveStderr
tempStderr = tempStderr.getvalue()
for attr, line in requiredWarnings:
self.assertEquals((attr, line in tempStderr), (attr, True))
if __name__ == "__main__":
from robofab.test.testSupport import runTests
runTests()

View File

@ -0,0 +1,565 @@
import os
import shutil
import unittest
import tempfile
from robofab.plistlib import readPlist
import robofab
from robofab.ufoLib import UFOReader, UFOWriter
from robofab.test.testSupport import fontInfoVersion2, expectedFontInfo1To2Conversion
from robofab.objects.objectsFL import NewFont, OpenFont
vfbPath = os.path.dirname(robofab.__file__)
vfbPath = os.path.dirname(vfbPath)
vfbPath = os.path.dirname(vfbPath)
vfbPath = os.path.join(vfbPath, "TestData", "TestFont1.vfb")
ufoPath1 = os.path.dirname(robofab.__file__)
ufoPath1 = os.path.dirname(ufoPath1)
ufoPath1 = os.path.dirname(ufoPath1)
ufoPath1 = os.path.join(ufoPath1, "TestData", "TestFont1 (UFO1).ufo")
ufoPath2 = ufoPath1.replace("TestFont1 (UFO1).ufo", "TestFont1 (UFO2).ufo")
expectedFormatVersion1Features = """@myClass = [A B];
feature liga {
sub A A by b;
} liga;
"""
# robofab should remove these from the lib after a load.
removeFromFormatVersion1Lib = [
"org.robofab.opentype.classes",
"org.robofab.opentype.features",
"org.robofab.opentype.featureorder",
"org.robofab.postScriptHintData"
]
class ReadUFOFormatVersion1TestCase(unittest.TestCase):
def setUpFont(self, doInfo=False, doKerning=False, doGroups=False, doLib=False, doFeatures=False):
self.font = NewFont()
self.ufoPath = ufoPath1
self.font.readUFO(ufoPath1, doInfo=doInfo, doKerning=doKerning, doGroups=doGroups, doLib=doLib, doFeatures=doFeatures)
self.font.update()
def tearDownFont(self):
self.font.close()
self.font = None
def compareToUFO(self, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True):
reader = UFOReader(self.ufoPath)
results = {}
if doInfo:
infoMatches = True
info = self.font.info
for attr, expectedValue in expectedFontInfo1To2Conversion.items():
writtenValue = getattr(info, attr)
if expectedValue != writtenValue:
infoMatches = False
break
results["info"]= infoMatches
if doKerning:
kerning = self.font.kerning.asDict()
expectedKerning = reader.readKerning()
results["kerning"] = expectedKerning == kerning
if doGroups:
groups = dict(self.font.groups)
expectedGroups = reader.readGroups()
results["groups"] = expectedGroups == groups
if doFeatures:
features = self.font.features.text
expectedFeatures = expectedFormatVersion1Features
# FontLab likes to add lines to the features, so skip blank lines.
features = [line for line in features.splitlines() if line]
expectedFeatures = [line for line in expectedFeatures.splitlines() if line]
results["features"] = expectedFeatures == features
if doLib:
lib = dict(self.font.lib)
expectedLib = reader.readLib()
for key in removeFromFormatVersion1Lib:
if key in expectedLib:
del expectedLib[key]
results["lib"] = expectedLib == lib
return results
def testFull(self):
self.setUpFont(doInfo=True, doKerning=True, doGroups=True, doFeatures=True, doLib=True)
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], True)
self.assertEqual(otherResults["kerning"], True)
self.assertEqual(otherResults["groups"], True)
self.assertEqual(otherResults["features"], True)
self.assertEqual(otherResults["lib"], True)
self.tearDownFont()
def testInfo(self):
self.setUpFont(doInfo=True)
otherResults = self.compareToUFO(doInfo=False)
self.assertEqual(otherResults["kerning"], False)
self.assertEqual(otherResults["groups"], False)
self.assertEqual(otherResults["features"], False)
self.assertEqual(otherResults["lib"], False)
info = self.font.info
for attr, expectedValue in expectedFontInfo1To2Conversion.items():
writtenValue = getattr(info, attr)
self.assertEqual((attr, expectedValue), (attr, writtenValue))
self.tearDownFont()
def testFeatures(self):
self.setUpFont(doFeatures=True)
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], False)
self.assertEqual(otherResults["kerning"], False)
self.assertEqual(otherResults["groups"], False)
self.assertEqual(otherResults["features"], True)
self.assertEqual(otherResults["lib"], False)
self.tearDownFont()
def testKerning(self):
self.setUpFont(doKerning=True)
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], False)
self.assertEqual(otherResults["kerning"], True)
self.assertEqual(otherResults["groups"], False)
self.assertEqual(otherResults["features"], False)
self.assertEqual(otherResults["lib"], False)
self.tearDownFont()
def testGroups(self):
self.setUpFont(doGroups=True)
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], False)
self.assertEqual(otherResults["kerning"], False)
self.assertEqual(otherResults["groups"], True)
self.assertEqual(otherResults["features"], False)
self.assertEqual(otherResults["lib"], False)
self.tearDownFont()
def testLib(self):
self.setUpFont(doLib=True)
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], False)
self.assertEqual(otherResults["kerning"], False)
self.assertEqual(otherResults["groups"], False)
self.assertEqual(otherResults["features"], False)
self.assertEqual(otherResults["lib"], True)
self.tearDownFont()
class ReadUFOFormatVersion2TestCase(unittest.TestCase):
def setUpFont(self, doInfo=False, doKerning=False, doGroups=False, doLib=False, doFeatures=False):
self.font = NewFont()
self.ufoPath = ufoPath2
self.font.readUFO(ufoPath2, doInfo=doInfo, doKerning=doKerning, doGroups=doGroups, doLib=doLib, doFeatures=doFeatures)
self.font.update()
def tearDownFont(self):
self.font.close()
self.font = None
def compareToUFO(self, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True):
reader = UFOReader(self.ufoPath)
results = {}
if doInfo:
infoMatches = True
info = self.font.info
for attr, expectedValue in fontInfoVersion2.items():
# cheat by skipping attrs that aren't supported
if info._ufoToFLAttrMapping[attr]["nakedAttribute"] is None:
continue
writtenValue = getattr(info, attr)
if expectedValue != writtenValue:
infoMatches = False
break
results["info"]= infoMatches
if doKerning:
kerning = self.font.kerning.asDict()
expectedKerning = reader.readKerning()
results["kerning"] = expectedKerning == kerning
if doGroups:
groups = dict(self.font.groups)
expectedGroups = reader.readGroups()
results["groups"] = expectedGroups == groups
if doFeatures:
features = self.font.features.text
expectedFeatures = reader.readFeatures()
results["features"] = expectedFeatures == features
if doLib:
lib = dict(self.font.lib)
expectedLib = reader.readLib()
results["lib"] = expectedLib == lib
return results
def testFull(self):
self.setUpFont(doInfo=True, doKerning=True, doGroups=True, doFeatures=True, doLib=True)
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], True)
self.assertEqual(otherResults["kerning"], True)
self.assertEqual(otherResults["groups"], True)
self.assertEqual(otherResults["features"], True)
self.assertEqual(otherResults["lib"], True)
self.tearDownFont()
def testInfo(self):
self.setUpFont(doInfo=True)
otherResults = self.compareToUFO(doInfo=False)
self.assertEqual(otherResults["kerning"], False)
self.assertEqual(otherResults["groups"], False)
self.assertEqual(otherResults["features"], False)
self.assertEqual(otherResults["lib"], False)
info = self.font.info
for attr, expectedValue in fontInfoVersion2.items():
# cheat by skipping attrs that aren't supported
if info._ufoToFLAttrMapping[attr]["nakedAttribute"] is None:
continue
writtenValue = getattr(info, attr)
self.assertEqual((attr, expectedValue), (attr, writtenValue))
self.tearDownFont()
def testFeatures(self):
self.setUpFont(doFeatures=True)
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], False)
self.assertEqual(otherResults["kerning"], False)
self.assertEqual(otherResults["groups"], False)
self.assertEqual(otherResults["features"], True)
self.assertEqual(otherResults["lib"], False)
self.tearDownFont()
def testKerning(self):
self.setUpFont(doKerning=True)
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], False)
self.assertEqual(otherResults["kerning"], True)
self.assertEqual(otherResults["groups"], False)
self.assertEqual(otherResults["features"], False)
self.assertEqual(otherResults["lib"], False)
self.tearDownFont()
def testGroups(self):
self.setUpFont(doGroups=True)
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], False)
self.assertEqual(otherResults["kerning"], False)
self.assertEqual(otherResults["groups"], True)
self.assertEqual(otherResults["features"], False)
self.assertEqual(otherResults["lib"], False)
self.tearDownFont()
def testLib(self):
self.setUpFont(doLib=True)
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], False)
self.assertEqual(otherResults["kerning"], False)
self.assertEqual(otherResults["groups"], False)
self.assertEqual(otherResults["features"], False)
self.assertEqual(otherResults["lib"], True)
self.tearDownFont()
class WriteUFOFormatVersion1TestCase(unittest.TestCase):
def setUpFont(self, doInfo=False, doKerning=False, doGroups=False):
self.dstDir = tempfile.mktemp()
os.mkdir(self.dstDir)
self.font = OpenFont(vfbPath)
self.font.writeUFO(self.dstDir, doInfo=doInfo, doKerning=doKerning, doGroups=doGroups, formatVersion=1)
self.font.close()
def tearDownFont(self):
shutil.rmtree(self.dstDir)
def compareToUFO(self, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True):
readerExpected = UFOReader(ufoPath1)
readerWritten = UFOReader(self.dstDir)
results = {}
if doInfo:
matches = True
expectedPath = os.path.join(ufoPath1, "fontinfo.plist")
writtenPath = os.path.join(self.dstDir, "fontinfo.plist")
if not os.path.exists(writtenPath):
matches = False
else:
expected = readPlist(expectedPath)
written = readPlist(writtenPath)
for attr, expectedValue in expected.items():
if expectedValue != written[attr]:
matches = False
break
results["info"] = matches
if doKerning:
matches = True
expectedPath = os.path.join(ufoPath1, "kerning.plist")
writtenPath = os.path.join(self.dstDir, "kerning.plist")
if not os.path.exists(writtenPath):
matches = False
else:
matches = readPlist(expectedPath) == readPlist(writtenPath)
results["kerning"] = matches
if doGroups:
matches = True
expectedPath = os.path.join(ufoPath1, "groups.plist")
writtenPath = os.path.join(self.dstDir, "groups.plist")
if not os.path.exists(writtenPath):
matches = False
else:
matches = readPlist(expectedPath) == readPlist(writtenPath)
results["groups"] = matches
if doFeatures:
matches = True
featuresPath = os.path.join(self.dstDir, "features.fea")
libPath = os.path.join(self.dstDir, "lib.plist")
if os.path.exists(featuresPath):
matches = False
else:
fontLib = readPlist(libPath)
writtenText = [fontLib.get("org.robofab.opentype.classes", "")]
features = fontLib.get("org.robofab.opentype.features", {})
featureOrder= fontLib.get("org.robofab.opentype.featureorder", [])
for featureName in featureOrder:
writtenText.append(features.get(featureName, ""))
writtenText = "\n".join(writtenText)
# FontLab likes to add lines to the features, so skip blank lines.
expectedText = [line for line in expectedFormatVersion1Features.splitlines() if line]
writtenText = [line for line in writtenText.splitlines() if line]
matches = "\n".join(expectedText) == "\n".join(writtenText)
results["features"] = matches
if doLib:
matches = True
expectedPath = os.path.join(ufoPath1, "lib.plist")
writtenPath = os.path.join(self.dstDir, "lib.plist")
if not os.path.exists(writtenPath):
matches = False
else:
# the test file doesn't have the glyph order
# so purge it from the written
writtenLib = readPlist(writtenPath)
del writtenLib["org.robofab.glyphOrder"]
matches = readPlist(expectedPath) == writtenLib
results["lib"] = matches
return results
def testFull(self):
self.setUpFont(doInfo=True, doKerning=True, doGroups=True)
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], True)
self.assertEqual(otherResults["kerning"], True)
self.assertEqual(otherResults["groups"], True)
self.assertEqual(otherResults["features"], True)
self.assertEqual(otherResults["lib"], True)
self.tearDownFont()
def testInfo(self):
self.setUpFont(doInfo=True)
otherResults = self.compareToUFO(doInfo=False)
self.assertEqual(otherResults["kerning"], False)
self.assertEqual(otherResults["groups"], False)
expectedPath = os.path.join(ufoPath1, "fontinfo.plist")
writtenPath = os.path.join(self.dstDir, "fontinfo.plist")
expected = readPlist(expectedPath)
written = readPlist(writtenPath)
for attr, expectedValue in expected.items():
self.assertEqual((attr, expectedValue), (attr, written[attr]))
self.tearDownFont()
def testFeatures(self):
self.setUpFont()
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], False)
self.assertEqual(otherResults["kerning"], False)
self.assertEqual(otherResults["groups"], False)
self.assertEqual(otherResults["features"], True)
self.tearDownFont()
def testKerning(self):
self.setUpFont(doKerning=True)
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], False)
self.assertEqual(otherResults["kerning"], True)
self.assertEqual(otherResults["groups"], False)
self.tearDownFont()
def testGroups(self):
self.setUpFont(doGroups=True)
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], False)
self.assertEqual(otherResults["kerning"], False)
self.assertEqual(otherResults["groups"], True)
self.tearDownFont()
def testLib(self):
self.setUpFont()
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], False)
self.assertEqual(otherResults["kerning"], False)
self.assertEqual(otherResults["groups"], False)
self.assertEqual(otherResults["lib"], True)
self.tearDownFont()
class WriteUFOFormatVersion2TestCase(unittest.TestCase):
def setUpFont(self, doInfo=False, doKerning=False, doGroups=False, doLib=False, doFeatures=False):
self.dstDir = tempfile.mktemp()
os.mkdir(self.dstDir)
self.font = OpenFont(vfbPath)
self.font.writeUFO(self.dstDir, doInfo=doInfo, doKerning=doKerning, doGroups=doGroups, doLib=doLib, doFeatures=doFeatures)
self.font.close()
def tearDownFont(self):
shutil.rmtree(self.dstDir)
def compareToUFO(self, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True):
readerExpected = UFOReader(ufoPath2)
readerWritten = UFOReader(self.dstDir)
results = {}
if doInfo:
matches = True
expectedPath = os.path.join(ufoPath2, "fontinfo.plist")
writtenPath = os.path.join(self.dstDir, "fontinfo.plist")
if not os.path.exists(writtenPath):
matches = False
else:
dummyFont = NewFont()
_ufoToFLAttrMapping = dict(dummyFont.info._ufoToFLAttrMapping)
dummyFont.close()
expected = readPlist(expectedPath)
written = readPlist(writtenPath)
for attr, expectedValue in expected.items():
# cheat by skipping attrs that aren't supported
if _ufoToFLAttrMapping[attr]["nakedAttribute"] is None:
continue
if expectedValue != written[attr]:
matches = False
break
results["info"] = matches
if doKerning:
matches = True
expectedPath = os.path.join(ufoPath2, "kerning.plist")
writtenPath = os.path.join(self.dstDir, "kerning.plist")
if not os.path.exists(writtenPath):
matches = False
else:
matches = readPlist(expectedPath) == readPlist(writtenPath)
results["kerning"] = matches
if doGroups:
matches = True
expectedPath = os.path.join(ufoPath2, "groups.plist")
writtenPath = os.path.join(self.dstDir, "groups.plist")
if not os.path.exists(writtenPath):
matches = False
else:
matches = readPlist(expectedPath) == readPlist(writtenPath)
results["groups"] = matches
if doFeatures:
matches = True
expectedPath = os.path.join(ufoPath2, "features.fea")
writtenPath = os.path.join(self.dstDir, "features.fea")
if not os.path.exists(writtenPath):
matches = False
else:
f = open(expectedPath, "r")
expectedText = f.read()
f.close()
f = open(writtenPath, "r")
writtenText = f.read()
f.close()
# FontLab likes to add lines to the features, so skip blank lines.
expectedText = [line for line in expectedText.splitlines() if line]
writtenText = [line for line in writtenText.splitlines() if line]
matches = "\n".join(expectedText) == "\n".join(writtenText)
results["features"] = matches
if doLib:
matches = True
expectedPath = os.path.join(ufoPath2, "lib.plist")
writtenPath = os.path.join(self.dstDir, "lib.plist")
if not os.path.exists(writtenPath):
matches = False
else:
# the test file doesn't have the glyph order
# so purge it from the written
writtenLib = readPlist(writtenPath)
del writtenLib["org.robofab.glyphOrder"]
matches = readPlist(expectedPath) == writtenLib
results["lib"] = matches
return results
def testFull(self):
self.setUpFont(doInfo=True, doKerning=True, doGroups=True, doFeatures=True, doLib=True)
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], True)
self.assertEqual(otherResults["kerning"], True)
self.assertEqual(otherResults["groups"], True)
self.assertEqual(otherResults["features"], True)
self.assertEqual(otherResults["lib"], True)
self.tearDownFont()
def testInfo(self):
self.setUpFont(doInfo=True)
otherResults = self.compareToUFO(doInfo=False)
self.assertEqual(otherResults["kerning"], False)
self.assertEqual(otherResults["groups"], False)
self.assertEqual(otherResults["features"], False)
self.assertEqual(otherResults["lib"], False)
expectedPath = os.path.join(ufoPath2, "fontinfo.plist")
writtenPath = os.path.join(self.dstDir, "fontinfo.plist")
expected = readPlist(expectedPath)
written = readPlist(writtenPath)
dummyFont = NewFont()
_ufoToFLAttrMapping = dict(dummyFont.info._ufoToFLAttrMapping)
dummyFont.close()
for attr, expectedValue in expected.items():
# cheat by skipping attrs that aren't supported
if _ufoToFLAttrMapping[attr]["nakedAttribute"] is None:
continue
self.assertEqual((attr, expectedValue), (attr, written[attr]))
self.tearDownFont()
def testFeatures(self):
self.setUpFont(doFeatures=True)
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], False)
self.assertEqual(otherResults["kerning"], False)
self.assertEqual(otherResults["groups"], False)
self.assertEqual(otherResults["features"], True)
self.assertEqual(otherResults["lib"], False)
self.tearDownFont()
def testKerning(self):
self.setUpFont(doKerning=True)
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], False)
self.assertEqual(otherResults["kerning"], True)
self.assertEqual(otherResults["groups"], False)
self.assertEqual(otherResults["features"], False)
self.assertEqual(otherResults["lib"], False)
self.tearDownFont()
def testGroups(self):
self.setUpFont(doGroups=True)
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], False)
self.assertEqual(otherResults["kerning"], False)
self.assertEqual(otherResults["groups"], True)
self.assertEqual(otherResults["features"], False)
self.assertEqual(otherResults["lib"], False)
self.tearDownFont()
def testLib(self):
self.setUpFont(doLib=True)
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], False)
self.assertEqual(otherResults["kerning"], False)
self.assertEqual(otherResults["groups"], False)
self.assertEqual(otherResults["features"], False)
self.assertEqual(otherResults["lib"], True)
self.tearDownFont()
if __name__ == "__main__":
from robofab.test.testSupport import runTests
runTests()

View File

@ -0,0 +1,321 @@
import os
import shutil
import unittest
import tempfile
from robofab.plistlib import readPlist
import robofab
from robofab.test.testSupport import fontInfoVersion2, expectedFontInfo1To2Conversion, expectedFontInfo2To1Conversion
from robofab.objects.objectsRF import NewFont, OpenFont
from robofab.ufoLib import UFOReader
ufoPath1 = os.path.dirname(robofab.__file__)
ufoPath1 = os.path.dirname(ufoPath1)
ufoPath1 = os.path.dirname(ufoPath1)
ufoPath1 = os.path.join(ufoPath1, "TestData", "TestFont1 (UFO1).ufo")
ufoPath2 = ufoPath1.replace("TestFont1 (UFO1).ufo", "TestFont1 (UFO2).ufo")
# robofab should remove these from the lib after a load.
removeFromFormatVersion1Lib = [
"org.robofab.opentype.classes",
"org.robofab.opentype.features",
"org.robofab.opentype.featureorder",
"org.robofab.postScriptHintData"
]
class ReadUFOFormatVersion1TestCase(unittest.TestCase):
def setUpFont(self):
self.font = OpenFont(ufoPath1)
self.font.update()
def tearDownFont(self):
self.font.close()
self.font = None
def compareToUFO(self, doInfo=True):
reader = UFOReader(ufoPath1)
results = {}
# info
infoMatches = True
info = self.font.info
for attr, expectedValue in expectedFontInfo1To2Conversion.items():
writtenValue = getattr(info, attr)
if expectedValue != writtenValue:
infoMatches = False
break
results["info"]= infoMatches
# kerning
kerning = self.font.kerning.asDict()
expectedKerning = reader.readKerning()
results["kerning"] = expectedKerning == kerning
# groups
groups = dict(self.font.groups)
expectedGroups = reader.readGroups()
results["groups"] = expectedGroups == groups
# features
features = self.font.features.text
f = open(os.path.join(ufoPath2, "features.fea"), "r")
expectedFeatures = f.read()
f.close()
match = True
features = [line for line in features.splitlines() if line]
expectedFeatures = [line for line in expectedFeatures.splitlines() if line]
if expectedFeatures != features or reader.readFeatures() != "":
match = False
results["features"] = match
# lib
lib = dict(self.font.lib)
expectedLib = reader.readLib()
for key in removeFromFormatVersion1Lib:
if key in expectedLib:
del expectedLib[key]
results["lib"] = expectedLib == lib
return results
def testFull(self):
self.setUpFont()
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], True)
self.assertEqual(otherResults["kerning"], True)
self.assertEqual(otherResults["groups"], True)
self.assertEqual(otherResults["features"], True)
self.assertEqual(otherResults["lib"], True)
self.tearDownFont()
def testInfo(self):
self.setUpFont()
info = self.font.info
for attr, expectedValue in expectedFontInfo1To2Conversion.items():
writtenValue = getattr(info, attr)
self.assertEqual((attr, expectedValue), (attr, writtenValue))
self.tearDownFont()
class ReadUFOFormatVersion2TestCase(unittest.TestCase):
def setUpFont(self):
self.font = OpenFont(ufoPath2)
self.font.update()
def tearDownFont(self):
self.font.close()
self.font = None
def compareToUFO(self, doInfo=True):
reader = UFOReader(ufoPath2)
results = {}
# info
infoMatches = True
info = self.font.info
for attr, expectedValue in fontInfoVersion2.items():
writtenValue = getattr(info, attr)
if expectedValue != writtenValue:
infoMatches = False
break
results["info"]= infoMatches
# kerning
kerning = self.font.kerning.asDict()
expectedKerning = reader.readKerning()
results["kerning"] = expectedKerning == kerning
# groups
groups = dict(self.font.groups)
expectedGroups = reader.readGroups()
results["groups"] = expectedGroups == groups
# features
features = self.font.features.text
expectedFeatures = reader.readFeatures()
results["features"] = expectedFeatures == features
# lib
lib = dict(self.font.lib)
expectedLib = reader.readLib()
results["lib"] = expectedLib == lib
return results
def testFull(self):
self.setUpFont()
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], True)
self.assertEqual(otherResults["kerning"], True)
self.assertEqual(otherResults["groups"], True)
self.assertEqual(otherResults["features"], True)
self.assertEqual(otherResults["lib"], True)
self.tearDownFont()
def testInfo(self):
self.setUpFont()
info = self.font.info
for attr, expectedValue in fontInfoVersion2.items():
writtenValue = getattr(info, attr)
self.assertEqual((attr, expectedValue), (attr, writtenValue))
self.tearDownFont()
class WriteUFOFormatVersion1TestCase(unittest.TestCase):
def setUpFont(self):
self.dstDir = tempfile.mktemp()
os.mkdir(self.dstDir)
self.font = OpenFont(ufoPath2)
self.font.save(self.dstDir, formatVersion=1)
def tearDownFont(self):
shutil.rmtree(self.dstDir)
def compareToUFO(self):
readerExpected = UFOReader(ufoPath1)
readerWritten = UFOReader(self.dstDir)
results = {}
# info
matches = True
expectedPath = os.path.join(ufoPath1, "fontinfo.plist")
writtenPath = os.path.join(self.dstDir, "fontinfo.plist")
if not os.path.exists(writtenPath):
matches = False
else:
expected = readPlist(expectedPath)
written = readPlist(writtenPath)
for attr, expectedValue in expected.items():
if expectedValue != written.get(attr):
matches = False
break
results["info"] = matches
# kerning
matches = True
expectedPath = os.path.join(ufoPath1, "kerning.plist")
writtenPath = os.path.join(self.dstDir, "kerning.plist")
if not os.path.exists(writtenPath):
matches = False
else:
matches = readPlist(expectedPath) == readPlist(writtenPath)
results["kerning"] = matches
# groups
matches = True
expectedPath = os.path.join(ufoPath1, "groups.plist")
writtenPath = os.path.join(self.dstDir, "groups.plist")
if not os.path.exists(writtenPath):
matches = False
else:
matches = readPlist(expectedPath) == readPlist(writtenPath)
results["groups"] = matches
# features
matches = True
expectedPath = os.path.join(ufoPath1, "features.fea")
writtenPath = os.path.join(self.dstDir, "features.fea")
if os.path.exists(writtenPath):
matches = False
results["features"] = matches
# lib
matches = True
expectedPath = os.path.join(ufoPath1, "lib.plist")
writtenPath = os.path.join(self.dstDir, "lib.plist")
if not os.path.exists(writtenPath):
matches = False
else:
writtenLib = readPlist(writtenPath)
matches = readPlist(expectedPath) == writtenLib
results["lib"] = matches
return results
def testFull(self):
self.setUpFont()
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], True)
self.assertEqual(otherResults["kerning"], True)
self.assertEqual(otherResults["groups"], True)
self.assertEqual(otherResults["features"], True)
self.assertEqual(otherResults["lib"], True)
self.tearDownFont()
class WriteUFOFormatVersion2TestCase(unittest.TestCase):
def setUpFont(self):
self.dstDir = tempfile.mktemp()
os.mkdir(self.dstDir)
self.font = OpenFont(ufoPath2)
self.font.save(self.dstDir)
def tearDownFont(self):
shutil.rmtree(self.dstDir)
def compareToUFO(self):
readerExpected = UFOReader(ufoPath2)
readerWritten = UFOReader(self.dstDir)
results = {}
# info
matches = True
expectedPath = os.path.join(ufoPath2, "fontinfo.plist")
writtenPath = os.path.join(self.dstDir, "fontinfo.plist")
if not os.path.exists(writtenPath):
matches = False
else:
expected = readPlist(expectedPath)
written = readPlist(writtenPath)
for attr, expectedValue in expected.items():
if expectedValue != written[attr]:
matches = False
break
results["info"] = matches
# kerning
matches = True
expectedPath = os.path.join(ufoPath2, "kerning.plist")
writtenPath = os.path.join(self.dstDir, "kerning.plist")
if not os.path.exists(writtenPath):
matches = False
else:
matches = readPlist(expectedPath) == readPlist(writtenPath)
results["kerning"] = matches
# groups
matches = True
expectedPath = os.path.join(ufoPath2, "groups.plist")
writtenPath = os.path.join(self.dstDir, "groups.plist")
if not os.path.exists(writtenPath):
matches = False
else:
matches = readPlist(expectedPath) == readPlist(writtenPath)
results["groups"] = matches
# features
matches = True
expectedPath = os.path.join(ufoPath2, "features.fea")
writtenPath = os.path.join(self.dstDir, "features.fea")
if not os.path.exists(writtenPath):
matches = False
else:
f = open(expectedPath, "r")
expectedText = f.read()
f.close()
f = open(writtenPath, "r")
writtenText = f.read()
f.close()
# FontLab likes to add lines to the features, so skip blank lines.
expectedText = [line for line in expectedText.splitlines() if line]
writtenText = [line for line in writtenText.splitlines() if line]
matches = "\n".join(expectedText) == "\n".join(writtenText)
results["features"] = matches
# lib
matches = True
expectedPath = os.path.join(ufoPath2, "lib.plist")
writtenPath = os.path.join(self.dstDir, "lib.plist")
if not os.path.exists(writtenPath):
matches = False
else:
writtenLib = readPlist(writtenPath)
matches = readPlist(expectedPath) == writtenLib
results["lib"] = matches
return results
def testFull(self):
self.setUpFont()
otherResults = self.compareToUFO()
self.assertEqual(otherResults["info"], True)
self.assertEqual(otherResults["kerning"], True)
self.assertEqual(otherResults["groups"], True)
self.assertEqual(otherResults["features"], True)
self.assertEqual(otherResults["lib"], True)
self.tearDownFont()
if __name__ == "__main__":
from robofab.test.testSupport import runTests
runTests()

View File

@ -12,7 +12,7 @@ from robofab.world import NewFont
from robofab.test.testSupport import getDemoFontPath, getDemoFontGlyphSetPath
from robofab.tools.glifImport import importAllGlifFiles
from robofab.pens.digestPen import DigestPointPen
from robofab.pens.adapterPens import SegmentToPointPen, FabToFontToolsPenAdapter
from robofab.pens.adapterPens import SegmentToPointPen
def getDigests(font):

View File

@ -102,34 +102,6 @@ def test():
<RGlyph for None.new>
>>> f["new"].psHints.asDict() == g.psHints.asDict()
True
# multiplication
>>> v = f.psHints * 2
>>> v.asDict() == {'vStems': [1000, 20], 'blueFuzz': 2, 'blueShift': 2, 'forceBold': 2, 'blueScale': 1.0, 'hStems': [200, 180]}
True
# division
>>> v = f.psHints / 2
>>> v.asDict() == {'vStems': [250.0, 5.0], 'blueFuzz': 0.5, 'blueShift': 0.5, 'forceBold': 0.5, 'blueScale': 0.25, 'hStems': [50.0, 45.0]}
True
# multiplication with x, y, factor
# note the h stems are multiplied by .5, the v stems (and blue values) are multiplied by 10
>>> v = f.psHints * (.5, 10)
>>> v.asDict() == {'vStems': [5000, 100], 'blueFuzz': 10, 'blueShift': 10, 'forceBold': 0.5, 'blueScale': 5.0, 'hStems': [50.0, 45.0]}
True
# multiplication with x, y, factor
# note the h stems are divided by .5, the v stems (and blue values) are divided by 10
>>> v = f.psHints / (.5, 10)
>>> v.asDict() == {'vStems': [50.0, 1.0], 'blueFuzz': 0.10000000000000001, 'blueShift': 0.10000000000000001, 'forceBold': 2.0, 'blueScale': 0.050000000000000003, 'hStems': [200.0, 180.0]}
True
>>> v = f.psHints * .333
>>> v.round()
>>> v.asDict() == {'vStems': [167, 3], 'blueScale': 0.16650000000000001, 'hStems': [33, 30]}
True
"""
if __name__ == "__main__":

File diff suppressed because it is too large Load Diff

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

@ -1,709 +0,0 @@
from warnings import warn
warn("FontLab contains a bug that renders nameTable.py inoperable", Warning)
"""
XXX: FontLab 4.6 contains a bug that renders this module inoperable.
A simple wrapper around the not so simple OpenType
name table API in FontLab.
For more information about the name table see:
http://www.microsoft.com/typography/otspec/name.htm
The PID, EID, LID and NID arguments in the various
methods can be integer values or string values (as
long as the string value matches the key in the lookup
dicts shown below). All values must be strings and all
platform line ending conversion is handled automatically
EXCEPT in the setSpecificRecord method. If you need to do
line ending conversion, the convertLineEndings method
is publicly available.
"""
from robofab import RoboFabError
##
## internal pid constants
##
UNI = 'unicode'
UNI_INT = 0
MAC = 'macintosh'
MAC_INT = 1
MS = 'microsoft'
MS_INT = 3
##
## lookup dicts
##
def _flipDict(aDict):
bDict = {}
for k, v in aDict.items():
bDict[v] = k
return bDict
pidName2Int = {
UNI : UNI_INT,
MAC : MAC_INT,
MS : MS_INT,
}
nidName2Int = {
'copyright' : 0,
'familyName' : 1,
'subfamilyName' : 2,
'uniqueID' : 3,
'fullName' : 4,
'versionString' : 5,
'postscriptName' : 6,
'trademark' : 7,
'manufacturer' : 8,
'designer' : 9,
'description' : 10,
'vendorURL' : 11,
'designerURL' : 12,
'license' : 13,
'licenseURL' : 14,
# ID 15 is reserved
'preferredFamily' : 16,
'preferredSubfamily' : 17,
'compatibleFull' : 18,
'sampleText' : 19,
'postscriptCID' : 20
}
nidInt2Name = _flipDict(nidName2Int)
uniEIDName2Int = {
"unicode_1.0" : 0,
"unicode_1.1" : 1,
"iso_10646:1993" : 2,
"unicode_2.0_bmp" : 3,
"unicode_2.0_full" : 4,
}
uniEIDInt2Name = _flipDict(uniEIDName2Int)
uniLIDName2Int = {}
uniLIDInt2Name = _flipDict(uniLIDName2Int)
msEIDName2Int = {
'symbol' : 0,
'unicode_bmp_only' : 1,
'shift_jis' : 2,
'prc' : 3,
'big5' : 4,
'wansung' : 5,
'johab' : 6,
# 7 is reserved
# 8 is reserved
# 9 is reserved
'unicode_full_repertoire' : 7,
}
msEIDInt2Name = _flipDict(msEIDName2Int)
msLIDName2Int = {
# need to find a parsable file
}
msLIDInt2Name = _flipDict(msLIDName2Int)
macEIDName2Int = {
"roman" : 0,
"japanese" : 1,
"chinese" : 2,
"korean" : 3,
"arabic" : 4,
"hebrew" : 5,
"greek" : 6,
"russian" : 7,
"rsymbol" : 8,
"devanagari" : 9,
"gurmukhi" : 10,
"gujarati" : 11,
"oriya" : 12,
"bengali" : 13,
"tamil" : 14,
"telugu" : 15,
"kannada" : 16,
"malayalam" : 17,
"sinhalese" : 18,
"burmese" : 19,
"khmer" : 20,
"thai" : 21,
"laotian" : 22,
"georgian" : 23,
"armenian" : 24,
"chinese" : 25,
"tibetan" : 26,
"mongolian" : 27,
"geez" : 28,
"slavic" : 29,
"vietnamese" : 30,
"sindhi" : 31,
"uninterpreted" : 32,
}
macEIDInt2Name = _flipDict(macEIDName2Int)
macLIDName2Int = {
"english" : 0,
"french" : 1,
"german" : 2,
"italian" : 3,
"dutch" : 4,
"swedish" : 5,
"spanish" : 6,
"danish" : 7,
"portuguese" : 8,
"norwegian" : 9,
"hebrew" : 10,
"japanese" : 11,
"arabic" : 12,
"finnish" : 13,
"inuktitut" : 14,
"icelandic" : 15,
"maltese" : 16,
"turkish" : 17,
"croatian" : 18,
"chinese" : 19,
"urdu" : 20,
"hindi" : 21,
"thai" : 22,
"korean" : 23,
"lithuanian" : 24,
"polish" : 25,
"hungarian" : 26,
"estonian" : 27,
"latvian" : 28,
"sami" : 29,
"faroese" : 30,
"farsi_persian" : 31,
"russian" : 32,
"chinese" : 33,
"flemish" : 34,
"irish gaelic" : 35,
"albanian" : 36,
"romanian" : 37,
"czech" : 38,
"slovak" : 39,
"slovenian" : 40,
"yiddish" : 41,
"serbian" : 42,
"macedonian" : 43,
"bulgarian" : 44,
"ukrainian" : 45,
"byelorussian" : 46,
"uzbek" : 47,
"kazakh" : 48,
"azerbaijani_cyrillic" : 49,
"azerbaijani_arabic" : 50,
"armenian" : 51,
"georgian" : 52,
"moldavian" : 53,
"kirghiz" : 54,
"tajiki" : 55,
"turkmen" : 56,
"mongolian_mongolian" : 57,
"mongolian_cyrillic" : 58,
"pashto" : 59,
"kurdish" : 60,
"kashmiri" : 61,
"sindhi" : 62,
"tibetan" : 63,
"nepali" : 64,
"sanskrit" : 65,
"marathi" : 66,
"bengali" : 67,
"assamese" : 68,
"gujarati" : 69,
"punjabi" : 70,
"oriya" : 71,
"malayalam" : 72,
"kannada" : 73,
"tamil" : 74,
"telugu" : 75,
"sinhalese" : 76,
"burmese" : 77,
"khmer" : 78,
"lao" : 79,
"vietnamese" : 80,
"indonesian" : 81,
"tagalong" : 82,
"malay_roman" : 83,
"malay_arabic" : 84,
"amharic" : 85,
"tigrinya" : 86,
"galla" : 87,
"somali" : 88,
"swahili" : 89,
"kinyarwanda_ruanda" : 90,
"rundi" : 91,
"nyanja_chewa" : 92,
"malagasy" : 93,
"esperanto" : 94,
"welsh" : 128,
"basque" : 129,
"catalan" : 130,
"latin" : 131,
"quenchua" : 132,
"guarani" : 133,
"aymara" : 134,
"tatar" : 135,
"uighur" : 136,
"dzongkha" : 137,
"javanese_roman" : 138,
"sundanese_roman" : 139,
"galician" : 140,
"afrikaans" : 141,
"breton" : 142,
"scottish_gaelic" : 144,
"manx_gaelic" : 145,
"irish_gaelic" : 146,
"tongan" : 147,
"greek_polytonic" : 148,
"greenlandic" : 149,
"azerbaijani_roman" : 150,
}
macLIDInt2Name = _flipDict(macLIDName2Int)
##
## value converters
##
def _convertNID2Int(nid):
if isinstance(nid, int):
return nid
return nidName2Int[nid]
def _convertPID2Int(pid):
if isinstance(pid, int):
return pid
return pidName2Int[pid]
def _convertEID2Int(pid, eid):
if isinstance(eid, int):
return eid
pid = _convertPID2Int(pid)
if pid == UNI_INT:
return uniEIDName2Int[eid]
elif pid == MAC_INT:
return macEIDName2Int[eid]
elif pid == MS_INT:
return msEIDName2Int[eid]
def _convertLID2Int(pid, lid):
if isinstance(lid, int):
return lid
pid = _convertPID2Int(pid)
if pid == UNI_INT:
return uniLIDName2Int[lid]
elif pid == MAC_INT:
return macLIDName2Int[lid]
elif pid == MS_INT:
return msLIDName2Int[lid]
def _compareValues(v1, v2):
if isinstance(v1, str):
v1 = v1.replace('\r\n', '\n')
if isinstance(v2, str):
v2 = v2.replace('\r\n', '\n')
return v1 == v2
def convertLineEndings(text, convertToMS=False):
"""convert the line endings in a given text string"""
if isinstance(text, str):
if convertToMS:
text = text.replace('\r\n', '\n')
text = text.replace('\n', '\r\n')
else:
text = text.replace('\r\n', '\n')
return text
##
## main object
##
class NameTable(object):
"""
An object that allows direct manipulation of the name table of a given font.
For example:
from robofab.world import CurrentFont
from robofab.tools.nameTable import NameTable
f = CurrentFont()
nt = NameTable(f)
# bluntly set all copyright records to a string
nt.copyright = "Copyright 2004 RoboFab"
# get a record
print nt.copyright
# set a specific record to a string
nt.setSpecificRecord(pid=1, eid=0, lid=0, nid=0, value="You Mac-Roman-English folks should know that this is Copyright 2004 RoboFab.")
# get a record again to show what happens
# when the records for a NID are not the same
print nt.copyright
# look at the code to see what else is possible
f.update()
"""
def __init__(self, font):
self._object = font
self._pid_eid_lid = {}
self._records = {}
self._indexRef = {}
self._populate()
def _populate(self):
# keys are tuples (pid, eid, lid, nid)
self._records = {}
# keys are tuples (pid, eid, lid, nid), values are indices
self._indexRef = {}
count = 0
for record in self._object.naked().fontnames:
pid = record.pid
eid = record.eid
lid = record.lid
nid = record.nid
value = record.name
self._records[(pid, eid, lid, nid)] = value
self._indexRef[(pid, eid, lid, nid)] = count
count = count + 1
def addRecord(self, pid, eid, lid, nidDict=None):
"""add a record. the optional nidDict is
a dictionary of NIDs and values. If no
nidDict is given, the method will make
an empty entry for ALL public NIDs."""
if nidDict is None:
nidDict = dict.fromkeys(nidInt2Name.keys())
for nid in nidDict.keys():
nidDict[nid] = ''
pid = _convertPID2Int(pid)
eid = _convertEID2Int(pid, eid)
lid = _convertLID2Int(pid, lid)
self.removeLID(pid, eid, lid)
for nid, value in nidDict.items():
nid = _convertNID2Int(nid)
self._setRecord(pid, eid, lid, nid, value)
def removePID(self, pid):
"""remove a PID entry"""
pid = _convertPID2Int(pid)
for _pid, _eid, _lid, _nid in self._records.keys():
if pid == _pid:
self._removeRecord(_pid, _eid, _lid, _nid)
def removeEID(self, pid, eid):
"""remove an EID from a PID entry"""
pid = _convertPID2Int(pid)
eid = _convertEID2Int(pid, eid)
for _pid, _eid, _lid, _nid in self._records.keys():
if pid == _pid and eid == _eid:
self._removeRecord(_pid, _eid, _lid, _nid)
def removeLID(self, pid, eid, lid):
"""remove a LID from a PID entry"""
pid = _convertPID2Int(pid)
eid = _convertEID2Int(pid, eid)
lid = _convertLID2Int(pid, lid)
for _pid, _eid, _lid, _nid in self._records.keys():
if pid == _pid and eid == _eid and lid == _lid:
self._removeRecord(_pid, _eid, _lid, _nid)
def removeNID(self, nid):
"""remove a NID from ALL PID, EID and LID entries"""
nid = _convertNID2Int(nid)
for _pid, _eid, _lid, _nid in self._records.keys():
if nid == _nid:
self._removeRecord(_pid, _eid, _lid, _nid)
def setSpecificRecord(self, pid, eid, lid, nid, value):
"""set a specific record based on the PID, EID, LID and NID
this method does not do platform lineending conversion"""
pid = _convertPID2Int(pid)
eid = _convertEID2Int(pid, eid)
lid = _convertLID2Int(pid, lid)
nid = _convertNID2Int(nid)
# id 18 is mac only, so it should
# not be set for other PIDs
if pid != MAC_INT and nid == 18:
raise RoboFabError, "NID 18 is Macintosh only"
self._setRecord(pid, eid, lid, nid, value)
#
# interface to FL name records
#
def _removeRecord(self, pid, eid, lid, nid):
# remove the record from the font
# note: this won't raise an error if the record doesn't exist
if self._indexRef.has_key((pid, eid, lid, nid)):
index = self._indexRef[(pid, eid, lid, nid)]
del self._object.naked().fontnames[index]
self._populate()
def _setRecord(self, pid, eid, lid, nid, value):
# set a record in the font
if pid != MAC_INT and nid == 18:
# id 18 is mac only, so it should
# not be set for other PIDs
return
if pid == UNI_INT or pid == MAC_INT:
value = convertLineEndings(value, convertToMS=False)
if pid == MS_INT:
value = convertLineEndings(value, convertToMS=True)
self._removeRecord(pid, eid, lid, nid)
from FL import NameRecord
nr = NameRecord(nid, pid, eid, lid, value)
self._object.naked().fontnames.append(nr)
self._populate()
def _setAllRecords(self, nid, value):
# set nid for all pid, eid and lid records
done = []
for _pid, _eid, _lid, _nid in self._records.keys():
if (_pid, _eid, _lid) not in done:
self._setRecord(_pid, _eid, _lid, nid, value)
done.append((_pid, _eid, _lid))
def _getAllRecords(self, nid):
# this retrieves all nid records and compares
# them. if the values are all the same, it returns
# the value. otherwise it returns a list of all values
# as tuples (pid, eid, lid, value).
found = []
for (_pid, _eid, _lid, _nid), value in self._records.items():
if nid == _nid:
found.append((_pid, _eid, _lid, value))
isSame = True
compare = {}
for pid, eid, lid, value in found:
if compare == {}:
compare = value
continue
vC = _compareValues(compare, value)
if not vC:
isSame = False
if isSame:
found = found[0][-1]
found = convertLineEndings(found, convertToMS=False)
return found
#
# attrs
#
def _get_copyright(self):
nid = 0
return self._getAllRecords(nid)
def _set_copyright(self, value):
nid = 0
self._setAllRecords(nid, value)
copyright = property(_get_copyright, _set_copyright, doc="NID 0")
def _get_familyName(self):
nid = 1
return self._getAllRecords(nid)
def _set_familyName(self, value):
nid = 1
self._setAllRecords(nid, value)
familyName = property(_get_familyName, _set_familyName, doc="NID 1")
def _get_subfamilyName(self):
nid = 2
return self._getAllRecords(nid)
def _set_subfamilyName(self, value):
nid = 2
self._setAllRecords(nid, value)
subfamilyName = property(_get_subfamilyName, _set_subfamilyName, doc="NID 2")
def _get_uniqueID(self):
nid = 3
return self._getAllRecords(nid)
def _set_uniqueID(self, value):
nid = 3
self._setAllRecords(nid, value)
uniqueID = property(_get_uniqueID, _set_uniqueID, doc="NID 3")
def _get_fullName(self):
nid = 4
return self._getAllRecords(nid)
def _set_fullName(self, value):
nid = 4
self._setAllRecords(nid, value)
fullName = property(_get_fullName, _set_fullName, doc="NID 4")
def _get_versionString(self):
nid = 5
return self._getAllRecords(nid)
def _set_versionString(self, value):
nid = 5
self._setAllRecords(nid, value)
versionString = property(_get_versionString, _set_versionString, doc="NID 5")
def _get_postscriptName(self):
nid = 6
return self._getAllRecords(nid)
def _set_postscriptName(self, value):
nid = 6
self._setAllRecords(nid, value)
postscriptName = property(_get_postscriptName, _set_postscriptName, doc="NID 6")
def _get_trademark(self):
nid = 7
return self._getAllRecords(nid)
def _set_trademark(self, value):
nid = 7
self._setAllRecords(nid, value)
trademark = property(_get_trademark, _set_trademark, doc="NID 7")
def _get_manufacturer(self):
nid = 8
return self._getAllRecords(nid)
def _set_manufacturer(self, value):
nid = 8
self._setAllRecords(nid, value)
manufacturer = property(_get_manufacturer, _set_manufacturer, doc="NID 8")
def _get_designer(self):
nid = 9
return self._getAllRecords(nid)
def _set_designer(self, value):
nid = 9
self._setAllRecords(nid, value)
designer = property(_get_designer, _set_designer, doc="NID 9")
def _get_description(self):
nid = 10
return self._getAllRecords(nid)
def _set_description(self, value):
nid = 10
self._setAllRecords(nid, value)
description = property(_get_description, _set_description, doc="NID 10")
def _get_vendorURL(self):
nid = 11
return self._getAllRecords(nid)
def _set_vendorURL(self, value):
nid = 11
self._setAllRecords(nid, value)
vendorURL = property(_get_vendorURL, _set_vendorURL, doc="NID 11")
def _get_designerURL(self):
nid = 12
return self._getAllRecords(nid)
def _set_designerURL(self, value):
nid = 12
self._setAllRecords(nid, value)
designerURL = property(_get_designerURL, _set_designerURL, doc="NID 12")
def _get_license(self):
nid = 13
return self._getAllRecords(nid)
def _set_license(self, value):
nid = 13
self._setAllRecords(nid, value)
license = property(_get_license, _set_license, doc="NID 13")
def _get_licenseURL(self):
nid = 14
return self._getAllRecords(nid)
def _set_licenseURL(self, value):
nid = 14
self._setAllRecords(nid, value)
licenseURL = property(_get_licenseURL, _set_licenseURL, doc="NID 14")
def _get_preferredFamily(self):
nid = 16
return self._getAllRecords(nid)
def _set_preferredFamily(self, value):
nid = 16
self._setAllRecords(nid, value)
preferredFamily = property(_get_preferredFamily, _set_preferredFamily, doc="NID 16")
def _get_preferredSubfamily(self):
nid = 17
return self._getAllRecords(nid)
def _set_preferredSubfamily(self, value):
nid = 17
self._setAllRecords(nid, value)
preferredSubfamily = property(_get_preferredSubfamily, _set_preferredSubfamily, doc="NID 17")
def _get_compatibleFull(self):
nid = 18
return self._getAllRecords(nid)
def _set_compatibleFull(self, value):
nid = 18
self._setAllRecords(nid, value)
compatibleFull = property(_get_compatibleFull, _set_compatibleFull, doc="NID 18")
def _get_sampleText(self):
nid = 19
return self._getAllRecords(nid)
def _set_sampleText(self, value):
nid = 19
self._setAllRecords(nid, value)
sampleText = property(_get_sampleText, _set_sampleText, doc="NID 19")
def _get_postscriptCID(self):
nid = 20
return self._getAllRecords(nid)
def _set_postscriptCID(self, value):
nid = 20
self._setAllRecords(nid, value)
postscriptCID = property(_get_postscriptCID, _set_postscriptCID, doc="NID 20")
if __name__ == "__main__":
from robofab.world import CurrentFont
f = CurrentFont()
nt = NameTable(f)
print nt.copyright
f.update()

View File

@ -12,34 +12,6 @@ else:
have_broken_macsupport = 0
import robofab
from robofab.plistlib import readPlist, writePlist
def readFoundrySettings(dstPath):
"""read the foundry settings xml file and return a keyed dict."""
fileName = os.path.basename(dstPath)
if not os.path.exists(dstPath):
import shutil
# hm -- a fresh install, make a new default settings file
print "RoboFab: creating a new foundry settings file at", dstPath
srcDir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(robofab.__file__))), 'Data')
srcPath = os.path.join(srcDir, 'template_' + fileName)
shutil.copy(srcPath, dstPath)
return readPlist(dstPath)
def getFoundrySetting(key, path):
"""get a specific setting from the foundry settings xml file."""
d = readFoundrySettings(path)
return d.get(key)
writeFoundrySettings = writePlist
def setFoundrySetting(key, value, dstPath):
"""write a specific entry in the foundry settings xml file."""
d = readFoundrySettings(dstPath)
d[key] = value
writeFoundrySettings(d, dstPath)
def readGlyphConstructions():
"""read GlyphConstruction and turn it into a dict"""
from robofab.tools.glyphConstruction import _glyphConstruction
@ -58,9 +30,6 @@ def readGlyphConstructions():
glyphConstructions[name] = build
return glyphConstructions
#
#
# glyph.unicode: ttFont["cmap"].getcmap(3, 1)

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,7 @@ for c in f:
compatibles[d].append(c.name)
print
print 'In %s, these glyphs could interpolate:'%(f.info.fullName)
print 'In %s, these glyphs could interpolate:'%(f.info.postscriptFullName)
for d, names in compatibles.items():
if len(names) > 1:
print ", ".join(names)

View File

@ -30,14 +30,14 @@ if f == None:
Message("You should open a font first, there's nothing to look at now!")
else:
# and another dialog.
Message("The current font is %s"%(f.info.fullName))
Message("The current font is %s"%(f.info.postscriptFullName))
# let's have a look at some of the attributes a RoboFab Font object has
print "the number of glyphs:", len(f)
# some of the attributes map straight to the FontLab Font class
# We just straightened the camelCase here and there
print "full name of this font:", f.info.fullName
print "full name of this font:", f.info.postscriptFullName
print "list of glyph names:", f.keys()
print 'ascender:', f.info.ascender
print 'descender:', f.info.descender

View File

@ -50,13 +50,13 @@ font.info.year = time.gmtime(time.time())[0]
# Apply those settings that we just loaded
font.info.copyright = mySettings['copyright']
font.info.trademark = mySettings['trademark']
font.info.license = mySettings['license']
font.info.licenseURL = mySettings['licenseurl']
font.info.notice = mySettings['notice']
font.info.ttVendor = mySettings['ttvendor']
font.info.vendorURL = mySettings['vendorurl']
font.info.designer = mySettings['designer']
font.info.designerURL = mySettings['designerurl']
font.info.openTypeNameLicense = mySettings['license']
font.info.openTypeNameLicenseURL = mySettings['licenseurl']
font.info.openTypeNameDescription = mySettings['notice']
font.info.openTypeOS2VendorID = mySettings['ttvendor']
font.info.openTypeNameManufacturerURL = mySettings['vendorurl']
font.info.openTypeNameDesigner = mySettings['designer']
font.info.openTypeNameDesignerURL = mySettings['designerurl']
# and call the update method
font.update()

View File

@ -29,7 +29,7 @@ kerning = f.kerning
# you call and attribute or make a change.
# kerning gives you access to some bits of global data
print "%s has %s kerning pairs"%(f.info.fullName, len(kerning))
print "%s has %s kerning pairs"%(f.info.postscriptFullName, len(kerning))
print "the average kerning value is %s"%kerning.getAverage()
min, max = kerning.getExtremes()
print "the largest kerning value is %s"%max

View File

@ -25,7 +25,7 @@ def makeDestination(root):
return macPath
def generateOne(f, dstDir):
print "generating %s"%f.info.fullName
print "generating %s"%f.info.postscriptFullName
f.generate('mactype1', dstDir)

View File

@ -8,13 +8,13 @@ af = AllFonts()
results = []
line = []
for n in af:
line.append(`n.info.fullName`)
line.append(`n.info.postscriptFullName`)
results.append(line)
for i in range(len(af)):
one = af[i]
line = []
line.append(af[i].info.fullName)
line.append(af[i].info.postscriptFullName)
for j in range(len(af)):
other = af[j]
line.append(`one==other`)

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ascender</key>
<integer>750</integer>
<key>capHeight</key>
<integer>750</integer>
<key>copyright</key>
<string>Copyright Some Foundry.</string>
<key>createdBy</key>
<string>Some Foundry</string>
<key>defaultWidth</key>
<integer>400</integer>
<key>descender</key>
<integer>-250</integer>
<key>designer</key>
<string>Some Designer</string>
<key>designerURL</key>
<string>http://somedesigner.com</string>
<key>familyName</key>
<string>Some Font (Family Name)</string>
<key>fondID</key>
<integer>15000</integer>
<key>fondName</key>
<string>SomeFont Regular (FOND Name)</string>
<key>fontName</key>
<string>SomeFont-Regular (Postscript Font Name)</string>
<key>fontStyle</key>
<integer>64</integer>
<key>fullName</key>
<string>Some Font-Regular (Postscript Full Name)</string>
<key>italicAngle</key>
<real>-12.5</real>
<key>license</key>
<string>License info for Some Foundry.</string>
<key>licenseURL</key>
<string>http://somefoundry.com/license</string>
<key>menuName</key>
<string>Some Font Regular (Style Map Family Name)</string>
<key>msCharSet</key>
<integer>0</integer>
<key>note</key>
<string>A note.</string>
<key>notice</key>
<string>Some Font by Some Designer for Some Foundry.</string>
<key>otFamilyName</key>
<string>Some Font (Preferred Family Name)</string>
<key>otMacName</key>
<string>Some Font Regular (Compatible Full Name)</string>
<key>otStyleName</key>
<string>Regular (Preferred Subfamily Name)</string>
<key>slantAngle</key>
<real>-12.5</real>
<key>styleName</key>
<string>Regular (Style Name)</string>
<key>trademark</key>
<string>Trademark Some Foundry</string>
<key>ttUniqueID</key>
<string>OpenType name Table Unique ID</string>
<key>ttVendor</key>
<string>SOME</string>
<key>ttVersion</key>
<string>OpenType name Table Version</string>
<key>uniqueID</key>
<integer>4000000</integer>
<key>unitsPerEm</key>
<integer>1000</integer>
<key>vendorURL</key>
<string>http://somefoundry.com</string>
<key>versionMajor</key>
<integer>1</integer>
<key>versionMinor</key>
<integer>0</integer>
<key>weightName</key>
<string>Medium</string>
<key>weightValue</key>
<integer>500</integer>
<key>widthName</key>
<string>Medium (normal)</string>
<key>xHeight</key>
<integer>500</integer>
<key>year</key>
<integer>2008</integer>
</dict>
</plist>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<glyph name="A" format="1">
<advance width="740"/>
<unicode hex="0041"/>
<outline>
<contour>
<point x="20" y="0" type="line"/>
<point x="720" y="0" type="line"/>
<point x="720" y="700" type="line"/>
<point x="20" y="700" type="line"/>
</contour>
</outline>
</glyph>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<glyph name="B" format="1">
<advance width="740"/>
<unicode hex="0042"/>
<outline>
<contour>
<point x="20" y="350" type="curve" smooth="yes"/>
<point x="20" y="157"/>
<point x="177" y="0"/>
<point x="370" y="0" type="curve" smooth="yes"/>
<point x="563" y="0"/>
<point x="720" y="157"/>
<point x="720" y="350" type="curve" smooth="yes"/>
<point x="720" y="543"/>
<point x="563" y="700"/>
<point x="370" y="700" type="curve" smooth="yes"/>
<point x="177" y="700"/>
<point x="20" y="543"/>
</contour>
</outline>
</glyph>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>A</key>
<string>A_.glif</string>
<key>B</key>
<string>B_.glif</string>
</dict>
</plist>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>group1</key>
<array>
<string>A</string>
</array>
<key>group2</key>
<array>
<string>A</string>
<string>B</string>
</array>
</dict>
</plist>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>A</key>
<dict>
<key>B</key>
<integer>100</integer>
</dict>
<key>B</key>
<dict>
<key>A</key>
<integer>-200</integer>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>org.robofab.opentype.classes</key>
<string>@myClass = [A B];
</string>
<key>org.robofab.opentype.featureorder</key>
<array>
<string>liga</string>
</array>
<key>org.robofab.opentype.features</key>
<dict>
<key>liga</key>
<string>feature liga {
sub A A by b;
} liga;
</string>
</dict>
<key>org.robofab.postScriptHintData</key>
<dict>
<key>blueFuzz</key>
<integer>1</integer>
<key>blueScale</key>
<real>0.039625</real>
<key>blueShift</key>
<integer>7</integer>
<key>blueValues</key>
<array>
<array>
<integer>500</integer>
<integer>510</integer>
</array>
</array>
<key>familyBlues</key>
<array>
<array>
<integer>500</integer>
<integer>510</integer>
</array>
</array>
<key>familyOtherBlues</key>
<array>
<array>
<integer>-260</integer>
<integer>-250</integer>
</array>
</array>
<key>forceBold</key>
<true/>
<key>hStems</key>
<array>
<integer>100</integer>
<integer>120</integer>
</array>
<key>otherBlues</key>
<array>
<array>
<integer>-260</integer>
<integer>-250</integer>
</array>
</array>
<key>vStems</key>
<array>
<integer>80</integer>
<integer>90</integer>
</array>
</dict>
<key>org.robofab.testFontLibData</key>
<string>Foo Bar</string>
</dict>
</plist>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>creator</key>
<string>org.robofab.ufoLib</string>
<key>formatVersion</key>
<integer>1</integer>
</dict>
</plist>

View File

@ -0,0 +1,5 @@
@myClass = [A B];
feature liga {
sub A A by b;
} liga;

View File

@ -0,0 +1,239 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ascender</key>
<integer>750</integer>
<key>capHeight</key>
<integer>750</integer>
<key>copyright</key>
<string>Copyright Some Foundry.</string>
<key>descender</key>
<integer>-250</integer>
<key>familyName</key>
<string>Some Font (Family Name)</string>
<key>italicAngle</key>
<real>-12.5</real>
<key>macintoshFONDFamilyID</key>
<integer>15000</integer>
<key>macintoshFONDName</key>
<string>SomeFont Regular (FOND Name)</string>
<key>note</key>
<string>A note.</string>
<key>openTypeHeadCreated</key>
<string>2000/01/01 00:00:00</string>
<key>openTypeHeadFlags</key>
<array>
<integer>0</integer>
<integer>1</integer>
</array>
<key>openTypeHeadLowestRecPPEM</key>
<integer>10</integer>
<key>openTypeHheaAscender</key>
<integer>750</integer>
<key>openTypeHheaCaretOffset</key>
<integer>0</integer>
<key>openTypeHheaCaretSlopeRise</key>
<integer>1</integer>
<key>openTypeHheaCaretSlopeRun</key>
<integer>0</integer>
<key>openTypeHheaDescender</key>
<integer>-250</integer>
<key>openTypeHheaLineGap</key>
<integer>200</integer>
<key>openTypeNameCompatibleFullName</key>
<string>Some Font Regular (Compatible Full Name)</string>
<key>openTypeNameDescription</key>
<string>Some Font by Some Designer for Some Foundry.</string>
<key>openTypeNameDesigner</key>
<string>Some Designer</string>
<key>openTypeNameDesignerURL</key>
<string>http://somedesigner.com</string>
<key>openTypeNameLicense</key>
<string>License info for Some Foundry.</string>
<key>openTypeNameLicenseURL</key>
<string>http://somefoundry.com/license</string>
<key>openTypeNameManufacturer</key>
<string>Some Foundry</string>
<key>openTypeNameManufacturerURL</key>
<string>http://somefoundry.com</string>
<key>openTypeNamePreferredFamilyName</key>
<string>Some Font (Preferred Family Name)</string>
<key>openTypeNamePreferredSubfamilyName</key>
<string>Regular (Preferred Subfamily Name)</string>
<key>openTypeNameSampleText</key>
<string>Sample Text for Some Font.</string>
<key>openTypeNameUniqueID</key>
<string>OpenType name Table Unique ID</string>
<key>openTypeNameVersion</key>
<string>OpenType name Table Version</string>
<key>openTypeNameWWSFamilyName</key>
<string>Some Font (WWS Family Name)</string>
<key>openTypeNameWWSSubfamilyName</key>
<string>Regular (WWS Subfamily Name)</string>
<key>openTypeOS2CodePageRanges</key>
<array>
<integer>0</integer>
<integer>1</integer>
</array>
<key>openTypeOS2Panose</key>
<array>
<integer>0</integer>
<integer>1</integer>
<integer>2</integer>
<integer>3</integer>
<integer>4</integer>
<integer>5</integer>
<integer>6</integer>
<integer>7</integer>
<integer>8</integer>
<integer>9</integer>
</array>
<key>openTypeOS2FamilyClass</key>
<array>
<integer>1</integer>
<integer>1</integer>
</array>
<key>openTypeOS2Selection</key>
<array>
<integer>3</integer>
</array>
<key>openTypeOS2StrikeoutPosition</key>
<integer>300</integer>
<key>openTypeOS2StrikeoutSize</key>
<integer>20</integer>
<key>openTypeOS2SubscriptXOffset</key>
<integer>0</integer>
<key>openTypeOS2SubscriptXSize</key>
<integer>200</integer>
<key>openTypeOS2SubscriptYOffset</key>
<integer>-100</integer>
<key>openTypeOS2SubscriptYSize</key>
<integer>400</integer>
<key>openTypeOS2SuperscriptXOffset</key>
<integer>0</integer>
<key>openTypeOS2SuperscriptXSize</key>
<integer>200</integer>
<key>openTypeOS2SuperscriptYOffset</key>
<integer>200</integer>
<key>openTypeOS2SuperscriptYSize</key>
<integer>400</integer>
<key>openTypeOS2Type</key>
<array>
</array>
<key>openTypeOS2TypoAscender</key>
<integer>750</integer>
<key>openTypeOS2TypoDescender</key>
<integer>-250</integer>
<key>openTypeOS2TypoLineGap</key>
<integer>200</integer>
<key>openTypeOS2UnicodeRanges</key>
<array>
<integer>0</integer>
<integer>1</integer>
</array>
<key>openTypeOS2VendorID</key>
<string>SOME</string>
<key>openTypeOS2WeightClass</key>
<integer>500</integer>
<key>openTypeOS2WidthClass</key>
<integer>5</integer>
<key>openTypeOS2WinAscent</key>
<integer>750</integer>
<key>openTypeOS2WinDescent</key>
<integer>-250</integer>
<key>openTypeVheaCaretOffset</key>
<integer>0</integer>
<key>openTypeVheaCaretSlopeRise</key>
<integer>0</integer>
<key>openTypeVheaCaretSlopeRun</key>
<integer>1</integer>
<key>openTypeVheaVertTypoAscender</key>
<integer>750</integer>
<key>openTypeVheaVertTypoDescender</key>
<integer>-250</integer>
<key>openTypeVheaVertTypoLineGap</key>
<integer>200</integer>
<key>postscriptBlueFuzz</key>
<integer>1</integer>
<key>postscriptBlueScale</key>
<real>0.039625</real>
<key>postscriptBlueShift</key>
<integer>7</integer>
<key>postscriptBlueValues</key>
<array>
<integer>500</integer>
<integer>510</integer>
</array>
<key>postscriptDefaultCharacter</key>
<string>.notdef</string>
<key>postscriptDefaultWidthX</key>
<integer>400</integer>
<key>postscriptFamilyBlues</key>
<array>
<integer>500</integer>
<integer>510</integer>
</array>
<key>postscriptFamilyOtherBlues</key>
<array>
<integer>-250</integer>
<integer>-260</integer>
</array>
<key>postscriptFontName</key>
<string>SomeFont-Regular (Postscript Font Name)</string>
<key>postscriptForceBold</key>
<true/>
<key>postscriptFullName</key>
<string>Some Font-Regular (Postscript Full Name)</string>
<key>postscriptIsFixedPitch</key>
<false/>
<key>postscriptNominalWidthX</key>
<integer>400</integer>
<key>postscriptOtherBlues</key>
<array>
<integer>-250</integer>
<integer>-260</integer>
</array>
<key>postscriptSlantAngle</key>
<real>-12.5</real>
<key>postscriptStemSnapH</key>
<array>
<integer>100</integer>
<integer>120</integer>
</array>
<key>postscriptStemSnapV</key>
<array>
<integer>80</integer>
<integer>90</integer>
</array>
<key>postscriptUnderlinePosition</key>
<integer>-200</integer>
<key>postscriptUnderlineThickness</key>
<integer>20</integer>
<key>postscriptUniqueID</key>
<integer>4000000</integer>
<key>postscriptWeightName</key>
<string>Medium</string>
<key>postscriptWindowsCharacterSet</key>
<integer>1</integer>
<key>styleMapFamilyName</key>
<string>Some Font Regular (Style Map Family Name)</string>
<key>styleMapStyleName</key>
<string>regular</string>
<key>styleName</key>
<string>Regular (Style Name)</string>
<key>trademark</key>
<string>Trademark Some Foundry</string>
<key>unitsPerEm</key>
<integer>1000</integer>
<key>versionMajor</key>
<integer>1</integer>
<key>versionMinor</key>
<integer>0</integer>
<key>xHeight</key>
<integer>500</integer>
<key>year</key>
<integer>2008</integer>
</dict>
</plist>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<glyph name="A" format="1">
<advance width="740"/>
<unicode hex="0041"/>
<outline>
<contour>
<point x="20" y="0" type="line"/>
<point x="720" y="0" type="line"/>
<point x="720" y="700" type="line"/>
<point x="20" y="700" type="line"/>
</contour>
</outline>
</glyph>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<glyph name="B" format="1">
<advance width="740"/>
<unicode hex="0042"/>
<outline>
<contour>
<point x="20" y="350" type="curve" smooth="yes"/>
<point x="20" y="157"/>
<point x="177" y="0"/>
<point x="370" y="0" type="curve" smooth="yes"/>
<point x="563" y="0"/>
<point x="720" y="157"/>
<point x="720" y="350" type="curve" smooth="yes"/>
<point x="720" y="543"/>
<point x="563" y="700"/>
<point x="370" y="700" type="curve" smooth="yes"/>
<point x="177" y="700"/>
<point x="20" y="543"/>
</contour>
</outline>
</glyph>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>A</key>
<string>A_.glif</string>
<key>B</key>
<string>B_.glif</string>
</dict>
</plist>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>group1</key>
<array>
<string>A</string>
</array>
<key>group2</key>
<array>
<string>A</string>
<string>B</string>
</array>
</dict>
</plist>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>A</key>
<dict>
<key>B</key>
<integer>100</integer>
</dict>
<key>B</key>
<dict>
<key>A</key>
<integer>-200</integer>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>org.robofab.testFontLibData</key>
<string>Foo Bar</string>
</dict>
</plist>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>creator</key>
<string>org.robofab.ufoLib</string>
<key>formatVersion</key>
<integer>2</integer>
</dict>
</plist>

BIN
TestData/TestFont1.vfb Normal file

Binary file not shown.