Skip to content

Instantly share code, notes, and snippets.

@typesupply
Forked from typemytype/quick_ufoLibTest.py
Last active June 14, 2018 17:30
Show Gist options
  • Save typesupply/687cbaf731b6d1f3b997bbc8846ce65a to your computer and use it in GitHub Desktop.
Save typesupply/687cbaf731b6d1f3b997bbc8846ce65a to your computer and use it in GitHub Desktop.
Test for ufoLib validation and speed improvements.
"""
This compares the speed of experimental changes in ufoLib
with the experimental ufoLib in fontTools. This requires the
"Roboto-Regular.ufo" font to be located next to this script.
"""
import os
import shutil
import timeit
import cProfile
from fontTools.pens.basePen import NullPen
from fontTools.ufoLib.objects import Font as FontToolsFont
import ufoLib
from defcon import Font as DefconFont
# -----------
# Source Font
# -----------
sourcePath = os.getcwd()
sourcePath = os.path.join(sourcePath, "Roboto-Regular.ufo")
# Make sure the source is UFO 3
source = DefconFont(sourcePath)
if source.ufoFormatVersion < 3:
print("Converting source to UFO 3...")
source.save(formatVersion=3)
del source
# -------
# Objects
# -------
class UFOLibFont(object):
UFOReader = ufoLib.UFOReader
UFOWriter = ufoLib.UFOWriter
def __init__(self, path=None, validate=False):
self.validate = validate
self.lib = Lib()
self.groups = Groups()
self.kerning = Kerning()
self.info = Info()
self.defaultLayerName = None
self.layers = Layers()
if path:
self._read(path)
def _read(self, path):
reader = self.UFOReader(path, validate=self.validate)
reader.readInfo(self.info)
self.kerning.update(reader.readKerning())
self.groups.update(reader.readGroups())
self.lib.update(reader.readLib())
self.defaultLayerName = reader.getDefaultLayerName()
for layerName in reader.getLayerNames():
layer = Layer()
glyphSet = reader.getGlyphSet(layerName)
for glyphName in glyphSet.keys():
glyph = Glyph()
glyphSet.readGlyph(glyphName, glyph, glyph.getPointPen())
layer[glyphName] = glyph
self.layers[layerName] = layer
def save(self, path=None):
if self.validate is not None:
writer = self.UFOWriter(path, validate=self.validate)
else:
writer = self.UFOWriter(path)
writer.writeInfo(self.info)
writer.writeGroups(self.groups)
writer.writeKerning(self.kerning)
writer.writeLib(self.lib)
# no images...yet
# no data...yet
self.layers.save(writer, defaultLayerName=self.defaultLayerName)
writer.setModificationTime()
def keys(self):
return self.layers[self.defaultLayerName].keys()
def __iter__(self):
return iter(self.layers[self.defaultLayerName])
def __len__(self):
return len(self.layers[self.defaultLayerName])
def __getitem__(self, glyphName):
return self.layers[self.defaultLayerName][glyphName]
class Lib(dict): pass
class Info(object): pass
class Groups(dict): pass
class Kerning(dict): pass
class Layers(dict):
def save(self, writer, defaultLayerName):
for layerName, layer in self.items():
isDefaultLayer = layerName == defaultLayerName
glyphSet = writer.getGlyphSet(layerName=layerName, defaultLayer=isDefaultLayer)
layer.save(glyphSet)
glyphSet.writeLayerInfo(layer)
writer.writeLayerContents(self.keys())
class Layer(dict):
def __iter__(self):
for key in self.keys():
yield self[key]
def save(self, glyphSet):
for glyphName, glyph in sorted(self.items()):
glyphSet.writeGlyph(glyph.name, glyph, glyph.drawPoints)
class Glyph(object):
def __init__(self):
self.name = None
self.width = 0
self.unicodes = []
self.lib = Lib()
self.contours = []
self.components = []
self.anchors = []
self.guidelines = []
@property
def unicode(self):
if self.unicodes:
return self.unicodes[0]
return None
def draw(self, pen):
pointPen = ufoLib.pointPen.PointToSegmentPen(pen)
self.drawPoints(pointPen)
def drawPoints(self, pointPen):
for contourIdentfier, contourData in self.contours:
pointPen.beginPath(identifier=contourIdentfier)
for point, segmentType, smooth, name, kwargs in contourData:
pointPen.addPoint(point, segmentType, smooth, name, **kwargs)
pointPen.endPath()
for baseGlyph, transformation, identifier in self.components:
pointPen.addComponent(baseGlyph, transformation, identifier=identifier)
def getPen(self):
pen = ufoLib.pointPen.SegmentToPointPen(self.getPointPen())
return pen
def getPointPen(self):
return self
def beginPath(self, identifier=None):
self.contours.append([identifier, []])
def addPoint(self, point, segmentType, smooth, name, **kwargs):
self.contours[-1][-1].append((point, segmentType, smooth, name, kwargs))
def addComponent(self, baseGlyph, transformation, identifier=None):
self.components.append((baseGlyph, transformation, identifier))
def endPath(self):
pass
# -----------
# Speed Tests
# -----------
NUMBER = 5
print("Speed reading...")
def testRead(module, fontClass, validate):
def run():
if validate is not None:
f = fontClass(sourcePath, validate=validate)
else:
f = fontClass(sourcePath)
for g in f:
g.draw(NullPen())
r = timeit.Timer(run).timeit(number=NUMBER) / float(NUMBER)
print("%s: validate=%r %f" % (module, validate, r))
testRead("ufoLib", UFOLibFont, True)
testRead("ufoLib", UFOLibFont, False)
testRead("fontTools", FontToolsFont, None)
print("Speed writing...")
def testWrite(module, fontClass, validate):
if validate is not None:
f = fontClass(sourcePath, validate=validate)
else:
f = fontClass(sourcePath)
for g in f:
g.draw(NullPen())
savePaths = [os.path.splitext(sourcePath)[0] + "-test%d.ufo" % i for i in range(NUMBER)]
removePaths = list(savePaths)
def run():
path = savePaths.pop(0)
f.save(path)
r = timeit.Timer(run).timeit(number=NUMBER) / float(NUMBER)
for path in removePaths:
shutil.rmtree(path)
print("%s: validate=%r %f" % (module, validate, r))
testWrite("ufoLib", UFOLibFont, True)
testWrite("ufoLib", UFOLibFont, False)
testWrite("fontTools", FontToolsFont, None)
# -------------
# Profile Tests
# -------------
print("Profile writing...")
def profileWrite():
f = UFOLibFont(sourcePath, validate=False)
for g in f:
g.draw(NullPen())
savePath = os.path.splitext(sourcePath)[0] + "-test1.ufo"
if os.path.exists(savePath):
shutil.rmtree(savePath)
def run():
f.save(savePath)
p = cProfile.Profile()
p.runcall(run)
p.print_stats(sort="tottime")
#shutil.rmtree(savePath)
profileWrite()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment