• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#########################
23 Scripting a designspace
3#########################
4
5It can be useful to build a designspace with a script rather than
6construct one with an interface like
7`Superpolator <http://superpolator.com>`__ or
8`DesignSpaceEditor <https://github.com/LettError/designSpaceRoboFontExtension>`__.
9
10`fontTools.designspaceLib` offers a some tools for building designspaces in
11Python. This document shows an example.
12
13********************************
14Filling-in a DesignSpaceDocument
15********************************
16
17So, suppose you installed the `fontTools` package through your favorite
18``git`` client.
19
20The ``DesignSpaceDocument`` object represents the document, whether it
21already exists or not. Make a new one:
22
23.. code:: python
24
25    from fontTools.designspaceLib import (DesignSpaceDocument, AxisDescriptor,
26                                          SourceDescriptor, InstanceDescriptor)
27    doc = DesignSpaceDocument()
28
29We want to create definitions for axes, sources and instances. That
30means there are a lot of attributes to set. The **DesignSpaceDocument
31object** uses objects to describe the axes, sources and instances. These
32are relatively simple objects, think of these as collections of
33attributes.
34
35-  Attributes of the :ref:`source-descriptor-object`
36-  Attributes of the :ref:`instance-descriptor-object`
37-  Attributes of the :ref:`axis-descriptor-object`
38-  Read about :ref:`subclassing-descriptors`
39
40Make an axis object
41===================
42
43Make a descriptor object and add it to the document.
44
45.. code:: python
46
47    a1 = AxisDescriptor()
48    a1.maximum = 1000
49    a1.minimum = 0
50    a1.default = 0
51    a1.name = "weight"
52    a1.tag = "wght"
53    doc.addAxis(a1)
54
55-  You can add as many axes as you need. OpenType has a maximum of
56   around 64K. DesignSpaceEditor has a maximum of 5.
57-  The ``name`` attribute is the name you'll be using as the axis name
58   in the locations.
59-  The ``tag`` attribute is the one of the registered `OpenType
60   Variation Axis
61   Tags <https://www.microsoft.com/typography/otspec/fvar.htm#VAT>`__
62-  The default master is expected at the intersection of all
63   default values of all axes.
64
65Option: add label names
66-----------------------
67
68The **labelnames** attribute is intended to store localisable, human
69readable names for this axis if this is not an axis that is registered
70by OpenType. Think "The label next to the slider". The attribute is a
71dictionary. The key is the `xml language
72tag <https://www.w3.org/International/articles/language-tags/>`__, the
73value is a ``unicode`` string with the name. Whether or not this attribute is
74used depends on the font building tool, the operating system and the
75authoring software. This, at least, is the place to record it.
76
77.. code:: python
78
79    a1.labelNames['fa-IR'] = u"قطر"
80    a1.labelNames['en'] = u"Wéíght"
81
82Option: add a map
83-----------------
84
85The **map** attribute is a list of (input, output) mapping values
86intended for `axis variations table of
87OpenType <https://www.microsoft.com/typography/otspec/avar.htm>`__.
88
89.. code:: python
90
91    # (user space, design space), (user space, design space)...
92    a1.map = [(0.0, 10.0), (401.0, 66.0), (1000.0, 990.0)]
93
94Make a source object
95====================
96
97A **source** is an object that points to a UFO file. It provides the
98outline geometry, kerning and font.info that we want to work with.
99
100.. code:: python
101
102    s0 = SourceDescriptor()
103    s0.path = "my/path/to/thin.ufo"
104    s0.name = "master.thin"
105    s0.location = dict(weight=0)
106    doc.addSource(s0)
107
108-  You'll need to have at least 2 sources in your document, so go ahead
109   and add another one.
110-  The **location** attribute is a dictionary with the designspace
111   location for this master.
112-  The axis names in the location have to match one of the ``axis.name``
113   values you defined before.
114-  The **path** attribute is the absolute path to an existing UFO.
115-  The **name** attribute is a unique name for this source used to keep
116   track it.
117-  The **layerName** attribute is the name of the UFO3 layer. Default None for ``foreground``.
118
119So go ahead and add another master:
120
121.. code:: python
122
123    s1 = SourceDescriptor()
124    s1.path = "my/path/to/bold.ufo"
125    s1.name = "master.bold"
126    s1.location = dict(weight=1000)
127    doc.addSource(s1)
128
129
130Option: exclude glyphs
131----------------------
132
133By default all glyphs in a source will be processed. If you want to
134exclude certain glyphs, add their names to the ``mutedGlyphNames`` list.
135
136.. code:: python
137
138    s1.mutedGlyphNames = ["A.test", "A.old"]
139
140Make an instance object
141=======================
142
143An **instance** is description of a UFO that you want to generate with
144the designspace. For an instance you can define more things. If you want
145to generate UFO instances with MutatorMath then you can define different
146names and set flags for if you want to generate kerning and font info
147and so on. You can also set a path where to generate the instance.
148
149.. code:: python
150
151    i0 = InstanceDescriptor()
152    i0.familyName = "MyVariableFontPrototype"
153    i0.styleName = "Medium"
154    i0.path = os.path.join(root, "instances","MyVariableFontPrototype-Medium.ufo")
155    i0.location = dict(weight=500)
156    i0.kerning = True
157    i0.info = True
158    doc.addInstance(i0)
159
160-  The ``path`` attribute needs to be the absolute (real or intended)
161   path for the instance. When the document is saved this path will
162   written as relative to the path of the document.
163-  instance paths should be on the same level as the document, or in a
164   level below.
165-  Instances for MutatorMath will generate to UFO.
166-  Instances for variable fonts become **named instances**.
167
168Option: add more names
169----------------------
170
171If you want you can add a PostScript font name, a stylemap familyName
172and a stylemap styleName.
173
174.. code:: python
175
176    i0.postScriptFontName = "MyVariableFontPrototype-Medium"
177    i0.styleMapFamilyName = "MyVarProtoMedium"
178    i0.styleMapStyleName = "regular"
179
180Option: add glyph specific masters
181----------------------------------
182
183This bit is not supported by OpenType variable fonts, but it is needed
184for some designspaces intended for generating instances with
185MutatorMath. The code becomes a bit verbose, so you're invited to wrap
186this into something clever.
187
188.. code:: python
189
190    # we're making a dict with all sorts of
191    #(optional) settings for a glyph.
192    #In this example: the dollar.
193    glyphData = dict(name="dollar", unicodeValue=0x24)
194
195    # you can specify a different location for a glyph
196    glyphData['instanceLocation'] = dict(weight=500)
197
198    # You can specify different masters
199    # for this specific glyph.
200    # You can also give those masters new
201    # locations. It's a miniature designspace.
202    # Remember the "name" attribute we assigned to the sources?
203    glyphData['masters'] = [
204        dict(font="master.thin",
205            glyphName="dollar.nostroke",
206            location=dict(weight=0)),
207        dict(font="master.bold",
208            glyphName="dollar.nostroke",
209            location=dict(weight=1000)),
210        ]
211
212    # With all of that set up, store it in the instance.
213    i4.glyphs['dollar'] = glyphData
214
215******
216Saving
217******
218
219.. code:: python
220
221    path = "myprototype.designspace"
222    doc.write(path)
223
224***********
225Generating?
226***********
227
228You can generate the UFOs with MutatorMath:
229
230.. code:: python
231
232    from mutatorMath.ufo import build
233    build("whatevs/myprototype.designspace")
234
235-  Assuming the outline data in the masters is compatible.
236
237Or you can use the file in making a **variable font** with varLib.
238
239
240.. _working_with_v5:
241
242**********************************
243Working with DesignSpace version 5
244**********************************
245
246The new version 5 allows "discrete" axes, which do not interpolate across their
247values. This is useful to store in one place family-wide data such as the STAT
248information, however it prevents the usual things done on designspaces that
249interpolate everywhere:
250
251- checking that all sources are compatible for interpolation
252- building variable fonts
253
254In order to allow the above in tools that want to handle designspace v5,
255the :mod:`fontTools.designspaceLib.split` module provides two methods to
256split a designspace into interpolable sub-spaces,
257:func:`splitInterpolable() <fontTools.designspaceLib.split.splitInterpolable>`
258and then
259:func:`splitVariableFonts() <fontTools.designspaceLib.split.splitVariableFonts>`.
260
261
262.. figure:: v5_split_downconvert.png
263   :width: 680px
264   :alt: Example process diagram to check and build DesignSpace 5
265
266   Example process process to check and build Designspace 5.
267
268
269Also, for older tools that don't know about the other version 5 additions such
270as the STAT data fields, the function
271:func:`convert5to4() <fontTools.designspaceLib.split.convert5to4>` allows to
272strip new information from a designspace version 5 to downgrade it to a
273collection of version 4 documents, one per variable font.
274