1. Introduction

(Last updated in August 2024)

Smith is a Python-based framework for building, testing and releasing fonts. It is based on waf. Smith orchestrates and integrates various utilities to make a standards-based open font design and production workflow much easier to manage.

Building a font involves numerous steps and various programs, which, if done by hand, would be prohibitively slow. Even working out what those steps are can take a lot of work. Smith uses a dedicated file at the root of the project (the file is python-based) to allow the user to describe how to build the font. By chaining the different build steps intelligently, smith reduces build times to seconds rather than minutes or hours, and makes build, test, fix, repeat cycles very manageable. By making these processes repeatable, including for a number of fonts at the same time, your project can be shared with others simply, or - better yet - it can be included in a CI (Continuous Integration) system. This allows for fonts (and their various source formats) to truly be libre/open source software and developed with open and collaborative methodologies.

Smith is Copyright (c) 2011-2024 SIL International (www.sil.org) and is released under the BSD license.

waf is Copyright (c) 2005-2011 Thomas Nagy.

1.1. Installation

A file called wscript needs to be created to control the build process. The convention is that this file is placed at the root of your font project source tree. This file is in fact a small python program, but the way it is run is designed to hide that as much as possible from the unsuspecting user.

Smith is really a larger toolchain with many dependencies that you install from a Docker registry which means you don’t have to install any of its fairly large number of components manually. In this manual, we assume you are using that approach as described at SIL Font Development Guide. Remember that smith is only the director and a wide range of utilities do the actual work. Installing just smith by itself (the python program) will not get you very far.

1.2. Moving your project to smith

Besides taking inspiration from the way smith is used in public projects, like the ones available on github.com/silnrsi for example, you can use the following command to populate a brand new project with basic templates and standard folders:

smith start

Then you can start adjusting the various files to the specifics of your font project.

1.3. Running builds

The heart of the build system is the wscript file that controls the build process. This is done by the python program creating a set of component objects. It then takes these objects and allows the user to run various build commands on them.

Waf, on which smith is built, works by creating a build directory into which all the results are stored. This is by design and a useful feature as it leaves the source directories pristine and makes for easy clearing up. The build directory is created using the command:

smith configure

This process creates the new results directory, checks that all the tools that smith needs to achieve the build as described in wscript are available, and sets up various internal environment variables. Thus, if any changes are made to the wscript that indicate what extra tools are needed, then the configure command needs to be rerun.

After configuration you can now launch a build. This is done using:

smith build

This creates development artifacts of the various components configured to be built. But it does not create any publishable releases - or packages that you can share with someone else - these need another command:

smith zip

This creates a zip of all the generated files and the corresponding documentation. Since this zip is targeted at Windows, text files have their line endings changed to CR LF. This is tagged with development version numbers.

smith tarball

This does the same work as the zip command except it uses LF Linux/macOS line endings and creates a .tar.xz, a compressed tarball. This is tagged with development version numbers.

smith release

This command does two things. First, it builds the various components, but marks the build for release. So things like font version strings no longer contain any development information in the form of git revision numbers, etc. Secondly, it builds the various release packages (zip and tarball). It also provides checksums, cryptographic signatures to allow comparison against the zip and tarball. This separate checksum file will allow to verify that what is distributed is really what has been produced.

smith graide

This creates a subdirectory called graide/ that contains one .cfg file per font. This config file will be read by Graide, the Graphite IDE to allow easier testing and development of smart font behaviours. If the font has no graphite smarts, no configuration file is created.

smith alltests

This creates font tests output by chaining all the available tests. The Tests section will go into more details.

smith clean

This removes the various files created by smith build in the build directory.

smith distclean

This removes the build directory completely, including any temporary files at the root of the project folder.

1.4. Writing your wscript

The wscript file is a Python program, but the internal environment is set up to minimise the amount of actual programming that needs to be done. There is no setup needed in the file, and object registration is automatic. It is possible to add waf specific extensions to the file, see details in the waf manual.

The basic steps needed to describe an entire build process is to create writing system component objects. These objects are font() or designspace and package(). Specific details on what information each of these objects requires is given in the corresponding sections of this document. Likewise examples are given in the sections.

2. Tutorial

In this tutorial we will examine a number of wscript files. The first section is the largest and builds up a complex font creation scenario from humble beginnings. We will use the UFO sources from the Andika-Mtihani project, and call our local copy "Example".

2.1. font1 - Simplicity

We start with a simple, single font.

1
2
font(target = 'Example-Regular.ttf',
     source = 'source/Example-Regular.ufo')

In line 1, we create a new font object that we want the system to build. We specify the target filename. This file will be created in the build tree (results or what is set in the out variable). Line 2 specifies where to find the source file. Notice that the target file is a .ttf file while the source is a .ufo file. Smith will use the necessary commands to build from one format to the other.

With this as our wscript file, we can build our font:

smith configure

This is the first step in building any project. This command tells smith to set up the build environment and search out all the programs that it may need for the various tasks we may ask of smith. If a necessary program is missing smith will stop at that point and indicate an error. Some programs are not strictly necessary and smith can run with reduced functionality without them. Such missing programs are listed in orange. All other programs that smith searches for and finds are listed, along with their locations, in green. So you can see exactly which program smith will use for any particular task. This is hepful especially in cases where you may have a locally installed self-compiled version: you can more easily see if smith has found the version in /usr/local/ (or other local paths) instead of the stock packaged version.

smith build

This command tells smith to go and build all the objects the wscript says to be built. In this case just the simple Example-Regular.ttf which will appear in results. Not very exciting, but a good start.

2.2. font2 - Multiple fonts

Most font packages consist of more than one font file and this project is no exception. Can we scale our project to handle more than one file?

1
2
3
for weight in ('-Regular', '-Bold'):
    font(target = 'Example' + weight + '.ttf',
        source = 'source/Example' + weight + '.ufo')

This example shows the power of integrating a description with a full programming language. wscript files are python programs, albeit very enhanced ones. So we can use any python-type constructs we might need. Usually the need is slight, and we show a typical example here.

Line 1 is the start of a loop. The lines below that are indented within the loop will be repeated for each value in the list. The first value is -Regular and the second is -Bold. Each time around the loop, the variable weight is set to the appropriate string. We will then use that variable to help set the appropriate values in the two font objects we are creating.

Each time around the loop, we create a new font object. In line 2 we create a new font object whose target font filename is dependent on the weight variable which is set to the various strings from the list at the start of the loop. So we will end up creating two fonts. One called Example-Regular.ttf as before, and one called Example-Bold.ttf. Line 3 gives the source files for each of these fonts.

It may seem easier just to expand out the loop and have two font() object commands but, as the complexity of this font() grows, we will see the value of using a loop. The advantage of adding the loop early is that we can make appropriate use of weight.

Now when we come to build this project, we will get two fonts:

smith configure
smith build

2.3. font3 - Packaged

It’s good that we can create multiple fonts, but what do we do with them then? There are two typical products that people want from a font project: a .zip file containing the fonts and corresponding files (with Windows line-endings CR LF) and a tarball (with Linux/macOS line-endings LF). Smith can create these two products from a wscript, but it needs just a little more information to do so:

1
2
3
4
5
6
APPNAME = 'Example'
VERSION = '0.0.1'

for weight in ('-Regular', '-Bold', '-Italic', '-BoldItalic'):
    font(target = 'Example' + weight + '.ttf',
        source = 'source/Example' + weight + '.ufo')

Line 1 gives the base name of the products that will be created and line 2 gives the version of that product. Notice that the version variable is a string and does not have to be numeric. Case is important here, these are, in effect, magic variables we are setting that smith looks up.

To build this project, we do the same as before, but we can also use two extra commands:

smith configure
smith build
smith zip
smith tarball

smith zip will create Example-0.0.1-dev-(git-commit-id).zip in the releases folder inside the results folder. (The git-commit-id part, e.g. 66d16eM, will be the first 7 characters taken from the git revision id. We assume you are working from within a git repository.)

smith tarball will create Example-0.0.1-dev-(git-commit-id).tar.xz in the releases folder inside the results folder. (The git-commit-id part, e.g. 66d16eM, will be the first 7 characters taken from the git revision id. We assume you are working from within a git repository.)

Notice the -dev- suffix to indicate that these are development versions, which means they have not been tagged as a stable and tested release.

This zip, or tarball file, contains the four target fonts the build created, since we have now added Bold and Bold-Italic as extra weights in the loop.

We also added extra text files at the root of the project folder: README.txt, README.md, FONTLOG.txt, OFL-FAQ.txt. These are just text files and more documentation than font sources, but they are nice to have and will help users and other developers of your font. We will go into more detail on packaging in the dedicated section.

2.4. font4 - Internal processing

Before our example gains smart font support and grows in complexity, there is one area of control that is worth examining. For the most part, when creating a wscript one fills in the various 'forms' that create the objects, and smith knows what needs to happen to make things turn out right. But while this makes for pretty tutorials, real world projects have unique quirks that require the ability to add commands into the processing or to create things dynamically. In this exercise we will add a process to the source font:

1
2
3
4
5
6
APPNAME = 'Example'
VERSION = '0.0.1'

for weight in ('-Regular', '-Bold', '-Italic', '-BoldItalic'):
    font(target = 'Example' + weight + '.ttf',
        source = process('source/Example' + weight + '.ufo', cmd('psfchangettfglyphnames ${SRC} ${DEP} ${TGT}')))

The interest lies in line 8. Here we use a process() function to tell smith that we want it to run a command over the source font before converting it to a .ttf. A process() function takes a file which already exists (either in the source tree or one that is generated by another process) and then runs the list of cmd() function results over it in order. In this case, the command is to run a simple script that removes glyphs from the background layer in a UFO. The command string takes some study. The program takes two command line parameters, an input font file and an output font file. We represent these in the command string by ${DEP} (the dependent file) as the input and ${TGT} as the output file. smith will fill these in appropriately when it comes to run the command. In addition, note the initial '../' at the start of the command string. This is because all commands in smith are run from the results directory and so we have to go up one level to get back to the project root where the wscript file is and then from there we can navigate to the actual script.

The rest of the new lines in this exercise are simply extra variables being used to make the file easier to read, otherwise some of the lines would become excessively long and confusing. Notice that all the magic variables in a wscript that smith considers are all caps. That is if you use a variable name with a lowercase letter in it, you are sure to be safe from smith assuming some special meaning to that variable.

For the most part, we are not very interested in precisely what smith is doing to get the results we want, but sometimes it helps to know, and all that cryptic output streaming by isn’t much help. But there is a way to get something more helpful. First we need to get back to a completely pristine source tree:

smith distclean

Now we can configure and run in a way that has smith tell us what it is doing:

smith configure
smith build -j1 -v

Thankfully, on a modern terminal, the colourising helps makes more sense of the voluminous information. But it is helpful once you learn to read it. The timestamped runner lines give the precise command-lines that are run at each stage of the build.

Clearly, the key to getting this output is in the command-line options to smith build. The -v says to output the extra information, (v stands for verbosity). But since smith tries to use multiple processors if you have them, to speed up the build (for example without the -j1, my build runs in 0.479s), it means the output can get interleaved. It is therefore wise to restrict smith to a single process (j stands for job) while outputting this information, and this is done using -j1.

2.5. font5 - Smarts and Basic Tests

In this exercise we grow our description to add OpenType and Graphite tables and also add some tests.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
APPNAME = 'Example'
VERSION = '0.0.1'

TESTDIR = 'tests'
TESTRESULTSDIR = 'tests'
DESC_SHORT = "Derivative Foobar"

for weight in ('-Regular', '-Bold', '-Italic', '-BoldItalic'):
    font(target = "Example" + weight + '.ttf',
        source = process('source/Example' + weight + '.ufo', 
            cmd('../tools/ufobackgroundclean' + ' ${DEP} ${TGT}')),
        opentype = internal(),
        graphite = gdl('source/' + APPNAME + '.gdl', 
            master = 'source/graphite/master.gdl'),
        ap = 'source/' + APPNAME + '.xml',
        script = 'latn',
        fret = fret(params = "-r")
        )

Line 12 tells smith how the OpenType tables will be generated for this font. It is possible to compile in VOLT tables, or, as here to use the internal description already in the font. The internal() does very little, but it does indicate to smith that the font has OpenType tables and that they should be tested.

Line 13 tells smith how the Graphite tables are to be added. There is currently only one form for Graphite source, and that is GDL. The gdl() object tells smith how to generate and bring together the various files that typically make up a Graphite description. A typical Graphite project has an autogenerated component (which is the first parameter to the gdl() (variable.gdl) and a common core .gdl file that is hand authored (the master parameter). Smith then does the work to generate the files and compile them into the font.

Line 15 talks about an attachment point database. This file holds information about glyphs in the font that cannot be held by TTF. Most importantly this file holds the positions of anchor points on the glyphs, and these positions are used when autogenerating smart code, either for volt() or gdl() or whatever. Depending on the source file format, the file may be autogenerated or be required as part of the source files.

These three lines are all it takes to add a sophisticated smart font build system to the font creation step. The rest of this section will look at basic font testing.

The basic principle of font testing in smith is that there is a directory containing test data, by default tests. This data is then applied to the various fonts and results are generated in the build tree. Test data can be of various formats, but the easiest to work with is simple .txt files that are treated as one paragraph per line files.

Line 4 gives the directory where the test files may be found. Since it is outside the project tree rooted in the directory containing the wscript, we have to specify where in the buildtree we want the test results to be put. Line 8 specifies that subdirectory.

Smith allows for user defined tests, but there are some defaults built-in, which we will examine here.

smith pdfs

This tells smith to generate pdfs of each test file for each font for each smart font technology. That’s quite a few for each, but it means that you can look at any particular font and its smart rendering technology for each test. The files end up in results/tests based on the value of TESTRESULTSDIR.

So for example, the test file Short.txt will generate 4 pdf files: for the regular font: Short_Example_ot.pdf, Short_Example_gr.pdf and for the bold font: Short_Example-bold_ot.pdf and Short_Example-bold_gr.pdf. The _ot extension is used for OpenType rendered texts and _gr for the Graphite rendered texts. The texts are rendered using XeTeX.

The other file in the tests directory is tests.htxt. The h in htxt tells smith to preprocess the file to convert strings of the form \uxxxx into the corresponding Unicode character before rendering. This makes it easier to create test files.

Line 16 is an important line for OpenType testing since it specifies which script to use when running the OpenType shaping engine.

Another aspect of testing is regression testing. Can we find out what has changed between this font and a known good version? The way this works is that we store known good versions of the fonts and then have smith run tests against both fonts and compare the results. The default directory to keep the font files in is references/.

smith test

The results end up in the TESTRESULTSDIR/tests directory as .html files. If there are no differences, the files are 0 length.

A further target that is useful is the ability to create font reports that show all the glyphs in a font. We set this as a font product rather than a kind of test, in line 17. The default target filename is the same as the target .ttf file but with a .pdf extension instead. The file is built as part of smith build.

There are two other targets that this wscript enables:

smith waterfall
smith xfont

Line 9 specifies a string that will be used in creating the waterfall files and also the cross font summary files. smith waterfall creates one file per font and technology and stores it in the waterfall sub directory of the TESTRESULTSDIR, prefixing each font and technology with waterfall. smith xfont creates one file per technology in the TESTRESULTSDIR called Crossfont_ot.pdf or Crossfont_gr.pdf that contains the test string output with the font name, one per line.

Another feature of smith is its ability to integrate with graide. Graide is a graphically based IDE for developing GDL Graphite source code. It also incorporates a Graphite debugger to help font developers see how their code executes.

smith graide
graide -p graide/Example.cfg

Running smith graide causes smith to create graide configuration files in a graide/ subdirectory. This is one of the few commands that creates files outside of the results/ tree. The user can then run graide referencing one of these configuration files. One file is made per font.

A word of advice. Since, most often, smith does not generate .gdx files when it runs grcompiler (.gdx files are grcompiler debug files), it is best to recompile the font on loading into graide.

The configuration is designed to restrict graide to just editing GDL. If you want to use graide to adjust attachment points or add them, then you will need to enable writing to the AP.xml, in the graide configuration, and you are then responsible for propagating those changes back from the AP.xml to your source font.

2.6. font6 - Metadata

So far we have concerned ourselves with the mechanics of font creation. But in order to release a font package we also need to concern ourselves with the metadata that is involved in producing a font release.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
APPNAME = 'Example'
VERSION = '0.0.2'

TESTDIR = 'tests'
TESTRESULTSDIR = 'tests'
DESC_SHORT = "Foobar is a derivative for learning"

for weight in ('-Regular', '-Bold', '-Italic', '-BoldItalic'):
    font(target = process(APPNAME + weight + '.ttf', name('Foobar')),
         source = process('source/Example' + weight + '.ufo', cmd('../tools/ufobackgroundclean ${DEP} ${TGT}')),
         version = VERSION,
         woff = woff(),
         opentype = internal(),
         graphite = gdl('source/' + APPNAME + '.gdl', master = 'source/graphite/master.gdl'),
         ap = 'source/' + APPNAME + '.xml',
         script = 'latn',
         fret = fret(params = "-r")
        )

def configure(conf) :
    conf.find_program('../tools/uforeport')

We have used an existing font: Andika Mtihani as our base font. The font has been released under the OFL (Open Font License) and so we are free to modify and develop our own derivative under that license. To avoid any confusion it is better to change the name to something different for our derivative, for example: Foobar. We do this in a number of places in the wscript: Line 19 changes both the name of the font file generated (and all derived products), but it also processes that font file to change the internal name to "Foobar", using a process() and a name() function that acts like a cmd() that is suited to font renaming.

We also set the internal version of the font using a version parameter.

The Web Open Font Format (WOFF) is designed particularly for distribution of webfonts and smith can generate such files from the target .ttf font file. The default parameters for this object, take the font target filename as the basis of the woff filename, which is sufficient for our needs. Both in v1 and v2 of the format.

2.7. font7 - More Tests

This section is for those interested in doing more advanced types of testing. For most projects there is no need to go to this level of complexity and many users never need to use these capabilities. So this exercise has been placed after the exercise that pretty much completes font creation. We also try to introduce as many advanced techniques as we can, even if the results end up being a little contrived.

Font testing is not limited to just the inbuilt test types. Smith supports the integration of other test programs as you the user desires, so long as they are command line based, non-interactive and report generators.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
APPNAME = 'Example'
VERSION = '0.0.3'

TESTDIR = 'tests'
TESTRESULTSDIR = 'tests'
DESC_SHORT = "Foobar is a derivative for learning"

for weight in ('-Regular', '-Bold', '-Italic', '-BoldItalic'):
    font(target = process(APPNAME + weight + '.ttf', name("Foobar")),
         source = process('source/Example' + weight + '.ufo'),
         version = VERSION,
         woff = woff(),
         opentype = internal(),
         graphite = gdl('source/' + APPNAME + '.gdl', master = 'source/graphite/master.gdl'),
         ap = 'source/' + APPNAME + '.xml',
         script = 'latn',
         fret = fret(params = "-r")
        )

def configure(ctx) :
    ctx.find_program('../tools/uforeport')

The interesting section is in lines 17-21. These lines create a fonttest object that is then referenced within the font at line 34. A fonttest object adds new smith commands. This example adds the three smith commands: pdfs, test and report. Notice that the smith pdfs command is actually implemented using a fonttest() object. The targets parameter to fonttest uses a python data structure called a dictionary. This is indicated by the { at the start (and } at the end). Dictionary elements consist of a string before a : and a value after it. The value before the : is known as the key and the value after as the value. So a dictionary is set of key, value pairs. In our case, the keys here indicate smith commands and the values are the test objects that get executed for the command.

The first two commands use default test objects appropriate to the type of command. The pdfs command executes a tex() object that does all the xetex processing of test files. Likewise the test command executes a default tests() object which implements the regression testing.

Our new command report also uses a tests() object. But in this, we give another dictionary of key, value pairs. The key is a subdirectory under the TESTRESULTSDIR and the value is a cmd() object that gives the command to execute. In this case, we are running the uforeport script. The reference to '${SRC[0]}` says to use the first element from the inputs. The inputs has 3 elements: the font, the text file to test and the corresponding references/ font file. We only need the first of these and list indices all start from 0 in python. In addition, we use the parameter coverage to say that we only want to run tests one per font, and not one per test file per shaper per font. The > ${TGT} says that the output that the program produces, which would normally be printed on the screen is to be sent to the target log file instead.

Another thing we have changed is that rather than hardwiring various of the specialist programs into our wscript, we now will get smith to go and search for them. We introduce another new Python concept: the function. Each smith command will search for a function in your wscript with the same name as the command and will execute it. For more information of what to do then you should read the manual for the underlying framework that smith is built on, which is waf. The variable passed to us is a waf context that can be used to do various things like add commands to the build process, etc. In our case we want to have smith search for various programs. find_program() is the key that tells smith to search for the programs. In the case of ufobackgroundclean, that is necessary for the build, so if it is missing we want the configuration to fail. But in the case of the test script, we only lose the ability of that one test type if the script is missing, so we don’t want to fail the configuration. This is a marginal call, but we do at least get to see the pattern for achieving this. In each case find_program() takes a list of paths to search, and it only searches those directories, not directories below those, unless explicitly listed.

2.8. font8 - Designspace

This final example shows how to use the designspace() object.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
APPNAME = 'Example'
VERSION = '0.0.3'

TESTDIR = 'tests'
TESTRESULTSDIR = 'tests'
DESC_SHORT = "Derivative"

# Get VERSION and BUILDLABEL from Regular UFO; must be first function call:
getufoinfo('source/masters/' + familyname + '-Regular' + '.ufo')


designspace('source/' +  process('source/Example' + weight + '.ufo'),
        target = process('${DS:FILENAME_BASE}.ttf', *cmds),
                instances = ['Example Regular']
         version = VERSION,
         woff = woff(),
         opentype = internal(),
         graphite = gdl('source/' + APPNAME + '.gdl', master = 'source/graphite/master.gdl'),
         ap = 'source/' + APPNAME + '.xml',
         script = 'latn',
         fret = fret(params = "-r")
        )

def configure(ctx) :
    ctx.find_program('../tools/uforeport')

3. Font parameters and smarts

The minimum attributes a font object needs are target and source. For example, the following wscript file is about as simple as one can get:

1
2
font(target = 'results/Example-Regular.ttf',
     source = 'Example-Regular.ttf')

This short file does more than might be expected. First of all it copies the input file Example-Regular.ttf to an output file results/Example-Regular.ttf (We will use Unix style / for path separators). This copy may seem redundant, but it is necessary for the rest of the system to work, and not all source fonts are unmodified .ttf files. If there are tests .txt files in a directory called tests then these can be run against this font.

Notice that an input and an output file may not have the same name. Even if the output file ends up in results/ it still corresponds to a real input file that may or may not be in results/. So file paths must also be unique if the files are unique.

What if the source isn’t a .ttf file? Then, we can simply change the above example to:

1
2
font(target = 'results/Example-Regular.ttf',
     source = 'Example-Regular.ufo')

and the system will automatically convert the UFO source font to TrueType as it is copied into the results directory tree. Here we wouldn’t actually need the results/ prefix to the target because the filename isn’t the same as the source attribute.

The complete list of core attributes to a font are:

target

Output file for the generated font within results.

source

Basic design file used to generate the initial form of the output font.

params

Command-line parameters passed to the program that converts the source font into the target font. This program changes depending on the source file format. For UFO it is psfufo2ttf.

version

This takes a version number in the form 'x'.'y' (a floating point number) and sets the output font to have that version. It may also be a tuple of the form (x.y, "text") where the text will be appended to the version string inside the font. If the tuple form is not used, then "text" is set to the package buildversion. If this is not wanted then use (x.y, "").

sfd_master

This attribute specifies a FontForge file that should be merged with the source FontForge file when creating the target. If the sfd_master file is the same as the source, then sfdmeld is not run. (You will have to install FontForge yourself as it is no longer part of the smith toolchain default dependencies)

ap

Attachment point database associated with the source font.

ap_params

Parameters passed to the program that creates the ap database from the source font.

classes

Classes .xml file that adds class information to the attachment point database before conversion into smart font source code.

no_test

If set to True will not include the font in the font tests. This can be set after the font object is created.

package

Package object to insert this font into. If not specified the global package is used.

typetuner

Specifies that typetuner should be run on the target and to use the given file as the typetuner configuration xml file.

3.1. OpenType

There are multiple ways of adding OpenType information to a font. One is to already have it in the source font. In this case, we need to indicate that we are working with an OpenType font, even if everything is internal to the font. The font builder needs to know for font testing purposes or if the font is generated from a legacy font.

1
2
3
font(target = 'results/Example-Regular.ttf',
     source = 'source/Example-Regular.ufo',
     opentype = internal())

This will generate tests pertinent to OpenType testing. See the section on font tests.

One approach sometimes used for FontForge based projects is to keep all the lookups in one font and then to share these lookups across all the fonts in a project. For this we simply specify a sfd_master attribute and the font builder will use sfdmeld to integrate the lookups in the master into each font as it is built. (You will have to install FontForge yourself as it is no longer part of the smith toolchain default dependencies)

1
2
3
font(target = 'results/Example-Regular.ttf',
     source = 'Example-Regular.sfd',
     sfd_master = 'mymaster.sfd')

Obviously, if the sfd_master attribute is the same as the source file then no merge occurs. This is an alternative way of specifying that the font is OpenType.

Another approach to adding OpenType tables to a font is to use an external tool or text file to create the lookups and then to have smith compile them into the font. Two formats for source files are supported: Microsoft’s VOLT (Visual OpenType Layout Tool) and Adobe’s Feature File.

3.1.1. VOLT

This approach uses a command-line VOLT compiler to integrate the .vtp source into the font. In addition, the .vtp source is autogenerated from a source and any other elements that go to make the final integrated source. For example, we show a maximal volt() integration to show all the components and then discuss them.

Notice that while the initial parameter to such objects as volt is required, all named parameters are optional.

1
2
3
4
5
6
font(target = 'results/Example-Regular.ttf',
     source = 'Example-Regular.ttf',
     opentype = volt('Example.vtp',
                     master = 'Example.vtp'),
     ap = 'Example.xml',
     classes = 'project_classes.xml')

We define the .vtp file to create for this font which will then be compiled into the font using volt2ttf as Example.vtp. We also declare a shared master volt project that is shared between all the fonts (well, at least this one!). In building a largely automated volt project, a program make_volt is used that can take attachment point information from an xml database Example.xml. This may be augmented with class information using project_classes.xml. These two file references are within the font rather than the volt details because they are shared with other smart font technologies particularly Graphite.

The complete list of attributes to Volt() are:

master

The volt source that is processed against the font to generate the font specific volt to be compiled into the font.

make_params

These parameters are passed to the make_volt process. The value is a string of parameters.

params

These parameters are passed to volt2ttf to modify the compiling of the volt source into OpenType tables.

no_make

If this attribute is present, make_volt isn’t run and the first parameter is assumed to be the specific .vtp for this font.

no_typetuner

The VOLT2TTF program used to compile the volt into opentype, also has the capability to emit an XML control file for typetuner. By default, if the font requests typetuner be run, the volt2ttf options will be set to generate this file. Setting this attribute stops this generation from happening and you will need to create the file some other way.

3.1.2. FEA

The Adobe Font Development Kit for OpenType (AFDKO) has defined a textual syntax for OpenType tables, called a feature file. smith handles .fea files by merging font-specific classes (built from the AP and classes files) with a provided master fea file, and the resulting font-specific fea file is then compiled into the font.

1
2
3
4
5
6
font(target = 'results/Example-Regular.ttf',
     source = 'Example-Regular.ttf',
     opentype = fea('Example.fea',
                    master = 'Example.fea'),
     ap = 'Example.xml',
     classes = 'project_classes.xml')

The complete list of attributes to fea() follow those of other classes:

master

The fea source that will be merged with autogenerated classes to create the font-specific .fea file.

make_params

Extra parameters to pass to makefea, the tool that is used to generate the font-specific .fea file.

no_make

If this attribute is present, then makefea isn’t run and the first parameter references a file that already exists rather than one that will be created by fea().

to_ufo

If this attribute is present and not false and also if the source file for the font ends in .ufo, the generated fea will be copied into the source .ufo as the features.fea file.

depends

A python list of additional source files on which the OpenType depends. Typically these are files mentioned via include() in the master fea file.

buildusingfontforge

If this attribute is present and not false, the FEA file will be compiled using FontForge instead of fonttools. (You will have to install FontForge yourself as it is no longer part of the smith toolchain default dependencies)

keep_feats

This boolean, used only when buildusingfontforge is true, tells FontForge to keep all the lookups associated with a given feature that are already in the font, and not wipe them when merging the feature file. For example, keeping the kern feature lookups, which are often best handled in a font design application rather than in fea files.

3.2. FEAX

Feax is a set of extensions to provide easier and more powerful ways to write fea code. It is a fea preprocessor. For the specification of the feax language see feaextensions.md.

makefea is the script to generate fea from a feax source file.

3.3. Graphite

Adding Graphite tables to a font is much like adding VOLT information. The relevant files are declared either to the font or a gdl() object. For example:

1
2
3
4
5
6
7
font(target = 'results/Example-Regular.ttf',
     source = 'Example-Regular.ttf',
     graphite = gdl('Example.gdl',
                     master = 'mymaster.gdl',
                     make_params = '-o "R C"'),
     ap = 'Example.xml',
     classes = 'project_classes.xml')

Notice that the ap and classes attributes have the same values and meaning as for OpenType tables. This is because the information is used in the creation of both sets of tables. The Example.gdl is created by the make_gdl process and it pulls in mymaster.gdl during compilation.

The complete list of attributes to a gdl() object are:

master

Non-font specific GDL that is #included into the font specific GDL.

make_params

Parameters passed to make_gdl.

params

Parameters to pass to grcompiler to control the compilation of Graphite source to Graphite tables in the font.

no_make

If this attribute is present, make_gdl is not run and the first parameter is assumed to be the gdl for the specific font.

depends

A python list of additional source files on which the GDL depends. Typically these are files mentioned via #include in the master GDL file.

3.4. Legacy Fonts

Fonts can also be built from another font, either legacy-encoded or generated from a source font or fonts. This can be achieved by giving a legacy() object as the source attribute for the font. For example, for a font generated from a legacy font using ttfbuilder we might do:

1
2
3
4
5
6
font(target = 'results/Example-Regular.ttf',
     source = legacy('myfont_src.ttf',
                     source = 'my_legacyfont.ttf',
                     xml = 'legacy_encoding.xml',
                     params = '-f ../roman_font.ttf',
                     ap = 'my_legacyfont.xml'))

The legacy object creates the source font that is then copied to the output and perhaps smarts are added too.

The complete set of attributes to a legacy() object is:

source

The legacy source font (.ttf) to use to convert to the Unicode source font.

xml

ttfbuilder configuration xml file to use for the conversion

params

Command line arguments to ttfbuilder. Note that files specified here need ../ prepended to them.

ap

Attachment point database of the legacy font that will be converted to the font.ap attribute file.

noap

Instructs the legacy converter not to create the ap database specified in the font. This would get used when another process, after legacy conversion, modifies the font and then you want the build system to autogenerate the ap database from that modified font rather than from the legacy font conversion process.

3.5. WOFF

WOFF and WOFF2 files are TTF files in special compressed formats used for webfont delivery. Smith can generate both WOFF and WOFF2 files. For example:

1
2
3
font(target = 'results/Example-Regular.ttf',
     source = 'Example-Regular.ttf',
     woff = woff('Example', metadata='metadata.xml')

The first parameter to woff() is the name of the woff file(s) to be generated. Filename extension, if present, is ignored.

The woff object takes these optional attributes:

params

This string is passed as additional command line options to the psfwoffit command.

metadata

Name of the xml file containing woff extended metadata

privdata

Name of the file containing woff private data

type

Indicates which type(s) of woff to generate; value can be 'woff' or 'woff2'. If not supplied or set to ('woff', 'woff2') then both woff and woff2 are generated.

cmd

A command string that should be used instead of psfwoffit to build woff file(s). Within the command string:

  • ${TGT} identifies the woff file to be built.

  • ${SRC[0]} identifies the TTF file to be used for input.

  • If the metadata attribute was provided, ${SRC[1]} will identify it.

  • If the privdata attribute was provided, the last item in the ${SRC} list will identify it.

By default, the font version is extracted from the input ttf and used as the version for the woff font. To override with a specific version use the params attribute:

1
2
3
font(target = 'results/Example-Regular.ttf',
     source = 'Example-Regular.ttf',
     woff = woff('Example', params='-v 3.2', metadata='metadata.xml')

To use a command other than psfwoffit to create woff files, the cmd attribute can provide the desired command and its options. For example, to use ttf2woff to create woff file:

1
2
3
4
font(target = 'results/Example-Regular.ttf',
     source = 'Example-Regular.ttf',
     woff = woff('Example', type='woff', metadata='metadata.xml',
                cmd='ttf2woff -m ${SRC[1]} -v 3.2 ${SRC[0]} ${TGT}')

3.6. Fret

Fret is a font reporting tool that generates a PDF report from a font file, giving information about all the glyphs in the font.

The fret object takes these attributes:

params

A parameter list to pass to fret. If not specified, then fret is run with the -r command line argument.

1
2
3
font(target = 'results/Example-Regular.ttf',
     source = 'Example-Regular.ttf',
     fret = fret('results/Example.pdf', params='-r -o i'))

3.7. DesignSpace

An alternative to the font object is the designspace object. A designspace specification normally defines a family of related fonts, and therefore the designspace object typically results in a number of fonts being generated — in essence the designspace object creates multiple font objects. Most of the attributes of a font object also apply to a designspace object, the differences are described below.

Instead of a source attribute, the designspace object uses a designspace file. Each instance described in the designspace file is treated as a source, and the designspace object iterates over all these instances and builds output from each.

Thus the minimum needed for the designspace object is a designspace file and target attribute:

1
2
designspace('source/Example.designspace',
    target = '${DS:FILENAME_BASE}.ttf')

Except for source and sfd_master, all other attributes of the font object can be used with the designspace object. Additionally the following attribute can be used:

instanceparams

Command line arguments to psfcreateinstances. A common usage is to supply the -W option to force weight-fixing for RIBBI font families.

instances

Sometimes it is not desirable to build all the instances in a designspace file. This attribute if not None is a list of instance names to build. If None, all instances will be built. This allows for such patterns as follows which limits a build to just one font in a set for quicker building:

1
2
3
opts = preprocess_args({'opt': '--quick'})
designspace('source/Example.designspace', # ...
    instances = ['Example Regular'] if '--quick' in opts else None)
shortcircuit

If this is set to True then if a design space instance has the same configuration parameters as a master, smith will not generate an instance, but use the master file directly. If False then a new instance is always created. Defaults to True.

Note, however, that in contrast to the simplest font object, the target attribute cannot be as simple as Example-Regular.ttf but must be an expression that yields an appropriate filename for each instance. This will also be true for some other attributes as well, for example the attachment point information specified by the ap attribute will need to be different for each instance.

To facilitate this, the designspace object provides a number of variables whose value is based on the particular instance being processed. To prevent possible name conflicts, the designspace object uses a DS: prefix for each of the variables it provides.

For a given instance, each attribute and each location introduce one or more variables. Consider the following instance definition:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<instance
    familyname="Example"
    stylename="Bold"
    name="Example Bold"
    filename="instances/Example-Bold.ufo"
    >
    <location>
        <dimension name="weight" xvalue="700" />
        <dimension name="width"  xvalue="100" />
        <dimension name="custom" xvalue="0" />
    </location>
    <info />
    <kerning />
</instance>

Based on the corresponding instance attributes, the following variables will be defined:

variable string value

${DS:FAMILYNAME}

Example

${DS:STYLENAME}

Bold

${DS:NAME}

Example Bold

${DS:FILENAME}

instances/Example-Bold.ufo

Additionally, for each variable above, three additional variables are defined. Adding _DASH to the variable name results in a value where all spaces are replaced with a hyphen. Adding _NOSPC produces a value where all spaces are removed. Finally, adding _BASE provides a value which is the basename (without the extension) of the original value. For example:

${DS:NAME_DASH}

Example-Bold

${DS:NAME_NOSPC}

ExampleBold

${DS:FILENAME_BASE}

Example-Bold

Based on the location specified for the instance, the following variables are defined:

${DS:AXIS_WEIGHT}

700

${DS:AXIS_WIDTH}

100

${DS:AXIS_CUSTOM}

0

One additional variable provides the path from the build directory to the instance UFO, which for our example would be:

${DS:FILE}

source/instances/Example-Bold.ufo

4. Tests

Testing is an important part of development, particularly for fonts. Smith provides a number of testing mechanisms. The majority of this section is concerned with font testing.

4.1. File Types

There are various source file types that can be used as the basis of many tests. These are:

txt

A .txt file is considered to be simple text, one paragraph, phrase or word per line. Typically the test results display the text file using the font as simple text.

htxt

Sometimes, creating a simple text file in a complex script is hard work just because entering the characters and checking that they are right is problematic. A .htxt file is a simple text file with the added processing that any string of the form `\u`xxxx or `\U`xxxx where xxxx is a sequence of hex digits (and all such digits are used) is converted to the corresponding Unicode character.

htex

These are TeX files that contain all the information to run the test. They are converted to per font tests by adding the line \buildfont{"[fontfile]parameters"} as per a XeTeX font definition, to the start of the file. Everything else is simply \input into the file.

ftml

.xml files are treated the same as .ftml files.

4.2. Standard commands (targets)

Test source files are stored in a standard place in the tree. The global variable TESTDIR can be used to specify where that place is, but the default is tests/. There are a number of test targets already defined in smith that can make use of these and other test information. The TESTDIR may also be a list of paths. The list of test directories can be extended with those specified in EXTRATESTDIR which may also be a string or list. These extra directories may be overridden using a ;-separated list specified in the SMITH_EXTRATESTDIR environment variable. This may, in its turn, be overridden by a ;-separated list specified in the command line option --extratestdir. If any of the directories in the list of test directories does not exist, it is quietly ignored.

Each test target, whether standard or user defined, creates a .html file in the results/tests directory (or as specified by the TESTRESULTSDIR variable). The file name is target\index.html from which links to the actual test results can be found.

4.2.1. pdfs

This target creates a pdf report for each font and its smart font technology and the script (if relevant) for each test file. XeTeX is used to render a .tex file, that is automatically created by smith for each test result, to a .pdf file.

Currently only .txt, .htxt and .htex file types are supported with this target.

It is possible to have a particular test file specify which language specialisation for the rendering of the file. If a test filename contains an underscore, the characters before the underscore are interpreted as a language tag and that language is passed to the font for rendering the text in that file only.

Text, by default, is rendered at 12pt. But this can be overridden using the TEXTSIZE global variable which is set to the size of text in points.

4.2.2. test

This test creates an html report describing the shaping (glyphs and positions) differences between the font created and a reference font found in references/ (or as specified in the STANDARDS variable). This allows a font developer to commit a known base reference version of the font to their git repository and then to see what has changed as a result of their work. In effect, this is a form of regression testing.

The standards directory is, in priority order: as specified in the test with a standards attribute, or via the command line --standards parameter, or from the STANDARDS global (or context) variable or references.

A regression report is generated for each .txt and .htxt test file, for each font, technology and script.

4.2.3. xtest

This tests creates a similar html report to that for regression testing, but it is concerned with the differences between the different smart font technologies and scripts. Thus a report is generated for each font and technology and script pair for each .txt and .htxt test file.

4.2.4. waterfall

This test does not use test files, instead it takes a single string and produces one file per font and technology and script of a single test string rendered at a number of font sizes. There are various variables that control the generated waterfalls:

TESTSTRING

This is the string to be rendered. Without it, the waterfall target does nothing.

WATERFALLSIZES

This is a python list (or tuple) of sizes to render the waterfall text at, in points. It defaults to [6, 8, 9, 10, 11, 12, 13, 14, 16, 18, 22, 24, 28, 32, 36, 42, 48, 56, 72].

TESTLINESPACINGFACTOR

This is a multiplier specifying what the interline space (space between baselines) should be based on the font size currently used. The default is 1.2.

4.2.5. xfont

This test is similar to the waterfall in that it uses a single test string, but it uses the test string to create a single report of the test string being rendered in all the fonts at a given point size. There are various variables that control the test.

TESTSTRING: This is the string to be rendered. Notice that it is the same string as for the waterfall. Lack of it means the test outputs nothing.

TESTFONTSIZE: The point size, in points, to render each font at. Defaults to 12.

4.2.6. ftml

FTML tests don’t generate any files (well perhaps some), instead all the interest is in the generated ftml_index.html file that creates complex links that run a particular xsl file against a particular ftml file giving the font and technology and script. The xsl file then generates an html report for the .ftml file given the font and that is then displayed in the browser.

In order to make the browser be able to load the various fonts and files, these and the necessary supporting files are copied into the results tests tree for this target. The test also supports .txt and .htxt test file types, converting them into .ftml as they are copied.

By default, smith does not come with any .ftml .xsl report generators already built in. Currently a wscript author has to specify where such an .xsl file may be found. They can do this using the ftmlTest() function that takes one parameter (a local path to an .xsl file) and various named parameters:

cmd

This specifies the particular test command to associate this xsl file with. The default is the default ftml target: ftml.

name

This is the name used to identify this xsl in the report. By default it is the xsl filename without the extension.

fonts

An optional list of fonts that will be passed along with the font under test to the xsl. This allows more than one font to be displayed in the same report.

addfontindex

This specifies where in the list of fonts specified in the fonts parameter, the test font should be inserted. Usually this is either 0 (the default) or len(fonts), the number of fonts in the fonts parameter list.

fontmode

This is the same as the fontmode parameter used in test creation. It can take 3 values, described later, with the following effects:

all

One link is created per font.

none

A single link is made passing all the fonts in the fonts list to the report.

collect

A single link is made passing all the fonts generated and any fonts in the fonts parameter list, to the report generator. The particular fontgroup used is called _allFonts.

shapers

This controls how many tests are produced per font. This is the same as the general shapers parameter found in tests, see that description for more details. There are 3 values this parameter may take, but ftml testing only supports 2:

0

Just produce one test per font, regardless of what smart font technologies are created.

1

Create one test per font and smart font technology and script.

4.2.7. sile

Smith can run sile (the SILE typesetter) for font testing. It processes .sil files.

sil

The file is assumed to be a fontproof based sile file. This means that sile will be called with the lua variable fontfile set to the fontfile the report is for.

4.2.8. alltests

There is one very simple test target: smith alltests. This runs all the test targets that smith can find, whether internal or user defined. If a test produces no output, it is skipped and no test_index.html file is created.

It may be that there are tests that a user wants to remove from the list of alltests. This can be achieved through listing the test commands to remove, as strings in a list under the NOALLTESTS global variable in the wscript file.

4.2.9. fbchecks

Another useful testing target is smith fbchecks. This runs all the generated fonts through the Font Bakery QA suite. It does so using the fontbakery profile in pysilfont which explicitly list certains checks, excludes others and provides new ones. Local project-specific checks can also be added in the form of a fontbakery.yaml file at the root of the project. A html report is generated with the results for each font family along with a summary at the command-line.

4.2.10. ots

This test target runs the fonts against the OTS the opentype sanitizer which is built-in various browser to reduce overflow risks. If the font does not pass the sanitizer it will be rejected by various browsers.

4.2.11. validate

This test target runs the fonts against FontValidator.

4.2.12. pyfontaine

This test target runs the fonts against pyfontaine for coverage reporting. It uses various character sets like fontconfig, glyphlists, hyperglot, subsets, uniblocks, unencoded, cldr, extensis.

4.2.13. differ

smith differ allows fonts recently built to be compared against the corresponding fonts in references (or whichever folder is defined by the STANDARDS variable) using diffenator2. HTML reports are generated in results/diffenator2.

4.3. Adding test files

Sometimes you want to create test files as part of the build. This can be done using testFile(). It takes the same parameters as for a create() and it does create the file, but it also adds it to the list of source test files as if it was stored in the tests/ directory (or wherever you have specified that test files are stored).

4.4. User-defined Tests

It is possible to add your own tests to the smith test system. One can create a variant of one of the standard tests listed above, and associate it with a new target. Or one can run a separate command to execute the test. The test is specified using a testCommand() function that takes a single fixed parameter of the target the command is to be associated with and a list of named parameters. It is possible to specify more than one testCommand be associated with the same target, in which case all the testCommands will be run when that target is specified.

type

This specifies the type of the test. It may take various values:

test

A general type test with a given command. This is the default.

FTML

An ftml test that can take multiple xsl report generators.

TeX

A TeX based test

Waterfall

A Waterfull based test. It is possible to set various per test values that would otherwise come from global variables:

text

The text to output, defaults to that specified in TESTSTRING.

sizes

A list of sizes to override those in WATERFALLSIZES or the defaults.

sizefactor

Overrides the TESTLINESPACEFACTOR or its default.

CrossFont

A CrossFont based test. It is possible to set various per test values that would otherwise come from global variables:

text

The text to output, overriding the TESTSTRING value.

size

The font size, overriding the TESTFONTSIZE value or its default.

cmd

This is a string that contains the command to execute to run the test. There are various substitution values that can be used. The value is between ${ and }. The default is that the corresponding parameter passed to the test is looked up. Other more specific values are:

SRC[0]

The test source file (text or otherwise). The test is considered dependent on the test file.

SRC[1]

The first font in the list of fonts passed to the test. Usually there is only one such font. You can pass more fonts via the fonts test parameter. Referencing a font this way introduces a dependency between the test and the font such that if the font changes the test will be rerun.

SRC[2]

If usestandards is true there will be a second font that can be referenced and this is the standard base font.

TGT

The generated output filename.

shaper

The shaper used for the first font: ot or gr.

script

The script for the first font. May be the empty string if the shaper is gr.

altshaper

The shaper used for the second font when shapers=2: ot or gr.

altscript

The script used for the second font when shapers=2.

CMDNAME

This is a command name that has been looked up during smith configure and is referenced here.

shapers

This specifies how many tests will be produced per font based on the value of this parameter:

0

Produce one test per font regardless of how many shapers or scripts are specified.

1

Produce one test per font per shaper per script. Although the script is only relevant to the 'ot' shaper. [Default]

2

Produce one test per shaper script pair for a font.

fontmode

This specifies how fonts are handled in relation to the test:

all

One test (or more) is generated for each font the project creates. [Default]

none

Only one test is produced. There is no font, although you may pass fonts as a list via the fonts parameter.

collect

Only one test is produced, but all the fonts are passed to that one test.

fonts

A list of fonts to pass to the command.

ext

What is the extension of the target filename from the report. The filename is autogenerated with the given extension. The default is .html.

supports

Some test commands only support certain types of test data. The extensions supported are given in this list. Specifying .txt implies .htxt support as well (via conversion to .txt). The default is ['.txt', '.ftml', '.xml']. If you want to support ftml you should specify both .ftml and .xml.

usestandards

If True, this says that the test expects that there is a corresponding reference font for each font and that the command in some way compares the test font with the corresponding reference font to produce its results.

5. Packages

Once a set of writing system components have been created, we need to package them for distribution. Smith works to make that as simple but as powerful as appropriate. There is an optional package attribute. If this attribute is set, it is set to a package object corresponding to which package the component should be added to. In addition, a global package object is created into which go all the components for which no package attribute has been set.

The global package can take its parameters from the wscript file as global variables with the same name as the attribute, but with the name uppercased.

The attributes to package are:

appname

Base application name to use for the installer in lower case. This is required.

version

Version number of the installer. This is required.

desc_short

One line description of the package.

docdir

Directory tree to walk pulling in all the files as source files. Used for identifying documentation files that you want to include in a zip/tarball/release. It can also be used to add documentation files to supplementary fonts built from the same repository. If your wscript produces multiple packages with WOFF files, you need to use a special technique to get the WOFF files to appear in the appropriate web folders in each package. Otherwise you may end up with all the WOFFs from both font families appearing in both packages. There are two steps to setting this up:

  • Adjust the woff() command in the wscript supplemental package designspace routine to place the WOFFs in a temporary folder when built. You also need to add dontship=True so that the contents of the temporary folder don’t get duplicated.

1
2
    woff = woff('web_book/${DS:FILENAME_BASE}.woff',
    metadata=f'../source/familybook-WOFF-metadata.xml', dontship=True)
  • Set the docdir in the package definition to map the temporary folder to the normal web folder. Example:

1
2
    bookpackage = package(appname = "FamilyBook",
        docdir = {"documentation": "documentation", "web_book": "web"})

Those steps will place the appropriate WOFF fonts in the package web folders.

However if you want the CSS and HTML docs from the repo web folder to appear you need to:

  • Create a permanent web_book folder in the project root

  • Copy the CSS and HTML docs from web into web_book

  • Modify those files to use the supplemental family names

You can also use docdir to use an alternative folder in the project as the source for the “documentation” folder in the supplemental package. For example, if you want your “Book” family to instead include documentation from a separate documentation_book, you could do this:

1
2
    bookpackage = package(appname = "FamilyBook",
        docdir = {"documentation_book": "documentation", "web_book": "web"})
package_files

This is a dictionary of filename globs as keys, including the use of ** for recursion. The values are replacement paths. If the value path ends in \/ the path from the key, up to the first *, is replaced with this value.

zipfile

Name of zip file to use when creating smith zip. Is auto-generated if not set, based on appname and version.

zipdir

Directory to store generated zip file in, relative to build directory.

readme

Name of readme file to include in the package (default README.txt)

buildversion

This is often defaulted. For a release build (-r or smith release) it is set to empty. For a non-release build, the core of buildversion is based on the current VCS commit identifier. For git this is the sha. The specification of its format is in buildformat. buildversion is a combination of buildlabel plus the generated identifier based on buildformat. The buildversion is included in the generated names of the zip and tarball targets and the font version (if the font version attribute is not a tuple).

buildlabel

The development version label, for example alpha2.

buildformat

Formats the vcs commit identifier or whatever in the development buildversion. This is a str.format type string and the following named parameters are available. The default for this variable is dev-{vcssha:6}{vcsmodified}

vcssha

Unique commit identifier

vcsmodifier

Returns M if the sources we are building from are not the same as the commit.

vcstype

Specifies the VCS system being used: git, hg, svn.

buildnumber

Continuous integration build build number

5.1. Functions

5.1.1. getufoinfo

This function takes a path to a UFO font. It extracts information from the font and sets the corresponding variables in the wscript as if they had been entered directly. Thus those variables are usable elsewhere in the wscript. Important note: getufoinfo() must be the first function called in the wscript.

The variables set are:

version

Taken from the VersionMajor and VersionMinor

buildlabel

Taken from the openTypeNameVersion parsed to remove the initial Version d.ddd and any dev-ident.

6. Global Functions

The build process is about creating files from other files. Most of these processes are internal to the object, but it is possible to do some advanced configuration allowing the wscript writer to take more control over the build process. The functions described here should be considered advanced, and the beginning authors should not need to concern themselves with them initially.

cmd(cmd, [input files], **options)

The cmd() function specifies a command to be executed in the build directory and then a list of one or more dependent input files.

The first parameter to the function is the command string to execute, which is executed from the build directory. Placeholders within the string are replaced with filenames:

  • ${TGT} will be replaced by the name of the target file (which is given by the context of the cmd() function)

  • ${SRC} will be replace by the list of dependent input files

  • ${SRC[n]} will be replaced by the nth dependent input file (0 indexed)

There are various options that can be added to a cmd():

late

If set to non zero, this says that the command should be executed as late in the sequence of commands to be run on a file as possible.

targets

This is a list of extra targets that this command generates. So a single command can create more than one file.

shell

If set, says that the command should not be broken on spaces into elements to pass to an exec call, but to be passed through the shell for shell processing. Use this if you use file redirection, for example.

create()

The create() function takes an initial parameter of the filename of the file to be created. The next parameter is a command to create the specified file. Usually this is a cmd() function. For example, consider a processing path on an input font:

1
2
source = create('xyz.ufo', cmd('myfirstprocess ${SRC} ${TGT}', ['infile.ufo']),
                           cmd('mysecondprocess ${DEP} ${TGT}'))
process()

This function does an in place modification of the first parameter file that is assumed to already exist. The remaining functions are used to process the file in place. Often this is a cmd() function, but some other file type specific functions do exist. For details of them, see the relevant component type section. To reference the temporary input file referenced, use ${DEP}

1
target = process('outfile.ttf', cmd('ttfautohint ${DEP} ${TGT}'))

When process() is used on a source file, smith has to think a little harder. smith works to a strict rule that no files are created or changed in the main source tree. This means that smith cannot change a source file in its original position. For similar reasons (which file should one read?), smith does not allow there to be an identically named file with the same path in the source tree and in the build tree. So we can’t simply copy the source file into the build tree and work on it there. Instead, smith creates a copy of the source file in the buildtree by stripping its path component and storing it in the tmp/ directory. It then processes that in place. For the most part authors do not have to consider this, and using process() on a source file will 'just work'. But there are rare situations where knowledge of the underlying actions are necessary.

Parameters for this function are:

nochange

If set, tells the system that there is no need to copy the dependency file before running the task. This is an internal parameter that users are very unlikely to need to use.

test()

This function applies a process to its output file with no expected output, so any cmd() would only have a ${TGT} in the string. Of course other dependent inputs may be used. This is used for running files through checking processes that can fail, and give reports.

getversion(format)

This function is designed to help generate a version string of your choosing. It gives access to various values that can be formatted using a format string. The default format string is: dev-{vcssha:.6}{vcsmodified}. This function supports git, mercurial and svn. If the command line option --release or -r is given, then this function will return different values. The keys for which there is access are:

vcssha

A unique identifier, depending on version control system for this commit. Blank if --release.

vcsmodified

If the current code is different from that committed at this version, this value is M. Blank if --release.

buildnumber

If being auto built, this is the build number

7. Environment Variables

The following environment variables are supported by smith:

SMITHARGS

append this string to the end of each command line, e.g. -r

8. Dependencies

Smith makes use of a fair amount of dependencies. And then there are secondary dependencies for these dependencies.

There are two ways to install the smith toolchain and its many dependencies:

  • rely on freshly built binaries from the upstream git repositories and the integrated smith Docker image to do all the installation and the configuration for you

  • build the various components yourself directly from source

As you can imagine, we highly recommend the first option. We are using Ubuntu as the base layer for the entire smith toolchain. It is only cross-platform in the sense that Windows or macOS users can make use of the Docker container to use smith. It has not been natively ported to other platforms besides Ubuntu.

The installation steps are described in more details in the SIL Font Development Guide. We are using the Docker container technology. The Smith Docker image is built from the Dockerfile at the root of the smith project repository which in turn uses various files in docker/.

The various manual steps previously described in this section of the manual have been removed because they got out of date too quickly and were hard to maintain accurately. If you’d still like to do the whole process manually, then we recommend you study the steps in the Dockerfile as well as the dependency definitions in the docker/ folder of the smith project repository.

Let us know of any issues and please report bugs.

Enjoy!