Create and edit designspaces with RoboFont 4.5+, with support for scripting and notifications.
By TypeMyType and LettError.
Axes Tab | Sources Tab | Instances Tab | Rules Editor | Location Labels Editor | Variable Fonts Editor | Problems Tab |
Open Preview Window | Save the Designspace | Open Documentation |
wght
, wdth
, slnt
, ital
and opsz
.TIME
or GRAD
. Make sure the minimum, default and maximum values make sense.wght
axis from 400 to 700.wdth
axis from 100 to 50opsz
axis, from 10 to 16.Double click the icon to get the popover editor for axis labels and axis maps. The editor represents the labels and maps in a simple syntax for editing. When the panel is closed, the text is converted to designspace objects. | |
Name | Nice human readable name for this axis. No spaces. This name will be for the default English language localisation. |
Tag | A 4-letter tag, the technical identifier for this axis.
|
Minimum | The minimum value of this axis, required for continuous axes. |
Maximum | The maximum value of this axis, required for continuous axes. |
Default | The default value of this axis, required. For a continuous axis the default needs to be between minimum and maximum or equal to one of them. For a discrete axis the default must be one of the axis values. |
Discrete Values | To define discrete axis values enter space separated numbers. For instance 30 60 100 . One of these values must match the default. Leave this field empty if this is to be a continuous, interpolating axis.
|
Hidden | Corresponds with the OpenType variable font specification flag HIDDEN_AXiS. to "provided to indicate a recommendation by the font developer that the axis not be exposed directly to end users in application user interfaces." |
π | Indicates this axis has a map. Click the for the Axis Map editor. |
π·οΈ | Indicates this axis has labels. Click the for the Axis Label editor. |
# user space value
# (numbers exposed to the user in a UI)
# maps to
# designspace value
# (the internal values used to position sources and instances in the designspace)
50 > 10
100 > 20
125 > 66
150 > 990
# Please note: in these examples, lines starting with # are comments.
# The comments are not saved in the designspace.
# Axis Labels syntax
# if the line starts with a ? <language tag> <localised string>
? fr 'Chasse'
# <label name> <value>
'Condensed' 50
# optionally add (elidable) or (olderSibling)
'Normal' 100 (elidable) (olderSibling)
# optionally add [linkeduservalue]
# for instance: upright style links to italic, on discrete italic axis:
'Upright' 0 (elidable) [1]
'Italic' 1 [0]
# set a range for a label name
# <label name> <min value> <default value> <max value>
'Extra Wide' 150 225 300
'Extra Light' 200 200 250
# add localisations for this 'Extra Light' label
? de 'Extraleicht'
? fr 'Extra lΓ©ger'
# Please note: in these examples, lines starting with # are comments.
# The comments are not saved in the designspace.
Double click in the icon opens a popover editor for Localised Family Name and Muted Glyphs.
Control click on a source for a contextual menu:
button for the popover editor | |
πΎ | shows a checkmark if the UFO is where we expect it to be |
π | Indicates this source is the default. |
UFO | the UFO filename and path relative to the designspace document |
Family Name | family name from the source UFO font info. Some applications look for these names so they do not have to open the UFOs. Optional |
Style Name | style name from the source UFO font info. Some applications look for these names so they do not have to open the UFOs. Optional |
Layer Name | name of the layer to be used as a source. Leave blank for default, "foreground". Optional See Note on Support Layers. |
π | indicates if there are localised names for this source. Click the for the Localised Family Name editor . |
π | indicates if there are muted glyphs in this source. Click the for the Muted Glyph editor. |
column for each axis |
Axis values for sources are in designspace coordinates.
If you have discrete axes, make sure this value matches one of the values of the axis. Make sure it is between axis minimum and axis maximum. No anisotropy. |
# localised name starts with a ? <language tag> <localised string>
? fr 'Montserrat'
? ja 'γ’γ³γ»γ©γΌγ'
# a space separated list of glyph names
a b c d
This tab shows the instances defined for this designspace. These items can become different things depending on the context. For instance, each instance can be compiled to a separate UFO file. But they can also be interpreted as named locations in a variable font.
Control click on an instance for a contextual menu:
instances/[familyName]-[styleName].ufo
button for the popover editor for additional names, as well as localised family and style names. | |
UFO | path and filename of destination UFO (if that is where you want to go obviously) |
Family Name | Family name for the UFO |
Style Name | Style name for the UFO and variable font. |
π·οΈ | Indicates if the instance has addtional names applied, like PostScript name (previously a column on the main table) or Style Map names. Click the for the Additional Names editor. |
π | Indicates if there are localised names for this source. Click the for the Localised Family Name & Style Name editors. |
π | The icons in this column indicate if the location values are specified as π€userspace (a person icon, because user), or βοΈdesignspace (a pencil icon because design). See Note 1. |
Column for Each Axis | Axis values can be in userspace or design coordinates. Anisotropic values are separated by a space. See Note 2 |
900 800
means: horizontal interpolation at value 900
and vertical interpolation at value 800
.
Click the magic wand next to each field to generate a guess at what the value should be based on the Family and Style name provided on the main table. The authors make no warranties or guarantees that they are correct! You should double-check their validity.
Identifying Name | A unique name that can be used to identify this font if it needs to be referenced elsewhere in the backend. If not provided it'll be made up. |
PostScript Font Name | Corresponds with font.info.postscriptFontName . Should ideally follow the rules for PostScript names, found in the OT Spec. |
Style Map Family & Style Map Style Names | Corresponds with font.info.styleMapFamilyName and font.info.styleMapStyleName . See some examples in the RF Docs. |
# localised name starts with a ? <language tag> <localised string>
? fr 'Montserrat'
? ja 'γ’γ³γ»γ©γΌγ'
# Rules syntax
# The name of the rule starts on a new line.
# The contents of the rule is indented:
switching a's
# a list of source glyph > substituted glyph
a > a.alt
agrave > agrave.alt
# conditions that trigger the rule:
# <axis name> startRange-endRange
optical 500-1000
# a condition set with two conditions
weight 800-1000 width 200-1000
# Please note: in these examples, lines starting with # are comments.
# The comments are not saved in the designspace.
The location labels tab is an editor for defining labels for specific points in the designspace. This data can be used to define parts of a STAT table.
See fontTools documentation on the top-levellabels
element.
# Location Labels
# styleName:
Neue Style Italic
# optional localisations
# starts with a ? <language tag> <localised string>
? fr "Un Style"
# optionally translation
? fr "Un Style"
# location name if the axis and value
weight 300
width 40
italic 1
boldness 30
# Please note: in these examples, lines starting with # are comments.
# The comments are not saved in the designspace.
This describes different subset variable fonts that can be generated from the current designspace file. For instance this could mean a smaller number of axes, or a specific range of an axis. You can also specify different filenames for these variable fonts.
See fontTools documentation on thevariable-font
element.
# Variable Font syntax
<name of the variableFont>
# indent
> "<fileName of the variable font file>" # optional
> "path/to/my/fontVF.ttf"
# a full axis subset
# i.e. "weight", not "wght"
<axisName>
# example: include the whole weight axis
weight
...
# an axis subset at specific userspace value
# i.e. "weight", not "wght"
<axisName> <value>
# example
weight 400
...
# an axis subset with a sub range
<axisName> <minimumUserValue> <userDefault> <maximumUserValue>
# example
weight 300 400 700
...
# Please note: in these examples, lines starting with # are comments.
# The comments are not saved in the designspace.
This tab lists some of the problems this designspace might have. From structural issues that block any further processing, to missing ufos, missing or duplicate values and glyphs with compatibility issues. This uses DesignspaceProblems. to do the analysis. It aims to list all the problems that prevent the previews and test fonts from being generated. It does not go very deep into the more exotic issues that may occur.
This tab is for your notes and are stored in the Designspace. This can be any kind of note or remark. Notes are not processed in any way.
The most requested feature for designspace editing tools: a preview for instance and source locations.
CurrentDesignspace()
, similar to CurrentFont()
and CurrentGlyph()
. This returns the designspace wrapped in a UFOOperator
object that can calculate single glyphs, kerning and font info.
d = CurrentDesignspace()
loc = d.newDefaultLocation()
g = d.makeOneGlyph("A", location=loc)
# matGlyph objects can easily be converted
g2 = RGlyph()
g2.fromMathGlyph(g)
What is happening here? Step by step: CurrentDesignspace returns a UFOOperator object for the current designspace.
d.newDefaultLocation() creates a dict representing the default location in design coordinates. For this example it is a quick way to get a valid location, but you will probably want to see more exotic locations. Then, makeOneGlyph creates a new glyph object for the given glyphName and a location dict. The result is a MathGlyph object that can be converted to a regular RoboFont glyph object.
d = CurrentDesignspace()
previewLoc = d.randomLocation()
d.setPreviewLocation(previewLoc)
AllDesignspaces()
to get a list of all open designspace documents. Optionally you can pass a font object with usingFont
to get only the designspaces that use that font.
import os
d = AllDesignspaces()
for i, doc in enumerate(AllDesignspaces()):
print(f"{i}: {os.path.basename(doc.path)}")
f = CurrentFont()
for doc in AllDesignspaces(usingFont=f):
print(f"\nOnly using {os.path.basename(f.path)}:", f"{os.path.basename(doc.path)}")
0: Sans_wdth.designspace
1: Experimental_serif.designspace
Only using Test-Light.ufo: Experimental_serif.designspace
makeOneKerning
accepts an optional list of pairs to calculate. This can be faster, for instance if you want to prepare a preview of just a handful of glyphs, but with proper kerning. If no pairs are given, all pairs will be calculated. This is still fast, but it all adds up,
d = CurrentDesignspace()
loc = d.newDefaultLocation()
pairs = [('public.kern1.i', 'public.kern2.b')]
kern = d.makeOneKerning(loc, pairs)
print(kern.items())
info = d.makeOneInfo(loc)
print(info)
CurrentDesignspace()
and draw interpolated glyphs.
#drawbot
size(1000, 500)
d = CurrentDesignspace()
fill(None)
stroke(0)
strokeWidth(.5)
with savedState():
translate(100, 100)
scale(0.5)
for i in range(20):
loc = d.randomLocation()
g = d.makeOneGlyph("A", location=loc)
bp = BezierPath()
g.draw(bp)
drawPath(bp)
More scripting examples in the GitHub designSpaceRoboFontExtension repository.
CurrentDesignspace
returns a UFOOperator
object representing the designspace. UFOOperator
takes care of the loading the fonts and it prepares interpolating objects for all the data, both in varlib and mutatorMath flavors. It understands anisotropic coordinates, and it can handle extrapolation. UFOOperator
also caches its results, so there is some economy. The object is pretty easy to script with as well. To see how UFOOperator works, please check the source on GitHub.
UFOOperator
has the many of the attributes and methods as the fonttools designspace object, and then some additional ones. Building a new designspace with UFOOperator is very similar to what is described here.
d = CurrentDesignspace()
# source objects
print(d.sources)
# instance objects
print(d.instances)
# axes objects
print(d.axes)
In general: d.useVarlib=True
will appply the fonttools.varlib model interpolations. d.useVarlib=False
will use the mutatorMath model for interpolations. The Varlib model is very close to how Variable Fonts work. MutatorMath is a bit more flexible: it can do anisotropic interpolation and it is much more flexible with incomplete sets of sources. Both models can extrapolate now, though their results might not be the same if there are unaligned, off-axis masters in the system.
UFOOperator will assume locations are in designspace coordinates. You can use the `bend` argument to indicate that a location has to be mapped from user coordinates to design coordinates. The competing coordinate systems can be a bit confusing, especially if you use userLocations in the instances. Often the axis mappings are added much later than the instances, so there will be a moment when you have to carefully look at where the instances are. The DSE has a menu for converting between User coordinates to Design coordinates. So, before you assume the code is getting the wrong results, check your axis mappings and locations, and make sure you are passing the right type to UFOOperator. If you're sure the location is in designspace coordinates, leave `bend=False`.
This is a selection of the methods available in UFOOperator that are probably must useful to the scripter. Check GitHub for more.
makeOneGlyph(glyphName, location, decomposeComponents=True, useVarlib=False, roundGeometry=False, clip=False)
make a glyph for this location.
location
is in design coordinatesdecomposeComponents
returns a glyph with outlines, useful if you want to draw a single glyph and you don't have a font to draw the components.
useVarlib
can be used to switch between varlib and mutatormath, just for this one glyph.
roundGeometry
will round all coordinates to full integers.
clip
prevents extrapolation in case the location axis values exceeds the axis extremes.
makeOneInfo(location, roundGeometry=False, clip=False)
: make an info object for this location.
location
is in design coordinatesclip
prevents extrapolation in case the location axis values exceeds the axis extremes.
makeOneKerning(location, pairs=None)
make a kerning object for this location.
location
is in design coordinatespairs
optional list of pairs to include. This can be faster, for instance if you want to prepare a preview of just a handful of glyphs, but with proper kerning. If no pairs are given, all kerning will be calculated.makeFontProportions(location, bend=False, roundGeometry=True)
Calculate the basic font proportions for this location, to map out expectations for drawing.
generateUFOs(useVarlib=None)
generate all available instance descriptors as UFOs.
makeInstance(instanceDescriptor, glyphNames=None, decomposeComponents=False, pairs=None, bend=False)
this saves a single UFO instance to disk.
glyphNames
is an optional list of glyph names to generate. Note this does not update any features.
decomposeComponents
can be True to decompose all glyphs. This is useful when generating small sets of outlines for a preview.
pairs
is an optional list of kern pairs to make. If no pairs given, all kerning will be calculated.
loadFonts()
: this loads the fonts, needs to called before anything can be calculated. Note: the designspace returned from CurrentDesignspace already has the fonts loaded, but if you construct your own UFOOperatpr designspace object, you will have to load the fonts.
updateFonts(fontObjects)
: fontObjects is a list of RoboFont font objects that are to be used in the designspace. If you want to explicitly want to put new fonts in the machine.
getFonts()
returns a list of (font object, location) tuples. This is not necessarily the same list as your sources: the same font object can be used in multiple locations.
findDefault(discreteLocation=None)
returns the default location for the system or the discrete location.
findAllDefaults()
collect all default sourcedescriptors for all discrete locations. In a designspace with a discrete axis, every discrete location will have its own default.
findDefaultFont(discreteLocation=None)
: returns the default fontObject for the system or the discrete location.
splitLocation(location)
: returns the design location split into a continouous and a discrete part.
getFullDesignLocation(location)
: returns the given location converted to designspace coordinates.
getDiscreteLocations()
: return a list of all possible discrete locations for this system.
newDefaultLocation(bend=False, discreteLocation=None)
: returns a new location that is on the default of all axes.
isAnisotropic(location)
returns True if this location is anisotropic.
splitAnisotropic(location)
returns the x and y components of this location as two separate location dicts.
getLocationType(location)
splits the location in its anisotropic components, a flag if this location is anisotropic, as well as continuous and discrete parts. Specifically returns: isAnisotropic, continuousLocation, discreteLocation, locHorizontal, locVertical
.
randomLocation(extrapolate=0, anisotropic=False, roundValues=True, discreteLocation=None)
returns a good, reliably valid random location, for quick testing and entertainment.
extrapolate=0
is a factor that determines the amount of extrapolation. The (max-min)
distance. So 0 = no extrapolation
. And 0.1 = 0.1 * (max - min)
anisotropic=True
all continuous axes get separate x, y values. This may not be very useful from a typographic viewpoint, but it can be good for testing.
locationWillClip(location
will return True is this designlocation will be clipped in one or more of the axes. Clipping can be an indication that the location is extrapolating.
clipDesignLocation(location)
returns a clipped version of this designlocation
getAxisExtremes(axisRecord)
returns the axisMinimum
, axisDefault
and axisMaximum
in design coordinates. Designspace axisDescriptors keep these axis values in userspace coordinates and that is not always practical. This is a one-stop conversion.
glyphChanged(glyphName, includeDependencies=False)
. This instructs the operator to clear any cached data related to this glyph. If you want to make sure the next time you calculate something with this glyph the results will be fresh. There are more specific cache-related methods.
collectForegroundLayerNames()
returns a list of the names of all the foreground layers used by all sources.
getCharacterMapping(discreteLocation=None)
returns a unicode -> glyphname map for the default of the whole system or discreteLocation. As the defaults at discrete locations can be quite different, UFOOperator makes no assumptions about what is available.
DesignspaceEditor2 sends notifications for events for the documents it has open. This means scripts and tools can subscribe to these and get access to live designspace data. With these notifications it will be easier to build new things that work with your designspaces.
The source for these notifications at github.com/LettError/designSpaceRoboFontExtension.
from mojo.subscriber import Subscriber, registerRoboFontSubscriber
class DesignspaceEditorSubscriber(Subscriber):
debug = True
def designspaceEditorDidOpenDesignspace(self, info):
print("opened a designspace:", info["designspace"])
def designspaceEditorDidCloseDesignspace(self, info):
print("closed a designspace:", info["designspace"])
def designspaceEditorAxesDidAddAxis(self, info):
print("created a new axis:", info["axis"], "for", info["designspace"])
def designspaceEditorRulesDidChange(self, info):
print("rules did chagne for:", info["designspace"])
registerRoboFontSubscriber(DesignspaceEditorSubscriber)
Notification name | Description | Possble actions |
Document related notifications | ||
designspaceEditorWillOpenDesignspace
|
DSE is about to open a designspace file. | |
designspaceEditorDidOpenDesignspace
|
DSE has opened a designspace file. | Load fonts, build mutators. |
designspaceEditorDidCloseDesignspace
|
DSE has closed designspace file. | |
Axis related notifications | ||
designspaceEditorAxisLabelsDidChange
|
DSE has changed one of the axis labels. | |
designspaceEditorAxisMapDidChange
|
DSE has changed the map of one of the axes. | Geometry has changed. |
designspaceEditorAxesWillRemoveAxis
|
DSE is about to remove one of the axes. | Geometry will change. |
designspaceEditorAxesDidRemoveAxis
|
DSE has removed one of the axes. | Geometry has changed. |
designspaceEditorAxesWillAddAxis
|
DSE is about to add a new axis. | Rebuild |
designspaceEditorAxesDidAddAxis
|
DSE has added a new axis. | Geometry has changed. Probably the new system will look the same, the geometry has changed and things need to rebuild anyway. |
designspaceEditorAxesDidChangeSelection
|
DSE changed the selected axis. (clarify) | No rebuild needed. |
designspaceEditorAxesDidChange
|
DSE made a change to the axes. (clarify) | Rebuild |
Source related notifications | ||
designspaceEditorSourcesWillRemoveSource
|
DSE is about to remove a source. | Geometry will change. |
designspaceEditorSourcesDidRemoveSource
|
DSE has removed a source. | Geometry has changed. |
designspaceEditorSourcesWillAddSource
|
DSE is about to add a source. | Geometry will change. |
designspaceEditorSourcesDidAddSource
|
DSE has added a source. | Rebuild |
designspaceEditorSourcesDidChangeSelection
|
DSE changed the selected source. (clarify) | |
designspaceEditorSourcesDidCloseUFO
|
DSE closed a UFO: there will be no visible change, but the source was closed RF and the UFO will load from file. | Rebuild. |
designspaceEditorSourcesDidOpenUFO
|
DSE opened a UFO. | Rebuild: there will be no visible change, but the source is now open in RF and the source will load from there. |
designspaceEditorSourcesDidChanged
|
DSE noticed one or more of its sources changed. | Rebuild |
Instance related notifications | ||
designspaceEditorInstancesWillRemoveInstance
|
DSE is about to remove an instance. | |
designspaceEditorInstancesDidRemoveInstance
|
DSE has removed an instance. | Rebuild instance lists |
designspaceEditorInstancesWillAddInstance
|
DSE is about to add an instance. | |
designspaceEditorInstancesDidAddInstance
|
DSE had added an instance. | Rebuild instance lists |
designspaceEditorInstancesDidChangeSelection
|
DSE changed the selected instance. | |
designspaceEditorInstancesDidChange
|
DSE noticed one or more of its instances changed. (Clarify) | |
Rule related notifications | ||
designspaceEditorRulesDidChange
|
DSE reports one or more of the rules changed. | |
Label related notifications | ||
designspaceEditorLabelsDidChange
|
DSE reports one or more of the location labels changed. | |
Note related notifications | ||
designspaceEditorNotesDidChange
|
DSE reports a change in the notes. | |
Notifications related to editing source data in RoboFont | ||
designspaceEditorSourceGlyphDidChange
|
DSE reports changes to a glyph of one of the open sources. | Rebuild glyph mutators |
designspaceEditorSourceInfoDidChange
|
DSE reports changes to font info of one of the open sources. | Rebuild info mutators |
designspaceEditorSourceKerningDidChange
|
DSE reports changes to font kerning of one of the open sources. | Rebuild kerning mutators |
designspaceEditorSourceGroupsDidChange
|
DSE reports changes to font groups of one of the open sources. | Rebuild kerning mutators |
designspaceEditorSourceGroupsDidChange
|
DSE reports changes to font groups by a RF editor. | |
Experimental / unsupported | ||
designspaceEditorDidChange
|
DSE reports every change. | Be careful, this will call a lot and might cause performance issues. |
designspaceEditorPreviewLocationDidChange
|
DSE report changes to the preview location. | |
designspaceEditorSourceDataDidChange
|
DSE reports every change to the source data. | Be careful, this will call a lot and might cause performance issues. |
designspaceEditorSourceFontDidChangedExternally
|
DSE reports every change noticed external changes to source data that is not open in RF. | Be careful, this will call a lot and might cause performance issues. |