Merge remote-tracking branch 'origin/master' into l3-instancer
This commit is contained in:
commit
7c695d411f
@ -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
|
||||
|
||||
<rules>
|
||||
<rules processing="last">
|
||||
<rule name="named.rule.1">
|
||||
<condition minimum="250" maximum="750" name="weight" />
|
||||
<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
|
||||
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.
|
||||
|
||||
|
||||
|
@ -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 = []
|
||||
|
@ -247,9 +247,11 @@ class SegmentToPointPen(AbstractPen):
|
||||
self.contour.append((pt, "move"))
|
||||
|
||||
def lineTo(self, pt):
|
||||
assert self.contour is not None, "contour missing required initial moveTo"
|
||||
self.contour.append((pt, "line"))
|
||||
|
||||
def curveTo(self, *pts):
|
||||
assert self.contour is not None, "contour missing required initial moveTo"
|
||||
for pt in pts[:-1]:
|
||||
self.contour.append((pt, None))
|
||||
self.contour.append((pts[-1], "curve"))
|
||||
@ -257,12 +259,15 @@ class SegmentToPointPen(AbstractPen):
|
||||
def qCurveTo(self, *pts):
|
||||
if pts[-1] is None:
|
||||
self.contour = []
|
||||
else:
|
||||
assert self.contour is not None, "contour missing required initial moveTo"
|
||||
for pt in pts[:-1]:
|
||||
self.contour.append((pt, None))
|
||||
if pts[-1] is not None:
|
||||
self.contour.append((pts[-1], "qcurve"))
|
||||
|
||||
def closePath(self):
|
||||
assert self.contour is not None, "contour missing required initial moveTo"
|
||||
if len(self.contour) > 1 and self.contour[0][0] == self.contour[-1][0]:
|
||||
self.contour[0] = self.contour[-1]
|
||||
del self.contour[-1]
|
||||
@ -276,6 +281,7 @@ class SegmentToPointPen(AbstractPen):
|
||||
self.contour = None
|
||||
|
||||
def endPath(self):
|
||||
assert self.contour is not None, "contour missing required initial moveTo"
|
||||
self._flushContour()
|
||||
self.contour = None
|
||||
|
||||
|
@ -513,6 +513,8 @@ class CountReference(object):
|
||||
table[name] = value
|
||||
else:
|
||||
assert table[name] == value, (name, table[name], value)
|
||||
def getValue(self):
|
||||
return self.table[self.name]
|
||||
def getCountData(self):
|
||||
v = self.table[self.name]
|
||||
if v is None: v = 0
|
||||
@ -690,8 +692,16 @@ class BaseTable(object):
|
||||
# table. We will later store it here.
|
||||
# We add a reference: by the time the data is assembled
|
||||
# the Count value will be filled in.
|
||||
ref = writer.writeCountReference(table, conv.name, conv.staticSize)
|
||||
table[conv.name] = None
|
||||
# We ignore the current count value since it will be recomputed,
|
||||
# unless it's a CountReference that was already initialized in a custom preWrite.
|
||||
if isinstance(value, CountReference):
|
||||
ref = value
|
||||
ref.size = conv.staticSize
|
||||
writer.writeData(ref)
|
||||
table[conv.name] = ref.getValue()
|
||||
else:
|
||||
ref = writer.writeCountReference(table, conv.name, conv.staticSize)
|
||||
table[conv.name] = None
|
||||
if conv.isPropagated:
|
||||
writer[conv.name] = ref
|
||||
elif conv.isLookupType:
|
||||
|
@ -702,6 +702,7 @@ otData = [
|
||||
('Version', 'Version', None, None, 'Version of the BASE table-initially 0x00010000'),
|
||||
('Offset', 'HorizAxis', None, None, 'Offset to horizontal Axis table-from beginning of BASE table-may be NULL'),
|
||||
('Offset', 'VertAxis', None, None, 'Offset to vertical Axis table-from beginning of BASE table-may be NULL'),
|
||||
('LOffset', 'VarStore', None, 'Version >= 0x00010001', 'Offset to variation store (may be NULL)'),
|
||||
]),
|
||||
|
||||
('Axis', [
|
||||
|
@ -7,7 +7,7 @@ converter objects from otConverters.py.
|
||||
"""
|
||||
from fontTools.misc.py23 import *
|
||||
from fontTools.misc.textTools import pad, safeEval
|
||||
from .otBase import BaseTable, FormatSwitchingBaseTable, ValueRecord
|
||||
from .otBase import BaseTable, FormatSwitchingBaseTable, ValueRecord, CountReference
|
||||
import logging
|
||||
import struct
|
||||
|
||||
@ -687,6 +687,25 @@ class VarIdxMap(BaseTable):
|
||||
mapping[glyph] = (outer << 16) | inner
|
||||
|
||||
|
||||
class VarRegionList(BaseTable):
|
||||
|
||||
def preWrite(self, font):
|
||||
# The OT spec says VarStore.VarRegionList.RegionAxisCount should always
|
||||
# be equal to the fvar.axisCount, and OTS < v8.0.0 enforces this rule
|
||||
# even when the VarRegionList is empty. We can't treat RegionAxisCount
|
||||
# like a normal propagated count (== len(Region[i].VarRegionAxis)),
|
||||
# otherwise it would default to 0 if VarRegionList is empty.
|
||||
# Thus, we force it to always be equal to fvar.axisCount.
|
||||
# https://github.com/khaledhosny/ots/pull/192
|
||||
fvarTable = font.get("fvar")
|
||||
if fvarTable:
|
||||
self.RegionAxisCount = len(fvarTable.axes)
|
||||
return {
|
||||
**self.__dict__,
|
||||
"RegionAxisCount": CountReference(self.__dict__, "RegionAxisCount")
|
||||
}
|
||||
|
||||
|
||||
class SingleSubst(FormatSwitchingBaseTable):
|
||||
|
||||
def populateDefaults(self, propagator=None):
|
||||
|
@ -213,15 +213,13 @@ def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True):
|
||||
|
||||
log.info("Generating gvar")
|
||||
assert "gvar" not in font
|
||||
gvar = font["gvar"] = newTable('gvar')
|
||||
gvar.version = 1
|
||||
gvar.reserved = 0
|
||||
gvar.variations = {}
|
||||
|
||||
glyf = font['glyf']
|
||||
|
||||
# use hhea.ascent of base master as default vertical origin when vmtx is missing
|
||||
baseAscent = font['hhea'].ascent
|
||||
|
||||
variations = {}
|
||||
for glyph in font.getGlyphOrder():
|
||||
|
||||
isComposite = glyf[glyph].isComposite()
|
||||
@ -241,7 +239,6 @@ def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True):
|
||||
del allControls
|
||||
|
||||
# Update gvar
|
||||
gvar.variations[glyph] = []
|
||||
deltas = model.getDeltas(allCoords)
|
||||
supports = model.supports
|
||||
assert len(deltas) == len(supports)
|
||||
@ -280,7 +277,14 @@ def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True):
|
||||
if optimized_len < unoptimized_len:
|
||||
var = var_opt
|
||||
|
||||
gvar.variations[glyph].append(var)
|
||||
variations.setdefault(glyph, []).append(var)
|
||||
|
||||
if variations:
|
||||
gvar = font["gvar"] = newTable('gvar')
|
||||
gvar.version = 1
|
||||
gvar.reserved = 0
|
||||
gvar.variations = {g: variations.get(g, []) for g in font.getGlyphOrder()}
|
||||
|
||||
|
||||
def _remove_TTHinting(font):
|
||||
for tag in ("cvar", "cvt ", "fpgm", "prep"):
|
||||
@ -350,19 +354,21 @@ def _merge_TTHinting(font, masterModel, master_ttfs, tolerance=0.5):
|
||||
_remove_TTHinting(font)
|
||||
return
|
||||
|
||||
# We can build the cvar table now.
|
||||
|
||||
cvar = font["cvar"] = newTable('cvar')
|
||||
cvar.version = 1
|
||||
cvar.variations = []
|
||||
|
||||
variations = []
|
||||
deltas, supports = masterModel.getDeltasAndSupports(all_cvs)
|
||||
for i,(delta,support) in enumerate(zip(deltas[1:], supports[1:])):
|
||||
delta = [otRound(d) for d in delta]
|
||||
if all(abs(v) <= tolerance for v in delta):
|
||||
continue
|
||||
var = TupleVariation(support, delta)
|
||||
cvar.variations.append(var)
|
||||
variations.append(var)
|
||||
|
||||
# We can build the cvar table now.
|
||||
if variations:
|
||||
cvar = font["cvar"] = newTable('cvar')
|
||||
cvar.version = 1
|
||||
cvar.variations = variations
|
||||
|
||||
|
||||
_MetricsFields = namedtuple('_MetricsFields',
|
||||
['tableTag', 'metricsTag', 'sb1', 'sb2', 'advMapping', 'vOrigMapping'])
|
||||
|
@ -165,7 +165,7 @@ def _merge_GlyphOrders(font, lst, values_lst=None, default=None):
|
||||
sortKey = font.getReverseGlyphMap().__getitem__
|
||||
order = sorted(combined, key=sortKey)
|
||||
# Make sure all input glyphsets were in proper order
|
||||
assert all(sorted(vs, key=sortKey) == vs for vs in lst)
|
||||
assert all(sorted(vs, key=sortKey) == vs for vs in lst), "glyph orders are not consistent across masters"
|
||||
del combined
|
||||
|
||||
paddedValues = None
|
||||
|
13
Tests/varLib/data/SingleMaster.designspace
Normal file
13
Tests/varLib/data/SingleMaster.designspace
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<designspace format="3">
|
||||
<axes>
|
||||
<axis default="400" maximum="400" minimum="400" name="weight" tag="wght" />
|
||||
</axes>
|
||||
<sources>
|
||||
<source familyname="Test Family" filename="master_ufo/TestFamily-Master0.ufo" name="master_0">
|
||||
<location>
|
||||
<dimension name="weight" xvalue="400" />
|
||||
</location>
|
||||
</source>
|
||||
</sources>
|
||||
</designspace>
|
98
Tests/varLib/data/test_results/SingleMaster.ttx
Normal file
98
Tests/varLib/data/test_results/SingleMaster.ttx
Normal file
@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.0">
|
||||
|
||||
<GDEF>
|
||||
<Version value="0x00010003"/>
|
||||
<GlyphClassDef Format="2">
|
||||
<ClassDef glyph="uni0024" class="1"/>
|
||||
<ClassDef glyph="uni0024.nostroke" class="1"/>
|
||||
<ClassDef glyph="uni0041" class="1"/>
|
||||
<ClassDef glyph="uni0061" class="1"/>
|
||||
</GlyphClassDef>
|
||||
</GDEF>
|
||||
|
||||
<HVAR>
|
||||
<Version value="0x00010000"/>
|
||||
<VarStore Format="1">
|
||||
<Format value="1"/>
|
||||
<VarRegionList>
|
||||
<!-- RegionAxisCount=1 -->
|
||||
<!-- RegionCount=0 -->
|
||||
</VarRegionList>
|
||||
<!-- VarDataCount=1 -->
|
||||
<VarData index="0">
|
||||
<!-- ItemCount=6 -->
|
||||
<NumShorts value="0"/>
|
||||
<!-- VarRegionCount=0 -->
|
||||
<Item index="0" value="[]"/>
|
||||
<Item index="1" value="[]"/>
|
||||
<Item index="2" value="[]"/>
|
||||
<Item index="3" value="[]"/>
|
||||
<Item index="4" value="[]"/>
|
||||
<Item index="5" value="[]"/>
|
||||
</VarData>
|
||||
</VarStore>
|
||||
</HVAR>
|
||||
|
||||
<STAT>
|
||||
<Version value="0x00010001"/>
|
||||
<DesignAxisRecordSize value="8"/>
|
||||
<!-- DesignAxisCount=1 -->
|
||||
<DesignAxisRecord>
|
||||
<Axis index="0">
|
||||
<AxisTag value="wght"/>
|
||||
<AxisNameID value="256"/> <!-- Weight -->
|
||||
<AxisOrdering value="0"/>
|
||||
</Axis>
|
||||
</DesignAxisRecord>
|
||||
<!-- AxisValueCount=0 -->
|
||||
<ElidedFallbackNameID value="2"/> <!-- Regular -->
|
||||
</STAT>
|
||||
|
||||
<fvar>
|
||||
|
||||
<!-- Weight -->
|
||||
<Axis>
|
||||
<AxisTag>wght</AxisTag>
|
||||
<Flags>0x0</Flags>
|
||||
<MinValue>400.0</MinValue>
|
||||
<DefaultValue>400.0</DefaultValue>
|
||||
<MaxValue>400.0</MaxValue>
|
||||
<AxisNameID>256</AxisNameID>
|
||||
</Axis>
|
||||
</fvar>
|
||||
|
||||
<name>
|
||||
<namerecord nameID="256" platformID="1" platEncID="0" langID="0x0" unicode="True">
|
||||
Weight
|
||||
</namerecord>
|
||||
<namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
|
||||
Test Family
|
||||
</namerecord>
|
||||
<namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
|
||||
Regular
|
||||
</namerecord>
|
||||
<namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
|
||||
Version 1.001;ADBO;Test Family Regular
|
||||
</namerecord>
|
||||
<namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
|
||||
Test Family
|
||||
</namerecord>
|
||||
<namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
|
||||
Version 1.001
|
||||
</namerecord>
|
||||
<namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
|
||||
TestFamily-Master0
|
||||
</namerecord>
|
||||
<namerecord nameID="9" platformID="3" platEncID="1" langID="0x409">
|
||||
Frank Grießhammer
|
||||
</namerecord>
|
||||
<namerecord nameID="17" platformID="3" platEncID="1" langID="0x409">
|
||||
Master 0
|
||||
</namerecord>
|
||||
<namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
|
||||
Weight
|
||||
</namerecord>
|
||||
</name>
|
||||
|
||||
</ttFont>
|
@ -584,6 +584,15 @@ class BuildTest(unittest.TestCase):
|
||||
self.expect_ttx(varfont, expected_ttx_path, tables)
|
||||
self.check_ttx_dump(varfont, expected_ttx_path, tables, suffix)
|
||||
|
||||
def test_varlib_build_single_master(self):
|
||||
self._run_varlib_build_test(
|
||||
designspace_name='SingleMaster',
|
||||
font_name='TestFamily',
|
||||
tables=['GDEF', 'HVAR', 'MVAR', 'STAT', 'fvar', 'cvar', 'gvar', 'name'],
|
||||
expected_ttx_name='SingleMaster',
|
||||
save_before_dump=True,
|
||||
)
|
||||
|
||||
def test_kerning_merging(self):
|
||||
"""Test the correct merging of class-based pair kerning.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user