Speeds up font compilation by around 200%

Cython is used to compile some hot paths into native Python extensions.
These hot paths were identified through running ufocompile with the hotshot
profiler and then converting file by file to Cython, starting with the "hottest"
paths and continuing until returns were deminishing. This means that only a few
Python files were converted to Cython.

Closes #23
Closes #20 (really this time)
This commit is contained in:
Rasmus Andersson 2017-09-03 23:03:17 -04:00
parent 31ae014e0c
commit 8234b62ab7
108 changed files with 26933 additions and 110 deletions

View file

@ -0,0 +1,8 @@
"""Directory for unit tests.
Modules here are typically named text_<something>.py, where <something> is
usually a module name, for example "test_flPen.py", but it can also be the name
of an area or concept to be tested, for example "test_drawing.py".
Testmodules should use the unittest framework.
"""

View file

@ -0,0 +1,27 @@
import os
import glob
import unittest
import robofab.test
if __name__ == "__main__":
testDir = os.path.dirname(robofab.test.__file__)
testFiles = glob.glob1(testDir, "test_*.py")
loader = unittest.TestLoader()
suites = []
for fileName in testFiles:
modName = "robofab.test." + fileName[:-3]
print "importing", fileName
try:
mod = __import__(modName, {}, {}, ["*"])
except ImportError:
print "*** skipped", fileName
continue
suites.append(loader.loadTestsFromModule(mod))
print "running tests..."
testRunner = unittest.TextTestRunner(verbosity=0)
testSuite = unittest.TestSuite(suites)
testRunner.run(testSuite)

View file

@ -0,0 +1,278 @@
"""Miscellaneous helpers for our test suite."""
import sys
import os
import types
import unittest
def getDemoFontPath():
"""Return the path to Data/DemoFont.ufo/."""
import robofab
root = os.path.dirname(os.path.dirname(os.path.dirname(robofab.__file__)))
return os.path.join(root, "Data", "DemoFont.ufo")
def getDemoFontGlyphSetPath():
"""Return the path to Data/DemoFont.ufo/glyphs/."""
return os.path.join(getDemoFontPath(), "glyphs")
def _gatherTestCasesFromCallerByMagic():
# UGLY magic: fetch TestClass subclasses from the globals of our
# caller's caller.
frame = sys._getframe(2)
return _gatherTestCasesFromDict(frame.f_globals)
def _gatherTestCasesFromDict(d):
testCases = []
for ob in d.values():
if isinstance(ob, type) and issubclass(ob, unittest.TestCase):
testCases.append(ob)
return testCases
def runTests(testCases=None, verbosity=1):
"""Run a series of tests."""
if testCases is None:
testCases = _gatherTestCasesFromCallerByMagic()
loader = unittest.TestLoader()
suites = []
for testCase in testCases:
suites.append(loader.loadTestsFromTestCase(testCase))
testRunner = unittest.TextTestRunner(verbosity=verbosity)
testSuite = unittest.TestSuite(suites)
testRunner.run(testSuite)
# font info values used by several tests
fontInfoVersion1 = {
"familyName" : "Some Font (Family Name)",
"styleName" : "Regular (Style Name)",
"fullName" : "Some Font-Regular (Postscript Full Name)",
"fontName" : "SomeFont-Regular (Postscript Font Name)",
"menuName" : "Some Font Regular (Style Map Family Name)",
"fontStyle" : 64,
"note" : "A note.",
"versionMajor" : 1,
"versionMinor" : 0,
"year" : 2008,
"copyright" : "Copyright Some Foundry.",
"notice" : "Some Font by Some Designer for Some Foundry.",
"trademark" : "Trademark Some Foundry",
"license" : "License info for Some Foundry.",
"licenseURL" : "http://somefoundry.com/license",
"createdBy" : "Some Foundry",
"designer" : "Some Designer",
"designerURL" : "http://somedesigner.com",
"vendorURL" : "http://somefoundry.com",
"unitsPerEm" : 1000,
"ascender" : 750,
"descender" : -250,
"capHeight" : 750,
"xHeight" : 500,
"defaultWidth" : 400,
"slantAngle" : -12.5,
"italicAngle" : -12.5,
"widthName" : "Medium (normal)",
"weightName" : "Medium",
"weightValue" : 500,
"fondName" : "SomeFont Regular (FOND Name)",
"otFamilyName" : "Some Font (Preferred Family Name)",
"otStyleName" : "Regular (Preferred Subfamily Name)",
"otMacName" : "Some Font Regular (Compatible Full Name)",
"msCharSet" : 0,
"fondID" : 15000,
"uniqueID" : 4000000,
"ttVendor" : "SOME",
"ttUniqueID" : "OpenType name Table Unique ID",
"ttVersion" : "OpenType name Table Version",
}
fontInfoVersion2 = {
"familyName" : "Some Font (Family Name)",
"styleName" : "Regular (Style Name)",
"styleMapFamilyName" : "Some Font Regular (Style Map Family Name)",
"styleMapStyleName" : "regular",
"versionMajor" : 1,
"versionMinor" : 0,
"year" : 2008,
"copyright" : "Copyright Some Foundry.",
"trademark" : "Trademark Some Foundry",
"unitsPerEm" : 1000,
"descender" : -250,
"xHeight" : 500,
"capHeight" : 750,
"ascender" : 750,
"italicAngle" : -12.5,
"note" : "A note.",
"openTypeHeadCreated" : "2000/01/01 00:00:00",
"openTypeHeadLowestRecPPEM" : 10,
"openTypeHeadFlags" : [0, 1],
"openTypeHheaAscender" : 750,
"openTypeHheaDescender" : -250,
"openTypeHheaLineGap" : 200,
"openTypeHheaCaretSlopeRise" : 1,
"openTypeHheaCaretSlopeRun" : 0,
"openTypeHheaCaretOffset" : 0,
"openTypeNameDesigner" : "Some Designer",
"openTypeNameDesignerURL" : "http://somedesigner.com",
"openTypeNameManufacturer" : "Some Foundry",
"openTypeNameManufacturerURL" : "http://somefoundry.com",
"openTypeNameLicense" : "License info for Some Foundry.",
"openTypeNameLicenseURL" : "http://somefoundry.com/license",
"openTypeNameVersion" : "OpenType name Table Version",
"openTypeNameUniqueID" : "OpenType name Table Unique ID",
"openTypeNameDescription" : "Some Font by Some Designer for Some Foundry.",
"openTypeNamePreferredFamilyName" : "Some Font (Preferred Family Name)",
"openTypeNamePreferredSubfamilyName" : "Regular (Preferred Subfamily Name)",
"openTypeNameCompatibleFullName" : "Some Font Regular (Compatible Full Name)",
"openTypeNameSampleText" : "Sample Text for Some Font.",
"openTypeNameWWSFamilyName" : "Some Font (WWS Family Name)",
"openTypeNameWWSSubfamilyName" : "Regular (WWS Subfamily Name)",
"openTypeOS2WidthClass" : 5,
"openTypeOS2WeightClass" : 500,
"openTypeOS2Selection" : [3],
"openTypeOS2VendorID" : "SOME",
"openTypeOS2Panose" : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
"openTypeOS2FamilyClass" : [1, 1],
"openTypeOS2UnicodeRanges" : [0, 1],
"openTypeOS2CodePageRanges" : [0, 1],
"openTypeOS2TypoAscender" : 750,
"openTypeOS2TypoDescender" : -250,
"openTypeOS2TypoLineGap" : 200,
"openTypeOS2WinAscent" : 750,
"openTypeOS2WinDescent" : -250,
"openTypeOS2Type" : [],
"openTypeOS2SubscriptXSize" : 200,
"openTypeOS2SubscriptYSize" : 400,
"openTypeOS2SubscriptXOffset" : 0,
"openTypeOS2SubscriptYOffset" : -100,
"openTypeOS2SuperscriptXSize" : 200,
"openTypeOS2SuperscriptYSize" : 400,
"openTypeOS2SuperscriptXOffset" : 0,
"openTypeOS2SuperscriptYOffset" : 200,
"openTypeOS2StrikeoutSize" : 20,
"openTypeOS2StrikeoutPosition" : 300,
"openTypeVheaVertTypoAscender" : 750,
"openTypeVheaVertTypoDescender" : -250,
"openTypeVheaVertTypoLineGap" : 200,
"openTypeVheaCaretSlopeRise" : 0,
"openTypeVheaCaretSlopeRun" : 1,
"openTypeVheaCaretOffset" : 0,
"postscriptFontName" : "SomeFont-Regular (Postscript Font Name)",
"postscriptFullName" : "Some Font-Regular (Postscript Full Name)",
"postscriptSlantAngle" : -12.5,
"postscriptUniqueID" : 4000000,
"postscriptUnderlineThickness" : 20,
"postscriptUnderlinePosition" : -200,
"postscriptIsFixedPitch" : False,
"postscriptBlueValues" : [500, 510],
"postscriptOtherBlues" : [-250, -260],
"postscriptFamilyBlues" : [500, 510],
"postscriptFamilyOtherBlues" : [-250, -260],
"postscriptStemSnapH" : [100, 120],
"postscriptStemSnapV" : [80, 90],
"postscriptBlueFuzz" : 1,
"postscriptBlueShift" : 7,
"postscriptBlueScale" : 0.039625,
"postscriptForceBold" : True,
"postscriptDefaultWidthX" : 400,
"postscriptNominalWidthX" : 400,
"postscriptWeightName" : "Medium",
"postscriptDefaultCharacter" : ".notdef",
"postscriptWindowsCharacterSet" : 1,
"macintoshFONDFamilyID" : 15000,
"macintoshFONDName" : "SomeFont Regular (FOND Name)",
}
expectedFontInfo1To2Conversion = {
"familyName" : "Some Font (Family Name)",
"styleMapFamilyName" : "Some Font Regular (Style Map Family Name)",
"styleMapStyleName" : "regular",
"styleName" : "Regular (Style Name)",
"unitsPerEm" : 1000,
"ascender" : 750,
"capHeight" : 750,
"xHeight" : 500,
"descender" : -250,
"italicAngle" : -12.5,
"versionMajor" : 1,
"versionMinor" : 0,
"year" : 2008,
"copyright" : "Copyright Some Foundry.",
"trademark" : "Trademark Some Foundry",
"note" : "A note.",
"macintoshFONDFamilyID" : 15000,
"macintoshFONDName" : "SomeFont Regular (FOND Name)",
"openTypeNameCompatibleFullName" : "Some Font Regular (Compatible Full Name)",
"openTypeNameDescription" : "Some Font by Some Designer for Some Foundry.",
"openTypeNameDesigner" : "Some Designer",
"openTypeNameDesignerURL" : "http://somedesigner.com",
"openTypeNameLicense" : "License info for Some Foundry.",
"openTypeNameLicenseURL" : "http://somefoundry.com/license",
"openTypeNameManufacturer" : "Some Foundry",
"openTypeNameManufacturerURL" : "http://somefoundry.com",
"openTypeNamePreferredFamilyName" : "Some Font (Preferred Family Name)",
"openTypeNamePreferredSubfamilyName": "Regular (Preferred Subfamily Name)",
"openTypeNameCompatibleFullName" : "Some Font Regular (Compatible Full Name)",
"openTypeNameUniqueID" : "OpenType name Table Unique ID",
"openTypeNameVersion" : "OpenType name Table Version",
"openTypeOS2VendorID" : "SOME",
"openTypeOS2WeightClass" : 500,
"openTypeOS2WidthClass" : 5,
"postscriptDefaultWidthX" : 400,
"postscriptFontName" : "SomeFont-Regular (Postscript Font Name)",
"postscriptFullName" : "Some Font-Regular (Postscript Full Name)",
"postscriptSlantAngle" : -12.5,
"postscriptUniqueID" : 4000000,
"postscriptWeightName" : "Medium",
"postscriptWindowsCharacterSet" : 1
}
expectedFontInfo2To1Conversion = {
"familyName" : "Some Font (Family Name)",
"menuName" : "Some Font Regular (Style Map Family Name)",
"fontStyle" : 64,
"styleName" : "Regular (Style Name)",
"unitsPerEm" : 1000,
"ascender" : 750,
"capHeight" : 750,
"xHeight" : 500,
"descender" : -250,
"italicAngle" : -12.5,
"versionMajor" : 1,
"versionMinor" : 0,
"copyright" : "Copyright Some Foundry.",
"trademark" : "Trademark Some Foundry",
"note" : "A note.",
"fondID" : 15000,
"fondName" : "SomeFont Regular (FOND Name)",
"fullName" : "Some Font Regular (Compatible Full Name)",
"notice" : "Some Font by Some Designer for Some Foundry.",
"designer" : "Some Designer",
"designerURL" : "http://somedesigner.com",
"license" : "License info for Some Foundry.",
"licenseURL" : "http://somefoundry.com/license",
"createdBy" : "Some Foundry",
"vendorURL" : "http://somefoundry.com",
"otFamilyName" : "Some Font (Preferred Family Name)",
"otStyleName" : "Regular (Preferred Subfamily Name)",
"otMacName" : "Some Font Regular (Compatible Full Name)",
"ttUniqueID" : "OpenType name Table Unique ID",
"ttVersion" : "OpenType name Table Version",
"ttVendor" : "SOME",
"weightValue" : 500,
"widthName" : "Medium (normal)",
"defaultWidth" : 400,
"fontName" : "SomeFont-Regular (Postscript Font Name)",
"fullName" : "Some Font-Regular (Postscript Full Name)",
"slantAngle" : -12.5,
"uniqueID" : 4000000,
"weightName" : "Medium",
"msCharSet" : 0,
"year" : 2008
}

View file

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

View file

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

View file

@ -0,0 +1,218 @@
import robofab.interface.all.dialogs
reload(robofab.interface.all.dialogs)
from robofab.interface.all.dialogs import *
import unittest
__all__ = [
"AskString", #x
"AskYesNoCancel", #x
"FindGlyph",
"GetFile", #x
"GetFolder", #x
"GetFileOrFolder", #x
"Message", #x
"OneList",
"PutFile", #x
"SearchList",
"SelectFont",
"SelectGlyph",
"TwoChecks",
"TwoFields",
"ProgressBar",
]
class DialogRunner(object):
def __init__(self):
prompt = "The prompt for %s."
message = "The message for %s."
title = "The title for %s."
informativeText = "The informative text for %s."
fileTypes = ['ufo']
fileName = "The_filename.txt"
self.fonts = fonts = [self.makeTestFont(n) for n in range(4)]
t = "AskString"
try:
print "About to try", t
print "\t>>>", AskString(
message=prompt%t,
value='',
title=title%t
)
except NotImplementedError:
print t, "is not implemented."
t = "AskYesNoCancel"
try:
print "About to try", t
print "\t>>>", AskYesNoCancel(
message=prompt%t+" default set to 0",
title=title%t,
default=0,
informativeText=informativeText%t
)
print "\t>>>", AskYesNoCancel(
message=prompt%t+" default set to 1",
title=title%t,
default=1,
informativeText=informativeText%t
)
except NotImplementedError:
print t, "is not implemented."
t = "GetFile"
try:
print "About to try", t
print "\t>>>", GetFile(
message=message%t+" Only fileTypes "+`fileTypes`,
title=title%t,
directory=None,
fileName=fileName,
allowsMultipleSelection=False,
fileTypes=fileTypes
)
print "\t>>>", GetFile(
message=message%t+" All filetypes, allow multiple selection.",
title=title%t,
directory=None,
fileName=fileName,
allowsMultipleSelection=True,
fileTypes=None
)
except NotImplementedError:
print t, "is not implemented."
t = "GetFolder"
try:
print "About to try", t
print "\t>>>", GetFolder(
message=message%t,
title=title%t,
directory=None,
allowsMultipleSelection=False
)
print "\t>>>", GetFolder(
message=message%t + " Allow multiple selection.",
title=title%t,
directory=None,
allowsMultipleSelection=True
)
except NotImplementedError:
print t, "is not implemented."
t = "GetFileOrFolder"
try:
print "About to try", t
print "\t>>>", GetFileOrFolder(
message=message%t+" Only fileTypes "+`fileTypes`,
title=title%t,
directory=None,
fileName=fileName,
allowsMultipleSelection=False,
fileTypes=fileTypes
)
print "\t>>>", GetFileOrFolder(
message=message%t + " Allow multiple selection.",
title=title%t,
directory=None,
fileName=fileName,
allowsMultipleSelection=True,
fileTypes=None
)
except NotImplementedError:
print t, "is not implemented."
t = "Message"
try:
print "About to try", t
print "\t>>>", Message(
message=message%t,
title=title%t,
informativeText=informativeText%t
)
except NotImplementedError:
print t, "is not implemented."
t = "PutFile"
try:
print "About to try", t
print "\t>>>", PutFile(
message=message%t,
fileName=fileName,
)
except NotImplementedError:
print t, "is not implemented."
# t = "SelectFont"
# try:
#print "About to try", t
# print "\t>>>", SelectFont(
# message=message%t,
# title=title%t,
# allFonts=fonts,
# )
# except NotImplementedError:
# print t, "is not implemented."
# t = 'SelectGlyph'
# try:
#print "About to try", t
# print "\t>>>", SelectGlyph(
# font=fonts[0],
# message=message%t,
# title=title%t,
# )
# except NotImplementedError:
# print t, "is not implemented."
print 'No more tests.'
def makeTestFont(self, number):
from robofab.objects.objectsRF import RFont as _RFont
f = _RFont()
f.info.familyName = "TestFamily"
f.info.styleName = "weight%d"%number
f.info.postscriptFullName = "%s %s"%(f.info.familyName, f.info.styleName)
# make some glyphs
for name in ['A', 'B', 'C']:
g = f.newGlyph(name)
pen = g.getPen()
pen.moveTo((0,0))
pen.lineTo((500, 0))
pen.lineTo((500, 800))
pen.lineTo((0, 800))
pen.closePath()
return f
class DialogTests(unittest.TestCase):
def setUp(self):
from robofab.interface.all.dialogs import test
test()
def tearDown(self):
pass
def testDialogs(self):
import robofab.interface.all.dialogs
dialogModuleName = robofab.interface.all.dialogs.platformApplicationModuleName
application = robofab.interface.all.dialogs.application
if application is None and dialogModuleName == "dialogs_mac_vanilla":
# in vanilla, but not in a host application, run with executeVanillaTest
print
print "I'm running these tests with executeVanillaTest"
from vanilla.test.testTools import executeVanillaTest
executeVanillaTest(DialogRunner)
else:
print
print "I'm running these tests natively in"
DialogRunner()
if __name__ == "__main__":
from robofab.test.testSupport import runTests
runTests()

View file

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

View file

@ -0,0 +1,150 @@
import os
import tempfile
import shutil
import unittest
from robofab.test.testSupport import getDemoFontGlyphSetPath
from robofab.glifLib import GlyphSet, glyphNameToFileName, READ_MODE
from robofab.tools.glyphNameSchemes import glyphNameToShortFileName
GLYPHSETDIR = getDemoFontGlyphSetPath()
class GlyphSetTests(unittest.TestCase):
def setUp(self):
self.dstDir = tempfile.mktemp()
os.mkdir(self.dstDir)
def tearDown(self):
shutil.rmtree(self.dstDir)
def testRoundTrip(self):
srcDir = GLYPHSETDIR
dstDir = self.dstDir
src = GlyphSet(srcDir)
dst = GlyphSet(dstDir)
for glyphName in src.keys():
g = src[glyphName]
g.drawPoints(None) # load attrs
dst.writeGlyph(glyphName, g, g.drawPoints)
# compare raw file data:
for glyphName in src.keys():
fileName = src.contents[glyphName]
org = file(os.path.join(srcDir, fileName), READ_MODE).read()
new = file(os.path.join(dstDir, fileName), READ_MODE).read()
self.assertEqual(org, new, "%r .glif file differs after round tripping" % glyphName)
def testRebuildContents(self):
gset = GlyphSet(GLYPHSETDIR)
contents = gset.contents
gset.rebuildContents()
self.assertEqual(contents, gset.contents)
def testReverseContents(self):
gset = GlyphSet(GLYPHSETDIR)
d = {}
for k, v in gset.getReverseContents().items():
d[v] = k
org = {}
for k, v in gset.contents.items():
org[k] = v.lower()
self.assertEqual(d, org)
def testReverseContents2(self):
src = GlyphSet(GLYPHSETDIR)
dst = GlyphSet(self.dstDir)
dstMap = dst.getReverseContents()
self.assertEqual(dstMap, {})
for glyphName in src.keys():
g = src[glyphName]
g.drawPoints(None) # load attrs
dst.writeGlyph(glyphName, g, g.drawPoints)
self.assertNotEqual(dstMap, {})
srcMap = dict(src.getReverseContents()) # copy
self.assertEqual(dstMap, srcMap)
del srcMap["a.glif"]
dst.deleteGlyph("a")
self.assertEqual(dstMap, srcMap)
def testCustomFileNamingScheme(self):
def myGlyphNameToFileName(glyphName, glyphSet):
return "prefix" + glyphNameToFileName(glyphName, glyphSet)
src = GlyphSet(GLYPHSETDIR)
dst = GlyphSet(self.dstDir, myGlyphNameToFileName)
for glyphName in src.keys():
g = src[glyphName]
g.drawPoints(None) # load attrs
dst.writeGlyph(glyphName, g, g.drawPoints)
d = {}
for k, v in src.contents.items():
print k, v
d[k] = "prefix" + v
self.assertEqual(d, dst.contents)
def testGetUnicodes(self):
src = GlyphSet(GLYPHSETDIR)
unicodes = src.getUnicodes()
for glyphName in src.keys():
g = src[glyphName]
g.drawPoints(None) # load attrs
if not hasattr(g, "unicodes"):
self.assertEqual(unicodes[glyphName], [])
else:
self.assertEqual(g.unicodes, unicodes[glyphName])
class FileNameTests(unittest.TestCase):
def testDefaultFileNameScheme(self):
self.assertEqual(glyphNameToFileName("a", None), "a.glif")
self.assertEqual(glyphNameToFileName("A", None), "A_.glif")
self.assertEqual(glyphNameToFileName("Aring", None), "Aring_.glif")
self.assertEqual(glyphNameToFileName("F_A_B", None), "F__A__B_.glif")
self.assertEqual(glyphNameToFileName("A.alt", None), "A_.alt.glif")
self.assertEqual(glyphNameToFileName("A.Alt", None), "A_.Alt_.glif")
self.assertEqual(glyphNameToFileName(".notdef", None), "_notdef.glif")
self.assertEqual(glyphNameToFileName("T_H", None), "T__H_.glif")
self.assertEqual(glyphNameToFileName("T_h", None), "T__h.glif")
self.assertEqual(glyphNameToFileName("t_h", None), "t_h.glif")
self.assertEqual(glyphNameToFileName('F_F_I', None), "F__F__I_.glif")
self.assertEqual(glyphNameToFileName('f_f_i', None), "f_f_i.glif")
def testShortFileNameScheme(self):
print "testShortFileNameScheme"
self.assertEqual(glyphNameToShortFileName("a", None), "a.glif")
self.assertEqual(glyphNameToShortFileName("A", None), "A_.glif")
self.assertEqual(glyphNameToShortFileName("aE", None), "aE_.glif")
self.assertEqual(glyphNameToShortFileName("AE", None), "A_E_.glif")
self.assertEqual(glyphNameToShortFileName("a.alt", None), "a_alt.glif")
self.assertEqual(glyphNameToShortFileName("A.alt", None), "A__alt.glif")
self.assertEqual(glyphNameToShortFileName("a.alt#swash", None), "a_alt_swash.glif")
self.assertEqual(glyphNameToShortFileName("A.alt", None), "A__alt.glif")
self.assertEqual(glyphNameToShortFileName(".notdef", None), "_notdef.glif")
self.assertEqual(glyphNameToShortFileName("f_f_i", None), "f_f_i.glif")
self.assertEqual(glyphNameToShortFileName("F_F_I", None), "F__F__I_.glif")
self.assertEqual(glyphNameToShortFileName("acircumflexdieresis.swash.alt1", None), "acircumflexdieresi0cfc8352.glif")
self.assertEqual(glyphNameToShortFileName("acircumflexdieresis.swash.alt2", None), "acircumflexdieresi95f5d2e8.glif")
self.assertEqual(glyphNameToShortFileName("Acircumflexdieresis.swash.alt1", None), "A_circumflexdieresed24fb56.glif")
self.assertEqual(glyphNameToShortFileName("F#weight0.800_width0.425", None), "F__weight0_800_width0_425.glif")
self.assertEqual(glyphNameToShortFileName("F#weight0.83245511_width0.425693567", None), "F__weight0_8324551c9a4143c.glif")
self.assertEqual(len(glyphNameToShortFileName("F#weight0.83245511_width0.425693567", None)), 31)
def testShortFileNameScheme_clashes(self):
# test for the condition in code.robofab.com ticket #5
name1 = glyphNameToShortFileName('Adieresis', None)
name2 = glyphNameToShortFileName('a_dieresis', None)
self.assertNotEqual(name1, name2)
name1 = glyphNameToShortFileName('AE', None)
name2 = glyphNameToShortFileName('aE', None)
self.assertNotEqual(name1, name2)
if __name__ == "__main__":
from robofab.test.testSupport import runTests
import sys
if len(sys.argv) > 1 and os.path.isdir(sys.argv[-1]):
GLYPHSETDIR = sys.argv.pop()
runTests()

View file

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

View file

@ -0,0 +1,54 @@
"""This test suite for various FontLab-specific tests."""
import FL # needed to quickly raise ImportError if run outside of FL
import os
import tempfile
import unittest
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
def getDigests(font):
digests = {}
for glyphName in font.keys():
pen = DigestPointPen()
font[glyphName].drawPoints(pen)
digests[glyphName] = pen.getDigest()
return digests
class FLTestCase(unittest.TestCase):
def testUFOVersusGlifImport(self):
font = NewFont()
font.readUFO(getDemoFontPath(), doProgress=False)
d1 = getDigests(font)
font.close(False)
font = NewFont()
importAllGlifFiles(font.naked(), getDemoFontGlyphSetPath(), doProgress=False)
d2 = getDigests(font)
self.assertEqual(d1, d2)
font.close(False)
def testTwoUntitledFonts(self):
font1 = NewFont()
font2 = NewFont()
font1.unitsPerEm = 1024
font2.unitsPerEm = 2048
self.assertNotEqual(font1.unitsPerEm, font2.unitsPerEm)
font1.update()
font2.update()
font1.close(False)
font2.close(False)
if __name__ == "__main__":
from robofab.test.testSupport import runTests
runTests()

View file

@ -0,0 +1,203 @@
"""This test suite for ufo glyph methods"""
import unittest
import os
import tempfile
import shutil
from robofab.objects.objectsRF import RFont
from robofab.test.testSupport import getDemoFontPath
from robofab.pens.digestPen import DigestPointPen
from robofab.pens.adapterPens import SegmentToPointPen, FabToFontToolsPenAdapter
class ContourMethodsTestCase(unittest.TestCase):
def setUp(self):
self.font = RFont(getDemoFontPath())
def testReverseContour(self):
for glyph in self.font:
pen = DigestPointPen()
glyph.drawPoints(pen)
digest1 = pen.getDigest()
for contour in glyph:
contour.reverseContour()
contour.reverseContour()
pen = DigestPointPen()
glyph.drawPoints(pen)
digest2 = pen.getDigest()
self.assertEqual(digest1, digest2, "%r not the same after reversing twice" % glyph.name)
def testStartSegment(self):
for glyph in self.font:
pen = DigestPointPen()
glyph.drawPoints(pen)
digest1 = pen.getDigest()
for contour in glyph:
contour.setStartSegment(2)
contour.setStartSegment(-2)
pen = DigestPointPen()
glyph.drawPoints(pen)
digest2 = pen.getDigest()
self.assertEqual(digest1, digest2, "%r not the same after seting start segment twice" % glyph.name)
def testAppendSegment(self):
for glyph in self.font:
pen = DigestPointPen()
glyph.drawPoints(pen)
digest1 = pen.getDigest()
for contour in glyph:
contour.insertSegment(2, "curve", [(100, 100), (200, 200), (300, 300)])
contour.removeSegment(2)
pen = DigestPointPen()
glyph.drawPoints(pen)
digest2 = pen.getDigest()
self.assertEqual(digest1, digest2, "%r not the same after inserting and removing segment" % glyph.name)
class GlyphsMethodsTestCase(ContourMethodsTestCase):
def testCopyGlyph(self):
for glyph in self.font:
pen = DigestPointPen()
glyph.drawPoints(pen)
digest1 = pen.getDigest()
copy = glyph.copy()
pen = DigestPointPen()
copy.drawPoints(pen)
digest2 = pen.getDigest()
self.assertEqual(digest1, digest2, "%r not the same after copying" % glyph.name)
self.assertEqual(glyph.lib, copy.lib, "%r's lib not the same after copying" % glyph.name)
self.assertEqual(glyph.width, copy.width, "%r's width not the same after copying" % glyph.name)
self.assertEqual(glyph.unicodes, copy.unicodes, "%r's unicodes not the same after copying" % glyph.name)
def testMoveGlyph(self):
for glyph in self.font:
pen = DigestPointPen()
glyph.drawPoints(pen)
digest1 = pen.getDigest()
glyph.move((100, 200))
glyph.move((-100, -200))
pen = DigestPointPen()
glyph.drawPoints(pen)
digest2 = pen.getDigest()
self.assertEqual(digest1, digest2, "%r not the same after moving twice" % glyph.name)
def testScaleGlyph(self):
for glyph in self.font:
pen = DigestPointPen()
glyph.drawPoints(pen)
digest1 = pen.getDigest()
glyph.scale((2, 2))
glyph.scale((.5, .5))
pen = DigestPointPen()
glyph.drawPoints(pen)
digest2 = pen.getDigest()
self.assertEqual(digest1, digest2, "%r not the same after scaling twice" % glyph.name)
def testSegmentPenInterface(self):
for glyph in self.font:
digestPen = DigestPointPen(ignoreSmoothAndName=True)
pen = SegmentToPointPen(digestPen)
glyph.draw(pen)
digest1 = digestPen.getDigest()
digestPen = DigestPointPen(ignoreSmoothAndName=True)
glyph.drawPoints(digestPen)
digest2 = digestPen.getDigest()
self.assertEqual(digest1, digest2, "%r not the same for gl.draw() and gl.drawPoints()" % glyph.name)
def testFabPenCompatibility(self):
for glyph in self.font:
digestPen = DigestPointPen(ignoreSmoothAndName=True)
pen = FabToFontToolsPenAdapter(SegmentToPointPen(digestPen))
glyph.draw(pen)
digest1 = digestPen.getDigest()
digestPen = DigestPointPen(ignoreSmoothAndName=True)
glyph.drawPoints(digestPen)
digest2 = digestPen.getDigest()
self.assertEqual(digest1, digest2, "%r not the same for gl.draw() and gl.drawPoints()" % glyph.name)
def testComponentTransformations(self):
from robofab.objects.objectsRF import RComponent
name = "baseGlyphName"
c = RComponent(name, transform=(1,0,0,1,0,0))
# get values
assert c.baseGlyph == "baseGlyphName"
assert c.transformation == c.transformation
assert c.scale == (1,1)
assert c.offset == (0,0)
# set values
c.offset = (12,34)
assert c.transformation == (1, 0, 0, 1, 12, 34)
c.offset = (0,0)
assert c.transformation == (1,0,0,1,0,0)
c.scale = (12,34)
assert c.transformation == (12, 0, 0, 34, 0, 0)
class SaveTestCase(ContourMethodsTestCase):
def testSaveAs(self):
path = tempfile.mktemp(".ufo")
try:
keys1 = self.font.keys()
self.font.save(path)
keys2 = self.font.keys()
keys1.sort()
keys2.sort()
self.assertEqual(keys1, keys2)
self.assertEqual(self.font.path, path)
font2 = RFont(path)
keys3 = font2.keys()
keys3.sort()
self.assertEqual(keys1, keys3)
finally:
if os.path.exists(path):
shutil.rmtree(path)
def testSaveAs2(self):
path = tempfile.mktemp(".ufo")
# copy a glyph
self.font["X"] = self.font["a"].copy()
# self.assertEqual(self.font["X"].name, "X")
# remove a glyph
self.font.removeGlyph("a")
keys1 = self.font.keys()
try:
self.font.save(path)
self.assertEqual(self.font.path, path)
keys2 = self.font.keys()
keys1.sort()
keys2.sort()
self.assertEqual(keys1, keys2)
font2 = RFont(path)
keys3 = font2.keys()
keys3.sort()
self.assertEqual(keys1, keys3)
finally:
if os.path.exists(path):
shutil.rmtree(path)
def testCustomFileNameScheme(self):
path = tempfile.mktemp(".ufo")
libKey = "org.robofab.glyphNameToFileNameFuncName"
self.font.lib[libKey] = "robofab.test.test_objectsUFO.testGlyphNameToFileName"
try:
self.font.save(path)
self.assertEqual(os.path.exists(os.path.join(path,
"glyphs", "test_a.glif")), True)
finally:
if os.path.exists(path):
shutil.rmtree(path)
def testGlyphNameToFileName(glyphName, glyphSet):
from robofab.glifLib import glyphNameToFileName
return "test_" + glyphNameToFileName(glyphName, glyphSet)
if __name__ == "__main__":
from robofab.test.testSupport import runTests
runTests()

View file

@ -0,0 +1,149 @@
"""This test suite test general Pen stuff, it should not contain
FontLab-specific code.
"""
import unittest
from robofab.pens.digestPen import DigestPointPen
from robofab.pens.adapterPens import SegmentToPointPen, PointToSegmentPen
from robofab.pens.adapterPens import GuessSmoothPointPen
from robofab.pens.reverseContourPointPen import ReverseContourPointPen
from robofab.test.testSupport import getDemoFontGlyphSetPath
from robofab.glifLib import GlyphSet
class TestShapes:
# Collection of test shapes. It's probably better to add these as
# glyphs to the demo font.
def square(pen):
# a simple square as a closed path (100, 100, 600, 600)
pen.beginPath()
pen.addPoint((100, 100), "line")
pen.addPoint((100, 600), "line")
pen.addPoint((600, 600), "line")
pen.addPoint((600, 100), "line")
pen.endPath()
square = staticmethod(square)
def onCurveLessQuadShape(pen):
pen.beginPath()
pen.addPoint((100, 100))
pen.addPoint((100, 600))
pen.addPoint((600, 600))
pen.addPoint((600, 100))
pen.endPath()
onCurveLessQuadShape = staticmethod(onCurveLessQuadShape)
def openPath(pen):
# a simple square as a closed path (100, 100, 600, 600)
pen.beginPath()
pen.addPoint((100, 100), "move")
pen.addPoint((100, 600), "line")
pen.addPoint((600, 600), "line")
pen.addPoint((600, 100), "line")
pen.endPath()
openPath = staticmethod(openPath)
def circle(pen):
pen.beginPath()
pen.addPoint((0, 500), "curve")
pen.addPoint((0, 800))
pen.addPoint((200, 1000))
pen.addPoint((500, 1000), "curve")
pen.addPoint((800, 1000))
pen.addPoint((1000, 800))
pen.addPoint((1000, 500), "curve")
pen.addPoint((1000, 200))
pen.addPoint((800, 0))
pen.addPoint((500, 0), "curve")
pen.addPoint((200, 0))
pen.addPoint((0, 200))
pen.endPath()
circle = staticmethod(circle)
class RoundTripTestCase(unittest.TestCase):
def _doTest(self, shapeFunc, shapeName):
pen = DigestPointPen(ignoreSmoothAndName=True)
shapeFunc(pen)
digest1 = pen.getDigest()
digestPen = DigestPointPen(ignoreSmoothAndName=True)
pen = PointToSegmentPen(SegmentToPointPen(digestPen))
shapeFunc(pen)
digest2 = digestPen.getDigest()
self.assertEqual(digest1, digest2, "%r failed round tripping" % shapeName)
def testShapes(self):
for name in dir(TestShapes):
if name[0] != "_":
self._doTest(getattr(TestShapes, name), name)
def testShapesFromGlyphSet(self):
glyphSet = GlyphSet(getDemoFontGlyphSetPath())
for name in glyphSet.keys():
self._doTest(glyphSet[name].drawPoints, name)
def testGuessSmoothPen(self):
glyphSet = GlyphSet(getDemoFontGlyphSetPath())
for name in glyphSet.keys():
digestPen = DigestPointPen()
glyphSet[name].drawPoints(digestPen)
digest1 = digestPen.getDigest()
digestPen = DigestPointPen()
pen = GuessSmoothPointPen(digestPen)
glyphSet[name].drawPoints(pen)
digest2 = digestPen.getDigest()
self.assertEqual(digest1, digest2)
class ReverseContourTestCase(unittest.TestCase):
def testReverseContourClosedPath(self):
digestPen = DigestPointPen()
TestShapes.square(digestPen)
d1 = digestPen.getDigest()
digestPen = DigestPointPen()
pen = ReverseContourPointPen(digestPen)
pen.beginPath()
pen.addPoint((100, 100), "line")
pen.addPoint((600, 100), "line")
pen.addPoint((600, 600), "line")
pen.addPoint((100, 600), "line")
pen.endPath()
d2 = digestPen.getDigest()
self.assertEqual(d1, d2)
def testReverseContourOpenPath(self):
digestPen = DigestPointPen()
TestShapes.openPath(digestPen)
d1 = digestPen.getDigest()
digestPen = DigestPointPen()
pen = ReverseContourPointPen(digestPen)
pen.beginPath()
pen.addPoint((600, 100), "move")
pen.addPoint((600, 600), "line")
pen.addPoint((100, 600), "line")
pen.addPoint((100, 100), "line")
pen.endPath()
d2 = digestPen.getDigest()
self.assertEqual(d1, d2)
def testReversContourFromGlyphSet(self):
glyphSet = GlyphSet(getDemoFontGlyphSetPath())
digestPen = DigestPointPen()
glyphSet["testglyph1"].drawPoints(digestPen)
digest1 = digestPen.getDigest()
digestPen = DigestPointPen()
pen = ReverseContourPointPen(digestPen)
glyphSet["testglyph1.reversed"].drawPoints(pen)
digest2 = digestPen.getDigest()
self.assertEqual(digest1, digest2)
if __name__ == "__main__":
from robofab.test.testSupport import runTests
runTests()

View file

@ -0,0 +1,110 @@
def test():
"""
# some tests for the ps Hints operations
>>> from robofab.world import RFont, RGlyph
>>> g = RGlyph()
>>> g.psHints.isEmpty()
True
>>> h = RGlyph()
>>> i = g + h
>>> i.psHints.isEmpty()
True
>>> i = g - h
>>> i.psHints.isEmpty()
True
>>> i = g * 2
>>> i.psHints.isEmpty()
True
>>> i = g / 2
>>> i.psHints.isEmpty()
True
>>> g.psHints.vHints = [(100, 50), (200, 50)]
>>> g.psHints.hHints = [(100, 50), (200, 5)]
>>> not g.psHints.isEmpty()
True
>>> gc = g.copy()
>>> gc.psHints.asDict() == g.psHints.asDict()
True
# multiplication
>>> v = g.psHints * 2
>>> v.asDict() == {'vHints': [[200, 100], [400, 100]], 'hHints': [[200, 100], [400, 10]]}
True
# division
>>> v = g.psHints / 2
>>> v.asDict() == {'vHints': [[50.0, 25.0], [100.0, 25.0]], 'hHints': [[50.0, 25.0], [100.0, 2.5]]}
True
# multiplication with x, y, factor
# vertically oriented values should respond different
>>> v = g.psHints * (.5, 10)
>>> v.asDict() == {'vHints': [[1000, 500], [2000, 500]], 'hHints': [[50.0, 25.0], [100.0, 2.5]]}
True
# division with x, y, factor
# vertically oriented values should respond different
>>> v = g.psHints / (.5, 10)
>>> v.asDict() == {'vHints': [[10.0, 5.0], [20.0, 5.0]], 'hHints': [[200.0, 100.0], [400.0, 10.0]]}
True
# rounding to integer
>>> v = g.psHints / 2
>>> v.round()
>>> v.asDict() == {'vHints': [(50, 25), (100, 25)], 'hHints': [(50, 25), (100, 3)]}
True
# "ps hint values calculating with a glyph"
# ps hint values as part of glyphmath operations.
# multiplication
>>> h = g * 10
>>> h.psHints.asDict() == {'vHints': [[1000, 500], [2000, 500]], 'hHints': [[1000, 500], [2000, 50]]}
True
# division
>>> h = g / 2
>>> h.psHints.asDict() == {'vHints': [[50.0, 25.0], [100.0, 25.0]], 'hHints': [[50.0, 25.0], [100.0, 2.5]]}
True
# x, y factor multiplication
>>> h = g * (.5, 10)
>>> h.psHints.asDict() == {'vHints': [[1000, 500], [2000, 500]], 'hHints': [[50.0, 25.0], [100.0, 2.5]]}
True
# x, y factor division
>>> h = g / (.5, 10)
>>> h.psHints.asDict() == {'vHints': [[10.0, 5.0], [20.0, 5.0]], 'hHints': [[200.0, 100.0], [400.0, 10.0]]}
True
# "font ps hint values"
>>> f = RFont()
>>> f.psHints.isEmpty()
True
>>> f.psHints.blueScale = .5
>>> f.psHints.blueShift = 1
>>> f.psHints.blueFuzz = 1
>>> f.psHints.forceBold = True
>>> f.psHints.hStems = (100, 90)
>>> f.psHints.vStems = (500, 10)
>>> not f.psHints.isEmpty()
True
>>> f.insertGlyph(g, name="new")
<RGlyph for None.new>
>>> f["new"].psHints.asDict() == g.psHints.asDict()
True
"""
if __name__ == "__main__":
import doctest
doctest.testmod()

File diff suppressed because it is too large Load diff