From b8fafdd259f4616e6f47735cb26759d8a2bc1c03 Mon Sep 17 00:00:00 2001 From: Just van Rossum Date: Thu, 7 Nov 2019 19:30:51 +0100 Subject: [PATCH] [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. --- Doc/source/designspaceLib/readme.rst | 296 ++++++++++++++--------- Lib/fontTools/designspaceLib/__init__.py | 2 +- 2 files changed, 187 insertions(+), 111 deletions(-) diff --git a/Doc/source/designspaceLib/readme.rst b/Doc/source/designspaceLib/readme.rst index 2594084af..69d6af195 100644 --- a/Doc/source/designspaceLib/readme.rst +++ b/Doc/source/designspaceLib/readme.rst @@ -2,23 +2,22 @@ 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 format is now also used in fontTools.varlib. -- Define sources, axes and instances. -- Not all values might be required by all applications. +- `The Python API of the objects <#python-api>`_ +- `The document XML structure <#document-xml-structure>`_ -A couple of differences between things that use designspaces: -- Varlib does not support anisotropic interpolations. -- MutatorMath and Superpolator 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. -- Need to expand the description of FDK use of designspace files. +********** +Python API +********** + + + +.. _designspacedocument-object: + +DesignSpaceDocument object +========================== The DesignSpaceDocument object can read and write ``.designspace`` data. 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. 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 - from designSpaceDocument import DesignSpaceDocument + from fontTools.designspaceLib import DesignSpaceDocument doc = DesignSpaceDocument() doc.read("some/path/to/my.designspace") doc.axes doc.sources doc.instances -********** -Validation -********** +Attributes +---------- -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 - locations will check their dimensions against the defined axes. If a - location uses an axis that is not defined it will be ignored. -- If there are no ``axes`` in the document, locations will accept all - axis names, so that we can.. -- Use ``doc.checkAxes()`` to reconstruct axes definitions based on the - ``source.location`` values. If you save the document the axes will be - there. +- ``read(path)``: read a designspace file from ``path`` +- ``write(path)``: write this designspace to ``path`` +- ``addSource(aSourceDescriptor)``: add this sourceDescriptor to + ``doc.sources``. +- ``addInstance(anInstanceDescriptor)``: add this instanceDescriptor + to ``doc.instances``. +- ``addAxis(anAxisDescriptor)``: add this instanceDescriptor to ``doc.axes``. +- ``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 ======================= @@ -138,8 +121,7 @@ Attributes - ``copyLib``: bool. Indicates if the contents of the font.lib need to be copied to the instances. MutatorMath. - ``copyInfo`` bool. Indicates if the non-interpolating font.info needs - to be copied to the instances. Also indicates this source is expected - to be the default font. MutatorMath + Varlib + to be copied to the instances. MutatorMath - ``copyGroups`` bool. Indicates if the groups need to be copied to the instances. MutatorMath. - ``copyFeatures`` bool. Indicates if the feature text needs to be @@ -182,6 +164,7 @@ InstanceDescriptor object .. attributes-1: + Attributes ---------- @@ -284,10 +267,9 @@ AxisDescriptor object - ``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 space. MutatorMath + Varlib. -- ``map``: list of input / output values that can describe a warp 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 in [(minimum, minimum), (maximum, maximum)]. - Varlib. +- ``map``: list of input / output values that can describe a warp + 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 + in [(minimum, minimum), (maximum, maximum)]. Varlib. .. code:: python @@ -312,6 +294,16 @@ RuleDescriptor object - ``subs``: list of substitutions - 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 r1 = RuleDescriptor() @@ -320,6 +312,7 @@ RuleDescriptor object r1.conditionsSets.append([dict(...), dict(...)]) r1.subs.append(("a", "a.alt")) + .. _subclassing-descriptors: Subclassing descriptors @@ -400,9 +393,9 @@ Attributes location elements. - ``tag``: required, string, 4 letters. Some axis tags are registered in the OpenType Specification. -- ``minimum``: required, number. The minimum value for this axis. -- ``maximum``: required, number. The maximum value for this axis. -- ``default``: required, number. The default 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, in user space coordinates. +- ``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 in interfaces. @@ -433,7 +426,7 @@ Value - The natural language name of this axis. -.. example-1: +.. example-2: Example ------- @@ -448,12 +441,12 @@ Example 1.2 map element =============== -- Defines a single node in a series of input value / output value - pairs. +- Defines a single node in a series of input value (user space coordinate) + to output value (designspace coordinate) pairs. - Together these values transform the designspace. - Child of ``axis`` element. -.. example-2: +.. example-3: Example ------- @@ -507,7 +500,7 @@ Attributes - ``yvalue``: optional, number. Separate value for anisotropic interpolations. -.. example-3: +.. example-4: Example ------- @@ -524,8 +517,9 @@ Example 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`` +- Location in designspace coordinates. .. attributes-5: @@ -584,9 +578,7 @@ There are two meanings for the ``lib`` element: - Child element of ``source`` - Defines if the instances can inherit the non-interpolating font info from this source. -- MutatorMath + Varlib -- NOTE: **This presence of this element indicates this source is to be - the default font.** +- MutatorMath .. 33-features-element: @@ -638,7 +630,7 @@ Attributes include the kerning of this source in the calculation. - MutatorMath only -.. example-4: +.. example-5: Example ------- @@ -669,6 +661,7 @@ Example - MutatorMath uses the ``glyphs`` element to describe how certain glyphs need different masters, mainly to describe the effects of conditional rules in Superpolator. +- Location in designspace coordinates. .. attributes-8: @@ -774,7 +767,7 @@ with an ``xml:lang`` attribute: - stylemapstylename - stylemapfamilyname -.. example-5: +.. example-6: Example ------- @@ -798,7 +791,7 @@ Attributes - ``source``: the identifier name of the source this master glyph needs to be loaded from -.. example-6: +.. example-7: Example ------- @@ -846,6 +839,19 @@ Example - Container for ``rule`` elements - 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: 5.1 rule element @@ -853,8 +859,8 @@ Example - Defines a named rule. - Each ``rule`` element contains one or more ``conditionset`` elements. -- Only one ``conditionset`` needs to be true to trigger the rule. -- All conditions in a ``conditionset`` must be true to make the ``conditionset`` true. +- **Only one** ``conditionset`` needs to be true to trigger the rule. +- **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. - 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. @@ -881,9 +887,10 @@ Attributes ======================= - Child element of ``conditionset`` -- Between the ``minimum`` and ``maximum`` this rule is ``True``. -- If ``minimum`` is not available, assume it is ``axis.minimum``. -- If ``maximum`` is not available, assume it is ``axis.maximum``. +- Between the ``minimum`` and ``maximum`` this condition is ``True``. +- ``minimum`` and ``maximum`` are in designspace coordinates. +- 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. .. attributes-12: @@ -893,8 +900,8 @@ Attributes - ``name``: string, required. Must match one of the defined ``axis`` name attributes. -- ``minimum``: number, required*. The low value. -- ``maximum``: number, required*. The high value. +- ``minimum``: number, required*. The low value, in designspace coordinates. +- ``maximum``: number, required*. The high value, in designspace coordinates. .. 513-sub-element: @@ -903,6 +910,9 @@ Attributes - Child element of ``rule``. - 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: @@ -914,7 +924,7 @@ Attributes - ``with``: string, required. The name of the glyph it is replaced with. -.. example-7: +.. example-8: Example ------- @@ -924,7 +934,7 @@ contained in a conditionset. .. code:: xml - + @@ -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 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. + + diff --git a/Lib/fontTools/designspaceLib/__init__.py b/Lib/fontTools/designspaceLib/__init__.py index 9859e43f3..b164b49fd 100644 --- a/Lib/fontTools/designspaceLib/__init__.py +++ b/Lib/fontTools/designspaceLib/__init__.py @@ -182,7 +182,7 @@ def evaluateConditions(conditions, location): 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 """ newNames = []