aboutsummaryrefslogtreecommitdiff
path: root/Doc/source/designspaceLib/scripting.rst
blob: 52ddbd6e233a88a8e5801ddaee5106eb1cb3a695 (plain)
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
#########################
3 Scripting a designspace
#########################

It can be useful to build a designspace with a script rather than
construct one with an interface like
`Superpolator <http://superpolator.com>`__ or
`DesignSpaceEditor <https://github.com/LettError/designSpaceRoboFontExtension>`__.

`fontTools.designspaceLib` offers a some tools for building designspaces in
Python. This document shows an example.

********************************
Filling-in a DesignSpaceDocument
********************************

So, suppose you installed the `fontTools` package through your favorite
``git`` client.

The ``DesignSpaceDocument`` object represents the document, whether it
already exists or not. Make a new one:

.. code:: python

    from fontTools.designspaceLib import (DesignSpaceDocument, AxisDescriptor,
                                          SourceDescriptor, InstanceDescriptor)
    doc = DesignSpaceDocument()

We want to create definitions for axes, sources and instances. That
means there are a lot of attributes to set. The **DesignSpaceDocument
object** uses objects to describe the axes, sources and instances. These
are relatively simple objects, think of these as collections of
attributes.

-  Attributes of the :ref:`source-descriptor-object`
-  Attributes of the :ref:`instance-descriptor-object`
-  Attributes of the :ref:`axis-descriptor-object`
-  Read about :ref:`subclassing-descriptors`

Make an axis object
===================

Make a descriptor object and add it to the document.

.. code:: python

    a1 = AxisDescriptor()
    a1.maximum = 1000
    a1.minimum = 0
    a1.default = 0
    a1.name = "weight"
    a1.tag = "wght"
    doc.addAxis(a1)

-  You can add as many axes as you need. OpenType has a maximum of
   around 64K. DesignSpaceEditor has a maximum of 5.
-  The ``name`` attribute is the name you'll be using as the axis name
   in the locations.
-  The ``tag`` attribute is the one of the registered `OpenType
   Variation Axis
   Tags <https://www.microsoft.com/typography/otspec/fvar.htm#VAT>`__
-  The default master is expected at the intersection of all
   default values of all axes.

Option: add label names
-----------------------

The **labelnames** attribute is intended to store localisable, human
readable names for this axis if this is not an axis that is registered
by OpenType. Think "The label next to the slider". The attribute is a
dictionary. The key is the `xml language
tag <https://www.w3.org/International/articles/language-tags/>`__, the
value is a ``unicode`` string with the name. Whether or not this attribute is
used depends on the font building tool, the operating system and the
authoring software. This, at least, is the place to record it.

.. code:: python

    a1.labelNames['fa-IR'] = u"قطر"
    a1.labelNames['en'] = u"Wéíght"

Option: add a map
-----------------

The **map** attribute is a list of (input, output) mapping values
intended for `axis variations table of
OpenType <https://www.microsoft.com/typography/otspec/avar.htm>`__.

.. code:: python

    # (user space, design space), (user space, design space)...
    a1.map = [(0.0, 10.0), (401.0, 66.0), (1000.0, 990.0)]

Make a source object
====================

A **source** is an object that points to a UFO file. It provides the
outline geometry, kerning and font.info that we want to work with.

.. code:: python

    s0 = SourceDescriptor()
    s0.path = "my/path/to/thin.ufo"
    s0.name = "master.thin"
    s0.location = dict(weight=0)
    doc.addSource(s0)

-  You'll need to have at least 2 sources in your document, so go ahead
   and add another one.
-  The **location** attribute is a dictionary with the designspace
   location for this master.
-  The axis names in the location have to match one of the ``axis.name``
   values you defined before.
-  The **path** attribute is the absolute path to an existing UFO.
-  The **name** attribute is a unique name for this source used to keep
   track it.
-  The **layerName** attribute is the name of the UFO3 layer. Default None for ``foreground``.

So go ahead and add another master:

.. code:: python

    s1 = SourceDescriptor()
    s1.path = "my/path/to/bold.ufo"
    s1.name = "master.bold"
    s1.location = dict(weight=1000)
    doc.addSource(s1)


Option: exclude glyphs
----------------------

By default all glyphs in a source will be processed. If you want to
exclude certain glyphs, add their names to the ``mutedGlyphNames`` list.

.. code:: python

    s1.mutedGlyphNames = ["A.test", "A.old"]

Make an instance object
=======================

An **instance** is description of a UFO that you want to generate with
the designspace. For an instance you can define more things. If you want
to generate UFO instances with MutatorMath then you can define different
names and set flags for if you want to generate kerning and font info
and so on. You can also set a path where to generate the instance.

.. code:: python

    i0 = InstanceDescriptor()
    i0.familyName = "MyVariableFontPrototype"
    i0.styleName = "Medium"
    i0.path = os.path.join(root, "instances","MyVariableFontPrototype-Medium.ufo")
    i0.location = dict(weight=500)
    i0.kerning = True
    i0.info = True
    doc.addInstance(i0)

-  The ``path`` attribute needs to be the absolute (real or intended)
   path for the instance. When the document is saved this path will
   written as relative to the path of the document.
-  instance paths should be on the same level as the document, or in a
   level below.
-  Instances for MutatorMath will generate to UFO.
-  Instances for variable fonts become **named instances**.

Option: add more names
----------------------

If you want you can add a PostScript font name, a stylemap familyName
and a stylemap styleName.

.. code:: python

    i0.postScriptFontName = "MyVariableFontPrototype-Medium"
    i0.styleMapFamilyName = "MyVarProtoMedium"
    i0.styleMapStyleName = "regular"

Option: add glyph specific masters
----------------------------------

This bit is not supported by OpenType variable fonts, but it is needed
for some designspaces intended for generating instances with
MutatorMath. The code becomes a bit verbose, so you're invited to wrap
this into something clever.

.. code:: python

    # we're making a dict with all sorts of
    #(optional) settings for a glyph.
    #In this example: the dollar.
    glyphData = dict(name="dollar", unicodeValue=0x24)

    # you can specify a different location for a glyph
    glyphData['instanceLocation'] = dict(weight=500)

    # You can specify different masters
    # for this specific glyph.
    # You can also give those masters new
    # locations. It's a miniature designspace.
    # Remember the "name" attribute we assigned to the sources?
    glyphData['masters'] = [
        dict(font="master.thin",
            glyphName="dollar.nostroke",
            location=dict(weight=0)),
        dict(font="master.bold",
            glyphName="dollar.nostroke",
            location=dict(weight=1000)),
        ]

    # With all of that set up, store it in the instance.
    i4.glyphs['dollar'] = glyphData

******
Saving
******

.. code:: python

    path = "myprototype.designspace"
    doc.write(path)

***********
Generating?
***********

You can generate the UFOs with MutatorMath:

.. code:: python

    from mutatorMath.ufo import build
    build("whatevs/myprototype.designspace")

-  Assuming the outline data in the masters is compatible.

Or you can use the file in making a **variable font** with varLib.


.. _working_with_v5:

**********************************
Working with DesignSpace version 5
**********************************

The new version 5 allows "discrete" axes, which do not interpolate across their
values. This is useful to store in one place family-wide data such as the STAT
information, however it prevents the usual things done on designspaces that
interpolate everywhere:

- checking that all sources are compatible for interpolation
- building variable fonts

In order to allow the above in tools that want to handle designspace v5,
the :mod:`fontTools.designspaceLib.split` module provides two methods to
split a designspace into interpolable sub-spaces,
:func:`splitInterpolable() <fontTools.designspaceLib.split.splitInterpolable>`
and then
:func:`splitVariableFonts() <fontTools.designspaceLib.split.splitVariableFonts>`.


.. figure:: v5_split_downconvert.png
   :width: 680px
   :alt: Example process diagram to check and build DesignSpace 5

   Example process process to check and build Designspace 5.


Also, for older tools that don't know about the other version 5 additions such
as the STAT data fields, the function
:func:`convert5to4() <fontTools.designspaceLib.split.convert5to4>` allows to
strip new information from a designspace version 5 to downgrade it to a
collection of version 4 documents, one per variable font.