defcon is a set of UFO based objects optimized for use in font editing applications. It is built to be fast and flexible.
The source code can be browsed here. Or, you can check it out with Subversion at http://svn.typesupply.com/packages/defcon.
I'm working on more extensive documentation, but for now, here are the main points:
Subclassing
defcon is built for subclassing. This allows you to build on the basic functionality provided by the defcon object. Subclassing is easy. For example, I want a custom glyph class:
class MyGlyph(Glyph):
def mySpecialMethod(self, something=None):
print "mySpecialMethod"
To make the font object use this class when it creates glyphs, you register the class when the font object is constructed:
font = Font(path, glyphClass=MyGlyph)
Notifications
defcon uses the notification concept for internal and external object communication. When something happens to an object, it generally a notification about what has happened. You may listen for these notifications by subscribing to the object you want to observe. For example, I want to be notified when a glyph changes:
glyph.addObserver(observer=myObject, methodName="glyphChanged", notification="Glyph.Changed")
The arguments are as follows:
| observer | The object that should be notified when the notification is posted. |
| methodName | The method that should be called when the notificaiton is posted. The observer must have this method. This method must accept a notification argument. A Notification object will be passed to this argument. |
| notification | A string defining the notification to listen for. In defcon, this is usually "ClassName?.Changed" |
The Notification object has the following attributes:
| name | The name of the notification. |
| object | The object that is responsible for the notification being posted. For example, if the notification of the Glyph.Changed variety, the object will be the glyph that changed. |
| data | Data about the change. This is most often None. |
UFO Observing
The font object can semi-smartly handle changes caused by external changes to the contents of the UFO it represents. It doesn't do this automatically all the time because that would be very expensive. Instead, you call a font method when you want it to look for changes:
changes = font.testForExternalChanges()
This will return a dictionary of this form:
{
"info" : boolean representing if fontinfo.plist has changed,
"kerning" : boolean representing if kerning.plist has changed,
"groups" : boolean representing if groups.plist has changed,
"lib" : boolean representing if lib.plist has changed,
"modifiedGlyphs" : a list of glyph names that have been changed,
"addedGlyhs" : a list of glyph names that have been added to the UFO,
"deletedGlyphs" : a list of glyph names that have been removed from the UFO
}
It is up to you to decide what to do with these changes. When you have decided, you call a font method to force a reload of the data. For example:
font.reloadInfo() font.reloadKerning() font.reloadGroups() font.reloadLib() font.reloadGlyphs(["A", "B", "C"])
Unicode Data
defcon has a powerful CMAP style object located at font.unicodeData. The basic behavior is like a standard dict. However, this object not only serves character mapping, it can give you info like the Unicode script, category or block for a glyph. It also allows you to sort glyphs in intelligent ways. All of this data is kept up to date as you change the glyphs and the font.
The basic character mapping works like this:
v = font.unicodeData.unicodeForGlyphName("A")
glyphName = font.unicodeData.glyphNameForUnicode(32)
This works fine for glyphs with defined Unicode values, but what about alternates? The UnicodeData? object can allow you to get "pseudo Unicode values" for glyphs with no defined Unicode value.
v = font.unicodeData.pseudoUnicodeForGlyphName("A.alt")
It does this by breaking the glyph name into parts and then looking for Unicode values for the parts. In this case, it would look for a Unicode value for "A" and return it.
(Note: should "pseudo" be "implied"? Maybe that makes more sense.)
The object can also tell you information about a glyph name:
script = font.unicodeData.scriptForGlyphName("A", allowPseudoUnicode=True)
block = font.unicodeData.blockForGlyphName("A", allowPseudoUnicode=True)
category = font.unicodeData.categoryForGlyphName("A", allowPseudoUnicode=True)
You can also get the lowest possible base glyph for an accented glyph based on the Unicode decomposition rules:
font.unicodeData.decompositionBaseForGlyphName("Aacute", allowPseudoUnicode=True)
The most powerful thing that the object does is glyph sorting. To sort glyphs you define a list of sort descriptors and pass them to the sorting method. For example:
sortDescriptors = [ dict(type="category", allowPseudoUnicode=True), dict(type="suffix"), dict(type="unicode", allowPseudoUnicode=True), ] sortedGlyhNames = font.unicodeData.sortGlyphNames(font.keys(), sortDescriptors=sortDescriptors)
The documentation string for this method gives lots more information on this.
Representations and Representation Factories
One of the painful parts of developing an app that modifies glyphs is managing the visual representation of the glyphs. When the glyph changes, all representations of it in cached data, the user interface, etc. need to change. There are several ways to handle this, but they are all fairly cumbersome. defcon gives you a very simple way of dealing with this: representations and representation factories.
A representation is object that represents a glyph. It can be a string of GLIF text, a NSBezierPath, a tree of point location tuples or anything else you can imagine. A representation factory is a function that creates a representation. You don't manage the representations yourself. Rather, you register the factory and then ask the glyphs for the representations you need. When the glyphs change, the related representations are destroyed and recreated as needed.
For example, here is a representation factory that creates a NSBezierath representation:
def NSBezierPathFactory(glyph, font):
from fontTools.pens.cocoaPen import CocoaPen
pen = CocoaPen(font)
glyph.draw(pen)
return pen.path
To register this factory, you do this:
from defcon.objects.glyph import addRepresentationFactory
addRepresentationFactory("NSBezierPath", NSBezierPathFactory)
The first argument is the name of the factory. The second is the function.
Now, when you need a representation, you simply do this:
path = glyph.getRepresentation("NSBezierFactory")
The first argument must be the name of a known factory. You can have any number of keyword arguments after that. Those arguments will be passed to the factory. This is useful if you need to do something like get an image of a certain size. glyph.getRepresentation("MyNSImageFactory", size=(40, 40)).
When the representation is first requested, the factory will be used to create the representation. The representation will be cached inside of the glyph. The next time you request the representation, it will pass you back the cached representation. This makes it super fast. When you change the glyph in any way, point structure, width, etc., the cached representation will be discarded and the next time you ask for the representation, the glyph will make you a new one.
