[WIP] [designspaceLib] designspace docs edits (#1750)

Various changes in the designspaceLib readme.rst
- added docs for DesignSpaceDocument object, methods and attributes
- removed comments on validation, localisation and generating UFO instances.
- added note that axis minimum, default and maximum are in userspace coordinates.
- added clarification to map input (userpace!) / output (designspace!) values.
- added note that sourceDescriptor location is in designspace coordinates.
- moved comment on rule subs to rule descriptor object.
- added proposed "processing" flag to rules element
- move note on sub element
- implementation differences
- varlib vs. mutatormath
- older versions
- rules and generating static ufo instances
- Updated the description of the `copyInfo` flag of the sourceDescriptor.
- Change the example import.
- Remove additional mention of copyInfo.
This commit is contained in:
Just van Rossum 2019-11-07 19:30:51 +01:00 committed by GitHub
parent 9df78f303b
commit b8fafdd259
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 187 additions and 111 deletions

View File

@ -2,23 +2,22 @@
DesignSpaceDocument Specification DesignSpaceDocument Specification
################################# #################################
An object to read, write and edit interpolation systems for typefaces. An object to read, write and edit interpolation systems for typefaces. Define sources, axes, rules and instances.
- the format was originally written for MutatorMath. - `The Python API of the objects <#python-api>`_
- the format is now also used in fontTools.varlib. - `The document XML structure <#document-xml-structure>`_
- Define sources, axes and instances.
- Not all values might be required by all applications.
A couple of differences between things that use designspaces:
- Varlib does not support anisotropic interpolations. **********
- MutatorMath and Superpolator will extrapolate over the boundaries of Python API
the axes. Varlib can not (at the moment). **********
- Varlib requires much less data to define an instance than
MutatorMath.
- The goals of Varlib and MutatorMath are different, so not all
attributes are always needed. .. _designspacedocument-object:
- Need to expand the description of FDK use of designspace files.
DesignSpaceDocument object
==========================
The DesignSpaceDocument object can read and write ``.designspace`` data. The DesignSpaceDocument object can read and write ``.designspace`` data.
It imports the axes, sources and instances to very basic **descriptor** It imports the axes, sources and instances to very basic **descriptor**
@ -28,87 +27,71 @@ adding them to the document. This makes it easy to integrate this object
in different contexts. in different contexts.
The **DesignSpaceDocument** object can be subclassed to work with The **DesignSpaceDocument** object can be subclassed to work with
different objects, as long as they have the same attributes. different objects, as long as they have the same attributes. Reader and
Writer objects can be subclassed as well.
.. example-1:
.. code:: python .. code:: python
from designSpaceDocument import DesignSpaceDocument from fontTools.designspaceLib import DesignSpaceDocument
doc = DesignSpaceDocument() doc = DesignSpaceDocument()
doc.read("some/path/to/my.designspace") doc.read("some/path/to/my.designspace")
doc.axes doc.axes
doc.sources doc.sources
doc.instances doc.instances
********** Attributes
Validation ----------
**********
Some validation is done when reading. - ``axes``: list of axisDescriptors
- ``sources``: list of sourceDescriptors
- ``instances``: list of instanceDescriptors
- ``rules``: list if ruleDescriptors
- ``readerClass``: class of the reader object
- ``writerClass``: class of the writer object
- ``lib``: dict for user defined, custom data that needs to be stored
in the designspace. Use reverse-DNS notation to identify your own data.
Respect the data stored by others.
- ``rulesProcessingLast``: This flag indicates whether the substitution rules should be applied before or after other glyph substitution features. False: before, True: after.
Axes Methods
==== -------
- If the ``axes`` element is available in the document then all - ``read(path)``: read a designspace file from ``path``
locations will check their dimensions against the defined axes. If a - ``write(path)``: write this designspace to ``path``
location uses an axis that is not defined it will be ignored. - ``addSource(aSourceDescriptor)``: add this sourceDescriptor to
- If there are no ``axes`` in the document, locations will accept all ``doc.sources``.
axis names, so that we can.. - ``addInstance(anInstanceDescriptor)``: add this instanceDescriptor
- Use ``doc.checkAxes()`` to reconstruct axes definitions based on the to ``doc.instances``.
``source.location`` values. If you save the document the axes will be - ``addAxis(anAxisDescriptor)``: add this instanceDescriptor to ``doc.axes``.
there. - ``newDefaultLocation()``: returns a dict with the default location
in designspace coordinates.
- ``updateFilenameFromPath(masters=True, instances=True, force=False)``:
set a descriptor filename attr from the path and this document.
- ``newAxisDescriptor()``: return a new axisDescriptor object.
- ``newSourceDescriptor()``: return a new sourceDescriptor object.
- ``newInstanceDescriptor()``: return a new instanceDescriptor object.
- ``getAxisOrder()``: return a list of axisnames
- ``findDefault()``: return the sourceDescriptor that is on the default
location. Returns None if there isn't one.
- ``normalizeLocation(aLocation)``: return a dict with normalized axis values.
- ``normalize()``: normalize the geometry of this designspace: scale all the
locations of all masters and instances to the ``-1 - 0 - 1`` value.
- ``loadSourceFonts()``: Ensure SourceDescriptor.font attributes are loaded,
and return list of fonts.
- ``tostring(encoding=None)``: Returns the designspace as a string. Default
encoding `utf-8`.
Default font Class Methods
============ -------------
- ``fromfile(path)``
- ``fromstring(string)``
- The source with the ``copyInfo`` flag indicates this is the default
font.
- In mutatorMath the default font is selected automatically. A warning
is printed if the mutatorMath default selection differs from the one
set by ``copyInfo``. But the ``copyInfo`` source will be used.
- If no source has a ``copyInfo`` flag, mutatorMath will be used to
select one. This source gets its ``copyInfo`` flag set. If you save
the document this flag will be set.
- Use ``doc.checkDefault()`` to set the default font.
************
Localisation
************
Some of the descriptors support localised names. The names are stored in
dictionaries using the language code as key. That means that there are
now two places to store names: the old attribute and the new localised
dictionary, ``obj.stylename`` and ``obj.localisedStyleName['en']``.
*****
Rules
*****
Rules describe designspace areas in which one glyph should be replaced by another.
A rule has a name and a number of conditionsets. The rule also contains a list of
glyphname pairs: the glyphs that need to be substituted. For a rule to be triggered
**only one** of the conditionsets needs to be true, ``OR``. Within a conditionset
**all** conditions need to be true, ``AND``.
The ``sub`` element contains a pair of glyphnames. The ``name`` attribute is the glyph that should be visible when the rule evaluates to **False**. The ``with`` attribute is the glyph that should be visible when the rule evaluates to **True**.
UFO instances
=============
- When making instances as UFOs however, we need to swap the glyphs so
that the original shape is still available. For instance, if a rule
swaps ``a`` for ``a.alt``, but a glyph that references ``a`` in a
component would then show the new ``a.alt``.
- But that can lead to unexpected results. So, if there are no rules
for ``adieresis`` (assuming it references ``a``) then that glyph
**should not change appearance**. That means that when the rule swaps
``a`` and ``a.alt`` it also swaps all components that reference these
glyphs so they keep their appearance.
- The swap function also needs to take care of swapping the names in
kerning data.
**********
Python API
**********
SourceDescriptor object SourceDescriptor object
======================= =======================
@ -138,8 +121,7 @@ Attributes
- ``copyLib``: bool. Indicates if the contents of the font.lib need to - ``copyLib``: bool. Indicates if the contents of the font.lib need to
be copied to the instances. MutatorMath. be copied to the instances. MutatorMath.
- ``copyInfo`` bool. Indicates if the non-interpolating font.info needs - ``copyInfo`` bool. Indicates if the non-interpolating font.info needs
to be copied to the instances. Also indicates this source is expected to be copied to the instances. MutatorMath
to be the default font. MutatorMath + Varlib
- ``copyGroups`` bool. Indicates if the groups need to be copied to the - ``copyGroups`` bool. Indicates if the groups need to be copied to the
instances. MutatorMath. instances. MutatorMath.
- ``copyFeatures`` bool. Indicates if the feature text needs to be - ``copyFeatures`` bool. Indicates if the feature text needs to be
@ -182,6 +164,7 @@ InstanceDescriptor object
.. attributes-1: .. attributes-1:
Attributes Attributes
---------- ----------
@ -284,10 +267,9 @@ AxisDescriptor object
- ``default``: number. The default value for this axis, i.e. when a new - ``default``: number. The default value for this axis, i.e. when a new
location is created, this is the value this axis will get in user location is created, this is the value this axis will get in user
space. MutatorMath + Varlib. space. MutatorMath + Varlib.
- ``map``: list of input / output values that can describe a warp of user space - ``map``: list of input / output values that can describe a warp
to design space coordinates. If no map values are present, it is assumed user of user space to design space coordinates. If no map values are present, it is assumed user space is the same as design space, as
space is the same as design space, as in [(minimum, minimum), (maximum, maximum)]. in [(minimum, minimum), (maximum, maximum)]. Varlib.
Varlib.
.. code:: python .. code:: python
@ -312,6 +294,16 @@ RuleDescriptor object
- ``subs``: list of substitutions - ``subs``: list of substitutions
- Each substitution is stored as tuples of glyphnames, e.g. ("a", "a.alt"). - Each substitution is stored as tuples of glyphnames, e.g. ("a", "a.alt").
Evaluating rules
----------------
- ``evaluateRule(rule, location)``: Return True if any of the rule's conditionsets
matches the given location.
- ``evaluateConditions(conditions, location)``: Return True if all the conditions
matches the given location.
- ``processRules(rules, location, glyphNames)``: Apply all the rules to the list
of glyphNames. Return a new list of glyphNames with substitutions applied.
.. code:: python .. code:: python
r1 = RuleDescriptor() r1 = RuleDescriptor()
@ -320,6 +312,7 @@ RuleDescriptor object
r1.conditionsSets.append([dict(...), dict(...)]) r1.conditionsSets.append([dict(...), dict(...)])
r1.subs.append(("a", "a.alt")) r1.subs.append(("a", "a.alt"))
.. _subclassing-descriptors: .. _subclassing-descriptors:
Subclassing descriptors Subclassing descriptors
@ -400,9 +393,9 @@ Attributes
location elements. location elements.
- ``tag``: required, string, 4 letters. Some axis tags are registered - ``tag``: required, string, 4 letters. Some axis tags are registered
in the OpenType Specification. in the OpenType Specification.
- ``minimum``: required, number. The minimum value for this axis. - ``minimum``: required, number. The minimum value for this axis, in user space coordinates.
- ``maximum``: required, number. The maximum value for this axis. - ``maximum``: required, number. The maximum value for this axis, in user space coordinates.
- ``default``: required, number. The default value for this axis. - ``default``: required, number. The default value for this axis, in user space coordinates.
- ``hidden``: optional, 0 or 1. Records whether this axis needs to be - ``hidden``: optional, 0 or 1. Records whether this axis needs to be
hidden in interfaces. hidden in interfaces.
@ -433,7 +426,7 @@ Value
- The natural language name of this axis. - The natural language name of this axis.
.. example-1: .. example-2:
Example Example
------- -------
@ -448,12 +441,12 @@ Example
1.2 map element 1.2 map element
=============== ===============
- Defines a single node in a series of input value / output value - Defines a single node in a series of input value (user space coordinate)
pairs. to output value (designspace coordinate) pairs.
- Together these values transform the designspace. - Together these values transform the designspace.
- Child of ``axis`` element. - Child of ``axis`` element.
.. example-2: .. example-3:
Example Example
------- -------
@ -507,7 +500,7 @@ Attributes
- ``yvalue``: optional, number. Separate value for anisotropic - ``yvalue``: optional, number. Separate value for anisotropic
interpolations. interpolations.
.. example-3: .. example-4:
Example Example
------- -------
@ -524,8 +517,9 @@ Example
3. source element 3. source element
================= =================
- Defines a single font that contributes to the designspace. - Defines a single font or layer that contributes to the designspace.
- Child element of ``sources`` - Child element of ``sources``
- Location in designspace coordinates.
.. attributes-5: .. attributes-5:
@ -584,9 +578,7 @@ There are two meanings for the ``lib`` element:
- Child element of ``source`` - Child element of ``source``
- Defines if the instances can inherit the non-interpolating font info - Defines if the instances can inherit the non-interpolating font info
from this source. from this source.
- MutatorMath + Varlib - MutatorMath
- NOTE: **This presence of this element indicates this source is to be
the default font.**
.. 33-features-element: .. 33-features-element:
@ -638,7 +630,7 @@ Attributes
include the kerning of this source in the calculation. include the kerning of this source in the calculation.
- MutatorMath only - MutatorMath only
.. example-4: .. example-5:
Example Example
------- -------
@ -669,6 +661,7 @@ Example
- MutatorMath uses the ``glyphs`` element to describe how certain - MutatorMath uses the ``glyphs`` element to describe how certain
glyphs need different masters, mainly to describe the effects of glyphs need different masters, mainly to describe the effects of
conditional rules in Superpolator. conditional rules in Superpolator.
- Location in designspace coordinates.
.. attributes-8: .. attributes-8:
@ -774,7 +767,7 @@ with an ``xml:lang`` attribute:
- stylemapstylename - stylemapstylename
- stylemapfamilyname - stylemapfamilyname
.. example-5: .. example-6:
Example Example
------- -------
@ -798,7 +791,7 @@ Attributes
- ``source``: the identifier name of the source this master glyph needs - ``source``: the identifier name of the source this master glyph needs
to be loaded from to be loaded from
.. example-6: .. example-7:
Example Example
------- -------
@ -846,6 +839,19 @@ Example
- Container for ``rule`` elements - Container for ``rule`` elements
- The rules are evaluated in this order. - The rules are evaluated in this order.
Rules describe designspace areas in which one glyph should be replaced by another.
A rule has a name and a number of conditionsets. The rule also contains a list of
glyphname pairs: the glyphs that need to be substituted. For a rule to be triggered
**only one** of the conditionsets needs to be true, ``OR``. Within a conditionset
**all** conditions need to be true, ``AND``.
Attributes
----------
- ``processing``: flag, optional. Valid values are [``first``, ``last``]. This flag indicates whether the substitution rules should be applied before or after other glyph substitution features.
- If no ``processing`` attribute is given, interpret as ``first``.
.. 51-rule-element: .. 51-rule-element:
5.1 rule element 5.1 rule element
@ -853,8 +859,8 @@ Example
- Defines a named rule. - Defines a named rule.
- Each ``rule`` element contains one or more ``conditionset`` elements. - Each ``rule`` element contains one or more ``conditionset`` elements.
- Only one ``conditionset`` needs to be true to trigger the rule. - **Only one** ``conditionset`` needs to be true to trigger the rule.
- All conditions in a ``conditionset`` must be true to make the ``conditionset`` true. - **All** conditions in a ``conditionset`` must be true to make the ``conditionset`` true.
- For backwards compatibility a ``rule`` can contain ``condition`` elements outside of a conditionset. These are then understood to be part of a single, implied, ``conditionset``. Note: these conditions should be written wrapped in a conditionset. - For backwards compatibility a ``rule`` can contain ``condition`` elements outside of a conditionset. These are then understood to be part of a single, implied, ``conditionset``. Note: these conditions should be written wrapped in a conditionset.
- A rule element needs to contain one or more ``sub`` elements in order to be compiled to a variable font. - A rule element needs to contain one or more ``sub`` elements in order to be compiled to a variable font.
- Rules without sub elements should be ignored when compiling a font. - Rules without sub elements should be ignored when compiling a font.
@ -881,9 +887,10 @@ Attributes
======================= =======================
- Child element of ``conditionset`` - Child element of ``conditionset``
- Between the ``minimum`` and ``maximum`` this rule is ``True``. - Between the ``minimum`` and ``maximum`` this condition is ``True``.
- If ``minimum`` is not available, assume it is ``axis.minimum``. - ``minimum`` and ``maximum`` are in designspace coordinates.
- If ``maximum`` is not available, assume it is ``axis.maximum``. - If ``minimum`` is not available, assume it is ``axis.minimum``, mapped to designspace coordinates.
- If ``maximum`` is not available, assume it is ``axis.maximum``, mapped to designspace coordinates.
- The condition must contain at least a minimum or maximum or both. - The condition must contain at least a minimum or maximum or both.
.. attributes-12: .. attributes-12:
@ -893,8 +900,8 @@ Attributes
- ``name``: string, required. Must match one of the defined ``axis`` - ``name``: string, required. Must match one of the defined ``axis``
name attributes. name attributes.
- ``minimum``: number, required*. The low value. - ``minimum``: number, required*. The low value, in designspace coordinates.
- ``maximum``: number, required*. The high value. - ``maximum``: number, required*. The high value, in designspace coordinates.
.. 513-sub-element: .. 513-sub-element:
@ -903,6 +910,9 @@ Attributes
- Child element of ``rule``. - Child element of ``rule``.
- Defines which glyph to replace when the rule evaluates to **True**. - Defines which glyph to replace when the rule evaluates to **True**.
- The ``sub`` element contains a pair of glyphnames. The ``name`` attribute is the glyph that should be visible when the rule evaluates to **False**. The ``with`` attribute is the glyph that should be visible when the rule evaluates to **True**.
Axis values in Conditions are in designspace coordinates.
.. attributes-13: .. attributes-13:
@ -914,7 +924,7 @@ Attributes
- ``with``: string, required. The name of the glyph it is replaced - ``with``: string, required. The name of the glyph it is replaced
with. with.
.. example-7: .. example-8:
Example Example
------- -------
@ -924,7 +934,7 @@ contained in a conditionset.
.. code:: xml .. code:: xml
<rules> <rules processing="last">
<rule name="named.rule.1"> <rule name="named.rule.1">
<condition minimum="250" maximum="750" name="weight" /> <condition minimum="250" maximum="750" name="weight" />
<condition minimum="50" maximum="100" name="width" /> <condition minimum="50" maximum="100" name="width" />
@ -1059,9 +1069,75 @@ any of the UFOs. If the lib key is empty or not present in the Designspace, all
glyphs should be exported, regardless of what the same lib key in any of the glyphs should be exported, regardless of what the same lib key in any of the
UFOs says. UFOs says.
.. 8-this-document: .. 8-implementation-and-differences:
8 This document
8 Implementation and differences
================================
The designspace format has gone through considerable development.
- the format was originally written for MutatorMath.
- the format is now also used in fontTools.varlib.
- not all values are be required by all implementations.
8.1 Varlib vs. MutatorMath
--------------------------
There are some differences between the way MutatorMath and fontTools.varlib handle designspaces.
- Varlib does not support anisotropic interpolations.
- MutatorMath will extrapolate over the boundaries of
the axes. Varlib can not (at the moment).
- Varlib requires much less data to define an instance than
MutatorMath.
- The goals of Varlib and MutatorMath are different, so not all
attributes are always needed.
8.2 Older versions
------------------
- In some implementations that preceed Variable Fonts, the `copyInfo`
flag in a source indicated the source was to be treated as the default.
This is no longer compatible with the assumption that the default font
is located on the default value of each axis.
- Older implementations did not require axis records to be present in
the designspace file. The axis extremes for instance were generated
from the locations used in the sources. This is no longer possible.
8.3 Rules and generating static UFO instances
---------------------------------------------
When making instances as UFOs from a designspace with rules, it can
be useful to evaluate the rules so that the characterset of the ufo
reflects, as much as possible, the state of a variable font when seen
at the same location. This can be done by some swapping and renaming of
glyphs.
While useful for proofing or development work, it should be noted that
swapping and renaming leaves the UFOs with glyphnames that are no longer
descriptive. For instance, after a swap `dollar.bar` could contain a shape
without a bar. Also, when the swapped glyphs are part of other GSUB variations
it can become complex very quickly. So proceed with caution.
- Assuming `rulesProcessingLast = True`:
- We need to swap the glyphs so that the original shape is still available.
For instance, if a rule swaps ``a`` for ``a.alt``, a glyph
that references ``a`` in a component would then show the new ``a.alt``.
- But that can lead to unexpected results, the two glyphs may have different
widths or height. So, glyphs that are not specifically referenced in a rule
**should not change appearance**. That means that the implementation that swaps
``a`` and ``a.alt`` also swap all components that reference these
glyphs in order to preserve their appearance.
- The swap function also needs to take care of swapping the names in
kerning data and any GPOS code.
.. 9-this-document
9 This document
=============== ===============
- The package is rather new and changes are to be expected. - Changes are to be expected.

View File

@ -182,7 +182,7 @@ def evaluateConditions(conditions, location):
def processRules(rules, location, glyphNames): def processRules(rules, location, glyphNames):
""" Apply these rules at this location to these glyphnames.minimum """ Apply these rules at this location to these glyphnames
- rule order matters - rule order matters
""" """
newNames = [] newNames = []