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
makefeaisn’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
grcompilerto control the compilation of Graphite source to Graphite tables in the font. - no_make
-
If this attribute is present,
make_gdlis 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
#includein 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
psfwoffitcommand. - 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
psfwoffitto 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
metadataattribute was provided,${SRC[1]}will identify it. -
If the
privdataattribute 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
-rcommand 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-Woption 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 |
|---|---|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
Based on the location specified for the instance, the following variables are defined:
|
|
|
|
|
|
One additional variable provides the path from the build directory to the instance UFO, which for our example would be:
|
|
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
.txtfile 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
.htxtfile 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\inputinto the file. - ftml
-
.xmlfiles are treated the same as.ftmlfiles.
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
fontfileset 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:
otorgr. - 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:
otorgr. - altscript
-
The script used for the second font when shapers=2.
- CMDNAME
-
This is a command name that has been looked up during
smith configureand 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
.txtimplies.htxtsupport as well (via conversion to.txt). The default is['.txt', '.ftml', '.xml']. If you want to support ftml you should specify both.ftmland.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=Trueso 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_bookfolder 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.buildversionis a combination ofbuildlabelplus the generated identifier based onbuildformat. 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 thecmd()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 acmd()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 thetmp/directory. It then processes that in place. For the most part authors do not have to consider this, and usingprocess()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--releaseor-ris 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!