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:
parent
1eb53b404e
commit
ea39f12120
1922
Data/frequency.txt
1922
Data/frequency.txt
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||
|
@ -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)
|
||||
|
@ -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
@ -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"
|
||||
|
||||
|
@ -17,6 +17,7 @@ if __name__ == "__main__":
|
||||
mod = __import__(modName, {}, {}, ["*"])
|
||||
except ImportError:
|
||||
print "*** skipped", fileName
|
||||
continue
|
||||
|
||||
suites.append(loader.loadTestsFromModule(mod))
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
111
Lib/robofab/test/test_RInfoFL.py
Normal file
111
Lib/robofab/test/test_RInfoFL.py
Normal 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()
|
||||
|
56
Lib/robofab/test/test_RInfoRF.py
Normal file
56
Lib/robofab/test/test_RInfoRF.py
Normal 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()
|
565
Lib/robofab/test/test_fontLabUFOReadWrite.py
Normal file
565
Lib/robofab/test/test_fontLabUFOReadWrite.py
Normal 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()
|
321
Lib/robofab/test/test_noneLabUFOReadWrite.py
Normal file
321
Lib/robofab/test/test_noneLabUFOReadWrite.py
Normal 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()
|
@ -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):
|
||||
|
@ -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__":
|
||||
|
1659
Lib/robofab/test/test_ufoLib.py
Normal file
1659
Lib/robofab/test/test_ufoLib.py
Normal file
File diff suppressed because it is too large
Load Diff
85
Lib/robofab/tools/fontlabFeatureSplitter.py
Normal file
85
Lib/robofab/tools/fontlabFeatureSplitter.py
Normal 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()
|
@ -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()
|
@ -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
@ -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)
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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`)
|
||||
|
87
TestData/TestFont1 (UFO1).ufo/fontinfo.plist
Normal file
87
TestData/TestFont1 (UFO1).ufo/fontinfo.plist
Normal 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>
|
||||
|
13
TestData/TestFont1 (UFO1).ufo/glyphs/A_.glif
Normal file
13
TestData/TestFont1 (UFO1).ufo/glyphs/A_.glif
Normal 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>
|
21
TestData/TestFont1 (UFO1).ufo/glyphs/B_.glif
Normal file
21
TestData/TestFont1 (UFO1).ufo/glyphs/B_.glif
Normal 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>
|
10
TestData/TestFont1 (UFO1).ufo/glyphs/contents.plist
Normal file
10
TestData/TestFont1 (UFO1).ufo/glyphs/contents.plist
Normal 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>
|
15
TestData/TestFont1 (UFO1).ufo/groups.plist
Normal file
15
TestData/TestFont1 (UFO1).ufo/groups.plist
Normal 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>
|
16
TestData/TestFont1 (UFO1).ufo/kerning.plist
Normal file
16
TestData/TestFont1 (UFO1).ufo/kerning.plist
Normal 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>
|
72
TestData/TestFont1 (UFO1).ufo/lib.plist
Normal file
72
TestData/TestFont1 (UFO1).ufo/lib.plist
Normal 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>
|
10
TestData/TestFont1 (UFO1).ufo/metainfo.plist
Normal file
10
TestData/TestFont1 (UFO1).ufo/metainfo.plist
Normal 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>
|
5
TestData/TestFont1 (UFO2).ufo/features.fea
Normal file
5
TestData/TestFont1 (UFO2).ufo/features.fea
Normal file
@ -0,0 +1,5 @@
|
||||
@myClass = [A B];
|
||||
|
||||
feature liga {
|
||||
sub A A by b;
|
||||
} liga;
|
239
TestData/TestFont1 (UFO2).ufo/fontinfo.plist
Normal file
239
TestData/TestFont1 (UFO2).ufo/fontinfo.plist
Normal 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>
|
||||
|
13
TestData/TestFont1 (UFO2).ufo/glyphs/A_.glif
Normal file
13
TestData/TestFont1 (UFO2).ufo/glyphs/A_.glif
Normal 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>
|
21
TestData/TestFont1 (UFO2).ufo/glyphs/B_.glif
Normal file
21
TestData/TestFont1 (UFO2).ufo/glyphs/B_.glif
Normal 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>
|
10
TestData/TestFont1 (UFO2).ufo/glyphs/contents.plist
Normal file
10
TestData/TestFont1 (UFO2).ufo/glyphs/contents.plist
Normal 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>
|
15
TestData/TestFont1 (UFO2).ufo/groups.plist
Normal file
15
TestData/TestFont1 (UFO2).ufo/groups.plist
Normal 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>
|
16
TestData/TestFont1 (UFO2).ufo/kerning.plist
Normal file
16
TestData/TestFont1 (UFO2).ufo/kerning.plist
Normal 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>
|
8
TestData/TestFont1 (UFO2).ufo/lib.plist
Normal file
8
TestData/TestFont1 (UFO2).ufo/lib.plist
Normal 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>
|
10
TestData/TestFont1 (UFO2).ufo/metainfo.plist
Normal file
10
TestData/TestFont1 (UFO2).ufo/metainfo.plist
Normal 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
BIN
TestData/TestFont1.vfb
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user