Browse files

♪ Build-scripts and a Rakefile

Now you can compile a font! Works with both FontForge and the Adobe Font
Development Kit for OpenType. The former is what we aim for as our main
compiler, as the latter is closed-source, but to have both is great for
testing the UFO spec.

Run ''rake'' to generate the font. ''rake diagnostics'' will give you an
overview of your current build environment and advise you how to proceed if it
can’t find the right build tools.

If you want more control you can use tools/ which provides a command
line interface: you can choose input and output files, and which compiler to
  • Loading branch information...
codingisacopingstrategy committed Mar 8, 2011
1 parent f37be0c commit 3cd3c6b05a67ccb36b2a7d6f4aafa4064b8220e7
Showing with 235 additions and 0 deletions.
  1. +38 −0 Rakefile
  2. +20 −0 tools/
  3. +2 −0 tools/ufo2otf/
  4. +35 −0 tools/ufo2otf/
  5. +140 −0 tools/ufo2otf/
@@ -0,0 +1,38 @@
# Todo: this uses posix specific path seperator ('/'),
# so it won’t work on Windows.
# Right now, OpenBaskerville.otf is regenerated when a file inside
# OpenBaskerville.ufo is changed.
# This should be: if the contents of *any* UFO folder changes,
# the *corresponding* otf should be regenerated. This allows for additional
# font styles, and for the reuse of this Rakefile across font projects.
# version = `python ./tools/`
UFO ='OpenBaskerville.ufo/**/*.*')
task :default => "OpenBaskerville.otf"
desc "Generate OpenType Font"
file 'OpenBaskerville.otf' => UFO do
puts "Generating otf.."
sh "python ./tools/ OpenBaskerville.ufo OpenBaskerville.otf"
puts "Done!"
desc "Install OpenType Fonts"
task :install => "OpenBaskerville.otf" do
if RUBY_PLATFORM.include? 'darwin'
sh "cp *.otf ~/Library/Fonts/"
puts "Not implemented yet for this platform.
Please contribute patches!
You can consult for details on where to install fonts:"
desc "Diagnose font build environment"
task :diagnostics do
sh 'python ./tools/ufo2otf/'
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Todo: use argparse. add option to print diagnostics.
# Make ufo2otf a python package, provide this script as a command line utility
# (I couldn’t figure out how, in distutils, one can specify for scripts to be
# installed into bin?)
"""Calls the compiler.
from sys import argv, exit
import ufo2otf
if 3 <= len(argv) <= 4:
args = argv[1:]
compiler = ufo2otf.Compiler(*args)
print """usage: ./ infile.ufo outfile.otf [compiler]
compiler: fontforge or afdko"""
@@ -0,0 +1,2 @@
from compilers import Compiler
from diagnostics import diagnostics, FontError
@@ -0,0 +1,35 @@
from diagnostics import diagnostics, known_compilers, FontError
diagnostics = diagnostics()
class Compiler:
def __init__(self,infile,outfile,compiler=None):
self.infile = infile
self.outfile = outfile
if compiler:
if compiler in known_compilers and diagnostics[compiler]:
self.compile = getattr(self, compiler)
raise FontError(compiler, diagnostics)
if diagnostics['fontforge']:
self.compile = self.fontforge
elif diagnostics['afdko']:
self.compile = self.afdko
raise FontError(diagnostics=diagnostics)
def fontforge(self):
import fontforge
font =
def afdko(self):
import ufo2fdk
from robofab.objects.objectsRF import RFont
compiler = ufo2fdk.OTFCompiler()
font = RFont(self.infile)
@@ -0,0 +1,140 @@
# -*- coding: utf-8 -*-
import subprocess
import textwrap
known_compilers = ['fontforge','afdko']
def diagnostics():
Returns a dict with information about the state of possible build tools on
the users system. This is probably better implemented as a class.
diagnostics = {}
# FontForge
# Via the Python bindings?
import fontforge
diagnostics['pyff'] = diagnostics['fontforge'] = True
except ImportError:
diagnostics['pyff'] = diagnostics['fontforge'] = False
# The main fontforge program (with or without bindings):
diagnostics['ff'] = subprocess.Popen(['which','fontforge'], stdout=subprocess.PIPE).communicate()[0].strip()
# The Adobe Font Development Kit for OpenType
# The ufo2fdk python bridge:
import ufo2fdk
diagnostics['ufo2fdk'] = True
except ImportError:
diagnostics['ufo2fdk'] = False
# The fdk itself:
diagnostics['fdk'] = subprocess.Popen(['which','makeotf'], stdout=subprocess.PIPE).communicate()[0].strip()
# We need both:
diagnostics['afdko'] = False
if diagnostics['ufo2fdk'] and diagnostics['fdk']:
diagnostics['afdko'] = True
return diagnostics
class FontError(Exception):
Raise nicely verbose errors, should help encourage users in setting
up a font development workflow.
def __init__(self, compiler=False,diagnostics=diagnostics()):
self.compiler = compiler
self.diagnostics = diagnostics
self.err = []
def _format(self, text):
return textwrap.dedent(text)
def fontforge_error(self):
if self.diagnostics['fontforge']:
return """\
Fontforge appears to be installed an accesible via python.
elif self.diagnostics['ff']:
return """\
FontForge appears to be installed (in %s),
but the python hooks can’t be found.
Depending on your OS, you might be able to install them
$ sudo apt-get install python-fontforge
Or you might have to reinstall fontforge with the
--enable-pyextension flag:
brew install fontforge --enable-pyextension

This comment has been minimized.


codingisacopingstrategy Mar 8, 2011


Actually, this isn’t true—yet. I still have to finish my update to the fontforge Homebrew formula… But this definitely gives me impetus

""" % self.diagnostics['ff']
return """\
Neither FontForge itself nor its python hooks can be found by the
build script. This means you will have to install FontForge:
ubuntu, debian:
sudo apt-get fontforge python-fontforge
os x:
brew install fontforge --enable-pyextension
More info see:"""
def afdko_error(self):
if self.diagnostics['afdko']:
return """\
The AFDKO appears to be installed and accesible via UFO2FDK.
elif self.diagnostics['ufo2fdk']:
return """\
We can find the ufo2fdk bridge but we’re not sure about the
AFDKO itself. Please check for installation
elif self.diagnostics['fdk']:
return """\
It appears the AFDKO is installed, but we can’t import the ufo2fdk
bridge. Ufo2fdk requires fonttools and robofab. For installation
instructions see:
return """\
We can’t find the ufo2fdk bridge and we can’t find the
AFDKO itself either. Please refer to for further
def error_message(self):
if not self.compiler:
return [self.fontforge_error(), self.afdko_error()]
elif self.compiler not in known_compilers:
return ["""\
You specified an unkown compiler.
Known compilers are: %s""" %
', '.join(self.known_compilers())]
err = ["The build script tried to compile with %s" %
error = getattr(self, self.compiler + '_error')
if self.compiler == 'afdko':
if self.diagnostics['fontforge']:
err.append('FontForge appears to be working, though.')
err.extend(['You might also want to try out FontForge. It’s free and open source.',self.fontorge_error()])
return err
def __str__(self):
return '\n\n'.join(map(self._format,self.error_message()))
if __name__ == "__main__":
# This will print diagnostics to stdout
e = FontError()
print e

1 comment on commit 3cd3c6b


This comment has been minimized.


klepas commented on 3cd3c6b Mar 8, 2011

Hey, awesome. Will have to test when I get time. (:

Please sign in to comment.