aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSadaf Ebrahimi <sadafebrahimi@google.com>2022-05-13 19:37:36 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2022-05-13 19:37:36 +0000
commitaf25362c777686a57c878f4e2f2978b682e50356 (patch)
tree1dff6c1c2a08ce8152e5b964fc0021acdf9696f1
parentff045b23dd13563d24b07941cdb691cafcdc5d30 (diff)
parentdebb1cba85fe0ed630b67e5d4a6d0ec5d7ef0284 (diff)
downloadfonttools-af25362c777686a57c878f4e2f2978b682e50356.tar.gz
Updating fonttools 4.31.2 to fonttools 4.33.3 am: fc762b5499 am: 4b0e404dd9 am: 2f76e5ea08 am: 312bed341d am: debb1cba85
Original change: https://android-review.googlesource.com/c/platform/external/fonttools/+/2097373 Change-Id: Idc72b9e81e808896f79519b322651f0abf103037 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--.codecov.yml11
-rw-r--r--.gitignore3
-rw-r--r--Doc/README.md6
-rw-r--r--Doc/source/config.rst8
-rw-r--r--Doc/source/designspaceLib/index.rst221
-rw-r--r--Doc/source/designspaceLib/python.rst199
-rw-r--r--Doc/source/designspaceLib/readme.rst1150
-rw-r--r--Doc/source/designspaceLib/scripting.rst49
-rw-r--r--Doc/source/designspaceLib/v5_class_diagram.pngbin0 -> 273627 bytes
-rw-r--r--Doc/source/designspaceLib/v5_class_diagram.puml292
-rw-r--r--Doc/source/designspaceLib/v5_split_downconvert.pngbin0 -> 99688 bytes
-rw-r--r--Doc/source/designspaceLib/v5_split_downconvert.puml147
-rw-r--r--Doc/source/designspaceLib/v5_variable_fonts_vs_instances.pngbin0 -> 144005 bytes
-rw-r--r--Doc/source/designspaceLib/xml.rst1014
-rw-r--r--Doc/source/index.rst4
-rw-r--r--Doc/source/misc/configTools.rst8
-rw-r--r--Doc/source/misc/index.rst1
-rw-r--r--Lib/fontTools/__init__.py2
-rw-r--r--Lib/fontTools/cffLib/__init__.py9
-rw-r--r--Lib/fontTools/config/__init__.py59
-rw-r--r--Lib/fontTools/designspaceLib/__init__.py1826
-rw-r--r--Lib/fontTools/designspaceLib/split.py424
-rw-r--r--Lib/fontTools/designspaceLib/statNames.py224
-rw-r--r--Lib/fontTools/designspaceLib/types.py122
-rw-r--r--Lib/fontTools/fontBuilder.py13
-rw-r--r--Lib/fontTools/merge/__init__.py5
-rw-r--r--Lib/fontTools/merge/tables.py2
-rw-r--r--Lib/fontTools/misc/configTools.py348
-rw-r--r--Lib/fontTools/misc/psCharStrings.py16
-rw-r--r--Lib/fontTools/misc/testTools.py28
-rw-r--r--Lib/fontTools/otlLib/builder.py21
-rw-r--r--Lib/fontTools/otlLib/optimize/__init__.py39
-rw-r--r--Lib/fontTools/otlLib/optimize/gpos.py65
-rw-r--r--Lib/fontTools/subset/__init__.py22
-rw-r--r--Lib/fontTools/ttLib/tables/O_S_2f_2.py14
-rw-r--r--Lib/fontTools/ttLib/tables/_c_m_a_p.py17
-rw-r--r--Lib/fontTools/ttLib/tables/otBase.py157
-rw-r--r--Lib/fontTools/ttLib/ttFont.py40
-rwxr-xr-xLib/fontTools/ufoLib/glifLib.py4
-rw-r--r--Lib/fontTools/varLib/__init__.py78
-rw-r--r--Lib/fontTools/varLib/instancer/__init__.py7
-rw-r--r--Lib/fontTools/varLib/interpolatable.py125
-rw-r--r--Lib/fontTools/varLib/merger.py15
-rw-r--r--Lib/fontTools/varLib/stat.py142
-rw-r--r--METADATA8
-rw-r--r--NEWS.rst77
-rw-r--r--Tests/config_test.py135
-rw-r--r--Tests/designspaceLib/__init__.py0
-rw-r--r--Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_Italics_Wght.designspace114
-rw-r--r--Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_Italics_WghtWdth.designspace316
-rw-r--r--Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_Wght.designspace103
-rw-r--r--Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_WghtWdth.designspace307
-rw-r--r--Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_WghtWdthItal.designspace670
-rw-r--r--Tests/designspaceLib/data/convert5to4_output/SourceSerif4Variable-Italic.designspace326
-rw-r--r--Tests/designspaceLib/data/convert5to4_output/SourceSerif4Variable-Roman.designspace334
-rw-r--r--Tests/designspaceLib/data/split_output/AktivGroteskVF_Italics_Wght.designspace96
-rw-r--r--Tests/designspaceLib/data/split_output/AktivGroteskVF_Italics_WghtWdth.designspace262
-rw-r--r--Tests/designspaceLib/data/split_output/AktivGroteskVF_Wght.designspace85
-rw-r--r--Tests/designspaceLib/data/split_output/AktivGroteskVF_WghtWdth.designspace253
-rw-r--r--Tests/designspaceLib/data/split_output/AktivGroteskVF_WghtWdthItal.designspace562
-rw-r--r--Tests/designspaceLib/data/split_output/MutatorSansVariable_Weight.designspace55
-rw-r--r--Tests/designspaceLib/data/split_output/MutatorSansVariable_Weight_Width.designspace129
-rw-r--r--Tests/designspaceLib/data/split_output/MutatorSansVariable_Width.designspace50
-rw-r--r--Tests/designspaceLib/data/split_output/MutatorSerifVariable_Width.designspace29
-rw-r--r--Tests/designspaceLib/data/split_output/SourceSerif4Variable-Italic.designspace266
-rw-r--r--Tests/designspaceLib/data/split_output/SourceSerif4Variable-Roman.designspace274
-rw-r--r--Tests/designspaceLib/data/split_output/test_v5_MutatorSans_and_Serif_serif_0.0.designspace147
-rw-r--r--Tests/designspaceLib/data/split_output/test_v5_MutatorSans_and_Serif_serif_1.0.designspace44
-rw-r--r--Tests/designspaceLib/data/split_output/test_v5_aktiv_.designspace595
-rw-r--r--Tests/designspaceLib/data/split_output/test_v5_sourceserif_italic_0.0.designspace282
-rw-r--r--Tests/designspaceLib/data/split_output/test_v5_sourceserif_italic_1.0.designspace274
-rw-r--r--Tests/designspaceLib/data/test_v4_original.designspace (renamed from Tests/designspaceLib/data/test.designspace)9
-rw-r--r--Tests/designspaceLib/data/test_v5.designspace294
-rw-r--r--Tests/designspaceLib/data/test_v5_MutatorSans_and_Serif.designspace206
-rw-r--r--Tests/designspaceLib/data/test_v5_aktiv.designspace621
-rw-r--r--Tests/designspaceLib/data/test_v5_decovar.designspace242
-rw-r--r--Tests/designspaceLib/data/test_v5_discrete.designspace139
-rw-r--r--Tests/designspaceLib/data/test_v5_original.designspace90
-rw-r--r--Tests/designspaceLib/data/test_v5_sourceserif.designspace646
-rw-r--r--Tests/designspaceLib/designspace_test.py74
-rw-r--r--Tests/designspaceLib/designspace_v5_test.py888
-rw-r--r--Tests/designspaceLib/fixtures.py8
-rw-r--r--Tests/designspaceLib/split_test.py150
-rw-r--r--Tests/designspaceLib/statNames_test.py61
-rw-r--r--Tests/feaLib/data/spec6h_ii.ttx24
-rw-r--r--Tests/feaLib/data/spec6h_iii_3d.ttx20
-rw-r--r--Tests/fontBuilder/fontBuilder_test.py16
-rw-r--r--Tests/merge/data/CFFFont_expected.ttx2
-rw-r--r--Tests/misc/configTools_test.py80
-rw-r--r--Tests/misc/psCharStrings_test.py9
-rw-r--r--Tests/otlLib/optimize_test.py39
-rw-r--r--Tests/subset/data/expect_harfbuzz_repacker.ttx78
-rw-r--r--Tests/subset/data/expect_lcar_0.ttx2
-rw-r--r--Tests/subset/data/expect_lcar_1.ttx2
-rw-r--r--Tests/subset/data/expect_opbd_0.ttx2
-rw-r--r--Tests/subset/data/expect_opbd_1.ttx2
-rw-r--r--Tests/subset/data/expect_prop_0.ttx2
-rw-r--r--Tests/subset/data/expect_prop_1.ttx2
-rw-r--r--Tests/subset/data/harfbuzz_repacker.ttx1542
-rw-r--r--Tests/subset/subset_test.py422
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutGPOS_8_diff.ttx (renamed from Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_diff.ttx)24
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutGPOS_8_same.ttx (renamed from Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_same.ttx)24
-rw-r--r--Tests/varLib/instancer/instancer_test.py9
-rw-r--r--Tests/varLib/interpolate_layout_test.py12
-rw-r--r--Tests/varLib/stat_test.py167
-rw-r--r--requirements.txt1
-rw-r--r--setup.cfg2
-rwxr-xr-xsetup.py6
108 files changed, 16869 insertions, 1778 deletions
diff --git a/.codecov.yml b/.codecov.yml
index 5e7474d3..40928b93 100644
--- a/.codecov.yml
+++ b/.codecov.yml
@@ -1,5 +1,10 @@
comment: false
+
coverage:
- status:
- project: off
- patch: off
+ status:
+ project:
+ default:
+ informational: true
+ patch:
+ default:
+ informational: true
diff --git a/.gitignore b/.gitignore
index 9c564fd5..e62c33dd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -58,3 +58,6 @@ Lib/**/*.c
# Ctags
tags
+
+# Documentation
+Doc/source/_build
diff --git a/Doc/README.md b/Doc/README.md
index 26641b72..6927bd62 100644
--- a/Doc/README.md
+++ b/Doc/README.md
@@ -1,6 +1,6 @@
# fontTools Documentation
-The fontTools project documentation updates continuously on Read the Docs as the project source changes.
+The fontTools project documentation updates continuously on Read the Docs as the project source changes.
The documentation is hosted at https://fonttools.readthedocs.io/.
@@ -19,7 +19,7 @@ You must have a Python 3 interpreter and the `pip` Python package manager instal
Pull the fontTools project source files, create a Python virtual environment, and then install fontTools and the documentation build dependencies by executing the following commands in the root of the fontTools source repository:
```
-$ pip install -e . [all]
+$ pip install -e .[all]
$ pip install -r Doc/docs-requirements.txt
```
@@ -112,7 +112,7 @@ Build a local set of HTML documentation files with the instructions above and re
### Submit a Pull Request
-Submit a Github pull request with your proposed improvements to the documentation.
+Submit a Github pull request with your proposed improvements to the documentation.
Thanks for your contribution!
diff --git a/Doc/source/config.rst b/Doc/source/config.rst
new file mode 100644
index 00000000..6be575d5
--- /dev/null
+++ b/Doc/source/config.rst
@@ -0,0 +1,8 @@
+###########################
+config: configure fontTools
+###########################
+
+.. automodule:: fontTools.config
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/designspaceLib/index.rst b/Doc/source/designspaceLib/index.rst
index 2c33df7b..5d17dc16 100644
--- a/Doc/source/designspaceLib/index.rst
+++ b/Doc/source/designspaceLib/index.rst
@@ -1,18 +1,217 @@
-##############
-designspaceLib
-##############
+#######################################################
+designspaceLib: Read, write, and edit designspace files
+#######################################################
-MutatorMath started out with its own reader and writer for designspaces.
-Since then the use of designspace has broadened and it would be useful
-to have a reader and writer that are independent of a specific system.
+Implements support for reading and manipulating ``designspace`` files.
+Allows the users to define axes, rules, sources, variable fonts and instances,
+and their STAT information.
.. toctree::
:maxdepth: 1
- readme
+ python
+ xml
scripting
-.. automodule:: fontTools.designspaceLib
- :inherited-members:
- :members:
- :undoc-members:
+
+Notes
+=====
+
+Paths and filenames
+-------------------
+
+A designspace file needs to store many references to UFO files.
+
+- designspace files can be part of versioning systems and appear on
+ different computers. This means it is not possible to store absolute
+ paths.
+- So, all paths are relative to the designspace document path.
+- Using relative paths allows designspace files and UFO files to be
+ **near** each other, and that they can be **found** without enforcing
+ one particular structure.
+- The **filename** attribute in the ``SourceDescriptor`` and
+ ``InstanceDescriptor`` classes stores the preferred relative path.
+- The **path** attribute in these objects stores the absolute path. It
+ is calculated from the document path and the relative path in the
+ filename attribute when the object is created.
+- Only the **filename** attribute is written to file.
+- Both **filename** and **path** must use forward slashes (``/``) as
+ path separators, even on Windows.
+
+Right before we save we need to identify and respond to the following
+situations:
+
+In each descriptor, we have to do the right thing for the filename
+attribute. Before writing to file, the ``documentObject.updatePaths()``
+method prepares the paths as follows:
+
+**Case 1**
+
+::
+
+ descriptor.filename == None
+ descriptor.path == None
+
+**Action**
+
+- write as is, descriptors will not have a filename attr. Useless, but
+ no reason to interfere.
+
+**Case 2**
+
+::
+
+ descriptor.filename == "../something"
+ descriptor.path == None
+
+**Action**
+
+- write as is. The filename attr should not be touched.
+
+**Case 3**
+
+::
+
+ descriptor.filename == None
+ descriptor.path == "~/absolute/path/there"
+
+**Action**
+
+- calculate the relative path for filename. We're not overwriting some
+ other value for filename, it should be fine.
+
+**Case 4**
+
+::
+
+ descriptor.filename == '../somewhere'
+ descriptor.path == "~/absolute/path/there"
+
+**Action**
+
+- There is a conflict between the given filename, and the path. The
+ difference could have happened for any number of reasons. Assuming
+ the values were not in conflict when the object was created, either
+ could have changed. We can't guess.
+- Assume the path attribute is more up to date. Calculate a new value
+ for filename based on the path and the document path.
+
+Recommendation for editors
+--------------------------
+
+- If you want to explicitly set the **filename** attribute, leave the
+ path attribute empty.
+- If you want to explicitly set the **path** attribute, leave the
+ filename attribute empty. It will be recalculated.
+- Use ``documentObject.updateFilenameFromPath()`` to explicitly set the
+ **filename** attributes for all instance and source descriptors.
+
+
+Common Lib Key Registry
+=======================
+
+public.skipExportGlyphs
+-----------------------
+
+This lib key works the same as the UFO lib key with the same name. The
+difference is that applications using a Designspace as the corner stone of the
+font compilation process should use the lib key in that Designspace instead of
+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.
+
+
+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.
+
+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.
+
+
+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.
+
+Version history
+===============
+
+Version 5.0
+-----------
+
+The format was extended to describe the entire design space of a reasonably
+regular font family in one file, with global data about the family to reduce
+repetition in sub-sections. "Reasonably regular" means that the sources and
+instances across the previously multiple Designspace files are positioned on a
+grid and derive their metadata (like style name) in a way that's compatible with
+the STAT model, based on their axis positions. Axis mappings must be the same
+across the entire space.
+
+1. Each axis can have labels attached to stops within the axis range, analogous to the
+ `OpenType STAT <https://docs.microsoft.com/en-us/typography/opentype/spec/stat>`_
+ table. Free-standing labels for locations are also allowed. The data is intended
+ to be compiled into a ``STAT`` table.
+2. The axes can be discrete, to say that they do not interpolate, like a distinctly
+ constructed upright and italic variant of a family.
+3. The data can be used to derive style and PostScript names for instances.
+4. A new ``variable-fonts`` element can subdivide the Designspace into multiple subsets that
+ mix and match the globally available axes. It is possible for these sub-spaces to have
+ a different default location from the global default location. It is required if the
+ Designspace contains a discrete axis and you want to produce a variable font.
+
+What is currently not supported is e.g.
+
+1. A setup where different sources sit at the same logical location in the design space,
+ think "MyFont Regular" and "MyFont SmallCaps Regular". (this situation could be
+ encoded by adding a "SmallCaps" discrete axis, if that makes sense).
+2. Anisotropic locations for axis labels.
+
+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.
+
diff --git a/Doc/source/designspaceLib/python.rst b/Doc/source/designspaceLib/python.rst
new file mode 100644
index 00000000..6a43bdcc
--- /dev/null
+++ b/Doc/source/designspaceLib/python.rst
@@ -0,0 +1,199 @@
+################################
+1 DesignSpaceDocument Python API
+################################
+
+An object to read, write and edit interpolation systems for typefaces.
+Define sources, axes, rules, variable fonts and instances.
+
+Get an overview of the available classes in the Class Diagram below:
+
+.. figure:: v5_class_diagram.png
+ :width: 650px
+ :alt: UML class diagram of designspaceLib
+
+ UML class diagram of designspaceLib. Click to enlarge.
+
+.. contents:: Table of contents
+ :local:
+
+.. _designspacedocument-object:
+
+===================
+DesignSpaceDocument
+===================
+
+.. autoclass:: fontTools.designspaceLib::DesignSpaceDocument
+ :members:
+ :undoc-members:
+ :member-order: bysource
+
+
+.. _axis-descriptor-object:
+
+AxisDescriptor
+==============
+
+.. autoclass:: fontTools.designspaceLib::AxisDescriptor
+ :members:
+ :undoc-members:
+ :inherited-members: SimpleDescriptor
+ :member-order: bysource
+
+
+DiscreteAxisDescriptor
+======================
+
+.. autoclass:: fontTools.designspaceLib::DiscreteAxisDescriptor
+ :members:
+ :undoc-members:
+ :inherited-members: SimpleDescriptor
+ :member-order: bysource
+
+
+AxisLabelDescriptor
+-------------------
+
+.. autoclass:: fontTools.designspaceLib::AxisLabelDescriptor
+ :members:
+ :undoc-members:
+ :member-order: bysource
+
+
+LocationLabelDescriptor
+=======================
+
+.. autoclass:: fontTools.designspaceLib::LocationLabelDescriptor
+ :members:
+ :undoc-members:
+ :member-order: bysource
+
+
+RuleDescriptor
+==============
+
+.. autoclass:: fontTools.designspaceLib::RuleDescriptor
+ :members:
+ :undoc-members:
+ :member-order: bysource
+
+
+Evaluating rules
+----------------
+
+.. autofunction:: fontTools.designspaceLib::evaluateRule
+.. autofunction:: fontTools.designspaceLib::evaluateConditions
+.. autofunction:: fontTools.designspaceLib::processRules
+
+
+.. _source-descriptor-object:
+
+SourceDescriptor
+================
+
+.. autoclass:: fontTools.designspaceLib::SourceDescriptor
+ :members:
+ :undoc-members:
+ :member-order: bysource
+
+
+VariableFontDescriptor
+======================
+
+.. autoclass:: fontTools.designspaceLib::VariableFontDescriptor
+ :members:
+ :undoc-members:
+ :member-order: bysource
+
+
+RangeAxisSubsetDescriptor
+-------------------------
+
+.. autoclass:: fontTools.designspaceLib::RangeAxisSubsetDescriptor
+ :members:
+ :undoc-members:
+ :member-order: bysource
+
+
+ValueAxisSubsetDescriptor
+-------------------------
+
+.. autoclass:: fontTools.designspaceLib::ValueAxisSubsetDescriptor
+ :members:
+ :undoc-members:
+ :member-order: bysource
+
+
+.. _instance-descriptor-object:
+
+InstanceDescriptor
+==================
+
+.. autoclass:: fontTools.designspaceLib::InstanceDescriptor
+ :members:
+ :undoc-members:
+ :member-order: bysource
+
+
+.. _subclassing-descriptors:
+
+=======================
+Subclassing descriptors
+=======================
+
+The DesignSpaceDocument can take subclassed Reader and Writer objects.
+This allows you to work with your own descriptors. You could subclass
+the descriptors. But as long as they have the basic attributes the
+descriptor does not need to be a subclass.
+
+.. code:: python
+
+ class MyDocReader(BaseDocReader):
+ axisDescriptorClass = MyAxisDescriptor
+ discreteAxisDescriptorClass = MyDiscreteAxisDescriptor
+ axisLabelDescriptorClass = MyAxisLabelDescriptor
+ locationLabelDescriptorClass = MyLocationLabelDescriptor
+ ruleDescriptorClass = MyRuleDescriptor
+ sourceDescriptorClass = MySourceDescriptor
+ variableFontsDescriptorClass = MyVariableFontDescriptor
+ valueAxisSubsetDescriptorClass = MyValueAxisSubsetDescriptor
+ rangeAxisSubsetDescriptorClass = MyRangeAxisSubsetDescriptor
+ instanceDescriptorClass = MyInstanceDescriptor
+
+ class MyDocWriter(BaseDocWriter):
+ axisDescriptorClass = MyAxisDescriptor
+ discreteAxisDescriptorClass = MyDiscreteAxisDescriptor
+ axisLabelDescriptorClass = MyAxisLabelDescriptor
+ locationLabelDescriptorClass = MyLocationLabelDescriptor
+ ruleDescriptorClass = MyRuleDescriptor
+ sourceDescriptorClass = MySourceDescriptor
+ variableFontsDescriptorClass = MyVariableFontDescriptor
+ valueAxisSubsetDescriptorClass = MyValueAxisSubsetDescriptor
+ rangeAxisSubsetDescriptorClass = MyRangeAxisSubsetDescriptor
+ instanceDescriptorClass = MyInstanceDescriptor
+
+ myDoc = DesignSpaceDocument(MyDocReader, MyDocWriter)
+
+
+==============
+Helper modules
+==============
+
+fontTools.designspaceLib.split
+==============================
+
+See :ref:`Scripting > Working with DesignSpace version 5 <working_with_v5>`
+for more information.
+
+.. automodule:: fontTools.designspaceLib.split
+
+
+fontTools.designspaceLib.stat
+=============================
+
+.. automodule:: fontTools.designspaceLib.stat
+
+
+fontTools.designspaceLib.statNames
+==================================
+
+.. automodule:: fontTools.designspaceLib.statNames
diff --git a/Doc/source/designspaceLib/readme.rst b/Doc/source/designspaceLib/readme.rst
deleted file mode 100644
index b9ba85a6..00000000
--- a/Doc/source/designspaceLib/readme.rst
+++ /dev/null
@@ -1,1150 +0,0 @@
-#################################
-DesignSpaceDocument Specification
-#################################
-
-An object to read, write and edit interpolation systems for typefaces. Define sources, axes, rules and instances.
-
-- `The Python API of the objects <#python-api>`_
-- `The document XML structure <#document-xml-structure>`_
-
-
-**********
-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**
-objects that store the data in attributes. Data is added to the document
-by creating such descriptor objects, filling them with data and then
-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. Reader and
-Writer objects can be subclassed as well.
-
-**Note:** Python attribute names are usually camelCased, the
-corresponding `XML <#document-xml-structure>`_ attributes are usually
-all lowercase.
-
-.. example-1:
-
-.. code:: python
-
- from fontTools.designspaceLib import DesignSpaceDocument
- doc = DesignSpaceDocument()
- doc.read("some/path/to/my.designspace")
- doc.axes
- doc.sources
- doc.instances
-
-Attributes
-----------
-
-- ``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.
-
-Methods
--------
-
-- ``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`.
-
-Class Methods
--------------
-- ``fromfile(path)``
-- ``fromstring(string)``
-
-
-
-
-.. _source-descriptor-object:
-
-SourceDescriptor object
-=======================
-
-Attributes
-----------
-
-- ``filename``: string. A relative path to the source file, **as it is
- in the document**. MutatorMath + Varlib.
-- ``path``: string. Absolute path to the source file, calculated from
- the document path and the string in the filename attr. MutatorMath +
- Varlib.
-- ``layerName``: string. The name of the layer in the source to look for
- outline data. Default ``None`` which means ``foreground``.
-- ``font``: Any Python object. Optional. Points to a representation of
- this source font that is loaded in memory, as a Python object
- (e.g. a ``defcon.Font`` or a ``fontTools.ttFont.TTFont``). The default
- document reader will not fill-in this attribute, and the default
- writer will not use this attribute. It is up to the user of
- ``designspaceLib`` to either load the resource identified by ``filename``
- and store it in this field, or write the contents of this field to the
- disk and make ``filename`` point to that.
-- ``name``: string. Optional. Unique identifier name for this source,
- if there is one or more ``instance.glyph`` elements in the document.
- MutatorMath.
-- ``location``: dict. Axis values for this source. MutatorMath + Varlib
-- ``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. 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
- copied to the instances. MutatorMath.
-- ``muteKerning``: bool. Indicates if the kerning data from this source
- needs to be muted (i.e. not be part of the calculations).
- MutatorMath.
-- ``muteInfo``: bool. Indicated if the interpolating font.info data for
- this source needs to be muted. MutatorMath.
-- ``mutedGlyphNames``: list. Glyphnames that need to be muted in the
- instances. MutatorMath.
-- ``familyName``: string. Family name of this source. Though this data
- can be extracted from the font, it can be efficient to have it right
- here. Varlib.
-- ``styleName``: string. Style name of this source. Though this data
- can be extracted from the font, it can be efficient to have it right
- here. Varlib.
-
-.. code:: python
-
- doc = DesignSpaceDocument()
- s1 = SourceDescriptor()
- s1.path = masterPath1
- s1.name = "master.ufo1"
- s1.font = defcon.Font("master.ufo1")
- s1.copyLib = True
- s1.copyInfo = True
- s1.copyFeatures = True
- s1.location = dict(weight=0)
- s1.familyName = "MasterFamilyName"
- s1.styleName = "MasterStyleNameOne"
- s1.mutedGlyphNames.append("A")
- s1.mutedGlyphNames.append("Z")
- doc.addSource(s1)
-
-.. _instance-descriptor-object:
-
-InstanceDescriptor object
-=========================
-
-.. attributes-1:
-
-
-Attributes
-----------
-
-- ``filename``: string. Relative path to the instance file, **as it is
- in the document**. The file may or may not exist. MutatorMath.
-- ``path``: string. Absolute path to the source file, calculated from
- the document path and the string in the filename attr. The file may
- or may not exist. MutatorMath.
-- ``name``: string. Unique identifier name of the instance, used to
- identify it if it needs to be referenced from elsewhere in the
- document.
-- ``location``: dict. Axis values for this source. MutatorMath +
- Varlib.
-- ``familyName``: string. Family name of this instance. MutatorMath +
- Varlib.
-- ``localisedFamilyName``: dict. A dictionary of localised family name
- strings, keyed by language code.
-- ``styleName``: string. Style name of this source. MutatorMath +
- Varlib.
-- ``localisedStyleName``: dict. A dictionary of localised stylename
- strings, keyed by language code.
-- ``postScriptFontName``: string. Postscript fontname for this
- instance. MutatorMath.
-- ``styleMapFamilyName``: string. StyleMap familyname for this
- instance. MutatorMath.
-- ``localisedStyleMapFamilyName``: A dictionary of localised style map
- familyname strings, keyed by language code.
-- ``localisedStyleMapStyleName``: A dictionary of localised style map
- stylename strings, keyed by language code.
-- ``styleMapStyleName``: string. StyleMap stylename for this instance.
- MutatorMath.
-- ``glyphs``: dict for special master definitions for glyphs. If glyphs
- need special masters (to record the results of executed rules for
- example). MutatorMath.
-- ``kerning``: bool. Indicates if this instance needs its kerning
- calculated. MutatorMath.
-- ``info``: bool. Indicated if this instance needs the interpolating
- font.info calculated.
-- ``lib``: dict. Custom data associated with this instance.
-
-Methods
--------
-
-These methods give easier access to the localised names.
-
-- ``setStyleName(styleName, languageCode="en")``
-- ``getStyleName(languageCode="en")``
-- ``setFamilyName(familyName, languageCode="en")``
-- ``getFamilyName(self, languageCode="en")``
-- ``setStyleMapStyleName(styleMapStyleName, languageCode="en")``
-- ``getStyleMapStyleName(languageCode="en")``
-- ``setStyleMapFamilyName(styleMapFamilyName, languageCode="en")``
-- ``getStyleMapFamilyName(languageCode="en")``
-
-Example
--------
-
-.. code:: python
-
- i2 = InstanceDescriptor()
- i2.path = instancePath2
- i2.familyName = "InstanceFamilyName"
- i2.styleName = "InstanceStyleName"
- i2.name = "instance.ufo2"
- # anisotropic location
- i2.location = dict(weight=500, width=(400,300))
- i2.postScriptFontName = "InstancePostscriptName"
- i2.styleMapFamilyName = "InstanceStyleMapFamilyName"
- i2.styleMapStyleName = "InstanceStyleMapStyleName"
- glyphMasters = [dict(font="master.ufo1", glyphName="BB", location=dict(width=20,weight=20)), dict(font="master.ufo2", glyphName="CC", location=dict(width=900,weight=900))]
- glyphData = dict(name="arrow", unicodeValue=1234)
- glyphData['masters'] = glyphMasters
- glyphData['note'] = "A note about this glyph"
- glyphData['instanceLocation'] = dict(width=100, weight=120)
- i2.glyphs['arrow'] = glyphData
- i2.glyphs['arrow2'] = dict(mute=False)
- i2.lib['com.coolDesignspaceApp.specimenText'] = 'Hamburgerwhatever'
- doc.addInstance(i2)
-
-.. _axis-descriptor-object:
-
-AxisDescriptor object
-=====================
-
-- ``tag``: string. Four letter tag for this axis. Some might be
- registered at the `OpenType
- specification <https://www.microsoft.com/typography/otspec/fvar.htm#VAT>`__.
- Privately-defined axis tags must begin with an uppercase letter and
- use only uppercase letters or digits.
-- ``name``: string. Name of the axis as it is used in the location
- dicts. MutatorMath + Varlib.
-- ``labelNames``: dict. When defining a non-registered axis, it will be
- necessary to define user-facing readable names for the axis. Keyed by
- xml:lang code. Values are required to be ``unicode`` strings, even if
- they only contain ASCII characters.
-- ``minimum``: number. The minimum value for this axis in user space.
- MutatorMath + Varlib.
-- ``maximum``: number. The maximum value for this axis in user space.
- MutatorMath + Varlib.
-- ``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.
-
-.. code:: python
-
- a1 = AxisDescriptor()
- a1.minimum = 1
- a1.maximum = 1000
- a1.default = 400
- a1.name = "weight"
- a1.tag = "wght"
- a1.labelNames[u'fa-IR'] = u"قطر"
- a1.labelNames[u'en'] = u"Wéíght"
- a1.map = [(1.0, 10.0), (400.0, 66.0), (1000.0, 990.0)]
-
-RuleDescriptor object
-=====================
-
-- ``name``: string. Unique name for this rule. Can be used to
- reference this rule data.
-- ``conditionSets``: a list of conditionsets
-- Each conditionset is a list of conditions.
-- Each condition is a dict with ``name``, ``minimum`` and ``maximum`` keys.
-- ``subs``: list of substitutions
-- Each substitution is stored as tuples of glyphnames, e.g. ("a", "a.alt").
-- Note: By default, rules are applied first, before other text shaping/OpenType layout, as they are part of the `Required Variation Alternates OpenType feature <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#-tag-rvrn>`_. See `5.0 rules element`_ § Attributes.
-
-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()
- r1.name = "unique.rule.name"
- r1.conditionSets.append([dict(name="weight", minimum=-10, maximum=10), dict(...)])
- r1.conditionSets.append([dict(...), dict(...)])
- r1.subs.append(("a", "a.alt"))
-
-
-.. _subclassing-descriptors:
-
-Subclassing descriptors
-=======================
-
-The DesignSpaceDocument can take subclassed Reader and Writer objects.
-This allows you to work with your own descriptors. You could subclass
-the descriptors. But as long as they have the basic attributes the
-descriptor does not need to be a subclass.
-
-.. code:: python
-
- class MyDocReader(BaseDocReader):
- ruleDescriptorClass = MyRuleDescriptor
- axisDescriptorClass = MyAxisDescriptor
- sourceDescriptorClass = MySourceDescriptor
- instanceDescriptorClass = MyInstanceDescriptor
-
- class MyDocWriter(BaseDocWriter):
- ruleDescriptorClass = MyRuleDescriptor
- axisDescriptorClass = MyAxisDescriptor
- sourceDescriptorClass = MySourceDescriptor
- instanceDescriptorClass = MyInstanceDescriptor
-
- myDoc = DesignSpaceDocument(KeyedDocReader, KeyedDocWriter)
-
-**********************
-Document xml structure
-**********************
-
-- The ``axes`` element contains one or more ``axis`` elements.
-- The ``sources`` element contains one or more ``source`` elements.
-- The ``instances`` element contains one or more ``instance`` elements.
-- The ``rules`` element contains one or more ``rule`` elements.
-- The ``lib`` element contains arbitrary data.
-
-.. code:: xml
-
- <?xml version='1.0' encoding='utf-8'?>
- <designspace format="3">
- <axes>
- <!-- define axes here -->
- <axis../>
- </axes>
- <sources>
- <!-- define masters here -->
- <source../>
- </sources>
- <instances>
- <!-- define instances here -->
- <instance../>
- </instances>
- <rules>
- <!-- define rules here -->
- <rule../>
- </rules>
- <lib>
- <dict>
- <!-- store custom data here -->
- </dict>
- </lib>
- </designspace>
-
-.. 1-axis-element:
-
-1. axis element
-===============
-
-- Define a single axis
-- Child element of ``axes``
-
-.. attributes-2:
-
-Attributes
-----------
-
-- ``name``: required, string. Name of the axis that is used in the
- 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, 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.
-
-.. code:: xml
-
- <axis name="weight" tag="wght" minimum="1" maximum="1000" default="400">
-
-.. 11-labelname-element:
-
-1.1 labelname element
-=====================
-
-- Defines a human readable name for UI use.
-- Optional for non-registered axis names.
-- Can be localised with ``xml:lang``
-- Child element of ``axis``
-
-.. attributes-3:
-
-Attributes
-----------
-
-- ``xml:lang``: required, string. `XML language
- definition <https://www.w3.org/International/questions/qa-when-xmllang.en>`__
-
-Value
------
-
-- The natural language name of this axis.
-
-.. example-2:
-
-Example
--------
-
-.. code:: xml
-
- <labelname xml:lang="fa-IR">قطر</labelname>
- <labelname xml:lang="en">Wéíght</labelname>
-
-.. 12-map-element:
-
-1.2 map element
-===============
-
-- 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-3:
-
-Example
--------
-
-.. code:: xml
-
- <map input="1.0" output="10.0" />
- <map input="400.0" output="66.0" />
- <map input="1000.0" output="990.0" />
-
-Example of all axis elements together:
---------------------------------------
-
-.. code:: xml
-
- <axes>
- <axis default="1" maximum="1000" minimum="0" name="weight" tag="wght">
- <labelname xml:lang="fa-IR">قطر</labelname>
- <labelname xml:lang="en">Wéíght</labelname>
- </axis>
- <axis default="100" maximum="200" minimum="50" name="width" tag="wdth">
- <map input="50.0" output="10.0" />
- <map input="100.0" output="66.0" />
- <map input="200.0" output="990.0" />
- </axis>
- </axes>
-
-.. 2-location-element:
-
-2. location element
-===================
-
-- Defines a coordinate in the design space.
-- Dictionary of axisname: axisvalue
-- Used in ``source``, ``instance`` and ``glyph`` elements.
-
-.. 21-dimension-element:
-
-2.1 dimension element
-=====================
-
-- Child element of ``location``
-
-.. attributes-4:
-
-Attributes
-----------
-
-- ``name``: required, string. Name of the axis.
-- ``xvalue``: required, number. The value on this axis.
-- ``yvalue``: optional, number. Separate value for anisotropic
- interpolations.
-
-.. example-4:
-
-Example
--------
-
-.. code:: xml
-
- <location>
- <dimension name="width" xvalue="0.000000" />
- <dimension name="weight" xvalue="0.000000" yvalue="0.003" />
- </location>
-
-.. 3-source-element:
-
-3. source element
-=================
-
-- Defines a single font or layer that contributes to the designspace.
-- Child element of ``sources``
-- Location in designspace coordinates.
-
-.. attributes-5:
-
-Attributes
-----------
-
-- ``familyname``: optional, string. The family name of the source font.
- While this could be extracted from the font data itself, it can be
- more efficient to add it here.
-- ``stylename``: optional, string. The style name of the source font.
-- ``name``: required, string. A unique name that can be used to
- identify this font if it needs to be referenced elsewhere.
-- ``filename``: required, string. A path to the source file, relative
- to the root path of this document. The path can be at the same level
- as the document or lower.
-- ``layer``: optional, string. The name of the layer in the source file.
- If no layer attribute is given assume the foreground layer should be used.
-
-.. 31-lib-element:
-
-3.1 lib element
-===============
-
-There are two meanings for the ``lib`` element:
-
-1. Source lib
- - Example: ``<lib copy="1" />``
- - Child element of ``source``
- - Defines if the instances can inherit the data in the lib of this
- source.
- - MutatorMath only
-
-2. Document and instance lib
- - Example:
-
- .. code:: xml
-
- <lib>
- <dict>
- <key>...</key>
- <string>The contents use the PLIST format.</string>
- </dict>
- </lib>
-
- - Child element of ``designspace`` and ``instance``
- - Contains arbitrary data about the whole document or about a specific
- instance.
- - Items in the dict need to use **reverse domain name notation** <https://en.wikipedia.org/wiki/Reverse_domain_name_notation>__
-
-.. 32-info-element:
-
-3.2 info element
-================
-
-- ``<info copy="1" />``
-- Child element of ``source``
-- Defines if the instances can inherit the non-interpolating font info
- from this source.
-- MutatorMath
-
-.. 33-features-element:
-
-3.3 features element
-====================
-
-- ``<features copy="1" />``
-- Defines if the instances can inherit opentype feature text from this
- source.
-- Child element of ``source``
-- MutatorMath only
-
-.. 34-glyph-element:
-
-3.4 glyph element
-=================
-
-- Can appear in ``source`` as well as in ``instance`` elements.
-- In a ``source`` element this states if a glyph is to be excluded from
- the calculation.
-- MutatorMath only
-
-.. attributes-6:
-
-Attributes
-----------
-
-- ``mute``: optional attribute, number 1 or 0. Indicate if this glyph
- should be ignored as a master.
-- ``<glyph mute="1" name="A"/>``
-- MutatorMath only
-
-.. 35-kerning-element:
-
-3.5 kerning element
-===================
-
-- ``<kerning mute="1" />``
-- Can appear in ``source`` as well as in ``instance`` elements.
-
-.. attributes-7:
-
-Attributes
-----------
-
-- ``mute``: required attribute, number 1 or 0. Indicate if the kerning
- data from this source is to be excluded from the calculation.
-- If the kerning element is not present, assume ``mute=0``, yes,
- include the kerning of this source in the calculation.
-- MutatorMath only
-
-.. example-5:
-
-Example
--------
-
-.. code:: xml
-
- <source familyname="MasterFamilyName" filename="masters/masterTest1.ufo" name="master.ufo1" stylename="MasterStyleNameOne">
- <lib copy="1" />
- <features copy="1" />
- <info copy="1" />
- <glyph mute="1" name="A" />
- <glyph mute="1" name="Z" />
- <location>
- <dimension name="width" xvalue="0.000000" />
- <dimension name="weight" xvalue="0.000000" />
- </location>
- </source>
-
-.. 4-instance-element:
-
-4. instance element
-===================
-
-- Defines a single font that can be calculated with the designspace.
-- Child element of ``instances``
-- For use in Varlib the instance element really only needs the names
- and the location. The ``glyphs`` element is not required.
-- 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:
-
-Attributes
-----------
-
-- ``familyname``: required, string. The family name of the instance
- font. Corresponds with ``font.info.familyName``
-- ``stylename``: required, string. The style name of the instance font.
- Corresponds with ``font.info.styleName``
-- ``name``: required, string. A unique name that can be used to
- identify this font if it needs to be referenced elsewhere.
-- ``filename``: string. Required for MutatorMath. A path to the
- instance file, relative to the root path of this document. The path
- can be at the same level as the document or lower.
-- ``postscriptfontname``: string. Optional for MutatorMath. Corresponds
- with ``font.info.postscriptFontName``
-- ``stylemapfamilyname``: string. Optional for MutatorMath. Corresponds
- with ``styleMapFamilyName``
-- ``stylemapstylename``: string. Optional for MutatorMath. Corresponds
- with ``styleMapStyleName``
-
-Example for varlib
-------------------
-
-.. code:: xml
-
- <instance familyname="InstanceFamilyName" filename="instances/instanceTest2.ufo" name="instance.ufo2" postscriptfontname="InstancePostscriptName" stylemapfamilyname="InstanceStyleMapFamilyName" stylemapstylename="InstanceStyleMapStyleName" stylename="InstanceStyleName">
- <location>
- <dimension name="width" xvalue="400" yvalue="300" />
- <dimension name="weight" xvalue="66" />
- </location>
- <kerning />
- <info />
- <lib>
- <dict>
- <key>com.coolDesignspaceApp.specimenText</key>
- <string>Hamburgerwhatever</string>
- </dict>
- </lib>
- </instance>
-
-.. 41-glyphs-element:
-
-4.1 glyphs element
-==================
-
-- Container for ``glyph`` elements.
-- Optional
-- MutatorMath only.
-
-.. 42-glyph-element:
-
-4.2 glyph element
-=================
-
-- Child element of ``glyphs``
-- May contain a ``location`` element.
-
-.. attributes-9:
-
-Attributes
-----------
-
-- ``name``: string. The name of the glyph.
-- ``unicode``: string. Unicode values for this glyph, in hexadecimal.
- Multiple values should be separated with a space.
-- ``mute``: optional attribute, number 1 or 0. Indicate if this glyph
- should be supressed in the output.
-
-.. 421-note-element:
-
-4.2.1 note element
-==================
-
-- String. The value corresponds to glyph.note in UFO.
-
-.. 422-masters-element:
-
-4.2.2 masters element
-=====================
-
-- Container for ``master`` elements
-- These ``master`` elements define an alternative set of glyph masters
- for this glyph.
-
-.. 4221-master-element:
-
-4.2.2.1 master element
-======================
-
-- Defines a single alternative master for this glyph.
-
-4.3 Localised names for instances
-=================================
-
-Localised names for instances can be included with these simple elements
-with an ``xml:lang`` attribute:
-`XML language definition <https://www.w3.org/International/questions/qa-when-xmllang.en>`__
-
-- stylename
-- familyname
-- stylemapstylename
-- stylemapfamilyname
-
-.. example-6:
-
-Example
--------
-
-.. code:: xml
-
- <stylename xml:lang="fr">Demigras</stylename>
- <stylename xml:lang="ja">半ば</stylename>
- <familyname xml:lang="fr">Montserrat</familyname>
- <familyname xml:lang="ja">モンセラート</familyname>
- <stylemapstylename xml:lang="de">Standard</stylemapstylename>
- <stylemapfamilyname xml:lang="de">Montserrat Halbfett</stylemapfamilyname>
- <stylemapfamilyname xml:lang="ja">モンセラート SemiBold</stylemapfamilyname>
-
-.. attributes-10:
-
-Attributes
-----------
-
-- ``glyphname``: the name of the alternate master glyph.
-- ``source``: the identifier name of the source this master glyph needs
- to be loaded from
-
-.. example-7:
-
-Example
--------
-
-.. code:: xml
-
- <instance familyname="InstanceFamilyName" filename="instances/instanceTest2.ufo" name="instance.ufo2" postscriptfontname="InstancePostscriptName" stylemapfamilyname="InstanceStyleMapFamilyName" stylemapstylename="InstanceStyleMapStyleName" stylename="InstanceStyleName">
- <location>
- <dimension name="width" xvalue="400" yvalue="300" />
- <dimension name="weight" xvalue="66" />
- </location>
- <glyphs>
- <glyph name="arrow2" />
- <glyph name="arrow" unicode="0x4d2 0x4d3">
- <location>
- <dimension name="width" xvalue="100" />
- <dimension name="weight" xvalue="120" />
- </location>
- <note>A note about this glyph</note>
- <masters>
- <master glyphname="BB" source="master.ufo1">
- <location>
- <dimension name="width" xvalue="20" />
- <dimension name="weight" xvalue="20" />
- </location>
- </master>
- </masters>
- </glyph>
- </glyphs>
- <kerning />
- <info />
- <lib>
- <dict>
- <key>com.coolDesignspaceApp.specimenText</key>
- <string>Hamburgerwhatever</string>
- </dict>
- </lib>
- </instance>
-
-.. 50-rules-element:
-
-5.0 rules element
-=================
-
-- 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-11:
-
-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``, and put the substitution rule in the `rvrn` feature.
-- If ``processing`` is ``last``, put it in `rclt`.
-
-.. 51-rule-element:
-
-5.1 rule element
-================
-
-- 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.
-- 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.
-- For authoring tools it might be necessary to save designspace files without ``sub`` elements just because the work is incomplete.
-
-.. attributes-11:
-
-Attributes
-----------
-
-- ``name``: optional, string. A unique name that can be used to
- identify this rule if it needs to be referenced elsewhere. The name
- is not important for compiling variable fonts.
-
-5.1.1 conditionset element
-==========================
-
-- Child element of ``rule``
-- Contains one or more ``condition`` elements.
-
-.. 512-condition-element:
-
-5.1.2 condition element
-=======================
-
-- Child element of ``conditionset``
-- 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:
-
-Attributes
-----------
-
-- ``name``: string, required. Must match one of the defined ``axis``
- name attributes.
-- ``minimum``: number, required*. The low value, in designspace coordinates.
-- ``maximum``: number, required*. The high value, in designspace coordinates.
-
-.. 513-sub-element:
-
-5.1.3 sub element
-=================
-
-- 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:
-
-Attributes
-----------
-
-- ``name``: string, required. The name of the glyph this rule looks
- for.
-- ``with``: string, required. The name of the glyph it is replaced
- with.
-
-.. example-8:
-
-Example
--------
-
-Example with an implied ``conditionset``. Here the conditions are not
-contained in a conditionset.
-
-.. code:: xml
-
- <rules processing="last">
- <rule name="named.rule.1">
- <condition minimum="250" maximum="750" name="weight" />
- <condition minimum="50" maximum="100" name="width" />
- <sub name="dollar" with="dollar.alt"/>
- </rule>
- </rules>
-
-Example with ``conditionsets``. All conditions in a conditionset must be true.
-
-.. code:: xml
-
- <rules>
- <rule name="named.rule.2">
- <conditionset>
- <condition minimum="250" maximum="750" name="weight" />
- <condition minimum="50" maximum="100" name="width" />
- </conditionset>
- <conditionset>
- <condition ... />
- <condition ... />
- </conditionset>
- <sub name="dollar" with="dollar.alt"/>
- </rule>
- </rules>
-
-.. 6-notes:
-
-6 Notes
-=======
-
-Paths and filenames
--------------------
-
-A designspace file needs to store many references to UFO files.
-
-- designspace files can be part of versioning systems and appear on
- different computers. This means it is not possible to store absolute
- paths.
-- So, all paths are relative to the designspace document path.
-- Using relative paths allows designspace files and UFO files to be
- **near** each other, and that they can be **found** without enforcing
- one particular structure.
-- The **filename** attribute in the ``SourceDescriptor`` and
- ``InstanceDescriptor`` classes stores the preferred relative path.
-- The **path** attribute in these objects stores the absolute path. It
- is calculated from the document path and the relative path in the
- filename attribute when the object is created.
-- Only the **filename** attribute is written to file.
-- Both **filename** and **path** must use forward slashes (``/``) as
- path separators, even on Windows.
-
-Right before we save we need to identify and respond to the following
-situations:
-
-In each descriptor, we have to do the right thing for the filename
-attribute. Before writing to file, the ``documentObject.updatePaths()``
-method prepares the paths as follows:
-
-**Case 1**
-
-::
-
- descriptor.filename == None
- descriptor.path == None
-
-**Action**
-
-- write as is, descriptors will not have a filename attr. Useless, but
- no reason to interfere.
-
-**Case 2**
-
-::
-
- descriptor.filename == "../something"
- descriptor.path == None
-
-**Action**
-
-- write as is. The filename attr should not be touched.
-
-**Case 3**
-
-::
-
- descriptor.filename == None
- descriptor.path == "~/absolute/path/there"
-
-**Action**
-
-- calculate the relative path for filename. We're not overwriting some
- other value for filename, it should be fine.
-
-**Case 4**
-
-::
-
- descriptor.filename == '../somewhere'
- descriptor.path == "~/absolute/path/there"
-
-**Action**
-
-- There is a conflict between the given filename, and the path. The
- difference could have happened for any number of reasons. Assuming
- the values were not in conflict when the object was created, either
- could have changed. We can't guess.
-- Assume the path attribute is more up to date. Calculate a new value
- for filename based on the path and the document path.
-
-Recommendation for editors
---------------------------
-
-- If you want to explicitly set the **filename** attribute, leave the
- path attribute empty.
-- If you want to explicitly set the **path** attribute, leave the
- filename attribute empty. It will be recalculated.
-- Use ``documentObject.updateFilenameFromPath()`` to explicitly set the
- **filename** attributes for all instance and source descriptors.
-
-.. 7-common-lib-key-registry:
-
-7 Common Lib Key Registry
-=========================
-
-public.skipExportGlyphs
------------------------
-
-This lib key works the same as the UFO lib key with the same name. The
-difference is that applications using a Designspace as the corner stone of the
-font compilation process should use the lib key in that Designspace instead of
-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-implementation-and-differences:
-
-
-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
-===============
-
-- Changes are to be expected.
-
-
diff --git a/Doc/source/designspaceLib/scripting.rst b/Doc/source/designspaceLib/scripting.rst
index 5a17816a..63235eec 100644
--- a/Doc/source/designspaceLib/scripting.rst
+++ b/Doc/source/designspaceLib/scripting.rst
@@ -1,6 +1,6 @@
-#######################
-Scripting a designspace
-#######################
+#########################
+3 Scripting a designspace
+#########################
It can be useful to build a designspace with a script rather than
construct one with an interface like
@@ -60,7 +60,7 @@ Make a descriptor object and add it to the document.
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.
+ default values of all axes.
Option: add label names
-----------------------
@@ -88,6 +88,7 @@ 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
@@ -124,7 +125,7 @@ So go ahead and add another master:
s1.name = "master.bold"
s1.location = dict(weight=1000)
doc.addSource(s1)
-
+
Option: exclude glyphs
----------------------
@@ -241,7 +242,7 @@ This is how you check the default font.
Generating?
***********
-You can generate the UFO's with MutatorMath:
+You can generate the UFOs with MutatorMath:
.. code:: python
@@ -251,3 +252,39 @@ You can generate the UFO's with MutatorMath:
- 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.
diff --git a/Doc/source/designspaceLib/v5_class_diagram.png b/Doc/source/designspaceLib/v5_class_diagram.png
new file mode 100644
index 00000000..7c75bcb9
--- /dev/null
+++ b/Doc/source/designspaceLib/v5_class_diagram.png
Binary files differ
diff --git a/Doc/source/designspaceLib/v5_class_diagram.puml b/Doc/source/designspaceLib/v5_class_diagram.puml
new file mode 100644
index 00000000..31f9e9c3
--- /dev/null
+++ b/Doc/source/designspaceLib/v5_class_diagram.puml
@@ -0,0 +1,292 @@
+@startuml v5_class_diagram
+
+title
+ Designspace v5 Class Diagram
+
+ <size:12>Note: the ""Descriptor"" suffix is omitted from most classes
+end title
+
+' left to right direction
+
+skinparam class {
+BackgroundColor<<New>> PaleGreen
+}
+
+class DesignSpaceDocument {
++ formatVersion: str = None
++ <color:green><b><<New>> elidedFallbackName: str = None
++ rulesProcessingLast: bool = False
++ lib: Dict = {}
+}
+
+note left of DesignSpaceDocument::elidedFallbackName
+STAT Style Attributes Header field ""elidedFallbackNameID""
+end note
+
+abstract class AbstractAxis {
++ tag: str
++ name: str
++ labelNames: Dict[str, str]
++ hidden: bool
++ map: List[Tuple[float, float]]
++ <color:green><b><<New>> axisOrdering: int
+}
+DesignSpaceDocument *-- "*" AbstractAxis: axes >
+note right of AbstractAxis::axisOrdering
+STAT Axis Record field
+end note
+
+class Axis {
++ minimum: float
++ maximum: float
++ default: float
+}
+AbstractAxis <|--- Axis
+note bottom of Axis
+This is the usual
+Axis, with a range
+of values.
+end note
+
+class DiscreteAxis <<New>> {
++ values: List[float]
++ default: float
+}
+AbstractAxis <|--- DiscreteAxis
+note bottom of DiscreteAxis
+A discrete axis is not
+interpolable, e.g.
+Uprights vs Italics, and
+so has "discrete" stops
+instead of a continuous
+range of values.
+end note
+
+Axis .[hidden] DiscreteAxis
+
+class AxisLabel <<New>> {
++ userMinimum: Optional[float]
++ userValue: float
++ userMaximum: Optional[float]
++ name: str
++ elidable: bool
++ olderSibling: bool
++ linkedUserValue: Optional[float]
++ labelNames: Dict[str, str]
+
++ getFormat(): 1 | 2 | 3
+}
+note right of AxisLabel
+Label for a
+stop on an Axis
+(STAT format
+1,2,3)
+end note
+AbstractAxis *-- "*" AxisLabel: <<New>> \n axisLabels >
+
+class LocationLabel <<New>> {
++ name: str
++ location: Dict[str, float]
++ elidable: bool
++ olderSibling: bool
++ labelNames: Dict[str, str]
+}
+note right of LocationLabel
+Label for a
+freestanding
+location
+(STAT format 4)
+end note
+DesignSpaceDocument *--- "*" LocationLabel: <<New>> \n locationLabels >
+
+class Rule {
++ name: str
++ conditionSets: List[ConditionSet]
++ subs: Dict[str, str]
+}
+DesignSpaceDocument *- "*" Rule: rules >
+
+class Source {
++ name: Optional[str]
++ filename: str
++ path: str
++ layerName: Optional[str]
++ <color:brown><s><<Deprecated>> location: Location
++ <color:green><b><<New>> designLocation: AnisotropicLocation
+....
++ font: Optional[Font]
+....
++ familyName: Optional[str]
++ styleName: Optional[str]
++ <color:green><b><<New>> localisedFamilyName: Dict
+....
++ <color:brown><s><<Deprecated>> copyLib: bool
++ <color:brown><s><<Deprecated>> copyInfo: bool
++ <color:brown><s><<Deprecated>> copyGroups: bool
++ <color:brown><s><<Deprecated>> copyFeatures: bool
+....
++ muteKerning: bool
++ muteInfo: bool
++ mutedGlyphNames: List[str]
+----
++ <color:green><b><<New>> getFullDesignLocation(doc)
+}
+DesignSpaceDocument *-- "*" Source: sources >
+note right of Source::localisedFamilyName
+New field to allow generation
+of localised instance names using
+STAT information.
+end note
+note right of Source::copyGroups
+These fields are already not meaningful
+anymore in version 4 (the default source
+will be used as "neutral" for groups, info
+and features. ''copyLib'' can be emulated
+by putting content in the designspace's lib.
+end note
+
+note as NLocSource
+The location of
+sources can still only
+be defined in design
+coordinates, and now
+also by relying on
+axis defaults.
+
+To build the final,
+"full" location, a
+helper method is
+provided, that uses
+axis defaults and
+axis mappings to
+fill in the blanks.
+end note
+NLocSource . Source::designLocation
+NLocSource . Source::getFullDesignLocation
+
+class VariableFont <<New>> {
++ filename: str
++ lib: Dict
+}
+DesignSpaceDocument *--- "*" VariableFont: <<New>> \n variableFonts >
+note right of VariableFont
+A variable font is a
+subset of the designspace
+where everything interpolates
+(and so can be compiled into
+an OpenType variable font).
+end note
+
+abstract class AbstractAxisSubset <<New>> {
++ name: str
+}
+VariableFont *-- "*" AbstractAxisSubset: <<New>> \n axisSubsets >
+
+note right of AbstractAxisSubset
+An axis subset selects a range
+or a spot on each the available
+axes from the whole designspace.
+
+By default, only the default value
+of each axis is used to define the
+variable font.
+
+Continuous axes can be specified
+to include their full range instead;
+or a subset of the range.
+
+Discrete axes can be specified
+to include a different spot than the
+default.
+end note
+
+class RangeAxisSubset <<New>> {
++ userMinimum: float
++ userDefault: float
++ userMaximum: float
+}
+AbstractAxisSubset <|-- RangeAxisSubset
+
+class ValueAxisSubset <<New>> {
++ userValue: float
+}
+AbstractAxisSubset <|-- ValueAxisSubset
+
+class Instance {
++ filename: str
++ path: str
++ <color:brown><s><<Deprecated>> location: Location
++ <color:green><b><<New>> locationLabel: str
++ <color:green><b><<New>> designLocation: AnisotropicLocation
++ <color:green><b><<New>> userLocation: SimpleLocation
+....
++ font: Optional[Font]
+....
++ <color:orange><b><<Changed>> name: Optional[str]
++ <color:orange><b><<Changed>> familyName: Optional[str]
++ <color:orange><b><<Changed>> styleName: Optional[str]
++ <color:orange><b><<Changed>> postScriptFontName: Optional[str]
++ <color:orange><b><<Changed>> styleMapFamilyName: Optional[str]
++ <color:orange><b><<Changed>> styleMapStyleName: Optional[str]
++ localisedFamilyName: Dict
++ localisedStyleName: Dict
++ localisedStyleMapFamilyName: Dict
++ localisedStyleMapStyleName: Dict
+....
++ <color:brown><s><<Deprecated>> glyphs: Dict
++ <color:brown><s><<Deprecated>> kerning: bool
++ <color:brown><s><<Deprecated>> info: bool
+....
++ lib: Dict
+----
++ <color:green><b><<New>> clearLocation()
++ <color:green><b><<New>> getLocationLabelDescriptor(doc)
++ <color:green><b><<New>> getFullDesignLocation(doc)
++ <color:green><b><<New>> getFullUserLocation(doc)
+}
+DesignSpaceDocument *-- "*" Instance: instances >
+note right of Instance::locationLabel
+The location can now alternatively
+be a string = name of a LocationLabel
+(STAT format 4). The instance then
+adopts the location of that label.
+See the Decovar example file.
+end note
+note right of Instance::styleMapStyleName
+All the name field are now optional.
+Their default values will be computed
+using the provided STAT table data
+and the STAT rules from the spec.
+For styleMap{Family,Style}Name, they
+would be computed using the STAT
+""linkedUserValue"" fields.
+end note
+note right of Instance::glyphs
+This attribute has been replaced by rules
+end note
+note right of Instance::kerning
+All instances get kerning and info
+nowadays.
+end note
+
+note as NLocInstance
+The location of instances
+can now be defined using
+either:
+- a STAT LocationLabel
+- design coordinates
+- user coordinates.
+- relying on axis defaults
+
+To build the final, "full"
+location, a few helper
+methods are provided,
+that aggregate data from
+these sources and apply
+the axis mappings.
+end note
+NLocInstance . Instance::designLocation
+NLocInstance . Instance::getFullDesignLocation
+
+@enduml
+
diff --git a/Doc/source/designspaceLib/v5_split_downconvert.png b/Doc/source/designspaceLib/v5_split_downconvert.png
new file mode 100644
index 00000000..8867378b
--- /dev/null
+++ b/Doc/source/designspaceLib/v5_split_downconvert.png
Binary files differ
diff --git a/Doc/source/designspaceLib/v5_split_downconvert.puml b/Doc/source/designspaceLib/v5_split_downconvert.puml
new file mode 100644
index 00000000..6f51f80c
--- /dev/null
+++ b/Doc/source/designspaceLib/v5_split_downconvert.puml
@@ -0,0 +1,147 @@
+@startuml v5_split_downconvert
+
+title
+How to split and down-convert a DesignSpace version 5
+<size:14>so that existing tools can work with it
+end title
+
+
+start
+
+#lightgrey:DesignSpace 5.0
+- with STAT data
+- optional instance names
+- with discrete axes
+- with multiple VFs]
+
+note left
+ - STAT data means that compilers will need
+ to write that data to the ouput TTFs
+ - optional instance names means that
+ compilers will need to generate any missing
+ names using the STAT data
+ - discrete axes mean that not all sources
+ are compatible for interpolation
+ - multiple VFs means that compilers will
+ need to output several TTFs for one DS
+end note
+
+split
+split again
+
+:""splitInterpolating()"";
+
+note left
+ - Create one DS document per interpolating
+ sub-space, for example: with a discrete
+ axis for Upright vs Italics, create 2
+ DesignSpaces, one for the Uprights, and
+ one for the Italics.
+ - Expand all missing instance names using
+ the STAT data.
+ - Drop all the STAT data because as we start
+ taking out discrete axes, the sub-documents
+ lose STAT data anyway (only the full
+ document at the start of the process should
+ be used to generate the STAT table)
+end note
+
+split
+
+ #lightgrey:DesignSpace 5.0
+ - (no STAT data)
+ - (explicit instance names)
+ - (at discrete location #1)
+ - with multiple VFs]
+
+ note left
+ All sources in this sub-space are
+ (supposed to be) compatible for
+ interpolation
+ end note
+
+split again
+
+ #lightgrey:DesignSpace 5.0
+ - (no STAT data)
+ - (explicit instance names)
+ - (at discrete location #2)
+ - with multiple VFs]
+ detach
+
+split again
+
+ #lightgrey:etc
+ ...]
+ detach
+
+end split
+
+:check compatibility;
+:build compatible master TTFs with cu2qu;
+
+:""splitVariableFonts()"";
+
+note left
+ Create one DS document per variable font,
+ for example: the above document may
+ describe a weight and width space, out of
+ which we'll build 3 variable fonts:
+ full weight + width, weight only, width only.
+end note
+
+split
+
+ #lightgrey:DesignSpace 5.0
+ - (no STAT data)
+ - (explicit instance names)
+ - (at discrete location #1)
+ - (describing just VF #1)]
+
+ note left
+ This document looks very much
+ like version 4.1, you can just
+ change the version number at
+ the top and feed it to a tool
+ that doesn't know about v5;
+ see ""convert5to4()"".
+ end note
+
+split again
+
+ #lightgrey:DesignSpace 5.0
+ - (no STAT data)
+ - (explicit instance names)
+ - (at discrete location #1)
+ - (describing just VF #2)]
+ detach
+
+split again
+
+ #lightgrey:etc
+ ...]
+ detach
+
+end split
+
+:""varLib.build()"";
+
+#lightgrey:Variable font TTF
+No STAT data]
+
+end split
+
+:""buildVFStatTable()"";
+
+note left
+Apply STAT data using the full document
+from the start of the process + filtering
+entries based on this VF's location
+end note
+
+#lightgrey:Variable font TTF
+With STAT data]
+
+end
+
+@enduml
diff --git a/Doc/source/designspaceLib/v5_variable_fonts_vs_instances.png b/Doc/source/designspaceLib/v5_variable_fonts_vs_instances.png
new file mode 100644
index 00000000..7a5915a3
--- /dev/null
+++ b/Doc/source/designspaceLib/v5_variable_fonts_vs_instances.png
Binary files differ
diff --git a/Doc/source/designspaceLib/xml.rst b/Doc/source/designspaceLib/xml.rst
new file mode 100644
index 00000000..58cf6a77
--- /dev/null
+++ b/Doc/source/designspaceLib/xml.rst
@@ -0,0 +1,1014 @@
+.. _document-xml-structure:
+
+**********************
+Document XML structure
+**********************
+
+.. sectnum::
+ :start: 2
+.. Note: impossible with Sphinx to avoid numbering the document title
+.. See this issue: https://github.com/sphinx-doc/sphinx/issues/4628
+
+.. contents:: Table of contents (levels match the document structure)
+ :local:
+
+========
+Overview
+========
+
+
+.. code:: xml
+
+ <?xml version='1.0' encoding='utf-8'?>
+ <designspace format="5.0">
+ <axes>
+ <!-- define axes here -->
+ <axis... />
+ </axes>
+ <labels>
+ <!-- define STAT format 4 labels here -->
+ <!-- New in version 5.0 -->
+ <label... />
+ </labels>
+ <sources>
+ <!-- define masters here -->
+ <source... />
+ </sources>
+ <variable-fonts>
+ <!-- define variable fonts here -->
+ <!-- New in version 5.0 -->
+ <variable-font... />
+ </variable-fonts>
+ <instances>
+ <!-- define instances here -->
+ <instance... />
+ </instances>
+ <rules>
+ <!-- define rules here -->
+ <rule... />
+ </rules>
+ <lib>
+ <dict>
+ <!-- store custom data here -->
+ </dict>
+ </lib>
+ </designspace>
+
+==================
+``<axes>`` element
+==================
+
+The ``<axes>`` element contains one or more ``<axis>`` elements.
+
+.. rubric:: Attributes
+
+- ``elidedfallbackname``: optional, string.
+ STAT Style Attributes Header field ``elidedFallbackNameID``.
+ See: `OTSpec STAT Style Attributes Header
+ <https://docs.microsoft.com/en-us/typography/opentype/spec/stat#style-attributes-header>`_
+
+ .. versionadded:: 5.0
+
+
+``<axis>`` element
+==================
+
+- Define a single axis
+- Child element of ``axes``
+- The axis can be either continuous (as in version 4.0) or discrete (new in version 5.0).
+ Discrete axes have a list of values instead of a range minimum and maximum.
+
+
+.. rubric:: Attributes
+
+- ``name``: required, string. Name of the axis that is used in the
+ location elements.
+- ``tag``: required, string, 4 letters. Some axis tags are registered
+ in the OpenType Specification.
+- ``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.
+
+For a continuous 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.
+
+For a discrete axis:
+ - ``values``: required, space-separated numbers. The exhaustive list of possible values along this axis.
+
+ .. versionadded:: 5.0
+
+
+.. rubric:: Example
+
+.. code:: xml
+
+ <axis name="weight" tag="wght" minimum="1" maximum="1000" default="400">
+
+ <!--
+ Discrete axes provide a list of discrete values.
+ No interpolation is allowed between these.
+ -->
+ <axis name="Italic" tag="ital" default="0" values="0 1">
+
+
+.. _labelname:
+
+``<labelname>`` element (axis)
+------------------------------
+
+- Defines a human readable name for UI use.
+- Optional for non-registered axis names.
+- Can be localised with ``xml:lang``
+- Child element of ``<axis>`` or ``<label>``
+
+
+.. rubric:: Attributes
+
+- ``xml:lang``: required, string. `XML language
+ definition <https://www.w3.org/International/questions/qa-when-xmllang.en>`__
+
+
+.. rubric:: Value
+
+- The natural language name of this axis or STAT label.
+
+
+.. rubric:: Example
+
+.. code:: xml
+
+ <labelname xml:lang="fa-IR">قطر</labelname>
+ <labelname xml:lang="en">Wéíght</labelname>
+
+
+``<map>`` element
+-----------------
+
+- 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.
+
+.. rubric:: Example
+
+.. code:: xml
+
+ <map input="1.0" output="10.0" />
+ <map input="400.0" output="66.0" />
+ <map input="1000.0" output="990.0" />
+
+
+``<labels>`` element (axis)
+---------------------------
+
+The ``<labels>`` element contains one or more ``<label>`` elements.
+
+.. versionadded:: 5.0
+
+
+``<label>`` element (axis)
+..........................
+
+- Define STAT format 1, 2, 3 labels for the locations on this axis.
+- The axis can have several child ``<label>`` elements, one for each STAT entry.
+- This ``<label>`` element can have several ``<labelname>`` child elements,
+ to provide translations of its ``name`` attribute.
+
+.. versionadded:: 5.0
+
+.. rubric:: Attributes
+
+- ``name``: required, string. the name of this label
+- ``elidable``: optional, boolean, default: false. STAT flag ``ELIDABLE_AXIS_VALUE_NAME``.
+- ``oldersibling``: optional, boolean, default: false. STAT flag ``OLDER_SIBLING_FONT_ATTRIBUTE``.
+
+ See: `OTSpec STAT Flags <https://docs.microsoft.com/en-us/typography/opentype/spec/stat#flags>`_
+
+Depending on the intended target STAT format, use a combination of the following
+field, all in user coordinates. Check the following table for the format
+correspondences.
+
+- ``uservalue``: (required) STAT field ``value`` (format 1, 3) or ``nominalValue`` (format 2).
+- ``userminimum``: STAT field ``rangeMinValue`` (format 2).
+- ``usermaximum``: STAT field ``rangeMaxValue`` (format 2).
+- ``linkeduservalue``: STAT field ``linkedValue`` (format 3).
+
+=========== ========= =========== =========== ===============
+STAT Format uservalue userminimum usermaximum linkeduservalue
+=========== ========= =========== =========== ===============
+1 ✅ ❌ ❌ ❌
+2 ✅ ✅ ✅ ❌
+3 ✅ ❌ ❌ ✅
+=========== ========= =========== =========== ===============
+
+.. rubric:: Example
+
+.. code:: xml
+
+ <label userminimum="200" uservalue="200" usermaximum="250" name="Extra Light">
+ <labelname xml:lang="de">Extraleicht</labelname>
+ <labelname xml:lang="fr">Extra léger</labelname>
+ </label>
+ <label userminimum="350" uservalue="400" usermaximum="450" name="Regular" elidable="true" />
+
+
+``<labelname>`` element (axis STAT label)
+,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
+
+User-facing translations of this STAT label. Keyed by ``xml:lang`` code.
+
+.. versionadded:: 5.0
+
+Same attribute and value as :ref:`the axis' \<labelname\> element <labelname>`.
+
+
+Example of all axis elements together
+=====================================
+
+.. code:: xml
+
+ <axes elidedfallbackname="Regular">
+ <axis default="1" maximum="1000" minimum="0" name="weight" tag="wght">
+ <labelname xml:lang="fa-IR">قطر</labelname>
+ <labelname xml:lang="en">Wéíght</labelname>
+ <labels>
+ <label userminimum="200" uservalue="200" usermaximum="250" name="Extra Light">
+ <labelname xml:lang="de">Extraleicht</labelname>
+ <labelname xml:lang="fr">Extra léger</labelname>
+ </label>
+ <label userminimum="350" uservalue="400" usermaximum="450" name="Regular" elidable="true" />
+ </labels>
+ </axis>
+ <axis default="100" maximum="200" minimum="50" name="width" tag="wdth">
+ <map input="50.0" output="10.0" />
+ <map input="100.0" output="66.0" />
+ <map input="200.0" output="990.0" />
+ </axis>
+ </axes>
+
+
+================================
+``<labels>`` element (top-level)
+================================
+
+The ``<labels>`` element contains one or more ``<label>`` elements.
+
+.. versionadded:: 5.0
+
+``<label>`` element (top-level)
+===============================
+
+- Define STAT format 4 labels for a free-standing location.
+- The designspace can have several top-level ``<label>`` elements, one for each
+ STAT format 4 entry.
+- This ``<label>`` element must have a child ``<location>`` element that
+ represents the location to which the label applies.
+- This ``<label>`` element may have several child ``<labelname>`` elements to
+ provide translations of its ``name`` attribute.
+
+
+See: `OTSpec STAT Axis value table, format 4 <https://docs.microsoft.com/en-us/typography/opentype/spec/stat#axis-value-table-format-4>`_
+
+.. versionadded:: 5.0
+
+.. rubric:: Attributes
+
+- ``name``: required, string. the name of this label
+- ``elidable``: optional, boolean, default: false. STAT flag ``ELIDABLE_AXIS_VALUE_NAME``.
+- ``oldersibling``: optional, boolean, default: false. STAT flag ``OLDER_SIBLING_FONT_ATTRIBUTE``.
+
+ See: `OTSpec STAT Flags <https://docs.microsoft.com/en-us/typography/opentype/spec/stat#flags>`_
+
+
+.. _location:
+
+``<location>`` element (top-level STAT label)
+---------------------------------------------
+
+- Defines a coordinate in either user or design space.
+- Encodes a dictionary of ``{ axisname: axisvalue }``.
+- Also used in ``<source>``, ``<instance>`` and ``<glyph>`` elements.
+- This ``<location>`` element must have one or more child ``<dimension>``
+ elements.
+
+.. _dimension:
+
+``<dimension>`` element
+.......................
+
+- Child element of ``<location>``
+
+.. rubric:: Attributes
+
+- ``name``: required, string. Name of the axis.
+
+Depending on whether you're representing a location in user or design coordinates,
+provide one of the attributes below.
+
+For user-space coordinates:
+
+- ``uservalue``: required, number. The value on this axis in user coordinates.
+
+ .. versionadded:: 5.0
+
+For design-space coordinates:
+
+- ``xvalue``: required, number. The value on this axis in design coordinates.
+- ``yvalue``: optional, number. Separate value for anisotropic interpolations.
+
+
+.. rubric:: Example
+
+.. code:: xml
+
+ <location>
+ <dimension name="Width" uservalue="125" />
+ <dimension name="Weight" xvalue="10" yvalue="20.5" />
+ </location>
+
+
+``<labelname>`` element (top-level STAT label)
+----------------------------------------------
+
+User-facing translations of this STAT label. Keyed by ``xml:lang`` code.
+
+.. versionadded:: 5.0
+
+Same attribute and value as :ref:`the axis' \<labelname\> element <labelname>`.
+
+
+.. _rules-element:
+
+===================
+``<rules>`` element
+===================
+
+The ``<rules>`` element contains one or more ``<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``.
+
+
+.. rubric:: 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``, and put
+ the substitution rule in the ``rvrn`` feature.
+ - If ``processing`` is ``last``, put it in ``rclt``.
+ - The default is ``first``. For new projects, you probably want ``last``.
+ See the following issues for more information:
+ `fontTools#1371 <https://github.com/fonttools/fonttools/issues/1371#issuecomment-590214572>`__
+ `fontTools#2050 <https://github.com/fonttools/fonttools/issues/2050#issuecomment-678691020>`__
+ - If you want to use a different feature altogether, e.g. ``calt``,
+ use the lib key ``com.github.fonttools.varLib.featureVarsFeatureTag``
+
+ .. code:: xml
+
+ <lib>
+ <dict>
+ <key>com.github.fonttools.varLib.featureVarsFeatureTag</key>
+ <string>calt</string>
+ </dict>
+ </lib>
+
+
+
+``<rule>`` element
+==================
+
+- Defines a named rule.
+- Each ``<rule>`` element contains one or more ``<conditionset>`` elements.
+- **Only one** ``<conditionset>`` needs to be true to trigger the rule (logical OR).
+- **All** conditions in a ``<conditionset>`` must be true to make the ``<conditionset>`` true. (logical AND)
+- 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.
+- For authoring tools it might be necessary to save designspace files without ``<sub>`` elements just because the work is incomplete.
+
+
+.. rubric:: Attributes
+
+- ``name``: optional, string. A unique name that can be used to
+ identify this rule if it needs to be referenced elsewhere. The name
+ is not important for compiling variable fonts.
+
+``<conditionset>`` element
+--------------------------
+
+- Child element of ``<rule>``
+- Contains one or more ``<condition>`` elements.
+
+
+``<condition>`` element
+.......................
+
+- Child element of ``<conditionset>``
+- 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.
+
+
+.. rubric:: Attributes
+
+- ``name``: string, required. Must match one of the defined ``axis``
+ name attributes.
+- ``minimum``: number, required*. The low value, in design coordinates.
+- ``maximum``: number, required*. The high value, in design coordinates.
+
+.. If you want to specify the condition limits in design coordinates:
+
+.. If you want to specify the condition limits in user coordinates:
+
+.. - ``userminimum``: number, required*. The low value, in design coordinates.
+.. - ``usermaximum``: number, required*. The high value, in design coordinates.
+
+``<sub>`` element
+-----------------
+
+- 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**.
+
+
+.. rubric:: Attributes
+
+- ``name``: string, required. The name of the glyph this rule looks
+ for.
+- ``with``: string, required. The name of the glyph it is replaced
+ with.
+
+
+.. rubric:: Example
+
+Example with an implied ``<conditionset>``. Here the conditions are not
+contained in a conditionset.
+
+.. code:: xml
+
+ <rules processing="last">
+ <rule name="named.rule.1">
+ <condition minimum="250" maximum="750" name="weight" />
+ <condition minimum="50" maximum="100" name="width" />
+ <sub name="dollar" with="dollar.alt"/>
+ </rule>
+ </rules>
+
+Example with ``<conditionsets>``. All conditions in a conditionset must be true.
+
+.. code:: xml
+
+ <rules>
+ <rule name="named.rule.2">
+ <conditionset>
+ <condition minimum="250" maximum="750" name="weight" />
+ <condition minimum="50" maximum="100" name="width" />
+ </conditionset>
+ <conditionset>
+ <condition... />
+ <condition... />
+ </conditionset>
+ <sub name="dollar" with="dollar.alt"/>
+ </rule>
+ </rules>
+
+
+=====================
+``<sources>`` element
+=====================
+
+The ``<sources>`` element contains one or more ``<source>`` elements.
+
+
+``<source>`` element
+====================
+
+- Defines a single font or layer that contributes to the designspace.
+- Child element of ``<sources>``
+- Location in designspace coordinates.
+
+
+.. rubric:: Attributes
+
+- ``familyname``: optional, string. The family name of the source font.
+ While this could be extracted from the font data itself, it can be
+ more efficient to add it here.
+- ``stylename``: optional, string. The style name of the source font.
+- ``name``: required, string. A unique name that can be used to
+ identify this font if it needs to be referenced elsewhere.
+- ``filename``: required, string. A path to the source file, relative
+ to the root path of this document. The path can be at the same level
+ as the document or lower.
+- ``layer``: optional, string. The name of the layer in the source file.
+ If no layer attribute is given assume the foreground layer should be used.
+
+
+``<familyname>`` element: localised names for sources
+-----------------------------------------------------
+
+Localised family names for sources can be included with this ``<familyname>``
+element with an ``xml:lang`` attribute:
+`XML language definition <https://www.w3.org/International/questions/qa-when-xmllang.en>`__
+
+.. versionadded:: 5.0
+
+.. rubric:: Example
+
+.. code:: xml
+
+ <familyname xml:lang="fr">Montserrat</familyname>
+ <familyname xml:lang="ja">モンセラート</familyname>
+
+
+``<location>`` element (source)
+-------------------------------
+
+Defines the coordinates of this source in the design space.
+
+.. seealso:: `Full documentation of the <location> element <location>`__
+
+
+``<dimension>`` element (source)
+................................
+
+.. seealso:: `Full documentation of the <dimension> element <dimension>`__
+
+
+``<lib>`` element (source)
+--------------------------
+
+- Example: ``<lib copy="1" />``
+- Child element of ``<source>``
+- Defines if the instances can inherit the data in the lib of this source.
+- MutatorMath only.
+
+.. deprecated:: 5.0
+
+.. note::
+
+ Don't confuse with other ``<lib>`` elements which allow storing
+ arbitrary data. Sources don't have such a ``<lib>`` because usually the
+ backing UFO file has one itself.
+
+
+``<info>`` element
+------------------
+
+- Example: ``<info copy="1" />``
+- Child element of ``<source>``
+- Defines if the instances can inherit the non-interpolating font info
+ from this source.
+- MutatorMath only.
+
+.. deprecated:: 5.0
+
+
+``<features>`` element
+----------------------
+
+- Example: ``<features copy="1" />``
+- Defines if the instances can inherit opentype feature text from this
+ source.
+- Child element of ``<source>``
+- MutatorMath only.
+
+.. deprecated:: 5.0
+
+
+``<glyph>`` element (source)
+----------------------------
+
+- Example: ``<glyph mute="1" name="A"/>``
+- In a ``<source>`` element this states if a glyph is to be excluded from
+ the calculation.
+- MutatorMath only.
+
+.. rubric:: Attributes
+
+- ``mute``: optional attribute, number 1 or 0. Indicate if this glyph
+ should be ignored as a master.
+
+.. note::
+
+ Do not confuse with the ``<glyph>`` element in instances, which achieves
+ something different.
+
+
+.. _kerning_source:
+
+``<kerning>`` element (source)
+------------------------------
+
+- Example: ``<kerning mute="1" />``
+- Can appear in ``<source>`` as well as in ``<instance>`` elements.
+- MutatorMath only.
+
+.. rubric:: Attributes
+
+- ``mute``: required attribute, number 1 or 0. Indicate if the kerning
+ data from this source is to be excluded from the calculation.
+
+ - If the kerning element is not present, assume ``mute=0``, yes,
+ include the kerning of this source in the calculation.
+
+
+.. rubric:: Example
+
+.. code:: xml
+
+ <source familyname="MasterFamilyName" filename="masters/masterTest1.ufo" name="master.ufo1" stylename="MasterStyleNameOne">
+ <location>
+ <dimension name="width" xvalue="0.000000" />
+ <dimension name="weight" xvalue="0.000000" />
+ </location>
+ <glyph mute="1" name="A" />
+ <glyph mute="1" name="Z" />
+ </source>
+
+
+============================
+``<variable-fonts>`` element
+============================
+
+The ``<variable-fonts>`` element contains one or more ``<variable-font>`` elements.
+
+.. versionadded:: 5.0
+
+
+``<variable-font>`` element
+===========================
+
+- Child of ``<variable-fonts>``
+- Describe a variable font that can be built from an interpolating subset of
+ the design space.
+- The document may have zero to many variable fonts.
+
+ - If no variable fonts are defined, and all the axes are continuous, then we
+ assume, as in version 4 of the format, that the whole document describes
+ one variable font covering the whole space.
+
+- Each variable font covers a subset of the whole designspace, defined using
+ ``<axis-subset>`` elements.
+- Each variable font can have custom associated data using a ``<lib>`` element.
+
+.. versionadded:: 5.0
+
+.. rubric:: Attributes
+
+- ``name``: string, required. Each variable font has a name, that can be
+ used by build tools to refer to the font that gets built from this element.
+- ``filename``: string, optional. This filename will be used by tools to decide
+ where to store the built font on the disk. If not given, a filename can be
+ computed from the ``name``. The filename may include an extension (e.g.
+ `.ttf`) and the build tools can replace that extension with another (e.g.
+ `.otf` or `.woff2`) as needed.
+
+.. rubric:: Example
+
+.. code:: xml
+
+ <variable-font name="MyFontVF_Italic">
+ <axis-subsets>
+ <axis-subset name="Weight"/>
+ <axis-subset name="Italic" uservalue="1"/>
+ </axis-subsets>
+ </variable-font>
+
+
+``<axis-subsets>`` element
+--------------------------
+
+- Child of ``<variable-font>``
+- Defines the portion of the design space that this variable font covers.
+- Each axis that you want to include in the VF needs to be mentioned here.
+- Not mentioning an axis is equivalent to slicing the space at the default
+ value of that axis.
+
+.. versionadded:: 5.0
+
+
+``<axis-subset>`` element
+.........................
+
+- Child of ``<axis-subsets>``
+- Defines the subset of one axis, by ``name=""``, that the variable font covers.
+- If this axis is continuous, the VF can either cover:
+
+ 1. the whole axis
+
+ .. code:: xml
+
+ <axis-subset name="Weight"/>
+
+ 2. a sub-range of the full axis
+
+ .. code:: xml
+
+ <axis-subset name="Weight" userminimum="400" usermaximum="500" userdefault="400"/>
+
+ 3. a specific value along that axis; then the axis is not functional in the VF
+ but the design space is sliced at the given location.
+
+ .. code:: xml
+
+ <!-- Make a bold VF -->
+ <axis-subset name="Weight" uservalue="700"/>
+
+- If this axis is discrete, then only the third option above is possible:
+ give one value along the axis.
+
+ .. code:: xml
+
+ <!-- Make an italic VF -->
+ <axis-subset name="Italic" uservalue="1"/>
+
+
+.. versionadded:: 5.0
+
+.. rubric:: Attributes
+
+- ``name``: required, string. Name of the axis to subset.
+
+When defining a range:
+
+- ``userminimum``: optional, number.
+ Lower end of the range, in user coordinates.
+ If not mentioned, assume the axis's minimum.
+- ``usermaximum``: optional, number.
+ Upper end of the range, in user coordinates.
+ If not mentioned, assume the axis's maximum.
+- ``userdefault``: optional, number.
+ New default value of subset axis, in user coordinates.
+ If not mentioned, assume the axis's default.
+ If the axis's default falls outside of the subset range, then the new default
+ will be the extremum that is closest to the full axis's default.
+
+When defining a single value:
+
+- ``uservalue``: required, number.
+ Single value, in user coordinates, at which to snapshot the design space
+ while building this VF.
+
+
+``<lib>`` element (variable font)
+---------------------------------
+
+Arbitrary data about this variable font.
+
+.. versionadded:: 5.0
+
+.. seealso:: :ref:`lib`
+
+
+Instances included in the variable font
+---------------------------------------
+
+.. figure:: v5_variable_fonts_vs_instances.png
+ :width: 650px
+ :alt: A designspace version 5 lists many instances and variable fonts. Each
+ variable font gets in its fvar table whichever instances fall within
+ the bounds of the variable font's subset axes.
+
+ Illustration of instances included in a variable font.
+
+
+=======================
+``<instances>`` element
+=======================
+
+The ``<instances>`` element contains one or more ``<instance>`` elements.
+
+
+``<instance>`` element
+======================
+
+- Defines a single font that can be calculated with the designspace.
+- Child element of ``<instances>``
+- For use in Varlib the instance element really only needs the names
+ and the location. The ``<glyphs>`` element is not required.
+- 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.
+
+
+.. rubric:: Attributes
+
+- ``familyname``: required, string. The family name of the instance
+ font. Corresponds with ``font.info.familyName``
+- ``stylename``: required, string. The style name of the instance font.
+ Corresponds with ``font.info.styleName``
+- ``name``: required, string. A unique name that can be used to
+ identify this font if it needs to be referenced elsewhere.
+- ``filename``: string. Required for MutatorMath. A path to the
+ instance file, relative to the root path of this document. The path
+ can be at the same level as the document or lower.
+- ``postscriptfontname``: string. Optional for MutatorMath. Corresponds
+ with ``font.info.postscriptFontName``
+- ``stylemapfamilyname``: string. Optional for MutatorMath. Corresponds
+ with ``styleMapFamilyName``
+- ``stylemapstylename``: string. Optional for MutatorMath. Corresponds
+ with ``styleMapStyleName``
+
+
+``<location>`` element (instance)
+---------------------------------
+
+Defines the coordinates of this instance in the design space.
+
+.. seealso:: `Full documentation of the <location> element <location>`__
+
+
+``<dimension>`` element (instance)
+..................................
+
+.. seealso:: `Full documentation of the <dimension> element <dimension>`__
+
+
+``<lib>`` element (instance)
+----------------------------
+
+Arbitrary data about this instance.
+
+.. seealso:: :ref:`lib`
+
+
+``<stylename>``, ``<familyname>``, ``<stylemapstylename>``, ``<stylemapfamilyname>`` elements: localised names for instances
+----------------------------------------------------------------------------------------------------------------------------
+
+Localised names for instances can be included with these simple elements
+with an ``xml:lang`` attribute:
+`XML language definition <https://www.w3.org/International/questions/qa-when-xmllang.en>`__
+
+- ``<stylename>``
+- ``<familyname>``
+- ``<stylemapstylename>``
+- ``<stylemapfamilyname>``
+
+
+.. rubric:: Example
+
+.. code:: xml
+
+ <stylename xml:lang="fr">Demigras</stylename>
+ <stylename xml:lang="ja">半ば</stylename>
+ <familyname xml:lang="fr">Montserrat</familyname>
+ <familyname xml:lang="ja">モンセラート</familyname>
+ <stylemapstylename xml:lang="de">Standard</stylemapstylename>
+ <stylemapfamilyname xml:lang="de">Montserrat Halbfett</stylemapfamilyname>
+ <stylemapfamilyname xml:lang="ja">モンセラート SemiBold</stylemapfamilyname>
+
+
+Example for varlib
+------------------
+
+.. code:: xml
+
+ <instance familyname="InstanceFamilyName" filename="instances/instanceTest2.ufo" name="instance.ufo2" postscriptfontname="InstancePostscriptName" stylemapfamilyname="InstanceStyleMapFamilyName" stylemapstylename="InstanceStyleMapStyleName" stylename="InstanceStyleName">
+ <location>
+ <dimension name="width" xvalue="400" yvalue="300" />
+ <dimension name="weight" xvalue="66" />
+ </location>
+ <lib>
+ <dict>
+ <key>com.coolDesignspaceApp.specimenText</key>
+ <string>Hamburgerwhatever</string>
+ </dict>
+ </lib>
+ </instance>
+
+
+``<glyphs>`` element (instance)
+-------------------------------
+
+- Container for ``<glyph>`` elements.
+- Optional
+- MutatorMath only.
+
+.. deprecated:: 5.0
+
+
+``<glyph>`` element (instance)
+..............................
+
+- Child element of ``<glyphs>``
+- May contain a ``<location>`` element.
+
+.. deprecated:: 5.0
+
+.. rubric:: Attributes
+
+- ``name``: string. The name of the glyph.
+- ``unicode``: string. Unicode values for this glyph, in hexadecimal.
+ Multiple values should be separated with a space.
+- ``mute``: optional attribute, number 1 or 0. Indicate if this glyph
+ should be supressed in the output.
+
+
+``<note>`` element
+,,,,,,,,,,,,,,,,,,
+
+- String. The value corresponds to glyph.note in UFO.
+
+.. deprecated:: 5.0
+
+
+``<masters>`` element
+,,,,,,,,,,,,,,,,,,,,,
+
+- Container for ``<master>`` elements
+- These ``<master>`` elements define an alternative set of glyph masters
+ for this glyph.
+
+.. deprecated:: 5.0
+
+
+``<master>`` element
+++++++++++++++++++++
+
+- Defines a single alternative master for this glyph.
+
+.. deprecated:: 5.0
+
+.. rubric:: Attributes
+
+- ``glyphname``: the name of the alternate master glyph.
+- ``source``: the identifier name of the source this master glyph needs
+ to be loaded from
+
+
+Example for MutatorMath
+.......................
+
+.. code:: xml
+
+ <instance familyname="InstanceFamilyName" filename="instances/instanceTest2.ufo" name="instance.ufo2" postscriptfontname="InstancePostscriptName" stylemapfamilyname="InstanceStyleMapFamilyName" stylemapstylename="InstanceStyleMapStyleName" stylename="InstanceStyleName">
+ <location>
+ <dimension name="width" xvalue="400" yvalue="300" />
+ <dimension name="weight" xvalue="66" />
+ </location>
+ <glyphs>
+ <glyph name="arrow2" />
+ <glyph name="arrow" unicode="0x4d2 0x4d3">
+ <location>
+ <dimension name="width" xvalue="100" />
+ <dimension name="weight" xvalue="120" />
+ </location>
+ <note>A note about this glyph</note>
+ <masters>
+ <master glyphname="BB" source="master.ufo1">
+ <location>
+ <dimension name="width" xvalue="20" />
+ <dimension name="weight" xvalue="20" />
+ </location>
+ </master>
+ </masters>
+ </glyph>
+ </glyphs>
+ <kerning />
+ <info />
+ <lib>
+ <dict>
+ <key>com.coolDesignspaceApp.specimenText</key>
+ <string>Hamburgerwhatever</string>
+ </dict>
+ </lib>
+ </instance>
+
+
+.. _lib:
+
+=============================
+``<lib>`` element (top-level)
+=============================
+
+The ``<lib>`` element contains arbitrary data.
+
+- Child element of ``<designspace>``, ``<variable-font>`` and ``<instance>``
+- Contains arbitrary data about the whole document or about a specific
+ variable font or instance.
+- Items in the dict need to use **reverse domain name notation**
+ <https://en.wikipedia.org/wiki/Reverse_domain_name_notation>__
+
+.. rubric:: Example:
+
+.. code:: xml
+
+ <lib>
+ <dict>
+ <key>com.company.fontEditor.myString</key>
+ <string>The contents use the PLIST format.</string>
+ </dict>
+ </lib>
+
+
diff --git a/Doc/source/index.rst b/Doc/source/index.rst
index 784834d8..571ef8dd 100644
--- a/Doc/source/index.rst
+++ b/Doc/source/index.rst
@@ -69,6 +69,7 @@ libraries in the fontTools suite:
- :py:mod:`fontTools.agl`: Access to the Adobe Glyph List
- :py:mod:`fontTools.cffLib`: Read/write tools for Adobe CFF fonts
- :py:mod:`fontTools.colorLib`: Module for handling colors in CPAL/COLR fonts
+- :py:mod:`fontTools.config`: Configure fontTools
- :py:mod:`fontTools.cu2qu`: Module for cubic to quadratic conversion
- :py:mod:`fontTools.designspaceLib`: Read and write designspace files
- :py:mod:`fontTools.encodings`: Support for font-related character encodings
@@ -120,6 +121,7 @@ Table of Contents
agl
cffLib/index
colorLib/index
+ config
cu2qu/index
designspaceLib/index
encodings/index
@@ -152,4 +154,4 @@ Table of Contents
:target: https://pypi.org/project/FontTools
.. |Gitter Chat| image:: https://badges.gitter.im/fonttools-dev/Lobby.svg
:alt: Join the chat at https://gitter.im/fonttools-dev/Lobby
- :target: https://gitter.im/fonttools-dev/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge \ No newline at end of file
+ :target: https://gitter.im/fonttools-dev/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
diff --git a/Doc/source/misc/configTools.rst b/Doc/source/misc/configTools.rst
new file mode 100644
index 00000000..1d935411
--- /dev/null
+++ b/Doc/source/misc/configTools.rst
@@ -0,0 +1,8 @@
+###########
+configTools
+###########
+
+.. automodule:: fontTools.misc.configTools
+ :inherited-members:
+ :members:
+ :undoc-members:
diff --git a/Doc/source/misc/index.rst b/Doc/source/misc/index.rst
index 003c48a5..cfe52055 100644
--- a/Doc/source/misc/index.rst
+++ b/Doc/source/misc/index.rst
@@ -12,6 +12,7 @@ utilities by fontTools, but some of which may be more generally useful.
bezierTools
classifyTools
cliTools
+ configTools
eexec
encodingTools
etree
diff --git a/Lib/fontTools/__init__.py b/Lib/fontTools/__init__.py
index 7fa7b304..9a39ea0f 100644
--- a/Lib/fontTools/__init__.py
+++ b/Lib/fontTools/__init__.py
@@ -3,6 +3,6 @@ from fontTools.misc.loggingTools import configLogger
log = logging.getLogger(__name__)
-version = __version__ = "4.31.2"
+version = __version__ = "4.33.3"
__all__ = ["version", "log", "configLogger"]
diff --git a/Lib/fontTools/cffLib/__init__.py b/Lib/fontTools/cffLib/__init__.py
index 07d0d513..fc82bb27 100644
--- a/Lib/fontTools/cffLib/__init__.py
+++ b/Lib/fontTools/cffLib/__init__.py
@@ -337,7 +337,7 @@ class CFFFontSet(object):
topDict = TopDict(
GlobalSubrs=self.GlobalSubrs,
cff2GetGlyphOrder=cff2GetGlyphOrder)
- self.topDictIndex = TopDictIndex(None, cff2GetGlyphOrder, None)
+ self.topDictIndex = TopDictIndex(None, cff2GetGlyphOrder)
self.topDictIndex.append(topDict)
for element in content:
if isinstance(element, str):
@@ -375,7 +375,7 @@ class CFFFontSet(object):
filled via :meth:`decompile`.)"""
self.major = 2
cff2GetGlyphOrder = self.otFont.getGlyphOrder
- topDictData = TopDictIndex(None, cff2GetGlyphOrder, None)
+ topDictData = TopDictIndex(None, cff2GetGlyphOrder)
topDictData.items = self.topDictIndex.items
self.topDictIndex = topDictData
topDict = topDictData[0]
@@ -1004,11 +1004,6 @@ class VarStoreData(object):
def decompile(self):
if self.file:
- class GlobalState(object):
- def __init__(self, tableType, cachingStats):
- self.tableType = tableType
- self.cachingStats = cachingStats
- globalState = GlobalState(tableType="VarStore", cachingStats={})
# read data in from file. Assume position is correct.
length = readCard16(self.file)
self.data = self.file.read(length)
diff --git a/Lib/fontTools/config/__init__.py b/Lib/fontTools/config/__init__.py
new file mode 100644
index 00000000..f5a62eaf
--- /dev/null
+++ b/Lib/fontTools/config/__init__.py
@@ -0,0 +1,59 @@
+"""
+Define all configuration options that can affect the working of fontTools
+modules. E.g. optimization levels of varLib IUP, otlLib GPOS compression level,
+etc. If this file gets too big, split it into smaller files per-module.
+
+An instance of the Config class can be attached to a TTFont object, so that
+the various modules can access their configuration options from it.
+"""
+from textwrap import dedent
+
+from fontTools.misc.configTools import *
+
+
+class Config(AbstractConfig):
+ options = Options()
+
+
+OPTIONS = Config.options
+
+
+Config.register_option(
+ name="fontTools.otlLib.optimize.gpos:COMPRESSION_LEVEL",
+ help=dedent(
+ """\
+ GPOS Lookup type 2 (PairPos) compression level:
+ 0 = do not attempt to compact PairPos lookups;
+ 1 to 8 = create at most 1 to 8 new subtables for each existing
+ subtable, provided that it would yield a 50%% file size saving;
+ 9 = create as many new subtables as needed to yield a file size saving.
+ Default: 0.
+
+ This compaction aims to save file size, by splitting large class
+ kerning subtables (Format 2) that contain many zero values into
+ smaller and denser subtables. It's a trade-off between the overhead
+ of several subtables versus the sparseness of one big subtable.
+
+ See the pull request: https://github.com/fonttools/fonttools/pull/2326
+ """
+ ),
+ default=0,
+ parse=int,
+ validate=lambda v: v in range(10),
+)
+
+Config.register_option(
+ name="fontTools.ttLib.tables.otBase:USE_HARFBUZZ_REPACKER",
+ help=dedent(
+ """\
+ FontTools tries to use the HarfBuzz Repacker to serialize GPOS/GSUB tables
+ if the uharfbuzz python bindings are importable, otherwise falls back to its
+ slower, less efficient serializer. Set to False to always use the latter.
+ Set to True to explicitly request the HarfBuzz Repacker (will raise an
+ error if uharfbuzz cannot be imported).
+ """
+ ),
+ default=None,
+ parse=Option.parse_optional_bool,
+ validate=Option.validate_optional_bool,
+)
diff --git a/Lib/fontTools/designspaceLib/__init__.py b/Lib/fontTools/designspaceLib/__init__.py
index 4b706827..400e960e 100644
--- a/Lib/fontTools/designspaceLib/__init__.py
+++ b/Lib/fontTools/designspaceLib/__init__.py
@@ -1,13 +1,19 @@
-# -*- coding: utf-8 -*-
+from __future__ import annotations
-from fontTools.misc.loggingTools import LogMixin
-from fontTools.misc.textTools import tobytes, tostr
import collections
-from io import BytesIO, StringIO
+import copy
+import itertools
+import math
import os
import posixpath
+from io import BytesIO, StringIO
+from textwrap import indent
+from typing import Any, Dict, List, MutableMapping, Optional, Tuple, Union
+
from fontTools.misc import etree as ET
from fontTools.misc import plistlib
+from fontTools.misc.loggingTools import LogMixin
+from fontTools.misc.textTools import tobytes, tostr
"""
designSpaceDocument
@@ -40,6 +46,7 @@ def posix(path):
def posixpath_property(private_name):
+ """Generate a propery that holds a path always using forward slashes."""
def getter(self):
# Normal getter
return getattr(self, private_name)
@@ -93,16 +100,41 @@ class SimpleDescriptor(AsDictMixin):
except AssertionError:
print("failed attribute", attr, getattr(self, attr), "!=", getattr(other, attr))
+ def __repr__(self):
+ attrs = [f"{a}={repr(getattr(self, a))}," for a in self._attrs]
+ attrs = indent('\n'.join(attrs), ' ')
+ return f"{self.__class__.__name__}(\n{attrs}\n)"
+
class SourceDescriptor(SimpleDescriptor):
- """Simple container for data related to the source"""
+ """Simple container for data related to the source
+
+ .. code:: python
+
+ doc = DesignSpaceDocument()
+ s1 = SourceDescriptor()
+ s1.path = masterPath1
+ s1.name = "master.ufo1"
+ s1.font = defcon.Font("master.ufo1")
+ s1.location = dict(weight=0)
+ s1.familyName = "MasterFamilyName"
+ s1.styleName = "MasterStyleNameOne"
+ s1.localisedFamilyName = dict(fr="Caractère")
+ s1.mutedGlyphNames.append("A")
+ s1.mutedGlyphNames.append("Z")
+ doc.addSource(s1)
+
+ """
flavor = "source"
_attrs = ['filename', 'path', 'name', 'layerName',
'location', 'copyLib',
'copyGroups', 'copyFeatures',
'muteKerning', 'muteInfo',
'mutedGlyphNames',
- 'familyName', 'styleName']
+ 'familyName', 'styleName', 'localisedFamilyName']
+
+ filename = posixpath_property("_filename")
+ path = posixpath_property("_path")
def __init__(
self,
@@ -112,9 +144,11 @@ class SourceDescriptor(SimpleDescriptor):
font=None,
name=None,
location=None,
+ designLocation=None,
layerName=None,
familyName=None,
styleName=None,
+ localisedFamilyName=None,
copyLib=False,
copyInfo=False,
copyGroups=False,
@@ -124,8 +158,10 @@ class SourceDescriptor(SimpleDescriptor):
mutedGlyphNames=None,
):
self.filename = filename
- """The original path as found in the document."""
+ """string. A relative path to the source file, **as it is in the document**.
+ MutatorMath + VarLib.
+ """
self.path = path
"""The absolute path, calculated from filename."""
@@ -142,27 +178,158 @@ class SourceDescriptor(SimpleDescriptor):
"""
self.name = name
- self.location = location
+ """string. Optional. Unique identifier name for this source.
+
+ MutatorMath + Varlib.
+ """
+
+ self.designLocation = designLocation if designLocation is not None else location or {}
+ """dict. Axis values for this source, in design space coordinates.
+
+ MutatorMath + Varlib.
+
+ This may be only part of the full design location.
+ See :meth:`getFullDesignLocation()`
+
+ .. versionadded:: 5.0
+ """
+
self.layerName = layerName
+ """string. The name of the layer in the source to look for
+ outline data. Default ``None`` which means ``foreground``.
+ """
self.familyName = familyName
+ """string. Family name of this source. Though this data
+ can be extracted from the font, it can be efficient to have it right
+ here.
+
+ Varlib.
+ """
self.styleName = styleName
+ """string. Style name of this source. Though this data
+ can be extracted from the font, it can be efficient to have it right
+ here.
+
+ Varlib.
+ """
+ self.localisedFamilyName = localisedFamilyName or {}
+ """dict. A dictionary of localised family name strings, keyed by
+ language code.
+
+ If present, will be used to build localized names for all instances.
+
+ .. versionadded:: 5.0
+ """
self.copyLib = copyLib
+ """bool. Indicates if the contents of the font.lib need to
+ be copied to the instances.
+
+ MutatorMath.
+
+ .. deprecated:: 5.0
+ """
self.copyInfo = copyInfo
+ """bool. Indicates if the non-interpolating font.info needs
+ to be copied to the instances.
+
+ MutatorMath.
+
+ .. deprecated:: 5.0
+ """
self.copyGroups = copyGroups
+ """bool. Indicates if the groups need to be copied to the
+ instances.
+
+ MutatorMath.
+
+ .. deprecated:: 5.0
+ """
self.copyFeatures = copyFeatures
+ """bool. Indicates if the feature text needs to be
+ copied to the instances.
+
+ MutatorMath.
+
+ .. deprecated:: 5.0
+ """
self.muteKerning = muteKerning
+ """bool. Indicates if the kerning data from this source
+ needs to be muted (i.e. not be part of the calculations).
+
+ MutatorMath only.
+ """
self.muteInfo = muteInfo
+ """bool. Indicated if the interpolating font.info data for
+ this source needs to be muted.
+
+ MutatorMath only.
+ """
self.mutedGlyphNames = mutedGlyphNames or []
+ """list. Glyphnames that need to be muted in the
+ instances.
+
+ MutatorMath only.
+ """
+
+ @property
+ def location(self):
+ """dict. Axis values for this source, in design space coordinates.
+
+ MutatorMath + Varlib.
+
+ .. deprecated:: 5.0
+ Use the more explicit alias for this property :attr:`designLocation`.
+ """
+ return self.designLocation
+
+ @location.setter
+ def location(self, location: Optional[AnisotropicLocationDict]):
+ self.designLocation = location or {}
+
+ def setFamilyName(self, familyName, languageCode="en"):
+ """Setter for :attr:`localisedFamilyName`
+
+ .. versionadded:: 5.0
+ """
+ self.localisedFamilyName[languageCode] = tostr(familyName)
+
+ def getFamilyName(self, languageCode="en"):
+ """Getter for :attr:`localisedFamilyName`
+
+ .. versionadded:: 5.0
+ """
+ return self.localisedFamilyName.get(languageCode)
- path = posixpath_property("_path")
- filename = posixpath_property("_filename")
+
+ def getFullDesignLocation(self, doc: 'DesignSpaceDocument') -> AnisotropicLocationDict:
+ """Get the complete design location of this source, from its
+ :attr:`designLocation` and the document's axis defaults.
+
+ .. versionadded:: 5.0
+ """
+ result: AnisotropicLocationDict = {}
+ for axis in doc.axes:
+ if axis.name in self.designLocation:
+ result[axis.name] = self.designLocation[axis.name]
+ else:
+ result[axis.name] = axis.map_forward(axis.default)
+ return result
class RuleDescriptor(SimpleDescriptor):
- """Represents the rule descriptor element
+ """Represents the rule descriptor element: a set of glyph substitutions to
+ trigger conditionally in some parts of the designspace.
- .. code-block:: xml
+ .. code:: python
+
+ r1 = RuleDescriptor()
+ r1.name = "unique.rule.name"
+ r1.conditionSets.append([dict(name="weight", minimum=-10, maximum=10), dict(...)])
+ r1.conditionSets.append([dict(...), dict(...)])
+ r1.subs.append(("a", "a.alt"))
+
+ .. code:: xml
<!-- optional: list of substitution rules -->
<rules>
@@ -181,21 +348,36 @@ class RuleDescriptor(SimpleDescriptor):
def __init__(self, *, name=None, conditionSets=None, subs=None):
self.name = name
+ """string. Unique name for this rule. Can be used to reference this rule data."""
# list of lists of dict(name='aaaa', minimum=0, maximum=1000)
self.conditionSets = conditionSets or []
+ """a list of conditionsets.
+
+ - Each conditionset is a list of conditions.
+ - Each condition is a dict with ``name``, ``minimum`` and ``maximum`` keys.
+ """
# list of substitutions stored as tuples of glyphnames ("a", "a.alt")
self.subs = subs or []
+ """list of substitutions.
+
+ - Each substitution is stored as tuples of glyphnames, e.g. ("a", "a.alt").
+ - Note: By default, rules are applied first, before other text
+ shaping/OpenType layout, as they are part of the
+ `Required Variation Alternates OpenType feature <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#-tag-rvrn>`_.
+ See ref:`rules-element` § Attributes.
+ """
def evaluateRule(rule, location):
- """ Return True if any of the rule's conditionsets matches the given location."""
+ """Return True if any of the rule's conditionsets matches the given location."""
return any(evaluateConditions(c, location) for c in rule.conditionSets)
def evaluateConditions(conditions, location):
- """ Return True if all the conditions matches the given location.
- If a condition has no minimum, check for < maximum.
- If a condition has no maximum, check for > minimum.
+ """Return True if all the conditions matches the given location.
+
+ - If a condition has no minimum, check for < maximum.
+ - If a condition has no maximum, check for > minimum.
"""
for cd in conditions:
value = location[cd['name']]
@@ -211,8 +393,11 @@ def evaluateConditions(conditions, location):
def processRules(rules, location, glyphNames):
- """ Apply these rules at this location to these glyphnames
- - rule order matters
+ """Apply these rules at this location to these glyphnames.
+
+ Return a new list of glyphNames with substitutions applied.
+
+ - rule order matters
"""
newNames = []
for rule in rules:
@@ -232,22 +417,54 @@ def processRules(rules, location, glyphNames):
return glyphNames
+AnisotropicLocationDict = Dict[str, Union[float, Tuple[float, float]]]
+SimpleLocationDict = Dict[str, float]
+
+
class InstanceDescriptor(SimpleDescriptor):
- """Simple container for data related to the instance"""
+ """Simple container for data related to the instance
+
+
+ .. code:: python
+
+ i2 = InstanceDescriptor()
+ i2.path = instancePath2
+ i2.familyName = "InstanceFamilyName"
+ i2.styleName = "InstanceStyleName"
+ i2.name = "instance.ufo2"
+ # anisotropic location
+ i2.designLocation = dict(weight=500, width=(400,300))
+ i2.postScriptFontName = "InstancePostscriptName"
+ i2.styleMapFamilyName = "InstanceStyleMapFamilyName"
+ i2.styleMapStyleName = "InstanceStyleMapStyleName"
+ i2.lib['com.coolDesignspaceApp.specimenText'] = 'Hamburgerwhatever'
+ doc.addInstance(i2)
+ """
flavor = "instance"
_defaultLanguageCode = "en"
- _attrs = ['path',
+ _attrs = ['filename',
+ 'path',
'name',
- 'location',
+ 'locationLabel',
+ 'designLocation',
+ 'userLocation',
'familyName',
'styleName',
'postScriptFontName',
'styleMapFamilyName',
'styleMapStyleName',
+ 'localisedFamilyName',
+ 'localisedStyleName',
+ 'localisedStyleMapFamilyName',
+ 'localisedStyleMapStyleName',
+ 'glyphs',
'kerning',
'info',
'lib']
+ filename = posixpath_property("_filename")
+ path = posixpath_property("_path")
+
def __init__(
self,
*,
@@ -256,6 +473,9 @@ class InstanceDescriptor(SimpleDescriptor):
font=None,
name=None,
location=None,
+ locationLabel=None,
+ designLocation=None,
+ userLocation=None,
familyName=None,
styleName=None,
postScriptFontName=None,
@@ -270,34 +490,148 @@ class InstanceDescriptor(SimpleDescriptor):
info=True,
lib=None,
):
- # the original path as found in the document
self.filename = filename
- # the absolute path, calculated from filename
+ """string. Relative path to the instance file, **as it is
+ in the document**. The file may or may not exist.
+
+ MutatorMath + VarLib.
+ """
self.path = path
- # Same as in SourceDescriptor.
+ """string. Absolute path to the instance file, calculated from
+ the document path and the string in the filename attr. The file may
+ or may not exist.
+
+ MutatorMath.
+ """
self.font = font
+ """Same as :attr:`SourceDescriptor.font`
+
+ .. seealso:: :attr:`SourceDescriptor.font`
+ """
self.name = name
- self.location = location
+ """string. Unique identifier name of the instance, used to
+ identify it if it needs to be referenced from elsewhere in the
+ document.
+ """
+ self.locationLabel = locationLabel
+ """Name of a :class:`LocationLabelDescriptor`. If
+ provided, the instance should have the same location as the
+ LocationLabel.
+
+ .. seealso::
+ :meth:`getFullDesignLocation`
+ :meth:`getFullUserLocation`
+
+ .. versionadded:: 5.0
+ """
+ self.designLocation: AnisotropicLocationDict = designLocation if designLocation is not None else (location or {})
+ """dict. Axis values for this instance, in design space coordinates.
+
+ MutatorMath + Varlib.
+
+ .. seealso:: This may be only part of the full location. See:
+ :meth:`getFullDesignLocation`
+ :meth:`getFullUserLocation`
+
+ .. versionadded:: 5.0
+ """
+ self.userLocation: SimpleLocationDict = userLocation or {}
+ """dict. Axis values for this instance, in user space coordinates.
+
+ MutatorMath + Varlib.
+
+ .. seealso:: This may be only part of the full location. See:
+ :meth:`getFullDesignLocation`
+ :meth:`getFullUserLocation`
+
+ .. versionadded:: 5.0
+ """
self.familyName = familyName
+ """string. Family name of this instance.
+
+ MutatorMath + Varlib.
+ """
self.styleName = styleName
+ """string. Style name of this instance.
+
+ MutatorMath + Varlib.
+ """
self.postScriptFontName = postScriptFontName
+ """string. Postscript fontname for this instance.
+
+ MutatorMath + Varlib.
+ """
self.styleMapFamilyName = styleMapFamilyName
+ """string. StyleMap familyname for this instance.
+
+ MutatorMath + Varlib.
+ """
self.styleMapStyleName = styleMapStyleName
+ """string. StyleMap stylename for this instance.
+
+ MutatorMath + Varlib.
+ """
self.localisedFamilyName = localisedFamilyName or {}
+ """dict. A dictionary of localised family name
+ strings, keyed by language code.
+ """
self.localisedStyleName = localisedStyleName or {}
+ """dict. A dictionary of localised stylename
+ strings, keyed by language code.
+ """
self.localisedStyleMapFamilyName = localisedStyleMapFamilyName or {}
+ """A dictionary of localised style map
+ familyname strings, keyed by language code.
+ """
self.localisedStyleMapStyleName = localisedStyleMapStyleName or {}
+ """A dictionary of localised style map
+ stylename strings, keyed by language code.
+ """
self.glyphs = glyphs or {}
+ """dict for special master definitions for glyphs. If glyphs
+ need special masters (to record the results of executed rules for
+ example).
+
+ MutatorMath.
+
+ .. deprecated:: 5.0
+ Use rules or sparse sources instead.
+ """
self.kerning = kerning
+ """ bool. Indicates if this instance needs its kerning
+ calculated.
+
+ MutatorMath.
+
+ .. deprecated:: 5.0
+ """
self.info = info
+ """bool. Indicated if this instance needs the interpolating
+ font.info calculated.
+
+ .. deprecated:: 5.0
+ """
self.lib = lib or {}
"""Custom data associated with this instance."""
- path = posixpath_property("_path")
- filename = posixpath_property("_filename")
+ @property
+ def location(self):
+ """dict. Axis values for this instance.
+
+ MutatorMath + Varlib.
+
+ .. deprecated:: 5.0
+ Use the more explicit alias for this property :attr:`designLocation`.
+ """
+ return self.designLocation
+
+ @location.setter
+ def location(self, location: Optional[AnisotropicLocationDict]):
+ self.designLocation = location or {}
def setStyleName(self, styleName, languageCode="en"):
+ """These methods give easier access to the localised names."""
self.localisedStyleName[languageCode] = tostr(styleName)
def getStyleName(self, languageCode="en"):
@@ -321,6 +655,106 @@ class InstanceDescriptor(SimpleDescriptor):
def getStyleMapFamilyName(self, languageCode="en"):
return self.localisedStyleMapFamilyName.get(languageCode)
+ def clearLocation(self, axisName: Optional[str] = None):
+ """Clear all location-related fields. Ensures that
+ :attr:``designLocation`` and :attr:``userLocation`` are dictionaries
+ (possibly empty if clearing everything).
+
+ In order to update the location of this instance wholesale, a user
+ should first clear all the fields, then change the field(s) for which
+ they have data.
+
+ .. code:: python
+
+ instance.clearLocation()
+ instance.designLocation = {'Weight': (34, 36.5), 'Width': 100}
+ instance.userLocation = {'Opsz': 16}
+
+ In order to update a single axis location, the user should only clear
+ that axis, then edit the values:
+
+ .. code:: python
+
+ instance.clearLocation('Weight')
+ instance.designLocation['Weight'] = (34, 36.5)
+
+ Args:
+ axisName: if provided, only clear the location for that axis.
+
+ .. versionadded:: 5.0
+ """
+ self.locationLabel = None
+ if axisName is None:
+ self.designLocation = {}
+ self.userLocation = {}
+ else:
+ if self.designLocation is None:
+ self.designLocation = {}
+ if axisName in self.designLocation:
+ del self.designLocation[axisName]
+ if self.userLocation is None:
+ self.userLocation = {}
+ if axisName in self.userLocation:
+ del self.userLocation[axisName]
+
+ def getLocationLabelDescriptor(self, doc: 'DesignSpaceDocument') -> Optional[LocationLabelDescriptor]:
+ """Get the :class:`LocationLabelDescriptor` instance that matches
+ this instances's :attr:`locationLabel`.
+
+ Raises if the named label can't be found.
+
+ .. versionadded:: 5.0
+ """
+ if self.locationLabel is None:
+ return None
+ label = doc.getLocationLabel(self.locationLabel)
+ if label is None:
+ raise DesignSpaceDocumentError(
+ 'InstanceDescriptor.getLocationLabelDescriptor(): '
+ f'unknown location label `{self.locationLabel}` in instance `{self.name}`.'
+ )
+ return label
+
+ def getFullDesignLocation(self, doc: 'DesignSpaceDocument') -> AnisotropicLocationDict:
+ """Get the complete design location of this instance, by combining data
+ from the various location fields, default axis values and mappings, and
+ top-level location labels.
+
+ The source of truth for this instance's location is determined for each
+ axis independently by taking the first not-None field in this list:
+
+ - ``locationLabel``: the location along this axis is the same as the
+ matching STAT format 4 label. No anisotropy.
+ - ``designLocation[axisName]``: the explicit design location along this
+ axis, possibly anisotropic.
+ - ``userLocation[axisName]``: the explicit user location along this
+ axis. No anisotropy.
+ - ``axis.default``: default axis value. No anisotropy.
+
+ .. versionadded:: 5.0
+ """
+ label = self.getLocationLabelDescriptor(doc)
+ if label is not None:
+ return doc.map_forward(label.userLocation) # type: ignore
+ result: AnisotropicLocationDict = {}
+ for axis in doc.axes:
+ if axis.name in self.designLocation:
+ result[axis.name] = self.designLocation[axis.name]
+ elif axis.name in self.userLocation:
+ result[axis.name] = axis.map_forward(self.userLocation[axis.name])
+ else:
+ result[axis.name] = axis.map_forward(axis.default)
+ return result
+
+ def getFullUserLocation(self, doc: 'DesignSpaceDocument') -> SimpleLocationDict:
+ """Get the complete user location for this instance.
+
+ .. seealso:: :meth:`getFullDesignLocation`
+
+ .. versionadded:: 5.0
+ """
+ return doc.map_backward(self.getFullDesignLocation(doc))
+
def tagForAxisName(name):
# try to find or make a tag name for this axis name
@@ -340,12 +774,8 @@ def tagForAxisName(name):
return tag, dict(en=name)
-class AxisDescriptor(SimpleDescriptor):
- """ Simple container for the axis data
- Add more localisations?
- """
+class AbstractAxisDescriptor(SimpleDescriptor):
flavor = "axis"
- _attrs = ['tag', 'name', 'maximum', 'minimum', 'default', 'map']
def __init__(
self,
@@ -353,23 +783,122 @@ class AxisDescriptor(SimpleDescriptor):
tag=None,
name=None,
labelNames=None,
- minimum=None,
- default=None,
- maximum=None,
hidden=False,
map=None,
+ axisOrdering=None,
+ axisLabels=None,
):
# opentype tag for this axis
self.tag = tag
+ """string. Four letter tag for this axis. Some might be
+ registered at the `OpenType
+ specification <https://www.microsoft.com/typography/otspec/fvar.htm#VAT>`__.
+ Privately-defined axis tags must begin with an uppercase letter and
+ use only uppercase letters or digits.
+ """
# name of the axis used in locations
self.name = name
+ """string. Name of the axis as it is used in the location dicts.
+
+ MutatorMath + Varlib.
+ """
# names for UI purposes, if this is not a standard axis,
self.labelNames = labelNames or {}
+ """dict. When defining a non-registered axis, it will be
+ necessary to define user-facing readable names for the axis. Keyed by
+ xml:lang code. Values are required to be ``unicode`` strings, even if
+ they only contain ASCII characters.
+ """
+ self.hidden = hidden
+ """bool. Whether this axis should be hidden in user interfaces.
+ """
+ self.map = map or []
+ """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.
+ """
+ self.axisOrdering = axisOrdering
+ """STAT table field ``axisOrdering``.
+
+ See: `OTSpec STAT Axis Record <https://docs.microsoft.com/en-us/typography/opentype/spec/stat#axis-records>`_
+
+ .. versionadded:: 5.0
+ """
+ self.axisLabels: List[AxisLabelDescriptor] = axisLabels or []
+ """STAT table entries for Axis Value Tables format 1, 2, 3.
+
+ See: `OTSpec STAT Axis Value Tables <https://docs.microsoft.com/en-us/typography/opentype/spec/stat#axis-value-tables>`_
+
+ .. versionadded:: 5.0
+ """
+
+
+class AxisDescriptor(AbstractAxisDescriptor):
+ """ Simple container for the axis data.
+
+ Add more localisations?
+
+ .. code:: python
+
+ a1 = AxisDescriptor()
+ a1.minimum = 1
+ a1.maximum = 1000
+ a1.default = 400
+ a1.name = "weight"
+ a1.tag = "wght"
+ a1.labelNames['fa-IR'] = "قطر"
+ a1.labelNames['en'] = "Wéíght"
+ a1.map = [(1.0, 10.0), (400.0, 66.0), (1000.0, 990.0)]
+ a1.axisOrdering = 1
+ a1.axisLabels = [
+ AxisLabelDescriptor(name="Regular", userValue=400, elidable=True)
+ ]
+ doc.addAxis(a1)
+ """
+ _attrs = ['tag', 'name', 'maximum', 'minimum', 'default', 'map', 'axisOrdering', 'axisLabels']
+
+ def __init__(
+ self,
+ *,
+ tag=None,
+ name=None,
+ labelNames=None,
+ minimum=None,
+ default=None,
+ maximum=None,
+ hidden=False,
+ map=None,
+ axisOrdering=None,
+ axisLabels=None,
+ ):
+ super().__init__(
+ tag=tag,
+ name=name,
+ labelNames=labelNames,
+ hidden=hidden,
+ map=map,
+ axisOrdering=axisOrdering,
+ axisLabels=axisLabels,
+ )
self.minimum = minimum
+ """number. The minimum value for this axis in user space.
+
+ MutatorMath + Varlib.
+ """
self.maximum = maximum
+ """number. The maximum value for this axis in user space.
+
+ MutatorMath + Varlib.
+ """
self.default = default
- self.hidden = hidden
- self.map = map or []
+ """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.
+ """
def serialize(self):
# output to a dict, used in testing
@@ -382,9 +911,12 @@ class AxisDescriptor(SimpleDescriptor):
default=self.default,
hidden=self.hidden,
map=self.map,
+ axisOrdering=self.axisOrdering,
+ axisLabels=self.axisLabels,
)
def map_forward(self, v):
+ """Maps value from axis mapping's input (user) to output (design)."""
from fontTools.varLib.models import piecewiseLinearMap
if not self.map:
@@ -392,18 +924,349 @@ class AxisDescriptor(SimpleDescriptor):
return piecewiseLinearMap(v, {k: v for k, v in self.map})
def map_backward(self, v):
+ """Maps value from axis mapping's output (design) to input (user)."""
from fontTools.varLib.models import piecewiseLinearMap
+ if isinstance(v, tuple):
+ v = v[0]
if not self.map:
return v
return piecewiseLinearMap(v, {v: k for k, v in self.map})
+class DiscreteAxisDescriptor(AbstractAxisDescriptor):
+ """Container for discrete axis data.
+
+ Use this for axes that do not interpolate. The main difference from a
+ continuous axis is that a continuous axis has a ``minimum`` and ``maximum``,
+ while a discrete axis has a list of ``values``.
+
+ Example: an Italic axis with 2 stops, Roman and Italic, that are not
+ compatible. The axis still allows to bind together the full font family,
+ which is useful for the STAT table, however it can't become a variation
+ axis in a VF.
+
+ .. code:: python
+
+ a2 = DiscreteAxisDescriptor()
+ a2.values = [0, 1]
+ a2.name = "Italic"
+ a2.tag = "ITAL"
+ a2.labelNames['fr'] = "Italique"
+ a2.map = [(0, 0), (1, -11)]
+ a2.axisOrdering = 2
+ a2.axisLabels = [
+ AxisLabelDescriptor(name="Roman", userValue=0, elidable=True)
+ ]
+ doc.addAxis(a2)
+
+ .. versionadded:: 5.0
+ """
+
+ flavor = "axis"
+ _attrs = ('tag', 'name', 'values', 'default', 'map', 'axisOrdering', 'axisLabels')
+
+ def __init__(
+ self,
+ *,
+ tag=None,
+ name=None,
+ labelNames=None,
+ values=None,
+ default=None,
+ hidden=False,
+ map=None,
+ axisOrdering=None,
+ axisLabels=None,
+ ):
+ super().__init__(
+ tag=tag,
+ name=name,
+ labelNames=labelNames,
+ hidden=hidden,
+ map=map,
+ axisOrdering=axisOrdering,
+ axisLabels=axisLabels,
+ )
+ self.default: float = default
+ """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.
+
+ However, this default value is less important than in continuous axes:
+
+ - it doesn't define the "neutral" version of outlines from which
+ deltas would apply, as this axis does not interpolate.
+ - it doesn't provide the reference glyph set for the designspace, as
+ fonts at each value can have different glyph sets.
+ """
+ self.values: List[float] = values or []
+ """List of possible values for this axis. Contrary to continuous axes,
+ only the values in this list can be taken by the axis, nothing in-between.
+ """
+
+ def map_forward(self, value):
+ """Maps value from axis mapping's input to output.
+
+ Returns value unchanged if no mapping entry is found.
+
+ Note: for discrete axes, each value must have its mapping entry, if
+ you intend that value to be mapped.
+ """
+ return next((v for k, v in self.map if k == value), value)
+
+ def map_backward(self, value):
+ """Maps value from axis mapping's output to input.
+
+ Returns value unchanged if no mapping entry is found.
+
+ Note: for discrete axes, each value must have its mapping entry, if
+ you intend that value to be mapped.
+ """
+ if isinstance(value, tuple):
+ value = value[0]
+ return next((k for k, v in self.map if v == value), value)
+
+
+class AxisLabelDescriptor(SimpleDescriptor):
+ """Container for axis label data.
+
+ Analogue of OpenType's STAT data for a single axis (formats 1, 2 and 3).
+ All values are user values.
+ See: `OTSpec STAT Axis value table, format 1, 2, 3 <https://docs.microsoft.com/en-us/typography/opentype/spec/stat#axis-value-table-format-1>`_
+
+ The STAT format of the Axis value depends on which field are filled-in,
+ see :meth:`getFormat`
+
+ .. versionadded:: 5.0
+ """
+
+ flavor = "label"
+ _attrs = ('userMinimum', 'userValue', 'userMaximum', 'name', 'elidable', 'olderSibling', 'linkedUserValue', 'labelNames')
+
+ def __init__(
+ self,
+ *,
+ name,
+ userValue,
+ userMinimum=None,
+ userMaximum=None,
+ elidable=False,
+ olderSibling=False,
+ linkedUserValue=None,
+ labelNames=None,
+ ):
+ self.userMinimum: Optional[float] = userMinimum
+ """STAT field ``rangeMinValue`` (format 2)."""
+ self.userValue: float = userValue
+ """STAT field ``value`` (format 1, 3) or ``nominalValue`` (format 2)."""
+ self.userMaximum: Optional[float] = userMaximum
+ """STAT field ``rangeMaxValue`` (format 2)."""
+ self.name: str = name
+ """Label for this axis location, STAT field ``valueNameID``."""
+ self.elidable: bool = elidable
+ """STAT flag ``ELIDABLE_AXIS_VALUE_NAME``.
+
+ See: `OTSpec STAT Flags <https://docs.microsoft.com/en-us/typography/opentype/spec/stat#flags>`_
+ """
+ self.olderSibling: bool = olderSibling
+ """STAT flag ``OLDER_SIBLING_FONT_ATTRIBUTE``.
+
+ See: `OTSpec STAT Flags <https://docs.microsoft.com/en-us/typography/opentype/spec/stat#flags>`_
+ """
+ self.linkedUserValue: Optional[float] = linkedUserValue
+ """STAT field ``linkedValue`` (format 3)."""
+ self.labelNames: MutableMapping[str, str] = labelNames or {}
+ """User-facing translations of this location's label. Keyed by
+ ``xml:lang`` code.
+ """
+
+ def getFormat(self) -> int:
+ """Determine which format of STAT Axis value to use to encode this label.
+
+ =========== ========= =========== =========== ===============
+ STAT Format userValue userMinimum userMaximum linkedUserValue
+ =========== ========= =========== =========== ===============
+ 1 ✅ ❌ ❌ ❌
+ 2 ✅ ✅ ✅ ❌
+ 3 ✅ ❌ ❌ ✅
+ =========== ========= =========== =========== ===============
+ """
+ if self.linkedUserValue is not None:
+ return 3
+ if self.userMinimum is not None or self.userMaximum is not None:
+ return 2
+ return 1
+
+ @property
+ def defaultName(self) -> str:
+ """Return the English name from :attr:`labelNames` or the :attr:`name`."""
+ return self.labelNames.get("en") or self.name
+
+
+class LocationLabelDescriptor(SimpleDescriptor):
+ """Container for location label data.
+
+ Analogue of OpenType's STAT data for a free-floating location (format 4).
+ All values are user values.
+
+ See: `OTSpec STAT Axis value table, format 4 <https://docs.microsoft.com/en-us/typography/opentype/spec/stat#axis-value-table-format-4>`_
+
+ .. versionadded:: 5.0
+ """
+
+ flavor = "label"
+ _attrs = ('name', 'elidable', 'olderSibling', 'userLocation', 'labelNames')
+
+ def __init__(
+ self,
+ *,
+ name,
+ userLocation,
+ elidable=False,
+ olderSibling=False,
+ labelNames=None,
+ ):
+ self.name: str = name
+ """Label for this named location, STAT field ``valueNameID``."""
+ self.userLocation: SimpleLocationDict = userLocation or {}
+ """Location in user coordinates along each axis.
+
+ If an axis is not mentioned, it is assumed to be at its default location.
+
+ .. seealso:: This may be only part of the full location. See:
+ :meth:`getFullUserLocation`
+ """
+ self.elidable: bool = elidable
+ """STAT flag ``ELIDABLE_AXIS_VALUE_NAME``.
+
+ See: `OTSpec STAT Flags <https://docs.microsoft.com/en-us/typography/opentype/spec/stat#flags>`_
+ """
+ self.olderSibling: bool = olderSibling
+ """STAT flag ``OLDER_SIBLING_FONT_ATTRIBUTE``.
+
+ See: `OTSpec STAT Flags <https://docs.microsoft.com/en-us/typography/opentype/spec/stat#flags>`_
+ """
+ self.labelNames: Dict[str, str] = labelNames or {}
+ """User-facing translations of this location's label. Keyed by
+ xml:lang code.
+ """
+
+ @property
+ def defaultName(self) -> str:
+ """Return the English name from :attr:`labelNames` or the :attr:`name`."""
+ return self.labelNames.get("en") or self.name
+
+ def getFullUserLocation(self, doc: 'DesignSpaceDocument') -> SimpleLocationDict:
+ """Get the complete user location of this label, by combining data
+ from the explicit user location and default axis values.
+
+ .. versionadded:: 5.0
+ """
+ return {
+ axis.name: self.userLocation.get(axis.name, axis.default)
+ for axis in doc.axes
+ }
+
+
+class VariableFontDescriptor(SimpleDescriptor):
+ """Container for variable fonts, sub-spaces of the Designspace.
+
+ Use-cases:
+
+ - From a single DesignSpace with discrete axes, define 1 variable font
+ per value on the discrete axes. Before version 5, you would have needed
+ 1 DesignSpace per such variable font, and a lot of data duplication.
+ - From a big variable font with many axes, define subsets of that variable
+ font that only include some axes and freeze other axes at a given location.
+
+ .. versionadded:: 5.0
+ """
+
+ flavor = "variable-font"
+ _attrs = ('filename', 'axisSubsets', 'lib')
+
+ filename = posixpath_property("_filename")
+
+ def __init__(self, *, name, filename=None, axisSubsets=None, lib=None):
+ self.name: str = name
+ """string, required. Name of this variable to identify it during the
+ build process and from other parts of the document, and also as a
+ filename in case the filename property is empty.
+
+ VarLib.
+ """
+ self.filename: str = filename
+ """string, optional. Relative path to the variable font file, **as it is
+ in the document**. The file may or may not exist.
+
+ If not specified, the :attr:`name` will be used as a basename for the file.
+ """
+ self.axisSubsets: List[Union[RangeAxisSubsetDescriptor, ValueAxisSubsetDescriptor]] = axisSubsets or []
+ """Axis subsets to include in this variable font.
+
+ If an axis is not mentioned, assume that we only want the default
+ location of that axis (same as a :class:`ValueAxisSubsetDescriptor`).
+ """
+ self.lib: MutableMapping[str, Any] = lib or {}
+ """Custom data associated with this variable font."""
+
+
+class RangeAxisSubsetDescriptor(SimpleDescriptor):
+ """Subset of a continuous axis to include in a variable font.
+
+ .. versionadded:: 5.0
+ """
+ flavor = "axis-subset"
+ _attrs = ('name', 'userMinimum', 'userDefault', 'userMaximum')
+
+ def __init__(self, *, name, userMinimum=-math.inf, userDefault=None, userMaximum=math.inf):
+ self.name: str = name
+ """Name of the :class:`AxisDescriptor` to subset."""
+ self.userMinimum: float = userMinimum
+ """New minimum value of the axis in the target variable font.
+ If not specified, assume the same minimum value as the full axis.
+ (default = ``-math.inf``)
+ """
+ self.userDefault: Optional[float] = userDefault
+ """New default value of the axis in the target variable font.
+ If not specified, assume the same default value as the full axis.
+ (default = ``None``)
+ """
+ self.userMaximum: float = userMaximum
+ """New maximum value of the axis in the target variable font.
+ If not specified, assume the same maximum value as the full axis.
+ (default = ``math.inf``)
+ """
+
+
+class ValueAxisSubsetDescriptor(SimpleDescriptor):
+ """Single value of a discrete or continuous axis to use in a variable font.
+
+ .. versionadded:: 5.0
+ """
+ flavor = "axis-subset"
+ _attrs = ('name', 'userValue')
+
+ def __init__(self, *, name, userValue):
+ self.name: str = name
+ """Name of the :class:`AxisDescriptor` or :class:`DiscreteAxisDescriptor`
+ to "snapshot" or "freeze".
+ """
+ self.userValue: float = userValue
+ """Value in user coordinates at which to freeze the given axis."""
+
+
class BaseDocWriter(object):
_whiteSpace = " "
- ruleDescriptorClass = RuleDescriptor
axisDescriptorClass = AxisDescriptor
+ discreteAxisDescriptorClass = DiscreteAxisDescriptor
+ axisLabelDescriptorClass = AxisLabelDescriptor
+ locationLabelDescriptorClass = LocationLabelDescriptor
+ ruleDescriptorClass = RuleDescriptor
sourceDescriptorClass = SourceDescriptor
+ variableFontDescriptorClass = VariableFontDescriptor
+ valueAxisSubsetDescriptorClass = ValueAxisSubsetDescriptor
+ rangeAxisSubsetDescriptorClass = RangeAxisSubsetDescriptor
instanceDescriptorClass = InstanceDescriptor
@classmethod
@@ -422,21 +1285,29 @@ class BaseDocWriter(object):
def getRuleDescriptor(cls):
return cls.ruleDescriptorClass()
- def __init__(self, documentPath, documentObject):
+ def __init__(self, documentPath, documentObject: DesignSpaceDocument):
self.path = documentPath
self.documentObject = documentObject
- self.documentVersion = "4.1"
+ self.effectiveFormatTuple = self._getEffectiveFormatTuple()
self.root = ET.Element("designspace")
- self.root.attrib['format'] = self.documentVersion
- self._axes = [] # for use by the writer only
- self._rules = [] # for use by the writer only
def write(self, pretty=True, encoding="UTF-8", xml_declaration=True):
- if self.documentObject.axes:
- self.root.append(ET.Element("axes"))
+ self.root.attrib['format'] = ".".join(str(i) for i in self.effectiveFormatTuple)
+
+ if self.documentObject.axes or self.documentObject.elidedFallbackName is not None:
+ axesElement = ET.Element("axes")
+ if self.documentObject.elidedFallbackName is not None:
+ axesElement.attrib['elidedfallbackname'] = self.documentObject.elidedFallbackName
+ self.root.append(axesElement)
for axisObject in self.documentObject.axes:
self._addAxis(axisObject)
+ if self.documentObject.locationLabels:
+ labelsElement = ET.Element("labels")
+ for labelObject in self.documentObject.locationLabels:
+ self._addLocationLabel(labelsElement, labelObject)
+ self.root.append(labelsElement)
+
if self.documentObject.rules:
if getattr(self.documentObject, "rulesProcessingLast", False):
attributes = {"processing": "last"}
@@ -451,13 +1322,19 @@ class BaseDocWriter(object):
for sourceObject in self.documentObject.sources:
self._addSource(sourceObject)
+ if self.documentObject.variableFonts:
+ variableFontsElement = ET.Element("variable-fonts")
+ for variableFont in self.documentObject.variableFonts:
+ self._addVariableFont(variableFontsElement, variableFont)
+ self.root.append(variableFontsElement)
+
if self.documentObject.instances:
self.root.append(ET.Element("instances"))
for instanceObject in self.documentObject.instances:
self._addInstance(instanceObject)
if self.documentObject.lib:
- self._addLib(self.documentObject.lib)
+ self._addLib(self.root, self.documentObject.lib, 2)
tree = ET.ElementTree(self.root)
tree.write(
@@ -468,6 +1345,34 @@ class BaseDocWriter(object):
pretty_print=pretty,
)
+ def _getEffectiveFormatTuple(self):
+ """Try to use the version specified in the document, or a sufficiently
+ recent version to be able to encode what the document contains.
+ """
+ minVersion = self.documentObject.formatTuple
+ if (
+ any(
+ isinstance(axis, DiscreteAxisDescriptor) or
+ axis.axisOrdering is not None or
+ axis.axisLabels
+ for axis in self.documentObject.axes
+ ) or
+ self.documentObject.locationLabels or
+ any(
+ source.localisedFamilyName
+ for source in self.documentObject.sources
+ ) or
+ self.documentObject.variableFonts or
+ any(
+ instance.locationLabel or
+ instance.userLocation
+ for instance in self.documentObject.instances
+ )
+ ):
+ if minVersion < (5, 0):
+ minVersion = (5, 0)
+ return minVersion
+
def _makeLocationElement(self, locationObject, name=None):
""" Convert Location dict to a locationElement."""
locElement = ET.Element("location")
@@ -492,11 +1397,10 @@ class BaseDocWriter(object):
def intOrFloat(self, num):
if int(num) == num:
return "%d" % num
- return "%f" % num
+ return ("%f" % num).rstrip('0').rstrip('.')
def _addRule(self, ruleObject):
# if none of the conditions have minimum or maximum values, do not add the rule.
- self._rules.append(ruleObject)
ruleElement = ET.Element('rule')
if ruleObject.name is not None:
ruleElement.attrib['name'] = ruleObject.name
@@ -524,32 +1428,102 @@ class BaseDocWriter(object):
self.root.findall('.rules')[0].append(ruleElement)
def _addAxis(self, axisObject):
- self._axes.append(axisObject)
axisElement = ET.Element('axis')
axisElement.attrib['tag'] = axisObject.tag
axisElement.attrib['name'] = axisObject.name
- axisElement.attrib['minimum'] = self.intOrFloat(axisObject.minimum)
- axisElement.attrib['maximum'] = self.intOrFloat(axisObject.maximum)
- axisElement.attrib['default'] = self.intOrFloat(axisObject.default)
- if axisObject.hidden:
- axisElement.attrib['hidden'] = "1"
- for languageCode, labelName in sorted(axisObject.labelNames.items()):
- languageElement = ET.Element('labelname')
- languageElement.attrib[XML_LANG] = languageCode
- languageElement.text = labelName
- axisElement.append(languageElement)
+ self._addLabelNames(axisElement, axisObject.labelNames)
if axisObject.map:
for inputValue, outputValue in axisObject.map:
mapElement = ET.Element('map')
mapElement.attrib['input'] = self.intOrFloat(inputValue)
mapElement.attrib['output'] = self.intOrFloat(outputValue)
axisElement.append(mapElement)
+ if axisObject.axisOrdering or axisObject.axisLabels:
+ labelsElement = ET.Element('labels')
+ if axisObject.axisOrdering is not None:
+ labelsElement.attrib['ordering'] = str(axisObject.axisOrdering)
+ for label in axisObject.axisLabels:
+ self._addAxisLabel(labelsElement, label)
+ axisElement.append(labelsElement)
+ if isinstance(axisObject, AxisDescriptor):
+ axisElement.attrib['minimum'] = self.intOrFloat(axisObject.minimum)
+ axisElement.attrib['maximum'] = self.intOrFloat(axisObject.maximum)
+ elif isinstance(axisObject, DiscreteAxisDescriptor):
+ axisElement.attrib['values'] = " ".join(self.intOrFloat(v) for v in axisObject.values)
+ axisElement.attrib['default'] = self.intOrFloat(axisObject.default)
+ if axisObject.hidden:
+ axisElement.attrib['hidden'] = "1"
self.root.findall('.axes')[0].append(axisElement)
+ def _addAxisLabel(self, axisElement: ET.Element, label: AxisLabelDescriptor) -> None:
+ labelElement = ET.Element('label')
+ labelElement.attrib['uservalue'] = self.intOrFloat(label.userValue)
+ if label.userMinimum is not None:
+ labelElement.attrib['userminimum'] = self.intOrFloat(label.userMinimum)
+ if label.userMaximum is not None:
+ labelElement.attrib['usermaximum'] = self.intOrFloat(label.userMaximum)
+ labelElement.attrib['name'] = label.name
+ if label.elidable:
+ labelElement.attrib['elidable'] = "true"
+ if label.olderSibling:
+ labelElement.attrib['oldersibling'] = "true"
+ if label.linkedUserValue is not None:
+ labelElement.attrib['linkeduservalue'] = self.intOrFloat(label.linkedUserValue)
+ self._addLabelNames(labelElement, label.labelNames)
+ axisElement.append(labelElement)
+
+ def _addLabelNames(self, parentElement, labelNames):
+ for languageCode, labelName in sorted(labelNames.items()):
+ languageElement = ET.Element('labelname')
+ languageElement.attrib[XML_LANG] = languageCode
+ languageElement.text = labelName
+ parentElement.append(languageElement)
+
+ def _addLocationLabel(self, parentElement: ET.Element, label: LocationLabelDescriptor) -> None:
+ labelElement = ET.Element('label')
+ labelElement.attrib['name'] = label.name
+ if label.elidable:
+ labelElement.attrib['elidable'] = "true"
+ if label.olderSibling:
+ labelElement.attrib['oldersibling'] = "true"
+ self._addLabelNames(labelElement, label.labelNames)
+ self._addLocationElement(labelElement, userLocation=label.userLocation)
+ parentElement.append(labelElement)
+
+ def _addLocationElement(
+ self,
+ parentElement,
+ *,
+ designLocation: AnisotropicLocationDict = None,
+ userLocation: SimpleLocationDict = None
+ ):
+ locElement = ET.Element("location")
+ for axis in self.documentObject.axes:
+ if designLocation is not None and axis.name in designLocation:
+ dimElement = ET.Element('dimension')
+ dimElement.attrib['name'] = axis.name
+ value = designLocation[axis.name]
+ if isinstance(value, tuple):
+ dimElement.attrib['xvalue'] = self.intOrFloat(value[0])
+ dimElement.attrib['yvalue'] = self.intOrFloat(value[1])
+ else:
+ dimElement.attrib['xvalue'] = self.intOrFloat(value)
+ locElement.append(dimElement)
+ elif userLocation is not None and axis.name in userLocation:
+ dimElement = ET.Element('dimension')
+ dimElement.attrib['name'] = axis.name
+ value = userLocation[axis.name]
+ dimElement.attrib['uservalue'] = self.intOrFloat(value)
+ locElement.append(dimElement)
+ if len(locElement) > 0:
+ parentElement.append(locElement)
+
def _addInstance(self, instanceObject):
instanceElement = ET.Element('instance')
if instanceObject.name is not None:
instanceElement.attrib['name'] = instanceObject.name
+ if instanceObject.locationLabel is not None:
+ instanceElement.attrib['location'] = instanceObject.locationLabel
if instanceObject.familyName is not None:
instanceElement.attrib['familyname'] = instanceObject.familyName
if instanceObject.styleName is not None:
@@ -596,9 +1570,19 @@ class BaseDocWriter(object):
localisedStyleMapFamilyNameElement.text = instanceObject.getStyleMapFamilyName(code)
instanceElement.append(localisedStyleMapFamilyNameElement)
- if instanceObject.location is not None:
- locationElement, instanceObject.location = self._makeLocationElement(instanceObject.location)
- instanceElement.append(locationElement)
+ if self.effectiveFormatTuple >= (5, 0):
+ if instanceObject.locationLabel is None:
+ self._addLocationElement(
+ instanceElement,
+ designLocation=instanceObject.designLocation,
+ userLocation=instanceObject.userLocation
+ )
+ else:
+ # Pre-version 5.0 code was validating and filling in the location
+ # dict while writing it out, as preserved below.
+ if instanceObject.location is not None:
+ locationElement, instanceObject.location = self._makeLocationElement(instanceObject.location)
+ instanceElement.append(locationElement)
if instanceObject.filename is not None:
instanceElement.attrib['filename'] = instanceObject.filename
if instanceObject.postScriptFontName is not None:
@@ -607,24 +1591,23 @@ class BaseDocWriter(object):
instanceElement.attrib['stylemapfamilyname'] = instanceObject.styleMapFamilyName
if instanceObject.styleMapStyleName is not None:
instanceElement.attrib['stylemapstylename'] = instanceObject.styleMapStyleName
- if instanceObject.glyphs:
- if instanceElement.findall('.glyphs') == []:
- glyphsElement = ET.Element('glyphs')
- instanceElement.append(glyphsElement)
- glyphsElement = instanceElement.findall('.glyphs')[0]
- for glyphName, data in sorted(instanceObject.glyphs.items()):
- glyphElement = self._writeGlyphElement(instanceElement, instanceObject, glyphName, data)
- glyphsElement.append(glyphElement)
- if instanceObject.kerning:
- kerningElement = ET.Element('kerning')
- instanceElement.append(kerningElement)
- if instanceObject.info:
- infoElement = ET.Element('info')
- instanceElement.append(infoElement)
- if instanceObject.lib:
- libElement = ET.Element('lib')
- libElement.append(plistlib.totree(instanceObject.lib, indent_level=4))
- instanceElement.append(libElement)
+ if self.effectiveFormatTuple < (5, 0):
+ # Deprecated members as of version 5.0
+ if instanceObject.glyphs:
+ if instanceElement.findall('.glyphs') == []:
+ glyphsElement = ET.Element('glyphs')
+ instanceElement.append(glyphsElement)
+ glyphsElement = instanceElement.findall('.glyphs')[0]
+ for glyphName, data in sorted(instanceObject.glyphs.items()):
+ glyphElement = self._writeGlyphElement(instanceElement, instanceObject, glyphName, data)
+ glyphsElement.append(glyphElement)
+ if instanceObject.kerning:
+ kerningElement = ET.Element('kerning')
+ instanceElement.append(kerningElement)
+ if instanceObject.info:
+ infoElement = ET.Element('info')
+ instanceElement.append(infoElement)
+ self._addLib(instanceElement, instanceObject.lib, 4)
self.root.findall('.instances')[0].append(instanceElement)
def _addSource(self, sourceObject):
@@ -641,6 +1624,16 @@ class BaseDocWriter(object):
sourceElement.attrib['stylename'] = sourceObject.styleName
if sourceObject.layerName is not None:
sourceElement.attrib['layer'] = sourceObject.layerName
+ if sourceObject.localisedFamilyName:
+ languageCodes = list(sourceObject.localisedFamilyName.keys())
+ languageCodes.sort()
+ for code in languageCodes:
+ if code == "en":
+ continue # already stored in the element attribute
+ localisedFamilyNameElement = ET.Element('familyname')
+ localisedFamilyNameElement.attrib[XML_LANG] = code
+ localisedFamilyNameElement.text = sourceObject.getFamilyName(code)
+ sourceElement.append(localisedFamilyNameElement)
if sourceObject.copyLib:
libElement = ET.Element('lib')
libElement.attrib['copy'] = "1"
@@ -670,14 +1663,45 @@ class BaseDocWriter(object):
glyphElement.attrib["name"] = name
glyphElement.attrib["mute"] = '1'
sourceElement.append(glyphElement)
- locationElement, sourceObject.location = self._makeLocationElement(sourceObject.location)
- sourceElement.append(locationElement)
+ if self.effectiveFormatTuple >= (5, 0):
+ self._addLocationElement(sourceElement, designLocation=sourceObject.location)
+ else:
+ # Pre-version 5.0 code was validating and filling in the location
+ # dict while writing it out, as preserved below.
+ locationElement, sourceObject.location = self._makeLocationElement(sourceObject.location)
+ sourceElement.append(locationElement)
self.root.findall('.sources')[0].append(sourceElement)
- def _addLib(self, dict):
+ def _addVariableFont(self, parentElement: ET.Element, vf: VariableFontDescriptor) -> None:
+ vfElement = ET.Element('variable-font')
+ vfElement.attrib['name'] = vf.name
+ if vf.filename is not None:
+ vfElement.attrib['filename'] = vf.filename
+ if vf.axisSubsets:
+ subsetsElement = ET.Element('axis-subsets')
+ for subset in vf.axisSubsets:
+ subsetElement = ET.Element('axis-subset')
+ subsetElement.attrib['name'] = subset.name
+ if isinstance(subset, RangeAxisSubsetDescriptor):
+ if subset.userMinimum != -math.inf:
+ subsetElement.attrib['userminimum'] = self.intOrFloat(subset.userMinimum)
+ if subset.userMaximum != math.inf:
+ subsetElement.attrib['usermaximum'] = self.intOrFloat(subset.userMaximum)
+ if subset.userDefault is not None:
+ subsetElement.attrib['userdefault'] = self.intOrFloat(subset.userDefault)
+ elif isinstance(subset, ValueAxisSubsetDescriptor):
+ subsetElement.attrib['uservalue'] = self.intOrFloat(subset.userValue)
+ subsetsElement.append(subsetElement)
+ vfElement.append(subsetsElement)
+ self._addLib(vfElement, vf.lib, 4)
+ parentElement.append(vfElement)
+
+ def _addLib(self, parentElement: ET.Element, data: Any, indent_level: int) -> None:
+ if not data:
+ return
libElement = ET.Element('lib')
- libElement.append(plistlib.totree(dict, indent_level=2))
- self.root.append(libElement)
+ libElement.append(plistlib.totree(data, indent_level=indent_level))
+ parentElement.append(libElement)
def _writeGlyphElement(self, instanceElement, instanceObject, glyphName, data):
glyphElement = ET.Element('glyph')
@@ -711,9 +1735,15 @@ class BaseDocWriter(object):
class BaseDocReader(LogMixin):
- ruleDescriptorClass = RuleDescriptor
axisDescriptorClass = AxisDescriptor
+ discreteAxisDescriptorClass = DiscreteAxisDescriptor
+ axisLabelDescriptorClass = AxisLabelDescriptor
+ locationLabelDescriptorClass = LocationLabelDescriptor
+ ruleDescriptorClass = RuleDescriptor
sourceDescriptorClass = SourceDescriptor
+ variableFontsDescriptorClass = VariableFontDescriptor
+ valueAxisSubsetDescriptorClass = ValueAxisSubsetDescriptor
+ rangeAxisSubsetDescriptorClass = RangeAxisSubsetDescriptor
instanceDescriptorClass = InstanceDescriptor
def __init__(self, documentPath, documentObject):
@@ -738,7 +1768,9 @@ class BaseDocReader(LogMixin):
def read(self):
self.readAxes()
+ self.readLabels()
self.readRules()
+ self.readVariableFonts()
self.readSources()
self.readInstances()
self.readLib()
@@ -810,17 +1842,24 @@ class BaseDocReader(LogMixin):
def readAxes(self):
# read the axes elements, including the warp map.
+ axesElement = self.root.find(".axes")
+ if axesElement is not None and 'elidedfallbackname' in axesElement.attrib:
+ self.documentObject.elidedFallbackName = axesElement.attrib['elidedfallbackname']
axisElements = self.root.findall(".axes/axis")
if not axisElements:
return
for axisElement in axisElements:
- axisObject = self.axisDescriptorClass()
+ if self.documentObject.formatTuple >= (5, 0) and "values" in axisElement.attrib:
+ axisObject = self.discreteAxisDescriptorClass()
+ axisObject.values = [float(s) for s in axisElement.attrib["values"].split(" ")]
+ else:
+ axisObject = self.axisDescriptorClass()
+ axisObject.minimum = float(axisElement.attrib.get("minimum"))
+ axisObject.maximum = float(axisElement.attrib.get("maximum"))
+ axisObject.default = float(axisElement.attrib.get("default"))
axisObject.name = axisElement.attrib.get("name")
- axisObject.minimum = float(axisElement.attrib.get("minimum"))
- axisObject.maximum = float(axisElement.attrib.get("maximum"))
if axisElement.attrib.get('hidden', False):
axisObject.hidden = True
- axisObject.default = float(axisElement.attrib.get("default"))
axisObject.tag = axisElement.attrib.get("tag")
for mapElement in axisElement.findall('map'):
a = float(mapElement.attrib['input'])
@@ -832,9 +1871,172 @@ class BaseDocReader(LogMixin):
for key, lang in labelNameElement.items():
if key == XML_LANG:
axisObject.labelNames[lang] = tostr(labelNameElement.text)
+ labelElement = axisElement.find(".labels")
+ if labelElement is not None:
+ if "ordering" in labelElement.attrib:
+ axisObject.axisOrdering = int(labelElement.attrib["ordering"])
+ for label in labelElement.findall(".label"):
+ axisObject.axisLabels.append(self.readAxisLabel(label))
self.documentObject.axes.append(axisObject)
self.axisDefaults[axisObject.name] = axisObject.default
+ def readAxisLabel(self, element: ET.Element):
+ xml_attrs = {'userminimum', 'uservalue', 'usermaximum', 'name', 'elidable', 'oldersibling', 'linkeduservalue'}
+ unknown_attrs = set(element.attrib) - xml_attrs
+ if unknown_attrs:
+ raise DesignSpaceDocumentError(f"label element contains unknown attributes: {', '.join(unknown_attrs)}")
+
+ name = element.get("name")
+ if name is None:
+ raise DesignSpaceDocumentError("label element must have a name attribute.")
+ valueStr = element.get("uservalue")
+ if valueStr is None:
+ raise DesignSpaceDocumentError("label element must have a uservalue attribute.")
+ value = float(valueStr)
+ minimumStr = element.get("userminimum")
+ minimum = float(minimumStr) if minimumStr is not None else None
+ maximumStr = element.get("usermaximum")
+ maximum = float(maximumStr) if maximumStr is not None else None
+ linkedValueStr = element.get("linkeduservalue")
+ linkedValue = float(linkedValueStr) if linkedValueStr is not None else None
+ elidable = True if element.get("elidable") == "true" else False
+ olderSibling = True if element.get("oldersibling") == "true" else False
+ labelNames = {
+ lang: label_name.text or ""
+ for label_name in element.findall("labelname")
+ for attr, lang in label_name.items()
+ if attr == XML_LANG
+ # Note: elementtree reads the "xml:lang" attribute name as
+ # '{http://www.w3.org/XML/1998/namespace}lang'
+ }
+ return self.axisLabelDescriptorClass(
+ name=name,
+ userValue=value,
+ userMinimum=minimum,
+ userMaximum=maximum,
+ elidable=elidable,
+ olderSibling=olderSibling,
+ linkedUserValue=linkedValue,
+ labelNames=labelNames,
+ )
+
+ def readLabels(self):
+ if self.documentObject.formatTuple < (5, 0):
+ return
+
+ xml_attrs = {'name', 'elidable', 'oldersibling'}
+ for labelElement in self.root.findall(".labels/label"):
+ unknown_attrs = set(labelElement.attrib) - xml_attrs
+ if unknown_attrs:
+ raise DesignSpaceDocumentError(f"Label element contains unknown attributes: {', '.join(unknown_attrs)}")
+
+ name = labelElement.get("name")
+ if name is None:
+ raise DesignSpaceDocumentError("label element must have a name attribute.")
+ designLocation, userLocation = self.locationFromElement(labelElement)
+ if designLocation:
+ raise DesignSpaceDocumentError(f'<label> element "{name}" must only have user locations (using uservalue="").')
+ elidable = True if labelElement.get("elidable") == "true" else False
+ olderSibling = True if labelElement.get("oldersibling") == "true" else False
+ labelNames = {
+ lang: label_name.text or ""
+ for label_name in labelElement.findall("labelname")
+ for attr, lang in label_name.items()
+ if attr == XML_LANG
+ # Note: elementtree reads the "xml:lang" attribute name as
+ # '{http://www.w3.org/XML/1998/namespace}lang'
+ }
+ locationLabel = self.locationLabelDescriptorClass(
+ name=name,
+ userLocation=userLocation,
+ elidable=elidable,
+ olderSibling=olderSibling,
+ labelNames=labelNames,
+ )
+ self.documentObject.locationLabels.append(locationLabel)
+
+ def readVariableFonts(self):
+ if self.documentObject.formatTuple < (5, 0):
+ return
+
+ xml_attrs = {'name', 'filename'}
+ for variableFontElement in self.root.findall(".variable-fonts/variable-font"):
+ unknown_attrs = set(variableFontElement.attrib) - xml_attrs
+ if unknown_attrs:
+ raise DesignSpaceDocumentError(f"variable-font element contains unknown attributes: {', '.join(unknown_attrs)}")
+
+ name = variableFontElement.get("name")
+ if name is None:
+ raise DesignSpaceDocumentError("variable-font element must have a name attribute.")
+
+ filename = variableFontElement.get("filename")
+
+ axisSubsetsElement = variableFontElement.find(".axis-subsets")
+ if axisSubsetsElement is None:
+ raise DesignSpaceDocumentError("variable-font element must contain an axis-subsets element.")
+ axisSubsets = []
+ for axisSubset in axisSubsetsElement.iterfind(".axis-subset"):
+ axisSubsets.append(self.readAxisSubset(axisSubset))
+
+ lib = None
+ libElement = variableFontElement.find(".lib")
+ if libElement is not None:
+ lib = plistlib.fromtree(libElement[0])
+
+ variableFont = self.variableFontsDescriptorClass(
+ name=name,
+ filename=filename,
+ axisSubsets=axisSubsets,
+ lib=lib,
+ )
+ self.documentObject.variableFonts.append(variableFont)
+
+ def readAxisSubset(self, element: ET.Element):
+ if "uservalue" in element.attrib:
+ xml_attrs = {'name', 'uservalue'}
+ unknown_attrs = set(element.attrib) - xml_attrs
+ if unknown_attrs:
+ raise DesignSpaceDocumentError(f"axis-subset element contains unknown attributes: {', '.join(unknown_attrs)}")
+
+ name = element.get("name")
+ if name is None:
+ raise DesignSpaceDocumentError("axis-subset element must have a name attribute.")
+ userValueStr = element.get("uservalue")
+ if userValueStr is None:
+ raise DesignSpaceDocumentError(
+ "The axis-subset element for a discrete subset must have a uservalue attribute."
+ )
+ userValue = float(userValueStr)
+
+ return self.valueAxisSubsetDescriptorClass(name=name, userValue=userValue)
+ else:
+ xml_attrs = {'name', 'userminimum', 'userdefault', 'usermaximum'}
+ unknown_attrs = set(element.attrib) - xml_attrs
+ if unknown_attrs:
+ raise DesignSpaceDocumentError(f"axis-subset element contains unknown attributes: {', '.join(unknown_attrs)}")
+
+ name = element.get("name")
+ if name is None:
+ raise DesignSpaceDocumentError("axis-subset element must have a name attribute.")
+
+ userMinimum = element.get("userminimum")
+ userDefault = element.get("userdefault")
+ userMaximum = element.get("usermaximum")
+ if userMinimum is not None and userDefault is not None and userMaximum is not None:
+ return self.rangeAxisSubsetDescriptorClass(
+ name=name,
+ userMinimum=float(userMinimum),
+ userDefault=float(userDefault),
+ userMaximum=float(userMaximum),
+ )
+ if all(v is None for v in (userMinimum, userDefault, userMaximum)):
+ return self.rangeAxisSubsetDescriptorClass(name=name)
+
+ raise DesignSpaceDocumentError(
+ "axis-subset element must have min/max/default values or none at all."
+ )
+
+
def readSources(self):
for sourceCount, sourceElement in enumerate(self.root.findall(".sources/source")):
filename = sourceElement.attrib.get('filename')
@@ -856,7 +2058,15 @@ class BaseDocReader(LogMixin):
styleName = sourceElement.attrib.get("stylename")
if styleName is not None:
sourceObject.styleName = styleName
- sourceObject.location = self.locationFromElement(sourceElement)
+ for familyNameElement in sourceElement.findall('familyname'):
+ for key, lang in familyNameElement.items():
+ if key == XML_LANG:
+ familyName = familyNameElement.text
+ sourceObject.setFamilyName(familyName, lang)
+ designLocation, userLocation = self.locationFromElement(sourceElement)
+ if userLocation:
+ raise DesignSpaceDocumentError(f'<source> element "{sourceName}" must only have design locations (using xvalue="").')
+ sourceObject.location = designLocation
layerName = sourceElement.attrib.get('layer')
if layerName is not None:
sourceObject.layerName = layerName
@@ -886,40 +2096,63 @@ class BaseDocReader(LogMixin):
self.documentObject.sources.append(sourceObject)
def locationFromElement(self, element):
- elementLocation = None
+ """Read a nested ``<location>`` element inside the given ``element``.
+
+ .. versionchanged:: 5.0
+ Return a tuple of (designLocation, userLocation)
+ """
+ elementLocation = (None, None)
for locationElement in element.findall('.location'):
elementLocation = self.readLocationElement(locationElement)
break
return elementLocation
def readLocationElement(self, locationElement):
- """ Format 0 location reader """
+ """Read a ``<location>`` element.
+
+ .. versionchanged:: 5.0
+ Return a tuple of (designLocation, userLocation)
+ """
if self._strictAxisNames and not self.documentObject.axes:
raise DesignSpaceDocumentError("No axes defined")
- loc = {}
+ userLoc = {}
+ designLoc = {}
for dimensionElement in locationElement.findall(".dimension"):
dimName = dimensionElement.attrib.get("name")
if self._strictAxisNames and dimName not in self.axisDefaults:
# In case the document contains no axis definitions,
self.log.warning("Location with undefined axis: \"%s\".", dimName)
continue
- xValue = yValue = None
+ userValue = xValue = yValue = None
+ try:
+ userValue = dimensionElement.attrib.get('uservalue')
+ if userValue is not None:
+ userValue = float(userValue)
+ except ValueError:
+ self.log.warning("ValueError in readLocation userValue %3.3f", userValue)
try:
xValue = dimensionElement.attrib.get('xvalue')
- xValue = float(xValue)
+ if xValue is not None:
+ xValue = float(xValue)
except ValueError:
- self.log.warning("KeyError in readLocation xValue %3.3f", xValue)
+ self.log.warning("ValueError in readLocation xValue %3.3f", xValue)
try:
yValue = dimensionElement.attrib.get('yvalue')
if yValue is not None:
yValue = float(yValue)
except ValueError:
- pass
+ self.log.warning("ValueError in readLocation yValue %3.3f", yValue)
+ if userValue is None == xValue is None:
+ raise DesignSpaceDocumentError(f'Exactly one of uservalue="" or xvalue="" must be provided for location dimension "{dimName}"')
if yValue is not None:
- loc[dimName] = (xValue, yValue)
+ if xValue is None:
+ raise DesignSpaceDocumentError(f'Missing xvalue="" for the location dimension "{dimName}"" with yvalue="{yValue}"')
+ designLoc[dimName] = (xValue, yValue)
+ elif xValue is not None:
+ designLoc[dimName] = xValue
else:
- loc[dimName] = xValue
- return loc
+ userLoc[dimName] = userValue
+ return designLoc, userLoc
def readInstances(self, makeGlyphs=True, makeKerning=True, makeInfo=True):
instanceElements = self.root.findall('.instances/instance')
@@ -974,9 +2207,13 @@ class BaseDocReader(LogMixin):
if key == XML_LANG:
styleMapFamilyName = styleMapFamilyNameElement.text
instanceObject.setStyleMapFamilyName(styleMapFamilyName, lang)
- instanceLocation = self.locationFromElement(instanceElement)
- if instanceLocation is not None:
- instanceObject.location = instanceLocation
+ designLocation, userLocation = self.locationFromElement(instanceElement)
+ locationLabel = instanceElement.attrib.get('location')
+ if (designLocation or userLocation) and locationLabel is not None:
+ raise DesignSpaceDocumentError('instance element must have at most one of the location="..." attribute or the nested location element')
+ instanceObject.locationLabel = locationLabel
+ instanceObject.userLocation = userLocation or {}
+ instanceObject.designLocation = designLocation or {}
for glyphElement in instanceElement.findall('.glyphs/glyph'):
self.readGlyphElement(glyphElement, instanceObject)
for infoElement in instanceElement.findall("info"):
@@ -993,19 +2230,16 @@ class BaseDocReader(LogMixin):
""" Read the info element."""
instanceObject.info = True
- def readKerningElement(self, kerningElement, instanceObject):
- """ Read the kerning element."""
- kerningLocation = self.locationFromElement(kerningElement)
- instanceObject.addKerning(kerningLocation)
-
def readGlyphElement(self, glyphElement, instanceObject):
"""
- Read the glyph element:
+ Read the glyph element, which could look like either one of these:
.. code-block:: xml
<glyph name="b" unicode="0x62"/>
+
<glyph name="b"/>
+
<glyph name="b">
<master location="location-token-bbb" source="master-token-aaa2"/>
<master glyphname="b.alt1" location="location-token-ccc" source="master-token-aaa3"/>
@@ -1033,19 +2267,23 @@ class BaseDocReader(LogMixin):
for noteElement in glyphElement.findall('.note'):
glyphData['note'] = noteElement.text
break
- instanceLocation = self.locationFromElement(glyphElement)
- if instanceLocation is not None:
- glyphData['instanceLocation'] = instanceLocation
+ designLocation, userLocation = self.locationFromElement(glyphElement)
+ if userLocation:
+ raise DesignSpaceDocumentError(f'<glyph> element "{glyphName}" must only have design locations (using xvalue="").')
+ if designLocation is not None:
+ glyphData['instanceLocation'] = designLocation
glyphSources = None
for masterElement in glyphElement.findall('.masters/master'):
fontSourceName = masterElement.attrib.get('source')
- sourceLocation = self.locationFromElement(masterElement)
+ designLocation, userLocation = self.locationFromElement(masterElement)
+ if userLocation:
+ raise DesignSpaceDocumentError(f'<master> element "{fontSourceName}" must only have design locations (using xvalue="").')
masterGlyphName = masterElement.attrib.get('glyphname')
if masterGlyphName is None:
# if we don't read a glyphname, use the one we have
masterGlyphName = glyphName
d = dict(font=fontSourceName,
- location=sourceLocation,
+ location=designLocation,
glyphName=masterGlyphName)
if glyphSources is None:
glyphSources = []
@@ -1061,9 +2299,43 @@ class BaseDocReader(LogMixin):
class DesignSpaceDocument(LogMixin, AsDictMixin):
- """ Read, write data from the designspace file"""
+ """The DesignSpaceDocument object can read and write ``.designspace`` data.
+ It imports the axes, sources, variable fonts and instances to very basic
+ **descriptor** objects that store the data in attributes. Data is added to
+ the document by creating such descriptor objects, filling them with data
+ and then 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. Reader and
+ Writer objects can be subclassed as well.
+
+ **Note:** Python attribute names are usually camelCased, the
+ corresponding `XML <document-xml-structure>`_ attributes are usually
+ all lowercase.
+
+ .. code:: python
+
+ from fontTools.designspaceLib import DesignSpaceDocument
+ doc = DesignSpaceDocument.fromfile("some/path/to/my.designspace")
+ doc.formatVersion
+ doc.elidedFallbackName
+ doc.axes
+ doc.locationLabels
+ doc.rules
+ doc.rulesProcessingLast
+ doc.sources
+ doc.variableFonts
+ doc.instances
+ doc.lib
+
+ """
+
def __init__(self, readerClass=None, writerClass=None):
self.path = None
+ """String, optional. When the document is read from the disk, this is
+ the full path that was given to :meth:`read` or :meth:`fromfile`.
+ """
self.filename = None
"""String, optional. When the document is read from the disk, this is
its original file name, i.e. the last part of its path.
@@ -1073,18 +2345,70 @@ class DesignSpaceDocument(LogMixin, AsDictMixin):
possible "good" filename, in case one wants to save the file somewhere.
"""
- self.formatVersion = None
- self.sources = []
- self.instances = []
- self.axes = []
- self.rules = []
- self.rulesProcessingLast = False
- self.default = None # name of the default master
+ self.formatVersion: Optional[str] = None
+ """Format version for this document, as a string. E.g. "4.0" """
- self.lib = {}
- """Custom data associated with the whole document."""
+ self.elidedFallbackName: Optional[str] = None
+ """STAT Style Attributes Header field ``elidedFallbackNameID``.
+
+ See: `OTSpec STAT Style Attributes Header <https://docs.microsoft.com/en-us/typography/opentype/spec/stat#style-attributes-header>`_
+
+ .. versionadded:: 5.0
+ """
+
+ self.axes: List[Union[AxisDescriptor, DiscreteAxisDescriptor]] = []
+ """List of this document's axes."""
+ self.locationLabels: List[LocationLabelDescriptor] = []
+ """List of this document's STAT format 4 labels.
+
+ .. versionadded:: 5.0"""
+ self.rules: List[RuleDescriptor] = []
+ """List of this document's rules."""
+ self.rulesProcessingLast: bool = False
+ """This flag indicates whether the substitution rules should be applied
+ before or after other glyph substitution features.
+
+ - False: before
+ - True: after.
+
+ Default is False. For new projects, you probably want True. See
+ the following issues for more information:
+ `fontTools#1371 <https://github.com/fonttools/fonttools/issues/1371#issuecomment-590214572>`__
+ `fontTools#2050 <https://github.com/fonttools/fonttools/issues/2050#issuecomment-678691020>`__
+
+ If you want to use a different feature altogether, e.g. ``calt``,
+ use the lib key ``com.github.fonttools.varLib.featureVarsFeatureTag``
+
+ .. code:: xml
+
+ <lib>
+ <dict>
+ <key>com.github.fonttools.varLib.featureVarsFeatureTag</key>
+ <string>calt</string>
+ </dict>
+ </lib>
+ """
+ self.sources: List[SourceDescriptor] = []
+ """List of this document's sources."""
+ self.variableFonts: List[VariableFontDescriptor] = []
+ """List of this document's variable fonts.
+
+ .. versionadded:: 5.0"""
+ self.instances: List[InstanceDescriptor] = []
+ """List of this document's instances."""
+ self.lib: Dict = {}
+ """User defined, custom data associated with the whole document.
+
+ Use reverse-DNS notation to identify your own data.
+ Respect the data stored by others.
+ """
+
+ self.default: Optional[str] = None
+ """Name of the default master.
+
+ This attribute is updated by the :meth:`findDefault`
+ """
- #
if readerClass is not None:
self.readerClass = readerClass
else:
@@ -1096,6 +2420,9 @@ class DesignSpaceDocument(LogMixin, AsDictMixin):
@classmethod
def fromfile(cls, path, readerClass=None, writerClass=None):
+ """Read a designspace file from ``path`` and return a new instance of
+ :class:.
+ """
self = cls(readerClass=readerClass, writerClass=writerClass)
self.read(path)
return self
@@ -1110,6 +2437,7 @@ class DesignSpaceDocument(LogMixin, AsDictMixin):
return self
def tostring(self, encoding=None):
+ """Returns the designspace as a string. Default encoding ``utf-8``."""
if encoding is str or (
encoding is not None and encoding.lower() == "unicode"
):
@@ -1126,6 +2454,9 @@ class DesignSpaceDocument(LogMixin, AsDictMixin):
return f.getvalue()
def read(self, path):
+ """Read a designspace file from ``path`` and populates the fields of
+ ``self`` with the data.
+ """
if hasattr(path, "__fspath__"): # support os.PathLike objects
path = path.__fspath__()
self.path = path
@@ -1136,6 +2467,7 @@ class DesignSpaceDocument(LogMixin, AsDictMixin):
self.findDefault()
def write(self, path):
+ """Write this designspace to ``path``."""
if hasattr(path, "__fspath__"): # support os.PathLike objects
path = path.__fspath__()
self.path = path
@@ -1150,8 +2482,10 @@ class DesignSpaceDocument(LogMixin, AsDictMixin):
def updatePaths(self):
"""
- Right before we save we need to identify and respond to the following situations:
- In each descriptor, we have to do the right thing for the filename attribute.
+ Right before we save we need to identify and respond to the following situations:
+ In each descriptor, we have to do the right thing for the filename attribute.
+
+ ::
case 1.
descriptor.filename == None
@@ -1187,8 +2521,6 @@ class DesignSpaceDocument(LogMixin, AsDictMixin):
there is a conflict between the given filename, and the path.
So we know where the file is relative to the document.
Can't guess why they're different, we just choose for path to be correct and update filename.
-
-
"""
assert self.path is not None
for descriptor in self.sources + self.instances:
@@ -1196,40 +2528,96 @@ class DesignSpaceDocument(LogMixin, AsDictMixin):
# case 3 and 4: filename gets updated and relativized
descriptor.filename = self._posixRelativePath(descriptor.path)
- def addSource(self, sourceDescriptor):
+ def addSource(self, sourceDescriptor: SourceDescriptor):
+ """Add the given ``sourceDescriptor`` to ``doc.sources``."""
self.sources.append(sourceDescriptor)
def addSourceDescriptor(self, **kwargs):
+ """Instantiate a new :class:`SourceDescriptor` using the given
+ ``kwargs`` and add it to ``doc.sources``.
+ """
source = self.writerClass.sourceDescriptorClass(**kwargs)
self.addSource(source)
return source
- def addInstance(self, instanceDescriptor):
+ def addInstance(self, instanceDescriptor: InstanceDescriptor):
+ """Add the given ``instanceDescriptor`` to :attr:`instances`."""
self.instances.append(instanceDescriptor)
def addInstanceDescriptor(self, **kwargs):
+ """Instantiate a new :class:`InstanceDescriptor` using the given
+ ``kwargs`` and add it to :attr:`instances`.
+ """
instance = self.writerClass.instanceDescriptorClass(**kwargs)
self.addInstance(instance)
return instance
- def addAxis(self, axisDescriptor):
+ def addAxis(self, axisDescriptor: Union[AxisDescriptor, DiscreteAxisDescriptor]):
+ """Add the given ``axisDescriptor`` to :attr:`axes`."""
self.axes.append(axisDescriptor)
def addAxisDescriptor(self, **kwargs):
- axis = self.writerClass.axisDescriptorClass(**kwargs)
+ """Instantiate a new :class:`AxisDescriptor` using the given
+ ``kwargs`` and add it to :attr:`axes`.
+
+ The axis will be and instance of :class:`DiscreteAxisDescriptor` if
+ the ``kwargs`` provide a ``value``, or a :class:`AxisDescriptor` otherwise.
+ """
+ if "values" in kwargs:
+ axis = self.writerClass.discreteAxisDescriptorClass(**kwargs)
+ else:
+ axis = self.writerClass.axisDescriptorClass(**kwargs)
self.addAxis(axis)
return axis
- def addRule(self, ruleDescriptor):
+ def addRule(self, ruleDescriptor: RuleDescriptor):
+ """Add the given ``ruleDescriptor`` to :attr:`rules`."""
self.rules.append(ruleDescriptor)
def addRuleDescriptor(self, **kwargs):
+ """Instantiate a new :class:`RuleDescriptor` using the given
+ ``kwargs`` and add it to :attr:`rules`.
+ """
rule = self.writerClass.ruleDescriptorClass(**kwargs)
self.addRule(rule)
return rule
+ def addVariableFont(self, variableFontDescriptor: VariableFontDescriptor):
+ """Add the given ``variableFontDescriptor`` to :attr:`variableFonts`.
+
+ .. versionadded:: 5.0
+ """
+ self.variableFonts.append(variableFontDescriptor)
+
+ def addVariableFontDescriptor(self, **kwargs):
+ """Instantiate a new :class:`VariableFontDescriptor` using the given
+ ``kwargs`` and add it to :attr:`variableFonts`.
+
+ .. versionadded:: 5.0
+ """
+ variableFont = self.writerClass.variableFontDescriptorClass(**kwargs)
+ self.addVariableFont(variableFont)
+ return variableFont
+
+ def addLocationLabel(self, locationLabelDescriptor: LocationLabelDescriptor):
+ """Add the given ``locationLabelDescriptor`` to :attr:`locationLabels`.
+
+ .. versionadded:: 5.0
+ """
+ self.locationLabels.append(locationLabelDescriptor)
+
+ def addLocationLabelDescriptor(self, **kwargs):
+ """Instantiate a new :class:`LocationLabelDescriptor` using the given
+ ``kwargs`` and add it to :attr:`locationLabels`.
+
+ .. versionadded:: 5.0
+ """
+ locationLabel = self.writerClass.locationLabelDescriptorClass(**kwargs)
+ self.addLocationLabel(locationLabel)
+ return locationLabel
+
def newDefaultLocation(self):
- """Return default location in design space."""
+ """Return a dict with the default location in design space coordinates."""
# Without OrderedDict, output XML would be non-deterministic.
# https://github.com/LettError/designSpaceDocument/issues/10
loc = collections.OrderedDict()
@@ -1239,9 +2627,21 @@ class DesignSpaceDocument(LogMixin, AsDictMixin):
)
return loc
+ def labelForUserLocation(self, userLocation: SimpleLocationDict) -> Optional[LocationLabelDescriptor]:
+ """Return the :class:`LocationLabel` that matches the given
+ ``userLocation``, or ``None`` if no such label exists.
+
+ .. versionadded:: 5.0
+ """
+ return next(
+ (label for label in self.locationLabels if label.userLocation == userLocation), None
+ )
+
def updateFilenameFromPath(self, masters=True, instances=True, force=False):
- # set a descriptor filename attr from the path and this document path
- # if the filename attribute is not None: skip it.
+ """Set a descriptor filename attr from the path and this document path.
+
+ If the filename attribute is not None: skip it.
+ """
if masters:
for descriptor in self.sources:
if descriptor.filename is not None and not force:
@@ -1256,49 +2656,102 @@ class DesignSpaceDocument(LogMixin, AsDictMixin):
descriptor.filename = self._posixRelativePath(descriptor.path)
def newAxisDescriptor(self):
- # Ask the writer class to make us a new axisDescriptor
+ """Ask the writer class to make us a new axisDescriptor."""
return self.writerClass.getAxisDecriptor()
def newSourceDescriptor(self):
- # Ask the writer class to make us a new sourceDescriptor
+ """Ask the writer class to make us a new sourceDescriptor."""
return self.writerClass.getSourceDescriptor()
def newInstanceDescriptor(self):
- # Ask the writer class to make us a new instanceDescriptor
+ """Ask the writer class to make us a new instanceDescriptor."""
return self.writerClass.getInstanceDescriptor()
def getAxisOrder(self):
+ """Return a list of axis names, in the same order as defined in the document."""
names = []
for axisDescriptor in self.axes:
names.append(axisDescriptor.name)
return names
def getAxis(self, name):
+ """Return the axis with the given ``name``, or ``None`` if no such axis exists."""
for axisDescriptor in self.axes:
if axisDescriptor.name == name:
return axisDescriptor
return None
+ def getLocationLabel(self, name: str) -> Optional[LocationLabelDescriptor]:
+ """Return the top-level location label with the given ``name``, or
+ ``None`` if no such label exists.
+
+ .. versionadded:: 5.0
+ """
+ for label in self.locationLabels:
+ if label.name == name:
+ return label
+ return None
+
+ def map_forward(self, userLocation: SimpleLocationDict) -> SimpleLocationDict:
+ """Map a user location to a design location.
+
+ Assume that missing coordinates are at the default location for that axis.
+
+ Note: the output won't be anisotropic, only the xvalue is set.
+
+ .. versionadded:: 5.0
+ """
+ return {
+ axis.name: axis.map_forward(userLocation.get(axis.name, axis.default))
+ for axis in self.axes
+ }
+
+ def map_backward(self, designLocation: AnisotropicLocationDict) -> SimpleLocationDict:
+ """Map a design location to a user location.
+
+ Assume that missing coordinates are at the default location for that axis.
+
+ When the input has anisotropic locations, only the xvalue is used.
+
+ .. versionadded:: 5.0
+ """
+ return {
+ axis.name: (
+ axis.map_backward(designLocation[axis.name])
+ if axis.name in designLocation
+ else axis.default
+ )
+ for axis in self.axes
+ }
+
def findDefault(self):
"""Set and return SourceDescriptor at the default location or None.
The default location is the set of all `default` values in user space
of all axes.
+
+ This function updates the document's :attr:`default` value.
+
+ .. versionchanged:: 5.0
+ Allow the default source to not specify some of the axis values, and
+ they are assumed to be the default.
+ See :meth:`SourceDescriptor.getFullDesignLocation()`
"""
self.default = None
# Convert the default location from user space to design space before comparing
# it against the SourceDescriptor locations (always in design space).
- default_location_design = self.newDefaultLocation()
+ defaultDesignLocation = self.newDefaultLocation()
for sourceDescriptor in self.sources:
- if sourceDescriptor.location == default_location_design:
+ if sourceDescriptor.getFullDesignLocation(self) == defaultDesignLocation:
self.default = sourceDescriptor
return sourceDescriptor
return None
def normalizeLocation(self, location):
+ """Return a dict with normalized axis values."""
from fontTools.varLib.models import normalizeValue
new = {}
@@ -1317,9 +2770,12 @@ class DesignSpaceDocument(LogMixin, AsDictMixin):
return new
def normalize(self):
- # Normalise the geometry of this designspace:
- # scale all the locations of all masters and instances to the -1 - 0 - 1 value.
- # we need the axis data to do the scaling, so we do those last.
+ """
+ Normalise the geometry of this designspace:
+
+ - scale all the locations of all masters and instances to the -1 - 0 - 1 value.
+ - we need the axis data to do the scaling, so we do those last.
+ """
# masters
for item in self.sources:
item.location = self.normalizeLocation(item.location)
@@ -1412,3 +2868,81 @@ class DesignSpaceDocument(LogMixin, AsDictMixin):
loaded[source.path] = source.font
fonts.append(source.font)
return fonts
+
+ @property
+ def formatTuple(self):
+ """Return the formatVersion as a tuple of (major, minor).
+
+ .. versionadded:: 5.0
+ """
+ if self.formatVersion is None:
+ return (5, 0)
+ numbers = (int(i) for i in self.formatVersion.split("."))
+ major = next(numbers)
+ minor = next(numbers, 0)
+ return (major, minor)
+
+ def getVariableFonts(self) -> List[VariableFontDescriptor]:
+ """Return all variable fonts defined in this document, or implicit
+ variable fonts that can be built from the document's continuous axes.
+
+ In the case of Designspace documents before version 5, the whole
+ document was implicitly describing a variable font that covers the
+ whole space.
+
+ In version 5 and above documents, there can be as many variable fonts
+ as there are locations on discrete axes.
+
+ .. seealso:: :func:`splitInterpolable`
+
+ .. versionadded:: 5.0
+ """
+ if self.variableFonts:
+ return self.variableFonts
+
+ variableFonts = []
+ discreteAxes = []
+ rangeAxisSubsets: List[Union[RangeAxisSubsetDescriptor, ValueAxisSubsetDescriptor]] = []
+ for axis in self.axes:
+ if isinstance(axis, DiscreteAxisDescriptor):
+ discreteAxes.append(axis)
+ else:
+ rangeAxisSubsets.append(RangeAxisSubsetDescriptor(name=axis.name))
+ valueCombinations = itertools.product(*[axis.values for axis in discreteAxes])
+ for values in valueCombinations:
+ basename = None
+ if self.filename is not None:
+ basename = os.path.splitext(self.filename)[0] + "-VF"
+ if self.path is not None:
+ basename = os.path.splitext(os.path.basename(self.path))[0] + "-VF"
+ if basename is None:
+ basename = "VF"
+ axisNames = "".join([f"-{axis.tag}{value}" for axis, value in zip(discreteAxes, values)])
+ variableFonts.append(VariableFontDescriptor(
+ name=f"{basename}{axisNames}",
+ axisSubsets=rangeAxisSubsets + [
+ ValueAxisSubsetDescriptor(name=axis.name, userValue=value)
+ for axis, value in zip(discreteAxes, values)
+ ]
+ ))
+ return variableFonts
+
+ def deepcopyExceptFonts(self):
+ """Allow deep-copying a DesignSpace document without deep-copying
+ attached UFO fonts or TTFont objects. The :attr:`font` attribute
+ is shared by reference between the original and the copy.
+
+ .. versionadded:: 5.0
+ """
+ fonts = [source.font for source in self.sources]
+ try:
+ for source in self.sources:
+ source.font = None
+ res = copy.deepcopy(self)
+ for source, font in zip(res.sources, fonts):
+ source.font = font
+ return res
+ finally:
+ for source, font in zip(self.sources, fonts):
+ source.font = font
+
diff --git a/Lib/fontTools/designspaceLib/split.py b/Lib/fontTools/designspaceLib/split.py
new file mode 100644
index 00000000..2a09418c
--- /dev/null
+++ b/Lib/fontTools/designspaceLib/split.py
@@ -0,0 +1,424 @@
+"""Allows building all the variable fonts of a DesignSpace version 5 by
+splitting the document into interpolable sub-space, then into each VF.
+"""
+
+from __future__ import annotations
+
+import itertools
+import logging
+import math
+from typing import Any, Callable, Dict, Iterator, List, Tuple
+
+from fontTools.designspaceLib import (
+ AxisDescriptor,
+ DesignSpaceDocument,
+ DiscreteAxisDescriptor,
+ InstanceDescriptor,
+ RuleDescriptor,
+ SimpleLocationDict,
+ SourceDescriptor,
+ VariableFontDescriptor,
+)
+from fontTools.designspaceLib.statNames import StatNames, getStatNames
+from fontTools.designspaceLib.types import (
+ Range,
+ Region,
+ ConditionSet,
+ getVFUserRegion,
+ locationInRegion,
+ regionInRegion,
+ userRegionToDesignRegion,
+)
+
+LOGGER = logging.getLogger(__name__)
+
+MakeInstanceFilenameCallable = Callable[
+ [DesignSpaceDocument, InstanceDescriptor, StatNames], str
+]
+
+
+def defaultMakeInstanceFilename(
+ doc: DesignSpaceDocument, instance: InstanceDescriptor, statNames: StatNames
+) -> str:
+ """Default callable to synthesize an instance filename
+ when makeNames=True, for instances that don't specify an instance name
+ in the designspace. This part of the name generation can be overriden
+ because it's not specified by the STAT table.
+ """
+ familyName = instance.familyName or statNames.familyNames.get("en")
+ styleName = instance.styleName or statNames.styleNames.get("en")
+ return f"{familyName}-{styleName}.ttf"
+
+
+def splitInterpolable(
+ doc: DesignSpaceDocument,
+ makeNames: bool = True,
+ expandLocations: bool = True,
+ makeInstanceFilename: MakeInstanceFilenameCallable = defaultMakeInstanceFilename,
+) -> Iterator[Tuple[SimpleLocationDict, DesignSpaceDocument]]:
+ """Split the given DS5 into several interpolable sub-designspaces.
+ There are as many interpolable sub-spaces as there are combinations of
+ discrete axis values.
+
+ E.g. with axes:
+ - italic (discrete) Upright or Italic
+ - style (discrete) Sans or Serif
+ - weight (continuous) 100 to 900
+
+ There are 4 sub-spaces in which the Weight axis should interpolate:
+ (Upright, Sans), (Upright, Serif), (Italic, Sans) and (Italic, Serif).
+
+ The sub-designspaces still include the full axis definitions and STAT data,
+ but the rules, sources, variable fonts, instances are trimmed down to only
+ keep what falls within the interpolable sub-space.
+
+ Args:
+ - ``makeNames``: Whether to compute the instance family and style
+ names using the STAT data.
+ - ``expandLocations``: Whether to turn all locations into "full"
+ locations, including implicit default axis values where missing.
+ - ``makeInstanceFilename``: Callable to synthesize an instance filename
+ when makeNames=True, for instances that don't specify an instance name
+ in the designspace. This part of the name generation can be overridden
+ because it's not specified by the STAT table.
+
+ .. versionadded:: 5.0
+ """
+ discreteAxes = []
+ interpolableUserRegion: Region = {}
+ for axis in doc.axes:
+ if isinstance(axis, DiscreteAxisDescriptor):
+ discreteAxes.append(axis)
+ else:
+ interpolableUserRegion[axis.name] = Range(
+ axis.minimum, axis.maximum, axis.default
+ )
+ valueCombinations = itertools.product(*[axis.values for axis in discreteAxes])
+ for values in valueCombinations:
+ discreteUserLocation = {
+ discreteAxis.name: value
+ for discreteAxis, value in zip(discreteAxes, values)
+ }
+ subDoc = _extractSubSpace(
+ doc,
+ {**interpolableUserRegion, **discreteUserLocation},
+ keepVFs=True,
+ makeNames=makeNames,
+ expandLocations=expandLocations,
+ makeInstanceFilename=makeInstanceFilename,
+ )
+ yield discreteUserLocation, subDoc
+
+
+def splitVariableFonts(
+ doc: DesignSpaceDocument,
+ makeNames: bool = False,
+ expandLocations: bool = False,
+ makeInstanceFilename: MakeInstanceFilenameCallable = defaultMakeInstanceFilename,
+) -> Iterator[Tuple[str, DesignSpaceDocument]]:
+ """Convert each variable font listed in this document into a standalone
+ designspace. This can be used to compile all the variable fonts from a
+ format 5 designspace using tools that can only deal with 1 VF at a time.
+
+ Args:
+ - ``makeNames``: Whether to compute the instance family and style
+ names using the STAT data.
+ - ``expandLocations``: Whether to turn all locations into "full"
+ locations, including implicit default axis values where missing.
+ - ``makeInstanceFilename``: Callable to synthesize an instance filename
+ when makeNames=True, for instances that don't specify an instance name
+ in the designspace. This part of the name generation can be overridden
+ because it's not specified by the STAT table.
+
+ .. versionadded:: 5.0
+ """
+ # Make one DesignspaceDoc v5 for each variable font
+ for vf in doc.getVariableFonts():
+ vfUserRegion = getVFUserRegion(doc, vf)
+ vfDoc = _extractSubSpace(
+ doc,
+ vfUserRegion,
+ keepVFs=False,
+ makeNames=makeNames,
+ expandLocations=expandLocations,
+ makeInstanceFilename=makeInstanceFilename,
+ )
+ vfDoc.lib = {**vfDoc.lib, **vf.lib}
+ yield vf.name, vfDoc
+
+
+def convert5to4(
+ doc: DesignSpaceDocument,
+) -> Dict[str, DesignSpaceDocument]:
+ """Convert each variable font listed in this document into a standalone
+ format 4 designspace. This can be used to compile all the variable fonts
+ from a format 5 designspace using tools that only know about format 4.
+
+ .. versionadded:: 5.0
+ """
+ vfs = {}
+ for _location, subDoc in splitInterpolable(doc):
+ for vfName, vfDoc in splitVariableFonts(subDoc):
+ vfDoc.formatVersion = "4.1"
+ vfs[vfName] = vfDoc
+ return vfs
+
+
+def _extractSubSpace(
+ doc: DesignSpaceDocument,
+ userRegion: Region,
+ *,
+ keepVFs: bool,
+ makeNames: bool,
+ expandLocations: bool,
+ makeInstanceFilename: MakeInstanceFilenameCallable,
+) -> DesignSpaceDocument:
+ subDoc = DesignSpaceDocument()
+ # Don't include STAT info
+ # FIXME: (Jany) let's think about it. Not include = OK because the point of
+ # the splitting is to build VFs and we'll use the STAT data of the full
+ # document to generate the STAT of the VFs, so "no need" to have STAT data
+ # in sub-docs. Counterpoint: what if someone wants to split this DS for
+ # other purposes? Maybe for that it would be useful to also subset the STAT
+ # data?
+ # subDoc.elidedFallbackName = doc.elidedFallbackName
+
+ def maybeExpandDesignLocation(object):
+ if expandLocations:
+ return object.getFullDesignLocation(doc)
+ else:
+ return object.designLocation
+
+ for axis in doc.axes:
+ range = userRegion[axis.name]
+ if isinstance(range, Range) and isinstance(axis, AxisDescriptor):
+ subDoc.addAxis(
+ AxisDescriptor(
+ # Same info
+ tag=axis.tag,
+ name=axis.name,
+ labelNames=axis.labelNames,
+ hidden=axis.hidden,
+ # Subset range
+ minimum=max(range.minimum, axis.minimum),
+ default=range.default or axis.default,
+ maximum=min(range.maximum, axis.maximum),
+ map=[
+ (user, design)
+ for user, design in axis.map
+ if range.minimum <= user <= range.maximum
+ ],
+ # Don't include STAT info
+ axisOrdering=None,
+ axisLabels=None,
+ )
+ )
+
+ # Don't include STAT info
+ # subDoc.locationLabels = doc.locationLabels
+
+ # Rules: subset them based on conditions
+ designRegion = userRegionToDesignRegion(doc, userRegion)
+ subDoc.rules = _subsetRulesBasedOnConditions(doc.rules, designRegion)
+ subDoc.rulesProcessingLast = doc.rulesProcessingLast
+
+ # Sources: keep only the ones that fall within the kept axis ranges
+ for source in doc.sources:
+ if not locationInRegion(doc.map_backward(source.designLocation), userRegion):
+ continue
+
+ subDoc.addSource(
+ SourceDescriptor(
+ filename=source.filename,
+ path=source.path,
+ font=source.font,
+ name=source.name,
+ designLocation=_filterLocation(
+ userRegion, maybeExpandDesignLocation(source)
+ ),
+ layerName=source.layerName,
+ familyName=source.familyName,
+ styleName=source.styleName,
+ muteKerning=source.muteKerning,
+ muteInfo=source.muteInfo,
+ mutedGlyphNames=source.mutedGlyphNames,
+ )
+ )
+
+ # Copy family name translations from the old default source to the new default
+ vfDefault = subDoc.findDefault()
+ oldDefault = doc.findDefault()
+ if vfDefault is not None and oldDefault is not None:
+ vfDefault.localisedFamilyName = oldDefault.localisedFamilyName
+
+ # Variable fonts: keep only the ones that fall within the kept axis ranges
+ if keepVFs:
+ # Note: call getVariableFont() to make the implicit VFs explicit
+ for vf in doc.getVariableFonts():
+ vfUserRegion = getVFUserRegion(doc, vf)
+ if regionInRegion(vfUserRegion, userRegion):
+ subDoc.addVariableFont(
+ VariableFontDescriptor(
+ name=vf.name,
+ filename=vf.filename,
+ axisSubsets=[
+ axisSubset
+ for axisSubset in vf.axisSubsets
+ if isinstance(userRegion[axisSubset.name], Range)
+ ],
+ lib=vf.lib,
+ )
+ )
+
+ # Instances: same as Sources + compute missing names
+ for instance in doc.instances:
+ if not locationInRegion(instance.getFullUserLocation(doc), userRegion):
+ continue
+
+ if makeNames:
+ statNames = getStatNames(doc, instance.getFullUserLocation(doc))
+ familyName = instance.familyName or statNames.familyNames.get("en")
+ styleName = instance.styleName or statNames.styleNames.get("en")
+ subDoc.addInstance(
+ InstanceDescriptor(
+ filename=instance.filename
+ or makeInstanceFilename(doc, instance, statNames),
+ path=instance.path,
+ font=instance.font,
+ name=instance.name or f"{familyName} {styleName}",
+ userLocation={} if expandLocations else instance.userLocation,
+ designLocation=_filterLocation(
+ userRegion, maybeExpandDesignLocation(instance)
+ ),
+ familyName=familyName,
+ styleName=styleName,
+ postScriptFontName=instance.postScriptFontName
+ or statNames.postScriptFontName,
+ styleMapFamilyName=instance.styleMapFamilyName
+ or statNames.styleMapFamilyNames.get("en"),
+ styleMapStyleName=instance.styleMapStyleName
+ or statNames.styleMapStyleName,
+ localisedFamilyName=instance.localisedFamilyName
+ or statNames.familyNames,
+ localisedStyleName=instance.localisedStyleName
+ or statNames.styleNames,
+ localisedStyleMapFamilyName=instance.localisedStyleMapFamilyName
+ or statNames.styleMapFamilyNames,
+ localisedStyleMapStyleName=instance.localisedStyleMapStyleName
+ or {},
+ lib=instance.lib,
+ )
+ )
+ else:
+ subDoc.addInstance(
+ InstanceDescriptor(
+ filename=instance.filename,
+ path=instance.path,
+ font=instance.font,
+ name=instance.name,
+ userLocation={} if expandLocations else instance.userLocation,
+ designLocation=_filterLocation(
+ userRegion, maybeExpandDesignLocation(instance)
+ ),
+ familyName=instance.familyName,
+ styleName=instance.styleName,
+ postScriptFontName=instance.postScriptFontName,
+ styleMapFamilyName=instance.styleMapFamilyName,
+ styleMapStyleName=instance.styleMapStyleName,
+ localisedFamilyName=instance.localisedFamilyName,
+ localisedStyleName=instance.localisedStyleName,
+ localisedStyleMapFamilyName=instance.localisedStyleMapFamilyName,
+ localisedStyleMapStyleName=instance.localisedStyleMapStyleName,
+ lib=instance.lib,
+ )
+ )
+
+ subDoc.lib = doc.lib
+
+ return subDoc
+
+
+def _conditionSetFrom(conditionSet: List[Dict[str, Any]]) -> ConditionSet:
+ c: Dict[str, Range] = {}
+ for condition in conditionSet:
+ c[condition["name"]] = Range(
+ condition.get("minimum", -math.inf),
+ condition.get("maximum", math.inf),
+ )
+ return c
+
+
+def _subsetRulesBasedOnConditions(
+ rules: List[RuleDescriptor], designRegion: Region
+) -> List[RuleDescriptor]:
+ # What rules to keep:
+ # - Keep the rule if any conditionset is relevant.
+ # - A conditionset is relevant if all conditions are relevant or it is empty.
+ # - A condition is relevant if
+ # - axis is point (C-AP),
+ # - and point in condition's range (C-AP-in)
+ # (in this case remove the condition because it's always true)
+ # - else (C-AP-out) whole conditionset can be discarded (condition false
+ # => conditionset false)
+ # - axis is range (C-AR),
+ # - (C-AR-all) and axis range fully contained in condition range: we can
+ # scrap the condition because it's always true
+ # - (C-AR-inter) and intersection(axis range, condition range) not empty:
+ # keep the condition with the smaller range (= intersection)
+ # - (C-AR-none) else, whole conditionset can be discarded
+ newRules: List[RuleDescriptor] = []
+ for rule in rules:
+ newRule: RuleDescriptor = RuleDescriptor(
+ name=rule.name, conditionSets=[], subs=rule.subs
+ )
+ for conditionset in rule.conditionSets:
+ cs = _conditionSetFrom(conditionset)
+ newConditionset: List[Dict[str, Any]] = []
+ discardConditionset = False
+ for selectionName, selectionValue in designRegion.items():
+ # TODO: Ensure that all(key in conditionset for key in region.keys())?
+ if selectionName not in cs:
+ # raise Exception("Selection has different axes than the rules")
+ continue
+ if isinstance(selectionValue, (float, int)): # is point
+ # Case C-AP-in
+ if selectionValue in cs[selectionName]:
+ pass # always matches, conditionset can stay empty for this one.
+ # Case C-AP-out
+ else:
+ discardConditionset = True
+ else: # is range
+ # Case C-AR-all
+ if selectionValue in cs[selectionName]:
+ pass # always matches, conditionset can stay empty for this one.
+ else:
+ intersection = cs[selectionName].intersection(selectionValue)
+ # Case C-AR-inter
+ if intersection is not None:
+ newConditionset.append(
+ {
+ "name": selectionName,
+ "minimum": intersection.minimum,
+ "maximum": intersection.maximum,
+ }
+ )
+ # Case C-AR-none
+ else:
+ discardConditionset = True
+ if not discardConditionset:
+ newRule.conditionSets.append(newConditionset)
+ if newRule.conditionSets:
+ newRules.append(newRule)
+
+ return newRules
+
+
+def _filterLocation(
+ userRegion: Region,
+ location: Dict[str, float],
+) -> Dict[str, float]:
+ return {
+ name: value
+ for name, value in location.items()
+ if name in userRegion and isinstance(userRegion[name], Range)
+ }
diff --git a/Lib/fontTools/designspaceLib/statNames.py b/Lib/fontTools/designspaceLib/statNames.py
new file mode 100644
index 00000000..0a475c89
--- /dev/null
+++ b/Lib/fontTools/designspaceLib/statNames.py
@@ -0,0 +1,224 @@
+"""Compute name information for a given location in user-space coordinates
+using STAT data. This can be used to fill-in automatically the names of an
+instance:
+
+.. code:: python
+
+ instance = doc.instances[0]
+ names = getStatNames(doc, instance.getFullUserLocation(doc))
+ print(names.styleNames)
+"""
+from __future__ import annotations
+
+from dataclasses import dataclass
+from typing import Dict, Optional, Tuple, Union
+import logging
+
+from fontTools.designspaceLib import (
+ AxisDescriptor,
+ AxisLabelDescriptor,
+ DesignSpaceDocument,
+ DesignSpaceDocumentError,
+ DiscreteAxisDescriptor,
+ SimpleLocationDict,
+ SourceDescriptor,
+)
+
+LOGGER = logging.getLogger(__name__)
+
+# TODO(Python 3.8): use Literal
+# RibbiStyleName = Union[Literal["regular"], Literal["bold"], Literal["italic"], Literal["bold italic"]]
+RibbiStyle = str
+BOLD_ITALIC_TO_RIBBI_STYLE = {
+ (False, False): "regular",
+ (False, True): "italic",
+ (True, False): "bold",
+ (True, True): "bold italic",
+}
+
+
+@dataclass
+class StatNames:
+ """Name data generated from the STAT table information."""
+
+ familyNames: Dict[str, str]
+ styleNames: Dict[str, str]
+ postScriptFontName: Optional[str]
+ styleMapFamilyNames: Dict[str, str]
+ styleMapStyleName: Optional[RibbiStyle]
+
+
+
+def getStatNames(
+ doc: DesignSpaceDocument, userLocation: SimpleLocationDict
+) -> StatNames:
+ """Compute the family, style, PostScript names of the given ``userLocation``
+ using the document's STAT information.
+
+ Also computes localizations.
+
+ If not enough STAT data is available for a given name, either its dict of
+ localized names will be empty (family and style names), or the name will be
+ None (PostScript name).
+
+ .. versionadded:: 5.0
+ """
+ familyNames: Dict[str, str] = {}
+ defaultSource: Optional[SourceDescriptor] = doc.findDefault()
+ if defaultSource is None:
+ LOGGER.warning("Cannot determine default source to look up family name.")
+ elif defaultSource.familyName is None:
+ LOGGER.warning(
+ "Cannot look up family name, assign the 'familyname' attribute to the default source."
+ )
+ else:
+ familyNames = {
+ "en": defaultSource.familyName,
+ **defaultSource.localisedFamilyName,
+ }
+
+ styleNames: Dict[str, str] = {}
+ # If a free-standing label matches the location, use it for name generation.
+ label = doc.labelForUserLocation(userLocation)
+ if label is not None:
+ styleNames = {"en": label.name, **label.labelNames}
+ # Otherwise, scour the axis labels for matches.
+ else:
+ # Gather all languages in which at least one translation is provided
+ # Then build names for all these languages, but fallback to English
+ # whenever a translation is missing.
+ labels = _getAxisLabelsForUserLocation(doc.axes, userLocation)
+ languages = set(language for label in labels for language in label.labelNames)
+ languages.add("en")
+ for language in languages:
+ styleName = " ".join(
+ label.labelNames.get(language, label.defaultName)
+ for label in labels
+ if not label.elidable
+ )
+ if not styleName and doc.elidedFallbackName is not None:
+ styleName = doc.elidedFallbackName
+ styleNames[language] = styleName
+
+ postScriptFontName = None
+ if "en" in familyNames and "en" in styleNames:
+ postScriptFontName = f"{familyNames['en']}-{styleNames['en']}".replace(" ", "")
+
+ styleMapStyleName, regularUserLocation = _getRibbiStyle(doc, userLocation)
+
+ styleNamesForStyleMap = styleNames
+ if regularUserLocation != userLocation:
+ regularStatNames = getStatNames(doc, regularUserLocation)
+ styleNamesForStyleMap = regularStatNames.styleNames
+
+ styleMapFamilyNames = {}
+ for language in set(familyNames).union(styleNames.keys()):
+ familyName = familyNames.get(language, familyNames["en"])
+ styleName = styleNamesForStyleMap.get(language, styleNamesForStyleMap["en"])
+ styleMapFamilyNames[language] = (familyName + " " + styleName).strip()
+
+ return StatNames(
+ familyNames=familyNames,
+ styleNames=styleNames,
+ postScriptFontName=postScriptFontName,
+ styleMapFamilyNames=styleMapFamilyNames,
+ styleMapStyleName=styleMapStyleName,
+ )
+
+
+def _getSortedAxisLabels(
+ axes: list[Union[AxisDescriptor, DiscreteAxisDescriptor]],
+) -> Dict[str, list[AxisLabelDescriptor]]:
+ """Returns axis labels sorted by their ordering, with unordered ones appended as
+ they are listed."""
+
+ # First, get the axis labels with explicit ordering...
+ sortedAxes = sorted(
+ (axis for axis in axes if axis.axisOrdering is not None),
+ key=lambda a: a.axisOrdering,
+ )
+ sortedLabels: Dict[str, list[AxisLabelDescriptor]] = {
+ axis.name: axis.axisLabels for axis in sortedAxes
+ }
+
+ # ... then append the others in the order they appear.
+ # NOTE: This relies on Python 3.7+ dict's preserved insertion order.
+ for axis in axes:
+ if axis.axisOrdering is None:
+ sortedLabels[axis.name] = axis.axisLabels
+
+ return sortedLabels
+
+
+def _getAxisLabelsForUserLocation(
+ axes: list[Union[AxisDescriptor, DiscreteAxisDescriptor]],
+ userLocation: SimpleLocationDict,
+) -> list[AxisLabelDescriptor]:
+ labels: list[AxisLabelDescriptor] = []
+
+ allAxisLabels = _getSortedAxisLabels(axes)
+ if allAxisLabels.keys() != userLocation.keys():
+ LOGGER.warning(
+ f"Mismatch between user location '{userLocation.keys()}' and available "
+ f"labels for '{allAxisLabels.keys()}'."
+ )
+
+ for axisName, axisLabels in allAxisLabels.items():
+ userValue = userLocation[axisName]
+ label: Optional[AxisLabelDescriptor] = next(
+ (
+ l
+ for l in axisLabels
+ if l.userValue == userValue
+ or (
+ l.userMinimum is not None
+ and l.userMaximum is not None
+ and l.userMinimum <= userValue <= l.userMaximum
+ )
+ ),
+ None,
+ )
+ if label is None:
+ LOGGER.debug(
+ f"Document needs a label for axis '{axisName}', user value '{userValue}'."
+ )
+ else:
+ labels.append(label)
+
+ return labels
+
+
+def _getRibbiStyle(
+ self: DesignSpaceDocument, userLocation: SimpleLocationDict
+) -> Tuple[RibbiStyle, SimpleLocationDict]:
+ """Compute the RIBBI style name of the given user location,
+ return the location of the matching Regular in the RIBBI group.
+
+ .. versionadded:: 5.0
+ """
+ regularUserLocation = {}
+ axes_by_tag = {axis.tag: axis for axis in self.axes}
+
+ bold: bool = False
+ italic: bool = False
+
+ axis = axes_by_tag.get("wght")
+ if axis is not None:
+ for regular_label in axis.axisLabels:
+ if regular_label.linkedUserValue == userLocation[axis.name]:
+ regularUserLocation[axis.name] = regular_label.userValue
+ bold = True
+ break
+
+ axis = axes_by_tag.get("ital") or axes_by_tag.get("slnt")
+ if axis is not None:
+ for urpright_label in axis.axisLabels:
+ if urpright_label.linkedUserValue == userLocation[axis.name]:
+ regularUserLocation[axis.name] = urpright_label.userValue
+ italic = True
+ break
+
+ return BOLD_ITALIC_TO_RIBBI_STYLE[bold, italic], {
+ **userLocation,
+ **regularUserLocation,
+ }
diff --git a/Lib/fontTools/designspaceLib/types.py b/Lib/fontTools/designspaceLib/types.py
new file mode 100644
index 00000000..8afea96c
--- /dev/null
+++ b/Lib/fontTools/designspaceLib/types.py
@@ -0,0 +1,122 @@
+from __future__ import annotations
+
+from dataclasses import dataclass
+from typing import Dict, List, Optional, Union
+
+from fontTools.designspaceLib import (
+ DesignSpaceDocument,
+ RangeAxisSubsetDescriptor,
+ SimpleLocationDict,
+ VariableFontDescriptor,
+)
+
+
+def clamp(value, minimum, maximum):
+ return min(max(value, minimum), maximum)
+
+
+@dataclass
+class Range:
+ minimum: float
+ """Inclusive minimum of the range."""
+ maximum: float
+ """Inclusive maximum of the range."""
+ default: float = 0
+ """Default value"""
+
+ def __post_init__(self):
+ self.minimum, self.maximum = sorted((self.minimum, self.maximum))
+ self.default = clamp(self.default, self.minimum, self.maximum)
+
+ def __contains__(self, value: Union[float, Range]) -> bool:
+ if isinstance(value, Range):
+ return self.minimum <= value.minimum and value.maximum <= self.maximum
+ return self.minimum <= value <= self.maximum
+
+ def intersection(self, other: Range) -> Optional[Range]:
+ if self.maximum < other.minimum or self.minimum > other.maximum:
+ return None
+ else:
+ return Range(
+ max(self.minimum, other.minimum),
+ min(self.maximum, other.maximum),
+ self.default, # We don't care about the default in this use-case
+ )
+
+
+# A region selection is either a range or a single value, as a Designspace v5
+# axis-subset element only allows a single discrete value or a range for a
+# variable-font element.
+Region = Dict[str, Union[Range, float]]
+
+# A conditionset is a set of named ranges.
+ConditionSet = Dict[str, Range]
+
+# A rule is a list of conditionsets where any has to be relevant for the whole rule to be relevant.
+Rule = List[ConditionSet]
+Rules = Dict[str, Rule]
+
+
+def locationInRegion(location: SimpleLocationDict, region: Region) -> bool:
+ for name, value in location.items():
+ if name not in region:
+ return False
+ regionValue = region[name]
+ if isinstance(regionValue, (float, int)):
+ if value != regionValue:
+ return False
+ else:
+ if value not in regionValue:
+ return False
+ return True
+
+
+def regionInRegion(region: Region, superRegion: Region) -> bool:
+ for name, value in region.items():
+ if not name in superRegion:
+ return False
+ superValue = superRegion[name]
+ if isinstance(superValue, (float, int)):
+ if value != superValue:
+ return False
+ else:
+ if value not in superValue:
+ return False
+ return True
+
+
+def userRegionToDesignRegion(doc: DesignSpaceDocument, userRegion: Region) -> Region:
+ designRegion = {}
+ for name, value in userRegion.items():
+ axis = doc.getAxis(name)
+ if isinstance(value, (float, int)):
+ designRegion[name] = axis.map_forward(value)
+ else:
+ designRegion[name] = Range(
+ axis.map_forward(value.minimum),
+ axis.map_forward(value.maximum),
+ axis.map_forward(value.default),
+ )
+ return designRegion
+
+
+def getVFUserRegion(doc: DesignSpaceDocument, vf: VariableFontDescriptor) -> Region:
+ vfUserRegion: Region = {}
+ # For each axis, 2 cases:
+ # - it has a range = it's an axis in the VF DS
+ # - it's a single location = use it to know which rules should apply in the VF
+ for axisSubset in vf.axisSubsets:
+ axis = doc.getAxis(axisSubset.name)
+ if isinstance(axisSubset, RangeAxisSubsetDescriptor):
+ vfUserRegion[axis.name] = Range(
+ max(axisSubset.userMinimum, axis.minimum),
+ min(axisSubset.userMaximum, axis.maximum),
+ axisSubset.userDefault or axis.default,
+ )
+ else:
+ vfUserRegion[axis.name] = axisSubset.userValue
+ # Any axis not mentioned explicitly has a single location = default value
+ for axis in doc.axes:
+ if axis.name not in vfUserRegion:
+ vfUserRegion[axis.name] = axis.default
+ return vfUserRegion
diff --git a/Lib/fontTools/fontBuilder.py b/Lib/fontTools/fontBuilder.py
index bf3b31b7..ad7180cb 100644
--- a/Lib/fontTools/fontBuilder.py
+++ b/Lib/fontTools/fontBuilder.py
@@ -486,15 +486,12 @@ class FontBuilder(object):
"""Create a new `OS/2` table and initialize it with default values,
which can be overridden by keyword arguments.
"""
- if "xAvgCharWidth" not in values:
- gs = self.font.getGlyphSet()
- widths = [
- gs[glyphName].width
- for glyphName in gs.keys()
- if gs[glyphName].width > 0
- ]
- values["xAvgCharWidth"] = int(round(sum(widths) / float(len(widths))))
self._initTableWithValues("OS/2", _OS2Defaults, values)
+ if "xAvgCharWidth" not in values:
+ assert (
+ "hmtx" in self.font
+ ), "the 'hmtx' table must be setup before the 'OS/2' table"
+ self.font["OS/2"].recalcAvgCharWidth(self.font)
if not (
"ulUnicodeRange1" in values
or "ulUnicodeRange2" in values
diff --git a/Lib/fontTools/merge/__init__.py b/Lib/fontTools/merge/__init__.py
index 152bf079..97106489 100644
--- a/Lib/fontTools/merge/__init__.py
+++ b/Lib/fontTools/merge/__init__.py
@@ -155,6 +155,11 @@ class Merger(object):
def _postMerge(self, font):
layoutPostMerge(font)
+ if "OS/2" in font:
+ # https://github.com/fonttools/fonttools/issues/2538
+ # TODO: Add an option to disable this?
+ font["OS/2"].recalcAvgCharWidth(font)
+
__all__ = [
'Options',
diff --git a/Lib/fontTools/merge/tables.py b/Lib/fontTools/merge/tables.py
index b266f7a9..ac6d59b5 100644
--- a/Lib/fontTools/merge/tables.py
+++ b/Lib/fontTools/merge/tables.py
@@ -132,7 +132,7 @@ ttLib.getTableClass('OS/2').mergeMap = {
'*': first,
'tableTag': equal,
'version': max,
- 'xAvgCharWidth': avg_int, # Apparently fontTools doesn't recalc this
+ 'xAvgCharWidth': first, # Will be recalculated at the end on the merged font
'fsType': mergeOs2FsType, # Will be overwritten
'panose': first, # FIXME: should really be the first Latin font
'ulUnicodeRange1': bitwise_or,
diff --git a/Lib/fontTools/misc/configTools.py b/Lib/fontTools/misc/configTools.py
new file mode 100644
index 00000000..38bbada2
--- /dev/null
+++ b/Lib/fontTools/misc/configTools.py
@@ -0,0 +1,348 @@
+"""
+Code of the config system; not related to fontTools or fonts in particular.
+
+The options that are specific to fontTools are in :mod:`fontTools.config`.
+
+To create your own config system, you need to create an instance of
+:class:`Options`, and a subclass of :class:`AbstractConfig` with its
+``options`` class variable set to your instance of Options.
+
+"""
+from __future__ import annotations
+
+import logging
+from dataclasses import dataclass
+from typing import (
+ Any,
+ Callable,
+ ClassVar,
+ Dict,
+ Iterable,
+ Mapping,
+ MutableMapping,
+ Optional,
+ Set,
+ Union,
+)
+
+
+log = logging.getLogger(__name__)
+
+__all__ = [
+ "AbstractConfig",
+ "ConfigAlreadyRegisteredError",
+ "ConfigError",
+ "ConfigUnknownOptionError",
+ "ConfigValueParsingError",
+ "ConfigValueValidationError",
+ "Option",
+ "Options",
+]
+
+
+class ConfigError(Exception):
+ """Base exception for the config module."""
+
+
+class ConfigAlreadyRegisteredError(ConfigError):
+ """Raised when a module tries to register a configuration option that
+ already exists.
+
+ Should not be raised too much really, only when developing new fontTools
+ modules.
+ """
+
+ def __init__(self, name):
+ super().__init__(f"Config option {name} is already registered.")
+
+
+class ConfigValueParsingError(ConfigError):
+ """Raised when a configuration value cannot be parsed."""
+
+ def __init__(self, name, value):
+ super().__init__(
+ f"Config option {name}: value cannot be parsed (given {repr(value)})"
+ )
+
+
+class ConfigValueValidationError(ConfigError):
+ """Raised when a configuration value cannot be validated."""
+
+ def __init__(self, name, value):
+ super().__init__(
+ f"Config option {name}: value is invalid (given {repr(value)})"
+ )
+
+
+class ConfigUnknownOptionError(ConfigError):
+ """Raised when a configuration option is unknown."""
+
+ def __init__(self, option_or_name):
+ name = (
+ f"'{option_or_name.name}' (id={id(option_or_name)})>"
+ if isinstance(option_or_name, Option)
+ else f"'{option_or_name}'"
+ )
+ super().__init__(f"Config option {name} is unknown")
+
+
+# eq=False because Options are unique, not fungible objects
+@dataclass(frozen=True, eq=False)
+class Option:
+ name: str
+ """Unique name identifying the option (e.g. package.module:MY_OPTION)."""
+ help: str
+ """Help text for this option."""
+ default: Any
+ """Default value for this option."""
+ parse: Callable[[str], Any]
+ """Turn input (e.g. string) into proper type. Only when reading from file."""
+ validate: Optional[Callable[[Any], bool]] = None
+ """Return true if the given value is an acceptable value."""
+
+ @staticmethod
+ def parse_optional_bool(v: str) -> Optional[bool]:
+ s = str(v).lower()
+ if s in {"0", "no", "false"}:
+ return False
+ if s in {"1", "yes", "true"}:
+ return True
+ if s in {"auto", "none"}:
+ return None
+ raise ValueError("invalid optional bool: {v!r}")
+
+ @staticmethod
+ def validate_optional_bool(v: Any) -> bool:
+ return v is None or isinstance(v, bool)
+
+
+class Options(Mapping):
+ """Registry of available options for a given config system.
+
+ Define new options using the :meth:`register()` method.
+
+ Access existing options using the Mapping interface.
+ """
+
+ __options: Dict[str, Option]
+
+ def __init__(self, other: "Options" = None) -> None:
+ self.__options = {}
+ if other is not None:
+ for option in other.values():
+ self.register_option(option)
+
+ def register(
+ self,
+ name: str,
+ help: str,
+ default: Any,
+ parse: Callable[[str], Any],
+ validate: Optional[Callable[[Any], bool]] = None,
+ ) -> Option:
+ """Create and register a new option."""
+ return self.register_option(Option(name, help, default, parse, validate))
+
+ def register_option(self, option: Option) -> Option:
+ """Register a new option."""
+ name = option.name
+ if name in self.__options:
+ raise ConfigAlreadyRegisteredError(name)
+ self.__options[name] = option
+ return option
+
+ def is_registered(self, option: Option) -> bool:
+ """Return True if the same option object is already registered."""
+ return self.__options.get(option.name) is option
+
+ def __getitem__(self, key: str) -> Option:
+ return self.__options.__getitem__(key)
+
+ def __iter__(self) -> Iterator[str]:
+ return self.__options.__iter__()
+
+ def __len__(self) -> int:
+ return self.__options.__len__()
+
+ def __repr__(self) -> str:
+ return (
+ f"{self.__class__.__name__}({{\n"
+ + "".join(
+ f" {k!r}: Option(default={v.default!r}, ...),\n"
+ for k, v in self.__options.items()
+ )
+ + "})"
+ )
+
+
+_USE_GLOBAL_DEFAULT = object()
+
+
+class AbstractConfig(MutableMapping):
+ """
+ Create a set of config values, optionally pre-filled with values from
+ the given dictionary or pre-existing config object.
+
+ The class implements the MutableMapping protocol keyed by option name (`str`).
+ For convenience its methods accept either Option or str as the key parameter.
+
+ .. seealso:: :meth:`set()`
+
+ This config class is abstract because it needs its ``options`` class
+ var to be set to an instance of :class:`Options` before it can be
+ instanciated and used.
+
+ .. code:: python
+
+ class MyConfig(AbstractConfig):
+ options = Options()
+
+ MyConfig.register_option( "test:option_name", "This is an option", 0, int, lambda v: isinstance(v, int))
+
+ cfg = MyConfig({"test:option_name": 10})
+
+ """
+
+ options: ClassVar[Options]
+
+ @classmethod
+ def register_option(
+ cls,
+ name: str,
+ help: str,
+ default: Any,
+ parse: Callable[[str], Any],
+ validate: Optional[Callable[[Any], bool]] = None,
+ ) -> Option:
+ """Register an available option in this config system."""
+ return cls.options.register(
+ name, help=help, default=default, parse=parse, validate=validate
+ )
+
+ _values: Dict[str, Any]
+
+ def __init__(
+ self,
+ values: Union[AbstractConfig, Dict[Union[Option, str], Any]] = {},
+ parse_values: bool = False,
+ skip_unknown: bool = False,
+ ):
+ self._values = {}
+ values_dict = values._values if isinstance(values, AbstractConfig) else values
+ for name, value in values_dict.items():
+ self.set(name, value, parse_values, skip_unknown)
+
+ def _resolve_option(self, option_or_name: Union[Option, str]) -> Option:
+ if isinstance(option_or_name, Option):
+ option = option_or_name
+ if not self.options.is_registered(option):
+ raise ConfigUnknownOptionError(option)
+ return option
+ elif isinstance(option_or_name, str):
+ name = option_or_name
+ try:
+ return self.options[name]
+ except KeyError:
+ raise ConfigUnknownOptionError(name)
+ else:
+ raise TypeError(
+ "expected Option or str, found "
+ f"{type(option_or_name).__name__}: {option_or_name!r}"
+ )
+
+ def set(
+ self,
+ option_or_name: Union[Option, str],
+ value: Any,
+ parse_values: bool = False,
+ skip_unknown: bool = False,
+ ):
+ """Set the value of an option.
+
+ Args:
+ * `option_or_name`: an `Option` object or its name (`str`).
+ * `value`: the value to be assigned to given option.
+ * `parse_values`: parse the configuration value from a string into
+ its proper type, as per its `Option` object. The default
+ behavior is to raise `ConfigValueValidationError` when the value
+ is not of the right type. Useful when reading options from a
+ file type that doesn't support as many types as Python.
+ * `skip_unknown`: skip unknown configuration options. The default
+ behaviour is to raise `ConfigUnknownOptionError`. Useful when
+ reading options from a configuration file that has extra entries
+ (e.g. for a later version of fontTools)
+ """
+ try:
+ option = self._resolve_option(option_or_name)
+ except ConfigUnknownOptionError as e:
+ if skip_unknown:
+ log.debug(str(e))
+ return
+ raise
+
+ # Can be useful if the values come from a source that doesn't have
+ # strict typing (.ini file? Terminal input?)
+ if parse_values:
+ try:
+ value = option.parse(value)
+ except Exception as e:
+ raise ConfigValueParsingError(option.name, value) from e
+
+ if option.validate is not None and not option.validate(value):
+ raise ConfigValueValidationError(option.name, value)
+
+ self._values[option.name] = value
+
+ def get(
+ self, option_or_name: Union[Option, str], default: Any = _USE_GLOBAL_DEFAULT
+ ) -> Any:
+ """
+ Get the value of an option. The value which is returned is the first
+ provided among:
+
+ 1. a user-provided value in the options's ``self._values`` dict
+ 2. a caller-provided default value to this method call
+ 3. the global default for the option provided in ``fontTools.config``
+
+ This is to provide the ability to migrate progressively from config
+ options passed as arguments to fontTools APIs to config options read
+ from the current TTFont, e.g.
+
+ .. code:: python
+
+ def fontToolsAPI(font, some_option):
+ value = font.cfg.get("someLib.module:SOME_OPTION", some_option)
+ # use value
+
+ That way, the function will work the same for users of the API that
+ still pass the option to the function call, but will favour the new
+ config mechanism if the given font specifies a value for that option.
+ """
+ option = self._resolve_option(option_or_name)
+ if option.name in self._values:
+ return self._values[option.name]
+ if default is not _USE_GLOBAL_DEFAULT:
+ return default
+ return option.default
+
+ def copy(self):
+ return self.__class__(self._values)
+
+ def __getitem__(self, option_or_name: Union[Option, str]) -> Any:
+ return self.get(option_or_name)
+
+ def __setitem__(self, option_or_name: Union[Option, str], value: Any) -> None:
+ return self.set(option_or_name, value)
+
+ def __delitem__(self, option_or_name: Union[Option, str]) -> None:
+ option = self._resolve_option(option_or_name)
+ del self._values[option.name]
+
+ def __iter__(self) -> Iterable[str]:
+ return self._values.__iter__()
+
+ def __len__(self) -> int:
+ return len(self._values)
+
+ def __repr__(self) -> str:
+ return f"{self.__class__.__name__}({repr(self._values)})"
diff --git a/Lib/fontTools/misc/psCharStrings.py b/Lib/fontTools/misc/psCharStrings.py
index 29c2d365..549dae25 100644
--- a/Lib/fontTools/misc/psCharStrings.py
+++ b/Lib/fontTools/misc/psCharStrings.py
@@ -502,11 +502,20 @@ class T2OutlineExtractor(T2WidthExtractor):
T2WidthExtractor.__init__(
self, localSubrs, globalSubrs, nominalWidthX, defaultWidthX, private)
self.pen = pen
+ self.subrLevel = 0
def reset(self):
T2WidthExtractor.reset(self)
self.currentPoint = (0, 0)
self.sawMoveTo = 0
+ self.subrLevel = 0
+
+ def execute(self, charString):
+ self.subrLevel += 1
+ super().execute(charString)
+ self.subrLevel -= 1
+ if self.subrLevel == 0:
+ self.endPath()
def _nextPoint(self, point):
x, y = self.currentPoint
@@ -536,8 +545,11 @@ class T2OutlineExtractor(T2WidthExtractor):
def endPath(self):
# In T2 there are no open paths, so always do a closePath when
- # finishing a sub path.
- self.closePath()
+ # finishing a sub path. We avoid spurious calls to closePath()
+ # because its a real T1 op we're emulating in T2 whereas
+ # endPath() is just a means to that emulation
+ if self.sawMoveTo:
+ self.closePath()
#
# hint operators
diff --git a/Lib/fontTools/misc/testTools.py b/Lib/fontTools/misc/testTools.py
index db316a82..871a9951 100644
--- a/Lib/fontTools/misc/testTools.py
+++ b/Lib/fontTools/misc/testTools.py
@@ -3,10 +3,12 @@
from collections.abc import Iterable
from io import BytesIO
import os
+import re
import shutil
import sys
import tempfile
from unittest import TestCase as _TestCase
+from fontTools.config import Config
from fontTools.misc.textTools import tobytes
from fontTools.misc.xmlWriter import XMLWriter
@@ -52,6 +54,7 @@ class FakeFont:
self.reverseGlyphOrderDict_ = {g: i for i, g in enumerate(glyphs)}
self.lazy = False
self.tables = {}
+ self.cfg = Config()
def __getitem__(self, tag):
return self.tables[tag]
@@ -133,6 +136,31 @@ def getXML(func, ttFont=None):
return xml.splitlines()
+def stripVariableItemsFromTTX(
+ string: str,
+ ttLibVersion: bool = True,
+ checkSumAdjustment: bool = True,
+ modified: bool = True,
+ created: bool = True,
+ sfntVersion: bool = False, # opt-in only
+) -> str:
+ """Strip stuff like ttLibVersion, checksums, timestamps, etc. from TTX dumps."""
+ # ttlib changes with the fontTools version
+ if ttLibVersion:
+ string = re.sub(' ttLibVersion="[^"]+"', "", string)
+ # sometimes (e.g. some subsetter tests) we don't care whether it's OTF or TTF
+ if sfntVersion:
+ string = re.sub(' sfntVersion="[^"]+"', "", string)
+ # head table checksum and creation and mod date changes with each save.
+ if checkSumAdjustment:
+ string = re.sub('<checkSumAdjustment value="[^"]+"/>', "", string)
+ if modified:
+ string = re.sub('<modified value="[^"]+"/>', "", string)
+ if created:
+ string = re.sub('<created value="[^"]+"/>', "", string)
+ return string
+
+
class MockFont(object):
"""A font-like object that automatically adds any looked up glyphname
to its glyphOrder."""
diff --git a/Lib/fontTools/otlLib/builder.py b/Lib/fontTools/otlLib/builder.py
index e3f33551..233edec2 100644
--- a/Lib/fontTools/otlLib/builder.py
+++ b/Lib/fontTools/otlLib/builder.py
@@ -12,8 +12,7 @@ from fontTools.ttLib.tables.otBase import (
from fontTools.ttLib.tables import otBase
from fontTools.feaLib.ast import STATNameStatement
from fontTools.otlLib.optimize.gpos import (
- GPOS_COMPACT_MODE_DEFAULT,
- GPOS_COMPACT_MODE_ENV_KEY,
+ _compression_level_from_env,
compact_lookup,
)
from fontTools.otlLib.error import OpenTypeLibError
@@ -367,9 +366,15 @@ class ChainContextualBuilder(LookupBuilder):
contextual positioning lookup.
"""
subtables = []
- chaining = False
+
rulesets = self.rulesets()
chaining = any(ruleset.hasPrefixOrSuffix for ruleset in rulesets)
+ # Unfortunately, as of 2022-03-07, Apple's CoreText renderer does not
+ # correctly process GPOS7 lookups, so for now we force contextual
+ # positioning lookups to be chaining (GPOS8).
+ if self.subtable_type == "Pos": # horrible separation of concerns breach
+ chaining = True
+
for ruleset in rulesets:
# Determine format strategy. We try to build formats 1, 2 and 3
# subtables and then work out which is best. candidates list holds
@@ -1408,10 +1413,14 @@ class PairPosBuilder(LookupBuilder):
# Compact the lookup
# This is a good moment to do it because the compaction should create
# smaller subtables, which may prevent overflows from happening.
- mode = os.environ.get(GPOS_COMPACT_MODE_ENV_KEY, GPOS_COMPACT_MODE_DEFAULT)
- if mode and mode != "0":
+ # Keep reading the value from the ENV until ufo2ft switches to the config system
+ level = self.font.cfg.get(
+ "fontTools.otlLib.optimize.gpos:COMPRESSION_LEVEL",
+ default=_compression_level_from_env(),
+ )
+ if level != 0:
log.info("Compacting GPOS...")
- compact_lookup(self.font, mode, lookup)
+ compact_lookup(self.font, level, lookup)
return lookup
diff --git a/Lib/fontTools/otlLib/optimize/__init__.py b/Lib/fontTools/otlLib/optimize/__init__.py
index 5c007e89..a9512fb0 100644
--- a/Lib/fontTools/otlLib/optimize/__init__.py
+++ b/Lib/fontTools/otlLib/optimize/__init__.py
@@ -1,39 +1,27 @@
from argparse import RawTextHelpFormatter
-from textwrap import dedent
-
+from fontTools.otlLib.optimize.gpos import COMPRESSION_LEVEL, compact
from fontTools.ttLib import TTFont
-from fontTools.otlLib.optimize.gpos import compact, GPOS_COMPACT_MODE_DEFAULT
+
def main(args=None):
"""Optimize the layout tables of an existing font."""
from argparse import ArgumentParser
+
from fontTools import configLogger
- parser = ArgumentParser(prog="otlLib.optimize", description=main.__doc__, formatter_class=RawTextHelpFormatter)
+ parser = ArgumentParser(
+ prog="otlLib.optimize",
+ description=main.__doc__,
+ formatter_class=RawTextHelpFormatter,
+ )
parser.add_argument("font")
parser.add_argument(
"-o", metavar="OUTPUTFILE", dest="outfile", default=None, help="output file"
)
parser.add_argument(
- "--gpos-compact-mode",
- help=dedent(
- f"""\
- GPOS Lookup type 2 (PairPos) compaction mode:
- 0 = do not attempt to compact PairPos lookups;
- 1 to 8 = create at most 1 to 8 new subtables for each existing
- subtable, provided that it would yield a 50%% file size saving;
- 9 = create as many new subtables as needed to yield a file size saving.
- Default: {GPOS_COMPACT_MODE_DEFAULT}.
-
- This compaction aims to save file size, by splitting large class
- kerning subtables (Format 2) that contain many zero values into
- smaller and denser subtables. It's a trade-off between the overhead
- of several subtables versus the sparseness of one big subtable.
-
- See the pull request: https://github.com/fonttools/fonttools/pull/2326
- """
- ),
- default=int(GPOS_COMPACT_MODE_DEFAULT),
+ "--gpos-compression-level",
+ help=COMPRESSION_LEVEL.help,
+ default=COMPRESSION_LEVEL.default,
choices=list(range(10)),
type=int,
)
@@ -51,12 +39,10 @@ def main(args=None):
)
font = TTFont(options.font)
- # TODO: switch everything to have type(mode) = int when using the Config class
- compact(font, str(options.gpos_compact_mode))
+ compact(font, options.gpos_compression_level)
font.save(options.outfile or options.font)
-
if __name__ == "__main__":
import sys
@@ -65,4 +51,3 @@ if __name__ == "__main__":
import doctest
sys.exit(doctest.testmod().failed)
-
diff --git a/Lib/fontTools/otlLib/optimize/gpos.py b/Lib/fontTools/otlLib/optimize/gpos.py
index 79873fad..0acd9ed0 100644
--- a/Lib/fontTools/otlLib/optimize/gpos.py
+++ b/Lib/fontTools/otlLib/optimize/gpos.py
@@ -1,24 +1,45 @@
import logging
+import os
from collections import defaultdict, namedtuple
from functools import reduce
from itertools import chain
from math import log2
from typing import DefaultDict, Dict, Iterable, List, Sequence, Tuple
+from fontTools.config import OPTIONS
from fontTools.misc.intTools import bit_count, bit_indices
from fontTools.ttLib import TTFont
from fontTools.ttLib.tables import otBase, otTables
-# NOTE: activating this optimization via the environment variable is
-# experimental and may not be supported once an alternative mechanism
-# is in place. See: https://github.com/fonttools/fonttools/issues/2349
+log = logging.getLogger(__name__)
+
+COMPRESSION_LEVEL = OPTIONS[f"{__name__}:COMPRESSION_LEVEL"]
+
+# Kept because ufo2ft depends on it, to be removed once ufo2ft uses the config instead
+# https://github.com/fonttools/fonttools/issues/2592
GPOS_COMPACT_MODE_ENV_KEY = "FONTTOOLS_GPOS_COMPACT_MODE"
-GPOS_COMPACT_MODE_DEFAULT = "0"
+GPOS_COMPACT_MODE_DEFAULT = str(COMPRESSION_LEVEL.default)
+
-log = logging.getLogger("fontTools.otlLib.optimize.gpos")
+def _compression_level_from_env() -> int:
+ env_level = GPOS_COMPACT_MODE_DEFAULT
+ if GPOS_COMPACT_MODE_ENV_KEY in os.environ:
+ import warnings
+
+ warnings.warn(
+ f"'{GPOS_COMPACT_MODE_ENV_KEY}' environment variable is deprecated. "
+ "Please set the 'fontTools.otlLib.optimize.gpos:COMPRESSION_LEVEL' option "
+ "in TTFont.cfg.",
+ DeprecationWarning,
+ )
+ env_level = os.environ[GPOS_COMPACT_MODE_ENV_KEY]
+ if len(env_level) == 1 and env_level in "0123456789":
+ return int(env_level)
+ raise ValueError(f"Bad {GPOS_COMPACT_MODE_ENV_KEY}={env_level}")
-def compact(font: TTFont, mode: str) -> TTFont:
+
+def compact(font: TTFont, level: int) -> TTFont:
# Ideal plan:
# 1. Find lookups of Lookup Type 2: Pair Adjustment Positioning Subtable
# https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-2-pair-adjustment-positioning-subtable
@@ -35,21 +56,21 @@ def compact(font: TTFont, mode: str) -> TTFont:
gpos = font["GPOS"]
for lookup in gpos.table.LookupList.Lookup:
if lookup.LookupType == 2:
- compact_lookup(font, mode, lookup)
+ compact_lookup(font, level, lookup)
elif lookup.LookupType == 9 and lookup.SubTable[0].ExtensionLookupType == 2:
- compact_ext_lookup(font, mode, lookup)
+ compact_ext_lookup(font, level, lookup)
return font
-def compact_lookup(font: TTFont, mode: str, lookup: otTables.Lookup) -> None:
- new_subtables = compact_pair_pos(font, mode, lookup.SubTable)
+def compact_lookup(font: TTFont, level: int, lookup: otTables.Lookup) -> None:
+ new_subtables = compact_pair_pos(font, level, lookup.SubTable)
lookup.SubTable = new_subtables
lookup.SubTableCount = len(new_subtables)
-def compact_ext_lookup(font: TTFont, mode: str, lookup: otTables.Lookup) -> None:
+def compact_ext_lookup(font: TTFont, level: int, lookup: otTables.Lookup) -> None:
new_subtables = compact_pair_pos(
- font, mode, [ext_subtable.ExtSubTable for ext_subtable in lookup.SubTable]
+ font, level, [ext_subtable.ExtSubTable for ext_subtable in lookup.SubTable]
)
new_ext_subtables = []
for subtable in new_subtables:
@@ -62,7 +83,7 @@ def compact_ext_lookup(font: TTFont, mode: str, lookup: otTables.Lookup) -> None
def compact_pair_pos(
- font: TTFont, mode: str, subtables: Sequence[otTables.PairPos]
+ font: TTFont, level: int, subtables: Sequence[otTables.PairPos]
) -> Sequence[otTables.PairPos]:
new_subtables = []
for subtable in subtables:
@@ -70,12 +91,12 @@ def compact_pair_pos(
# Not doing anything to Format 1 (yet?)
new_subtables.append(subtable)
elif subtable.Format == 2:
- new_subtables.extend(compact_class_pairs(font, mode, subtable))
+ new_subtables.extend(compact_class_pairs(font, level, subtable))
return new_subtables
def compact_class_pairs(
- font: TTFont, mode: str, subtable: otTables.PairPos
+ font: TTFont, level: int, subtable: otTables.PairPos
) -> List[otTables.PairPos]:
from fontTools.otlLib.builder import buildPairPosClassesSubtable
@@ -95,17 +116,9 @@ def compact_class_pairs(
getattr(class2, "Value1", None),
getattr(class2, "Value2", None),
)
-
- if len(mode) == 1 and mode in "123456789":
- grouped_pairs = cluster_pairs_by_class2_coverage_custom_cost(
- font, all_pairs, int(mode)
- )
- for pairs in grouped_pairs:
- subtables.append(
- buildPairPosClassesSubtable(pairs, font.getReverseGlyphMap())
- )
- else:
- raise ValueError(f"Bad {GPOS_COMPACT_MODE_ENV_KEY}={mode}")
+ grouped_pairs = cluster_pairs_by_class2_coverage_custom_cost(font, all_pairs, level)
+ for pairs in grouped_pairs:
+ subtables.append(buildPairPosClassesSubtable(pairs, font.getReverseGlyphMap()))
return subtables
diff --git a/Lib/fontTools/subset/__init__.py b/Lib/fontTools/subset/__init__.py
index 53b440da..56d9c0ef 100644
--- a/Lib/fontTools/subset/__init__.py
+++ b/Lib/fontTools/subset/__init__.py
@@ -2,9 +2,11 @@
#
# Google Author(s): Behdad Esfahbod
+from fontTools import config
from fontTools.misc.roundTools import otRound
from fontTools import ttLib
from fontTools.ttLib.tables import otTables
+from fontTools.ttLib.tables.otBase import USE_HARFBUZZ_REPACKER
from fontTools.otlLib.maxContextCalc import maxCtxFont
from fontTools.pens.basePen import NullPen
from fontTools.misc.loggingTools import Timer
@@ -144,6 +146,15 @@ Output options
The Zopfli Python bindings are available at:
https://pypi.python.org/pypi/zopfli
+--harfbuzz-repacker
+ By default, we serialize GPOS/GSUB using the HarfBuzz Repacker when
+ uharfbuzz can be imported and is successful, otherwise fall back to
+ the pure-python serializer. Set the option to force using the HarfBuzz
+ Repacker (raises an error if uharfbuzz can't be found or fails).
+
+--no-harfbuzz-repacker
+ Always use the pure-python serializer even if uharfbuzz is available.
+
Glyph set expansion
^^^^^^^^^^^^^^^^^^^
@@ -2642,6 +2653,7 @@ class Options(object):
self.flavor = None # May be 'woff' or 'woff2'
self.with_zopfli = False # use zopfli instead of zlib for WOFF 1.0
self.desubroutinize = False # Desubroutinize CFF CharStrings
+ self.harfbuzz_repacker = USE_HARFBUZZ_REPACKER.default
self.verbose = False
self.timing = False
self.xml = False
@@ -2964,11 +2976,10 @@ class Subsetter(object):
if old_uniranges != new_uniranges:
log.info("%s Unicode ranges pruned: %s", tag, sorted(new_uniranges))
if self.options.recalc_average_width:
- widths = [m[0] for m in font["hmtx"].metrics.values() if m[0] > 0]
- avg_width = otRound(sum(widths) / len(widths))
- if avg_width != font[tag].xAvgCharWidth:
- font[tag].xAvgCharWidth = avg_width
- log.info("%s xAvgCharWidth updated: %d", tag, avg_width)
+ old_avg_width = font[tag].xAvgCharWidth
+ new_avg_width = font[tag].recalcAvgCharWidth(font)
+ if old_avg_width != new_avg_width:
+ log.info("%s xAvgCharWidth updated: %d", tag, new_avg_width)
if self.options.recalc_max_context:
max_context = maxCtxFont(font)
if max_context != font[tag].usMaxContext:
@@ -3038,6 +3049,7 @@ def save_font(font, outfile, options):
from fontTools.ttLib import sfnt
sfnt.USE_ZOPFLI = True
font.flavor = options.flavor
+ font.cfg[USE_HARFBUZZ_REPACKER] = options.harfbuzz_repacker
font.save(outfile, reorderTables=options.canonical_order)
def parse_unicodes(s):
diff --git a/Lib/fontTools/ttLib/tables/O_S_2f_2.py b/Lib/fontTools/ttLib/tables/O_S_2f_2.py
index a5765224..ba2e3961 100644
--- a/Lib/fontTools/ttLib/tables/O_S_2f_2.py
+++ b/Lib/fontTools/ttLib/tables/O_S_2f_2.py
@@ -1,4 +1,5 @@
from fontTools.misc import sstruct
+from fontTools.misc.roundTools import otRound
from fontTools.misc.textTools import safeEval, num2binary, binary2num
from fontTools.ttLib.tables import DefaultTable
import bisect
@@ -299,6 +300,19 @@ class table_O_S_2f_2(DefaultTable.DefaultTable):
self.setUnicodeRanges(bits)
return bits
+ def recalcAvgCharWidth(self, ttFont):
+ """Recalculate xAvgCharWidth using metrics from ttFont's 'hmtx' table.
+
+ Set it to 0 if the unlikely event 'hmtx' table is not found.
+ """
+ avg_width = 0
+ hmtx = ttFont.get("hmtx")
+ if hmtx:
+ widths = [m[0] for m in hmtx.metrics.values() if m[0] > 0]
+ avg_width = otRound(sum(widths) / len(widths))
+ self.xAvgCharWidth = avg_width
+ return avg_width
+
# Unicode ranges data from the OpenType OS/2 table specification v1.7
diff --git a/Lib/fontTools/ttLib/tables/_c_m_a_p.py b/Lib/fontTools/ttLib/tables/_c_m_a_p.py
index a31b5059..9bd59a6b 100644
--- a/Lib/fontTools/ttLib/tables/_c_m_a_p.py
+++ b/Lib/fontTools/ttLib/tables/_c_m_a_p.py
@@ -91,6 +91,11 @@ class table__c_m_a_p(DefaultTable.DefaultTable):
(0, 1), # Unicode 1.1
(0, 0) # Unicode 1.0
+ This particular order matches what HarfBuzz uses to choose what
+ subtable to use by default. This order prefers the largest-repertoire
+ subtable, and among those, prefers the Windows-platform over the
+ Unicode-platform as the former has wider support.
+
This order can be customized via the ``cmapPreferences`` argument.
"""
for platformID, platEncID in cmapPreferences:
@@ -172,13 +177,11 @@ class table__c_m_a_p(DefaultTable.DefaultTable):
seen = {} # Some tables are the same object reference. Don't compile them twice.
done = {} # Some tables are different objects, but compile to the same data chunk
for table in self.tables:
- try:
- offset = seen[id(table.cmap)]
- except KeyError:
+ offset = seen.get(id(table.cmap))
+ if offset is None:
chunk = table.compile(ttFont)
- if chunk in done:
- offset = done[chunk]
- else:
+ offset = done.get(chunk)
+ if offset is None:
offset = seen[id(table.cmap)] = done[chunk] = totalOffset + len(tableData)
tableData = tableData + chunk
data = data + struct.pack(">HHl", table.platformID, table.platEncID, offset)
@@ -800,7 +803,6 @@ class cmap_format_4(CmapSubtable):
start = startCode[i]
delta = idDelta[i]
rangeOffset = idRangeOffset[i]
- # *someone* needs to get killed.
partial = rangeOffset // 2 - start + i - len(idRangeOffset)
rangeCharCodes = list(range(startCode[i], endCode[i] + 1))
@@ -891,7 +893,6 @@ class cmap_format_4(CmapSubtable):
idDelta.append((indices[0] - startCode[i]) % 0x10000)
idRangeOffset.append(0)
else:
- # someone *definitely* needs to get killed.
idDelta.append(0)
idRangeOffset.append(2 * (len(endCode) + len(glyphIndexArray) - i))
glyphIndexArray.extend(indices)
diff --git a/Lib/fontTools/ttLib/tables/otBase.py b/Lib/fontTools/ttLib/tables/otBase.py
index bc2c9fba..d30892f3 100644
--- a/Lib/fontTools/ttLib/tables/otBase.py
+++ b/Lib/fontTools/ttLib/tables/otBase.py
@@ -1,3 +1,4 @@
+from fontTools.config import OPTIONS
from fontTools.misc.textTools import Tag, bytesjoin
from .DefaultTable import DefaultTable
import sys
@@ -8,6 +9,15 @@ from typing import Iterator, NamedTuple, Optional
log = logging.getLogger(__name__)
+have_uharfbuzz = False
+try:
+ import uharfbuzz as hb
+ have_uharfbuzz = True
+except ImportError:
+ pass
+
+USE_HARFBUZZ_REPACKER = OPTIONS[f"{__name__}:USE_HARFBUZZ_REPACKER"]
+
class OverflowErrorRecord(object):
def __init__(self, overflowTuple):
self.tableType = overflowTuple[0]
@@ -66,11 +76,56 @@ class BaseTTXConverter(DefaultTable):
# If a lookup subtable overflows an offset, we have to start all over.
overflowRecord = None
+ # this is 3-state option: default (None) means automatically use hb.repack or
+ # silently fall back if it fails; True, use it and raise error if not possible
+ # or it errors out; False, don't use it, even if you can.
+ use_hb_repack = font.cfg[USE_HARFBUZZ_REPACKER]
+ if self.tableTag in ("GSUB", "GPOS"):
+ if use_hb_repack is False:
+ log.debug(
+ "hb.repack disabled, compiling '%s' with pure-python serializer",
+ self.tableTag,
+ )
+ elif not have_uharfbuzz:
+ if use_hb_repack is True:
+ raise ImportError("No module named 'uharfbuzz'")
+ else:
+ assert use_hb_repack is None
+ log.debug(
+ "uharfbuzz not found, compiling '%s' with pure-python serializer",
+ self.tableTag,
+ )
+ hb_first_error_logged = False
while True:
try:
writer = OTTableWriter(tableTag=self.tableTag)
self.table.compile(writer, font)
+ if (
+ use_hb_repack in (None, True)
+ and have_uharfbuzz
+ and self.tableTag in ("GSUB", "GPOS")
+ ):
+ try:
+ log.debug("serializing '%s' with hb.repack", self.tableTag)
+ return writer.getAllDataUsingHarfbuzz()
+ except (ValueError, MemoryError, hb.RepackerError) as e:
+ # Only log hb repacker errors the first time they occur in
+ # the offset-overflow resolution loop, they are just noisy.
+ # Maybe we can revisit this if/when uharfbuzz actually gives
+ # us more info as to why hb.repack failed...
+ if not hb_first_error_logged:
+ error_msg = f"{type(e).__name__}"
+ if str(e) != "":
+ error_msg += f": {e}"
+ log.warning(
+ "hb.repack failed to serialize '%s', reverting to "
+ "pure-python serializer; the error message was: %s",
+ self.tableTag,
+ error_msg,
+ )
+ hb_first_error_logged = True
+ return writer.getAllData(remove_duplicate=False)
return writer.getAllData()
except OTLOffsetOverflowError as e:
@@ -298,6 +353,20 @@ class OTTableWriter(object):
return bytesjoin(items)
+ def getDataForHarfbuzz(self):
+ """Assemble the data for this writer/table with all offset field set to 0"""
+ items = list(self.items)
+ packFuncs = {2: packUShort, 3: packUInt24, 4: packULong}
+ for i, item in enumerate(items):
+ if hasattr(item, "getData"):
+ # Offset value is not needed in harfbuzz repacker, so setting offset to 0 to avoid overflow here
+ if item.offsetSize in packFuncs:
+ items[i] = packFuncs[item.offsetSize](0)
+ else:
+ raise ValueError(item.offsetSize)
+
+ return bytesjoin(items)
+
def __hash__(self):
# only works after self._doneWriting() has been called
return hash(self.items)
@@ -402,11 +471,95 @@ class OTTableWriter(object):
selfTables.append(self)
- def getAllData(self):
- """Assemble all data, including all subtables."""
+ def _gatherGraphForHarfbuzz(self, tables, obj_list, done, objidx, virtual_edges):
+ real_links = []
+ virtual_links = []
+ item_idx = objidx
+
+ # Merge virtual_links from parent
+ for idx in virtual_edges:
+ virtual_links.append((0, 0, idx))
+
+ sortCoverageLast = False
+ coverage_idx = 0
+ if hasattr(self, "sortCoverageLast"):
+ # Find coverage table
+ for i, item in enumerate(self.items):
+ if getattr(item, 'name', None) == "Coverage":
+ sortCoverageLast = True
+ if id(item) not in done:
+ coverage_idx = item_idx = item._gatherGraphForHarfbuzz(tables, obj_list, done, item_idx, virtual_edges)
+ else:
+ coverage_idx = done[id(item)]
+ virtual_edges.append(coverage_idx)
+ break
+
+ child_idx = 0
+ offset_pos = 0
+ for i, item in enumerate(self.items):
+ if hasattr(item, "getData"):
+ pos = offset_pos
+ elif hasattr(item, "getCountData"):
+ offset_pos += item.size
+ continue
+ else:
+ offset_pos = offset_pos + len(item)
+ continue
+
+ if id(item) not in done:
+ child_idx = item_idx = item._gatherGraphForHarfbuzz(tables, obj_list, done, item_idx, virtual_edges)
+ else:
+ child_idx = done[id(item)]
+
+ real_edge = (pos, item.offsetSize, child_idx)
+ real_links.append(real_edge)
+ offset_pos += item.offsetSize
+
+ tables.append(self)
+ obj_list.append((real_links,virtual_links))
+ item_idx += 1
+ done[id(self)] = item_idx
+ if sortCoverageLast:
+ virtual_edges.pop()
+
+ return item_idx
+
+ def getAllDataUsingHarfbuzz(self):
+ """The Whole table is represented as a Graph.
+ Assemble graph data and call Harfbuzz repacker to pack the table.
+ Harfbuzz repacker is faster and retain as much sub-table sharing as possible, see also:
+ https://github.com/harfbuzz/harfbuzz/blob/main/docs/repacker.md
+ The input format for hb.repack() method is explained here:
+ https://github.com/harfbuzz/uharfbuzz/blob/main/src/uharfbuzz/_harfbuzz.pyx#L1149
+ """
internedTables = {}
self._doneWriting(internedTables)
tables = []
+ obj_list = []
+ done = {}
+ objidx = 0
+ virtual_edges = []
+ self._gatherGraphForHarfbuzz(tables, obj_list, done, objidx, virtual_edges)
+ # Gather all data in two passes: the absolute positions of all
+ # subtable are needed before the actual data can be assembled.
+ pos = 0
+ for table in tables:
+ table.pos = pos
+ pos = pos + table.getDataLength()
+
+ data = []
+ for table in tables:
+ tableData = table.getDataForHarfbuzz()
+ data.append(tableData)
+
+ return hb.repack(data, obj_list)
+
+ def getAllData(self, remove_duplicate=True):
+ """Assemble all data, including all subtables."""
+ if remove_duplicate:
+ internedTables = {}
+ self._doneWriting(internedTables)
+ tables = []
extTables = []
done = {}
self._gatherTables(tables, extTables, done)
diff --git a/Lib/fontTools/ttLib/ttFont.py b/Lib/fontTools/ttLib/ttFont.py
index 3929e2f3..d7f7ef83 100644
--- a/Lib/fontTools/ttLib/ttFont.py
+++ b/Lib/fontTools/ttLib/ttFont.py
@@ -1,4 +1,6 @@
+from fontTools.config import Config
from fontTools.misc import xmlWriter
+from fontTools.misc.configTools import AbstractConfig
from fontTools.misc.textTools import Tag, byteord, tostr
from fontTools.misc.loggingTools import deprecateArgument
from fontTools.ttLib import TTLibError
@@ -49,7 +51,7 @@ class TTFont(object):
>> tt2.importXML("afont.ttx")
>> tt2['maxp'].numGlyphs
242
-
+
The TTFont object may be used as a context manager; this will cause the file
reader to be closed after the context ``with`` block is exited::
@@ -89,7 +91,7 @@ class TTFont(object):
sfntVersion="\000\001\000\000", flavor=None, checkChecksums=0,
verbose=None, recalcBBoxes=True, allowVID=NotImplemented, ignoreDecompileErrors=False,
recalcTimestamp=True, fontNumber=-1, lazy=None, quiet=None,
- _tableCache=None):
+ _tableCache=None, cfg={}):
for name in ("verbose", "quiet"):
val = locals().get(name)
if val is not None:
@@ -101,6 +103,7 @@ class TTFont(object):
self.recalcTimestamp = recalcTimestamp
self.tables = {}
self.reader = None
+ self.cfg = cfg.copy() if isinstance(cfg, AbstractConfig) else Config(cfg)
self.ignoreDecompileErrors = ignoreDecompileErrors
if not file:
@@ -698,22 +701,27 @@ class TTFont(object):
return glyphs
def getBestCmap(self, cmapPreferences=((3, 10), (0, 6), (0, 4), (3, 1), (0, 3), (0, 2), (0, 1), (0, 0))):
- """Return the 'best' unicode cmap dictionary available in the font,
- or None, if no unicode cmap subtable is available.
+ """Returns the 'best' Unicode cmap dictionary available in the font
+ or ``None``, if no Unicode cmap subtable is available.
By default it will search for the following (platformID, platEncID)
- pairs::
-
- (3, 10),
- (0, 6),
- (0, 4),
- (3, 1),
- (0, 3),
- (0, 2),
- (0, 1),
- (0, 0)
-
- This can be customized via the ``cmapPreferences`` argument.
+ pairs in order::
+
+ (3, 10), # Windows Unicode full repertoire
+ (0, 6), # Unicode full repertoire (format 13 subtable)
+ (0, 4), # Unicode 2.0 full repertoire
+ (3, 1), # Windows Unicode BMP
+ (0, 3), # Unicode 2.0 BMP
+ (0, 2), # Unicode ISO/IEC 10646
+ (0, 1), # Unicode 1.1
+ (0, 0) # Unicode 1.0
+
+ This particular order matches what HarfBuzz uses to choose what
+ subtable to use by default. This order prefers the largest-repertoire
+ subtable, and among those, prefers the Windows-platform over the
+ Unicode-platform as the former has wider support.
+
+ This order can be customized via the ``cmapPreferences`` argument.
"""
return self["cmap"].getBestCmap(cmapPreferences=cmapPreferences)
diff --git a/Lib/fontTools/ufoLib/glifLib.py b/Lib/fontTools/ufoLib/glifLib.py
index 44622a14..89c9176a 100755
--- a/Lib/fontTools/ufoLib/glifLib.py
+++ b/Lib/fontTools/ufoLib/glifLib.py
@@ -95,11 +95,11 @@ class Glyph:
self.glyphName = glyphName
self.glyphSet = glyphSet
- def draw(self, pen):
+ def draw(self, pen, outputImpliedClosingLine=False):
"""
Draw this glyph onto a *FontTools* Pen.
"""
- pointPen = PointToSegmentPen(pen)
+ pointPen = PointToSegmentPen(pen, outputImpliedClosingLine=outputImpliedClosingLine)
self.drawPoints(pointPen)
def drawPoints(self, pointPen):
diff --git a/Lib/fontTools/varLib/__init__.py b/Lib/fontTools/varLib/__init__.py
index 15c2e700..4029a107 100644
--- a/Lib/fontTools/varLib/__init__.py
+++ b/Lib/fontTools/varLib/__init__.py
@@ -18,6 +18,7 @@ Then you can make a variable-font this way:
API *will* change in near future.
"""
+from typing import List
from fontTools.misc.vector import Vector
from fontTools.misc.roundTools import noRound, otRound
from fontTools.misc.textTools import Tag, tostr
@@ -33,7 +34,9 @@ from fontTools.varLib.merger import VariationMerger
from fontTools.varLib.mvar import MVAR_ENTRIES
from fontTools.varLib.iup import iup_delta_optimize
from fontTools.varLib.featureVars import addFeatureVariations
-from fontTools.designspaceLib import DesignSpaceDocument
+from fontTools.designspaceLib import DesignSpaceDocument, InstanceDescriptor
+from fontTools.designspaceLib.split import splitInterpolable, splitVariableFonts
+from fontTools.varLib.stat import buildVFStatTable
from functools import partial
from collections import OrderedDict, namedtuple
import os.path
@@ -53,7 +56,7 @@ FEAVAR_FEATURETAG_LIB_KEY = "com.github.fonttools.varLib.featureVarsFeatureTag"
# Creation routines
#
-def _add_fvar(font, axes, instances):
+def _add_fvar(font, axes, instances: List[InstanceDescriptor]):
"""
Add 'fvar' table to font.
@@ -81,7 +84,8 @@ def _add_fvar(font, axes, instances):
fvar.axes.append(axis)
for instance in instances:
- coordinates = instance.location
+ # Filter out discrete axis locations
+ coordinates = {name: value for name, value in instance.location.items() if name in axes}
if "en" not in instance.localisedStyleName:
if not instance.styleName:
@@ -198,11 +202,10 @@ def _add_avar(font, axes):
return avar
-def _add_stat(font, axes):
- # for now we just get the axis tags and nameIDs from the fvar,
- # so we can reuse the same nameIDs which were defined in there.
- # TODO make use of 'axes' once it adds style attributes info:
- # https://github.com/LettError/designSpaceDocument/issues/8
+def _add_stat(font):
+ # Note: this function only gets called by old code that calls `build()`
+ # directly. Newer code that wants to benefit from STAT data from the
+ # designspace should call `build_many()`
if "STAT" in font:
return
@@ -759,7 +762,8 @@ def load_designspace(designspace):
# Check all master and instance locations are valid and fill in defaults
for obj in masters+instances:
obj_name = obj.name or obj.styleName or ''
- loc = obj.location
+ loc = obj.getFullDesignLocation(ds)
+ obj.designLocation = loc
if loc is None:
raise VarLibValidationError(
f"Source or instance '{obj_name}' has no location."
@@ -770,22 +774,18 @@ def load_designspace(designspace):
f"Location axis '{axis_name}' unknown for '{obj_name}'."
)
for axis_name,axis in axes.items():
- if axis_name not in loc:
- # NOTE: `axis.default` is always user-space, but `obj.location` always design-space.
- loc[axis_name] = axis.map_forward(axis.default)
- else:
- v = axis.map_backward(loc[axis_name])
- if not (axis.minimum <= v <= axis.maximum):
- raise VarLibValidationError(
- f"Source or instance '{obj_name}' has out-of-range location "
- f"for axis '{axis_name}': is mapped to {v} but must be in "
- f"mapped range [{axis.minimum}..{axis.maximum}] (NOTE: all "
- "values are in user-space)."
- )
+ v = axis.map_backward(loc[axis_name])
+ if not (axis.minimum <= v <= axis.maximum):
+ raise VarLibValidationError(
+ f"Source or instance '{obj_name}' has out-of-range location "
+ f"for axis '{axis_name}': is mapped to {v} but must be in "
+ f"mapped range [{axis.minimum}..{axis.maximum}] (NOTE: all "
+ "values are in user-space)."
+ )
# Normalize master locations
- internal_master_locs = [o.location for o in masters]
+ internal_master_locs = [o.getFullDesignLocation(ds) for o in masters]
log.info("Internal master locations:\n%s", pformat(internal_master_locs))
# TODO This mapping should ideally be moved closer to logic in _add_fvar/avar
@@ -865,6 +865,38 @@ def set_default_weight_width_slant(font, location):
font["post"].italicAngle = italicAngle
+def build_many(designspace: DesignSpaceDocument, master_finder=lambda s:s, exclude=[], optimize=True, skip_vf=lambda vf_name: False):
+ """
+ Build variable fonts from a designspace file, version 5 which can define
+ several VFs, or version 4 which has implicitly one VF covering the whole doc.
+
+ If master_finder is set, it should be a callable that takes master
+ filename as found in designspace file and map it to master font
+ binary as to be opened (eg. .ttf or .otf).
+
+ skip_vf can be used to skip building some of the variable fonts defined in
+ the input designspace. It's a predicate that takes as argument the name
+ of the variable font and returns `bool`.
+
+ Always returns a Dict[str, TTFont] keyed by VariableFontDescriptor.name
+ """
+ res = {}
+ for _location, subDoc in splitInterpolable(designspace):
+ for name, vfDoc in splitVariableFonts(subDoc):
+ if skip_vf(name):
+ log.debug(f"Skipping variable TTF font: {name}")
+ continue
+ vf = build(
+ vfDoc,
+ master_finder,
+ exclude=list(exclude) + ["STAT"],
+ optimize=optimize
+ )[0]
+ if "STAT" not in exclude:
+ buildVFStatTable(vf, designspace, name)
+ res[name] = vf
+ return res
+
def build(designspace, master_finder=lambda s:s, exclude=[], optimize=True):
"""
Build variation font from a designspace file.
@@ -898,7 +930,7 @@ def build(designspace, master_finder=lambda s:s, exclude=[], optimize=True):
# TODO append masters as named-instances as well; needs .designspace change.
fvar = _add_fvar(vf, ds.axes, ds.instances)
if 'STAT' not in exclude:
- _add_stat(vf, ds.axes)
+ _add_stat(vf)
if 'avar' not in exclude:
_add_avar(vf, ds.axes)
diff --git a/Lib/fontTools/varLib/instancer/__init__.py b/Lib/fontTools/varLib/instancer/__init__.py
index cec802f3..6dad393e 100644
--- a/Lib/fontTools/varLib/instancer/__init__.py
+++ b/Lib/fontTools/varLib/instancer/__init__.py
@@ -1024,8 +1024,11 @@ def instantiateSTAT(varfont, axisLimits):
log.info("Instantiating STAT table")
newAxisValueTables = axisValuesFromAxisLimits(stat, axisLimits)
- stat.AxisValueArray.AxisValue = newAxisValueTables
- stat.AxisValueCount = len(stat.AxisValueArray.AxisValue)
+ stat.AxisValueCount = len(newAxisValueTables)
+ if stat.AxisValueCount:
+ stat.AxisValueArray.AxisValue = newAxisValueTables
+ else:
+ stat.AxisValueArray = None
def axisValuesFromAxisLimits(stat, axisLimits):
diff --git a/Lib/fontTools/varLib/interpolatable.py b/Lib/fontTools/varLib/interpolatable.py
index cff76ece..a9583a18 100644
--- a/Lib/fontTools/varLib/interpolatable.py
+++ b/Lib/fontTools/varLib/interpolatable.py
@@ -7,6 +7,7 @@ $ fonttools varLib.interpolatable font1 font2 ...
"""
from fontTools.pens.basePen import AbstractPen, BasePen
+from fontTools.pens.pointPen import SegmentToPointPen
from fontTools.pens.recordingPen import RecordingPen
from fontTools.pens.statisticsPen import StatisticsPen
from fontTools.pens.momentsPen import OpenContourError
@@ -14,6 +15,14 @@ from collections import OrderedDict
import itertools
import sys
+def _rot_list(l, k):
+ """Rotate list by k items forward. Ie. item at position 0 will be
+ at position k in returned list. Negative k is allowed."""
+ n = len(l)
+ k %= n
+ if not k: return l
+ return l[n-k:] + l[:n-k]
+
class PerContourPen(BasePen):
def __init__(self, Pen, glyphset=None):
@@ -55,6 +64,21 @@ class PerContourOrComponentPen(PerContourPen):
self.value[-1].addComponent(glyphName, transformation)
+class RecordingPointPen(BasePen):
+
+ def __init__(self):
+ self.value = []
+
+ def beginPath(self, identifier = None, **kwargs):
+ pass
+
+ def endPath(self) -> None:
+ pass
+
+ def addPoint(self, pt, segmentType=None):
+ self.value.append((pt, False if segmentType is None else True))
+
+
def _vdiff(v0, v1):
return tuple(b - a for a, b in zip(v0, v1))
@@ -65,6 +89,12 @@ def _vlen(vec):
v += x * x
return v
+def _complex_vlen(vec):
+ v = 0
+ for x in vec:
+ v += abs(x) * abs(x)
+ return v
+
def _matching_cost(G, matching):
return sum(G[i][j] for i, j in enumerate(matching))
@@ -125,6 +155,7 @@ def test(glyphsets, glyphs=None, names=None):
try:
allVectors = []
allNodeTypes = []
+ allContourIsomorphisms = []
for glyphset, name in zip(glyphsets, names):
# print('.', end='')
if glyph_name not in glyphset:
@@ -135,18 +166,24 @@ def test(glyphsets, glyphs=None, names=None):
perContourPen = PerContourOrComponentPen(
RecordingPen, glyphset=glyphset
)
- glyph.draw(perContourPen)
+ try:
+ glyph.draw(perContourPen, outputImpliedClosingLine=True)
+ except TypeError:
+ glyph.draw(perContourPen)
contourPens = perContourPen.value
del perContourPen
contourVectors = []
+ contourIsomorphisms = []
nodeTypes = []
allNodeTypes.append(nodeTypes)
allVectors.append(contourVectors)
+ allContourIsomorphisms.append(contourIsomorphisms)
for ix, contour in enumerate(contourPens):
- nodeTypes.append(
- tuple(instruction[0] for instruction in contour.value)
- )
+
+ nodeVecs = tuple(instruction[0] for instruction in contour.value)
+ nodeTypes.append(nodeVecs)
+
stats = StatisticsPen(glyphset=glyphset)
try:
contour.replay(stats)
@@ -168,6 +205,38 @@ def test(glyphsets, glyphs=None, names=None):
contourVectors.append(vector)
# print(vector)
+ # Check starting point
+ if nodeVecs[0] == 'addComponent':
+ continue
+ assert nodeVecs[0] == 'moveTo'
+ assert nodeVecs[-1] in ('closePath', 'endPath')
+ points = RecordingPointPen()
+ converter = SegmentToPointPen(points, False)
+ contour.replay(converter)
+ # points.value is a list of pt,bool where bool is true if on-curve and false if off-curve;
+ # now check all rotations and mirror-rotations of the contour and build list of isomorphic
+ # possible starting points.
+ bits = 0
+ for pt,b in points.value:
+ bits = (bits << 1) | b
+ n = len(points.value)
+ mask = (1 << n ) - 1
+ isomorphisms = []
+ contourIsomorphisms.append(isomorphisms)
+ for i in range(n):
+ b = ((bits << i) & mask) | ((bits >> (n - i)))
+ if b == bits:
+ isomorphisms.append(_rot_list ([complex(*pt) for pt,bl in points.value], i))
+ # Add mirrored rotations
+ mirrored = list(reversed(points.value))
+ reversed_bits = 0
+ for pt,b in mirrored:
+ reversed_bits = (reversed_bits << 1) | b
+ for i in range(n):
+ b = ((reversed_bits << i) & mask) | ((reversed_bits >> (n - i)))
+ if b == bits:
+ isomorphisms.append(_rot_list ([complex(*pt) for pt,bl in mirrored], i))
+
# Check each master against the next one in the list.
for i, (m0, m1) in enumerate(zip(allNodeTypes[:-1], allNodeTypes[1:])):
if len(m0) != len(m1):
@@ -223,7 +292,9 @@ def test(glyphsets, glyphs=None, names=None):
continue
costs = [[_vlen(_vdiff(v0, v1)) for v1 in m1] for v0 in m0]
matching, matching_cost = min_cost_perfect_bipartite_matching(costs)
- if matching != list(range(len(m0))):
+ identity_matching = list(range(len(m0)))
+ identity_cost = sum(costs[i][i] for i in range(len(m0)))
+ if matching != identity_matching and matching_cost < identity_cost * .95:
add_problem(
glyph_name,
{
@@ -235,23 +306,27 @@ def test(glyphsets, glyphs=None, names=None):
},
)
break
- upem = 2048
- item_cost = round(
- (matching_cost / len(m0) / len(m0[0])) ** 0.5 / upem * 100
- )
- hist.append(item_cost)
- threshold = 7
- if item_cost >= threshold:
- add_problem(
- glyph_name,
- {
- "type": "high_cost",
- "master_1": names[i],
- "master_2": names[i + 1],
- "value_1": item_cost,
- "value_2": threshold,
- },
- )
+
+ for i, (m0, m1) in enumerate(zip(allContourIsomorphisms[:-1], allContourIsomorphisms[1:])):
+ if len(m0) != len(m1):
+ # We already reported this
+ continue
+ if not m0:
+ continue
+ for contour0,contour1 in zip(m0,m1):
+ c0 = contour0[0]
+ costs = [v for v in (_complex_vlen(_vdiff(c0, c1)) for c1 in contour1)]
+ min_cost = min(costs)
+ first_cost = costs[0]
+ if min_cost < first_cost * .95:
+ add_problem(
+ glyph_name,
+ {
+ "type": "wrong_start_point",
+ "master_1": names[i],
+ "master_2": names[i + 1],
+ },
+ )
except ValueError as e:
add_problem(
@@ -351,14 +426,12 @@ def main(args=None):
p["master_2"],
)
)
- if p["type"] == "high_cost":
+ if p["type"] == "wrong_start_point":
print(
- " Interpolation has high cost: cost of %s to %s = %i, threshold %i"
+ " Contour start point differs: %s, %s"
% (
p["master_1"],
p["master_2"],
- p["value_1"],
- p["value_2"],
)
)
if problems:
diff --git a/Lib/fontTools/varLib/merger.py b/Lib/fontTools/varLib/merger.py
index 5a3a4f34..3e5d2a9b 100644
--- a/Lib/fontTools/varLib/merger.py
+++ b/Lib/fontTools/varLib/merger.py
@@ -16,9 +16,8 @@ from fontTools.varLib.varStore import VarStoreInstancer
from functools import reduce
from fontTools.otlLib.builder import buildSinglePos
from fontTools.otlLib.optimize.gpos import (
- compact_pair_pos,
- GPOS_COMPACT_MODE_DEFAULT,
- GPOS_COMPACT_MODE_ENV_KEY,
+ _compression_level_from_env,
+ compact_pair_pos,
)
log = logging.getLogger("fontTools.varLib.merger")
@@ -850,10 +849,14 @@ def merge(merger, self, lst):
# Compact the merged subtables
# This is a good moment to do it because the compaction should create
# smaller subtables, which may prevent overflows from happening.
- mode = os.environ.get(GPOS_COMPACT_MODE_ENV_KEY, GPOS_COMPACT_MODE_DEFAULT)
- if mode and mode != "0":
+ # Keep reading the value from the ENV until ufo2ft switches to the config system
+ level = merger.font.cfg.get(
+ "fontTools.otlLib.optimize.gpos:COMPRESSION_LEVEL",
+ default=_compression_level_from_env(),
+ )
+ if level != 0:
log.info("Compacting GPOS...")
- self.SubTable = compact_pair_pos(merger.font, mode, self.SubTable)
+ self.SubTable = compact_pair_pos(merger.font, level, self.SubTable)
self.SubTableCount = len(self.SubTable)
elif isSinglePos and flattened:
diff --git a/Lib/fontTools/varLib/stat.py b/Lib/fontTools/varLib/stat.py
new file mode 100644
index 00000000..46c9498d
--- /dev/null
+++ b/Lib/fontTools/varLib/stat.py
@@ -0,0 +1,142 @@
+"""Extra methods for DesignSpaceDocument to generate its STAT table data."""
+
+from __future__ import annotations
+
+from typing import Dict, List, Union
+
+import fontTools.otlLib.builder
+from fontTools.designspaceLib import (
+ AxisLabelDescriptor,
+ DesignSpaceDocument,
+ DesignSpaceDocumentError,
+ LocationLabelDescriptor,
+)
+from fontTools.designspaceLib.types import Region, getVFUserRegion, locationInRegion
+from fontTools.ttLib import TTFont
+
+
+def buildVFStatTable(ttFont: TTFont, doc: DesignSpaceDocument, vfName: str) -> None:
+ """Build the STAT table for the variable font identified by its name in
+ the given document.
+
+ Knowing which variable we're building STAT data for is needed to subset
+ the STAT locations to only include what the variable font actually ships.
+
+ .. versionadded:: 5.0
+
+ .. seealso::
+ - :func:`getStatAxes()`
+ - :func:`getStatLocations()`
+ - :func:`fontTools.otlLib.builder.buildStatTable()`
+ """
+ for vf in doc.getVariableFonts():
+ if vf.name == vfName:
+ break
+ else:
+ raise DesignSpaceDocumentError(
+ f"Cannot find the variable font by name {vfName}"
+ )
+
+ region = getVFUserRegion(doc, vf)
+
+ return fontTools.otlLib.builder.buildStatTable(
+ ttFont,
+ getStatAxes(doc, region),
+ getStatLocations(doc, region),
+ doc.elidedFallbackName if doc.elidedFallbackName is not None else 2,
+ )
+
+
+def getStatAxes(doc: DesignSpaceDocument, userRegion: Region) -> List[Dict]:
+ """Return a list of axis dicts suitable for use as the ``axes``
+ argument to :func:`fontTools.otlLib.builder.buildStatTable()`.
+
+ .. versionadded:: 5.0
+ """
+ # First, get the axis labels with explicit ordering
+ # then append the others in the order they appear.
+ maxOrdering = max(
+ (axis.axisOrdering for axis in doc.axes if axis.axisOrdering is not None),
+ default=-1,
+ )
+ axisOrderings = []
+ for axis in doc.axes:
+ if axis.axisOrdering is not None:
+ axisOrderings.append(axis.axisOrdering)
+ else:
+ maxOrdering += 1
+ axisOrderings.append(maxOrdering)
+ return [
+ dict(
+ tag=axis.tag,
+ name={"en": axis.name, **axis.labelNames},
+ ordering=ordering,
+ values=[
+ _axisLabelToStatLocation(label)
+ for label in axis.axisLabels
+ if locationInRegion({axis.name: label.userValue}, userRegion)
+ ],
+ )
+ for axis, ordering in zip(doc.axes, axisOrderings)
+ ]
+
+
+def getStatLocations(doc: DesignSpaceDocument, userRegion: Region) -> List[Dict]:
+ """Return a list of location dicts suitable for use as the ``locations``
+ argument to :func:`fontTools.otlLib.builder.buildStatTable()`.
+
+ .. versionadded:: 5.0
+ """
+ axesByName = {axis.name: axis for axis in doc.axes}
+ return [
+ dict(
+ name={"en": label.name, **label.labelNames},
+ # Location in the designspace is keyed by axis name
+ # Location in buildStatTable by axis tag
+ location={
+ axesByName[name].tag: value
+ for name, value in label.getFullUserLocation(doc).items()
+ },
+ flags=_labelToFlags(label),
+ )
+ for label in doc.locationLabels
+ if locationInRegion(label.getFullUserLocation(doc), userRegion)
+ ]
+
+
+def _labelToFlags(label: Union[AxisLabelDescriptor, LocationLabelDescriptor]) -> int:
+ flags = 0
+ if label.olderSibling:
+ flags |= 1
+ if label.elidable:
+ flags |= 2
+ return flags
+
+
+def _axisLabelToStatLocation(
+ label: AxisLabelDescriptor,
+) -> Dict:
+ label_format = label.getFormat()
+ name = {"en": label.name, **label.labelNames}
+ flags = _labelToFlags(label)
+ if label_format == 1:
+ return dict(name=name, value=label.userValue, flags=flags)
+ if label_format == 3:
+ return dict(
+ name=name,
+ value=label.userValue,
+ linkedValue=label.linkedUserValue,
+ flags=flags,
+ )
+ if label_format == 2:
+ res = dict(
+ name=name,
+ nominalValue=label.userValue,
+ flags=flags,
+ )
+ if label.userMinimum is not None:
+ res["rangeMinValue"] = label.userMinimum
+ if label.userMaximum is not None:
+ res["rangeMaxValue"] = label.userMaximum
+ return res
+ raise NotImplementedError("Unknown STAT label format")
diff --git a/METADATA b/METADATA
index 35f87b65..71fb8a6e 100644
--- a/METADATA
+++ b/METADATA
@@ -10,14 +10,14 @@ third_party {
}
url {
type: ARCHIVE
- value: "https://github.com/fonttools/fonttools/archive/4.31.2.zip"
+ value: "https://github.com/fonttools/fonttools/archive/4.33.3.zip"
}
- version: "4.31.2"
+ version: "4.33.3"
license_type: BY_EXCEPTION_ONLY
license_note: "contains OFL fonts"
last_upgrade_date {
year: 2022
- month: 3
- day: 25
+ month: 5
+ day: 12
}
}
diff --git a/NEWS.rst b/NEWS.rst
index 5971d3cc..d500dd95 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -1,3 +1,80 @@
+4.33.3 (released 2022-04-26)
+----------------------------
+
+- [designspaceLib] Fixed typo in ``deepcopyExceptFonts`` method, preventing font
+ references to be transferred (#2600). Fixed another typo in the name of ``Range``
+ dataclass's ``__post_init__`` magic method (#2597).
+
+4.33.2 (released 2022-04-22)
+----------------------------
+
+- [otBase] Make logging less verbose when harfbuzz fails to serialize. Do not exit
+ at the first failure but continue attempting to fix offset overflow error using
+ the pure-python serializer even when the ``USE_HARFBUZZ_REPACKER`` option was
+ explicitly set to ``True``. This is normal with fonts with relatively large
+ tables, at least until hb.repack implements proper table splitting.
+
+4.33.1 (released 2022-04-22)
+----------------------------
+
+- [otlLib] Put back the ``FONTTOOLS_GPOS_COMPACT_MODE`` environment variable to fix
+ regression in ufo2ft (and thus fontmake) introduced with v4.33.0 (#2592, #2593).
+ This is deprecated and will be removed one ufo2ft gets updated to use the new
+ config setup.
+
+4.33.0 (released 2022-04-21)
+----------------------------
+
+- [OS/2 / merge] Automatically recalculate ``OS/2.xAvgCharWidth`` after merging
+ fonts with ``fontTools.merge`` (#2591, #2538).
+- [misc/config] Added ``fontTools.misc.configTools`` module, a generic configuration
+ system (#2416, #2439).
+ Added ``fontTools.config`` module, a fontTools-specific configuration
+ system using ``configTools`` above.
+ Attached a ``Config`` object to ``TTFont``.
+- [otlLib] Replaced environment variable for GPOS compression level with an
+ equivalent option using the new config system.
+- [designspaceLib] Incremented format version to 5.0 (#2436).
+ Added discrete axes, variable fonts, STAT information, either design- or
+ user-space location on instances.
+ Added ``fontTools.designspaceLib.split`` module to split a designspace
+ into sub-spaces that interpolate and that represent the variable fonts
+ listed in the document.
+ Made instance names optional and allow computing them from STAT data instead.
+ Added ``fontTools.designspaceLib.statNames`` module.
+ Allow instances to have the same location as a previously defined STAT label.
+ Deprecated some attributes:
+ ``SourceDescriptor``: ``copyLib``, ``copyInfo``, ``copyGroups``, ``copyFeatures``.
+ ``InstanceDescriptor``: ``kerning``, ``info``; ``glyphs``: use rules or sparse
+ sources.
+ For both, ``location``: use the more explicit designLocation.
+ Note: all are soft deprecations and existing code should keep working.
+ Updated documentation for Python methods and the XML format.
+- [varLib] Added ``build_many`` to build several variable fonts from a single
+ designspace document (#2436).
+ Added ``fontTools.varLib.stat`` module to build STAT tables from a designspace
+ document.
+- [otBase] Try to use the Harfbuzz Repacker for packing GSUB/GPOS tables when
+ ``uharfbuzz`` python bindings are available (#2552). Disable it by setting the
+ "fontTools.ttLib.tables.otBase:USE_HARFBUZZ_REPACKER" config option to ``False``.
+ If the option is set explicitly to ``True`` but ``uharfbuzz`` can't be imported
+ or fails to serialize for any reasons, an error will be raised (ImportError or
+ uharfbuzz errors).
+- [CFF/T2] Ensure that ``pen.closePath()`` gets called for CFF2 charstrings (#2577).
+ Handle implicit CFF2 closePath within ``T2OutlineExtractor`` (#2580).
+
+4.32.0 (released 2022-04-08)
+----------------------------
+
+- [otlLib] Disable GPOS7 optimization to work around bug in Apple CoreText.
+ Always force Chaining GPOS8 for now (#2540).
+- [glifLib] Added ``outputImpliedClosingLine=False`` parameter to ``Glyph.draw()``,
+ to control behaviour of ``PointToSegmentPen`` (6b4e2e7).
+- [varLib.interpolatable] Check for wrong contour starting point (#2571).
+- [cffLib] Remove leftover ``GlobalState`` class and fix calls to ``TopDictIndex()``
+ (#2569, #2570).
+- [instancer] Clear ``AxisValueArray`` if it is empty after instantiating (#2563).
+
4.31.2 (released 2022-03-22)
----------------------------
diff --git a/Tests/config_test.py b/Tests/config_test.py
new file mode 100644
index 00000000..58163913
--- /dev/null
+++ b/Tests/config_test.py
@@ -0,0 +1,135 @@
+from fontTools.misc.configTools import AbstractConfig, Options
+import pytest
+from fontTools.config import (
+ OPTIONS,
+ Config,
+ ConfigUnknownOptionError,
+ ConfigValueParsingError,
+ ConfigValueValidationError,
+)
+from fontTools.ttLib import TTFont
+
+
+def test_can_register_option():
+ MY_OPTION = Config.register_option(
+ name="tests:MY_OPTION",
+ help="Test option, value should be True or False, default = True",
+ default=True,
+ parse=lambda v: v in ("True", "true", 1, True),
+ validate=lambda v: v == True or v == False,
+ )
+
+ assert MY_OPTION.name == "tests:MY_OPTION"
+ assert (
+ MY_OPTION.help == "Test option, value should be True or False, default = True"
+ )
+ assert MY_OPTION.default == True
+ assert MY_OPTION.parse("true") == True
+ assert MY_OPTION.validate("hello") == False
+
+ ttFont = TTFont(cfg={"tests:MY_OPTION": True})
+ assert True == ttFont.cfg.get("tests:MY_OPTION")
+ assert True == ttFont.cfg.get(MY_OPTION)
+
+
+# to parametrize tests of Config mapping interface accepting either a str or Option
+@pytest.fixture(
+ params=[
+ pytest.param("fontTools.otlLib.optimize.gpos:COMPRESSION_LEVEL", id="str"),
+ pytest.param(
+ OPTIONS["fontTools.otlLib.optimize.gpos:COMPRESSION_LEVEL"], id="Option"
+ ),
+ ]
+)
+def COMPRESSION_LEVEL(request):
+ return request.param
+
+
+def test_ttfont_has_config(COMPRESSION_LEVEL):
+ ttFont = TTFont(cfg={COMPRESSION_LEVEL: 8})
+ assert 8 == ttFont.cfg.get(COMPRESSION_LEVEL)
+
+
+def test_ttfont_can_take_superset_of_fonttools_config(COMPRESSION_LEVEL):
+ # Create MyConfig with all options from fontTools.config plus some
+ my_options = Options(Config.options)
+ MY_OPTION = my_options.register("custom:my_option", "help", "default", str, any)
+
+ class MyConfig(AbstractConfig):
+ options = my_options
+
+ ttFont = TTFont(cfg=MyConfig({"custom:my_option": "my_value"}))
+ assert 0 == ttFont.cfg.get(COMPRESSION_LEVEL)
+ assert "my_value" == ttFont.cfg.get(MY_OPTION)
+
+ # but the default Config doens't know about MY_OPTION
+ with pytest.raises(ConfigUnknownOptionError):
+ TTFont(cfg={MY_OPTION: "my_value"})
+
+
+def test_no_config_returns_default_values(COMPRESSION_LEVEL):
+ ttFont = TTFont()
+ assert 0 == ttFont.cfg.get(COMPRESSION_LEVEL)
+ assert 3 == ttFont.cfg.get(COMPRESSION_LEVEL, 3)
+
+
+def test_can_set_config(COMPRESSION_LEVEL):
+ ttFont = TTFont()
+ ttFont.cfg.set(COMPRESSION_LEVEL, 5)
+ assert 5 == ttFont.cfg.get(COMPRESSION_LEVEL)
+ ttFont.cfg.set(COMPRESSION_LEVEL, 6)
+ assert 6 == ttFont.cfg.get(COMPRESSION_LEVEL)
+
+
+def test_different_ttfonts_have_different_configs(COMPRESSION_LEVEL):
+ cfg = Config({COMPRESSION_LEVEL: 5})
+ ttFont1 = TTFont(cfg=cfg)
+ ttFont2 = TTFont(cfg=cfg)
+ ttFont2.cfg.set(COMPRESSION_LEVEL, 6)
+ assert 5 == ttFont1.cfg.get(COMPRESSION_LEVEL)
+ assert 6 == ttFont2.cfg.get(COMPRESSION_LEVEL)
+
+
+def test_cannot_set_inexistent_key():
+ with pytest.raises(ConfigUnknownOptionError):
+ TTFont(cfg={"notALib.notAModule.inexistent": 4})
+
+
+def test_value_not_parsed_by_default(COMPRESSION_LEVEL):
+ # Note: value given as a string
+ with pytest.raises(ConfigValueValidationError):
+ TTFont(cfg={COMPRESSION_LEVEL: "8"})
+
+
+def test_value_gets_parsed_if_asked(COMPRESSION_LEVEL):
+ # Note: value given as a string
+ ttFont = TTFont(cfg=Config({COMPRESSION_LEVEL: "8"}, parse_values=True))
+ assert 8 == ttFont.cfg.get(COMPRESSION_LEVEL)
+
+
+def test_value_parsing_can_error(COMPRESSION_LEVEL):
+ with pytest.raises(ConfigValueParsingError):
+ TTFont(
+ cfg=Config(
+ {COMPRESSION_LEVEL: "not an int"},
+ parse_values=True,
+ )
+ )
+
+
+def test_value_gets_validated(COMPRESSION_LEVEL):
+ # Note: 12 is not a valid value for GPOS compression level (must be in 0-9)
+ with pytest.raises(ConfigValueValidationError):
+ TTFont(cfg={COMPRESSION_LEVEL: 12})
+
+
+def test_implements_mutable_mapping(COMPRESSION_LEVEL):
+ cfg = Config()
+ cfg[COMPRESSION_LEVEL] = 2
+ assert 2 == cfg[COMPRESSION_LEVEL]
+ assert list(iter(cfg))
+ assert 1 == len(cfg)
+ del cfg[COMPRESSION_LEVEL]
+ assert 0 == cfg[COMPRESSION_LEVEL]
+ assert not list(iter(cfg))
+ assert 0 == len(cfg)
diff --git a/Tests/designspaceLib/__init__.py b/Tests/designspaceLib/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/Tests/designspaceLib/__init__.py
diff --git a/Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_Italics_Wght.designspace b/Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_Italics_Wght.designspace
new file mode 100644
index 00000000..aa8e4f9c
--- /dev/null
+++ b/Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_Italics_Wght.designspace
@@ -0,0 +1,114 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="4.1">
+ <axes>
+ <axis tag="wght" name="Weight" minimum="100" maximum="900" default="400">
+ <map input="100" output="22"/>
+ <map input="200" output="38"/>
+ <map input="300" output="57"/>
+ <map input="400" output="84"/>
+ <map input="500" output="98"/>
+ <map input="600" output="115"/>
+ <map input="700" output="133"/>
+ <map input="800" output="158"/>
+ <map input="900" output="185"/>
+ </axis>
+ </axes>
+ <rules processing="last">
+ <rule name="BRACKET.CYR">
+ <sub name="ghe.loclSRB" with="ghe.ital.loclSRB"/>
+ <sub name="ghe.loclMKD" with="ghe.ital.loclMKD"/>
+ <sub name="de.loclMKDSRB" with="de.ital.loclMKDSRB"/>
+ <sub name="pe.loclMKDSRB" with="pe.ital.loclMKDSRB"/>
+ <sub name="te.loclMKDSRB" with="te.ital.loclMKDSRB"/>
+ <sub name="gje.loclMKD" with="gje.ital.loclMKD"/>
+ <sub name="sha.loclMKDSRB" with="sha.ital.loclMKDSRB"/>
+ </rule>
+ </rules>
+ <sources>
+ <source filename="../AktivGrotesk_HairIt.ufo" name="Aktiv Grotesk Hair Italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_It.ufo" name="Aktiv Grotesk Italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_It.ufo" name="Aktiv Grotesk Italic {126,100,1}" layer="{126,100,1}">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_BlkIt.ufo" name="Aktiv Grotesk Black Italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance name="Aktiv Grotesk Hair Italic" familyname="Aktiv Grotesk" stylename="Hair Italic" filename="../instances/AktivGrotesk_HairIt.ufo" postscriptfontname="AktivGrotesk-HairItalic" stylemapfamilyname="Aktiv Grotesk Hair" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Thin Italic" familyname="Aktiv Grotesk" stylename="Thin Italic" filename="../instances/AktivGrotesk_ThIt.ufo" postscriptfontname="AktivGrotesk-ThinItalic" stylemapfamilyname="Aktiv Grotesk Thin" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Light Italic" familyname="Aktiv Grotesk" stylename="Light Italic" filename="../instances/AktivGrotesk_LtIt.ufo" postscriptfontname="AktivGrotesk-LightItalic" stylemapfamilyname="Aktiv Grotesk Light" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Italic" familyname="Aktiv Grotesk" stylename="Italic" filename="../instances/AktivGrotesk_It.ufo" postscriptfontname="AktivGrotesk-Italic" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Medium Italic" familyname="Aktiv Grotesk" stylename="Medium Italic" filename="../instances/AktivGrotesk_MdIt.ufo" postscriptfontname="AktivGrotesk-MediumItalic" stylemapfamilyname="Aktiv Grotesk Medium" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk SemiBold Italic" familyname="Aktiv Grotesk" stylename="SemiBold Italic" filename="../../build/instances/AktivGrotesk_SBdIt.ufo" postscriptfontname="AktivGrotesk-SemiBoldItalic" stylemapfamilyname="Aktiv Grotesk SemiBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Bold Italic" familyname="Aktiv Grotesk" stylename="Bold Italic" filename="../instances/AktivGrotesk_BdIt.ufo" postscriptfontname="AktivGrotesk-BoldItalic" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="bold italic">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk XBold Italic" familyname="Aktiv Grotesk" stylename="XBold Italic" filename="../instances/AktivGrotesk_XBdIt.ufo" postscriptfontname="AktivGrotesk-XBoldItalic" stylemapfamilyname="Aktiv Grotesk XBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Black Italic" familyname="Aktiv Grotesk" stylename="Black Italic" filename="../instances/AktivGrotesk_BlkIt.ufo" postscriptfontname="AktivGrotesk-BlackItalic" stylemapfamilyname="Aktiv Grotesk Black" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_Italics_WghtWdth.designspace b/Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_Italics_WghtWdth.designspace
new file mode 100644
index 00000000..2e22c019
--- /dev/null
+++ b/Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_Italics_WghtWdth.designspace
@@ -0,0 +1,316 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="4.1">
+ <axes>
+ <axis tag="wght" name="Weight" minimum="100" maximum="900" default="400">
+ <map input="100" output="22"/>
+ <map input="200" output="38"/>
+ <map input="300" output="57"/>
+ <map input="400" output="84"/>
+ <map input="500" output="98"/>
+ <map input="600" output="115"/>
+ <map input="700" output="133"/>
+ <map input="800" output="158"/>
+ <map input="900" output="185"/>
+ </axis>
+ <axis tag="wdth" name="Width" minimum="75" maximum="125" default="100"/>
+ </axes>
+ <rules processing="last">
+ <rule name="BRACKET.CYR">
+ <sub name="ghe.loclSRB" with="ghe.ital.loclSRB"/>
+ <sub name="ghe.loclMKD" with="ghe.ital.loclMKD"/>
+ <sub name="de.loclMKDSRB" with="de.ital.loclMKDSRB"/>
+ <sub name="pe.loclMKDSRB" with="pe.ital.loclMKDSRB"/>
+ <sub name="te.loclMKDSRB" with="te.ital.loclMKDSRB"/>
+ <sub name="gje.loclMKD" with="gje.ital.loclMKD"/>
+ <sub name="sha.loclMKDSRB" with="sha.ital.loclMKDSRB"/>
+ </rule>
+ <rule name="BRACKET.116.185">
+ <conditionset>
+ <condition name="Weight" minimum="116" maximum="185"/>
+ <condition name="Width" minimum="75" maximum="97.5"/>
+ </conditionset>
+ <sub name="cent" with="cent.BRACKET.130"/>
+ <sub name="dollar" with="dollar.BRACKET.130"/>
+ </rule>
+ </rules>
+ <sources>
+ <source filename="../AktivGroteskCd_HairIt.ufo" name="Aktiv Grotesk Cd Hair Italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_HairIt.ufo" name="Aktiv Grotesk Hair Italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_HairIt.ufo" name="Aktiv Grotesk Ex Hair Italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskCd_It.ufo" name="Aktiv Grotesk Cd Italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_It.ufo" name="Aktiv Grotesk Italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_It.ufo" name="Aktiv Grotesk Italic {126,100,1}" layer="{126,100,1}">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_It.ufo" name="Aktiv Grotesk Ex Italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskCd_BlkIt.ufo" name="Aktiv Grotesk Cd Black Italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_BlkIt.ufo" name="Aktiv Grotesk Black Italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_BlkIt.ufo" name="Aktiv Grotesk Ex Black Italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance name="Aktiv Grotesk Cd Hair Italic" familyname="Aktiv Grotesk" stylename="Cd Hair Italic" filename="../instances/AktivGroteskCd_HairIt.ufo" postscriptfontname="AktivGrotesk-CdHairItalic" stylemapfamilyname="Aktiv Grotesk Cd Hair" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Hair Italic" familyname="Aktiv Grotesk" stylename="Hair Italic" filename="../instances/AktivGrotesk_HairIt.ufo" postscriptfontname="AktivGrotesk-HairItalic" stylemapfamilyname="Aktiv Grotesk Hair" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Hair Italic" familyname="Aktiv Grotesk" stylename="Ex Hair Italic" filename="../instances/AktivGroteskEx_HairIt.ufo" postscriptfontname="AktivGrotesk-ExHairItalic" stylemapfamilyname="Aktiv Grotesk Ex Hair" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Thin Italic" familyname="Aktiv Grotesk" stylename="Cd Thin Italic" filename="../instances/AktivGroteskCd_ThIt.ufo" postscriptfontname="AktivGrotesk-CdThinItalic" stylemapfamilyname="Aktiv Grotesk Cd Thin" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Thin Italic" familyname="Aktiv Grotesk" stylename="Thin Italic" filename="../instances/AktivGrotesk_ThIt.ufo" postscriptfontname="AktivGrotesk-ThinItalic" stylemapfamilyname="Aktiv Grotesk Thin" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Thin Italic" familyname="Aktiv Grotesk" stylename="Ex Thin Italic" filename="../instances/AktivGroteskEx_ThIt.ufo" postscriptfontname="AktivGrotesk-ExThinItalic" stylemapfamilyname="Aktiv Grotesk Ex Thin" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Light Italic" familyname="Aktiv Grotesk" stylename="Cd Light Italic" filename="../instances/AktivGroteskCd_LtIt.ufo" postscriptfontname="AktivGrotesk-CdLightItalic" stylemapfamilyname="Aktiv Grotesk Cd Light" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Light Italic" familyname="Aktiv Grotesk" stylename="Light Italic" filename="../instances/AktivGrotesk_LtIt.ufo" postscriptfontname="AktivGrotesk-LightItalic" stylemapfamilyname="Aktiv Grotesk Light" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Light Italic" familyname="Aktiv Grotesk" stylename="Ex Light Italic" filename="../instances/AktivGroteskEx_LtIt.ufo" postscriptfontname="AktivGrotesk-ExLightItalic" stylemapfamilyname="Aktiv Grotesk Ex Light" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Italic" familyname="Aktiv Grotesk" stylename="Cd Italic" filename="../instances/AktivGroteskCd_It.ufo" postscriptfontname="AktivGrotesk-CdItalic" stylemapfamilyname="Aktiv Grotesk Cd" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Italic" familyname="Aktiv Grotesk" stylename="Italic" filename="../instances/AktivGrotesk_It.ufo" postscriptfontname="AktivGrotesk-Italic" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Italic" familyname="Aktiv Grotesk" stylename="Ex Italic" filename="../instances/AktivGroteskEx_It.ufo" postscriptfontname="AktivGrotesk-ExItalic" stylemapfamilyname="Aktiv Grotesk Ex" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Medium Italic" familyname="Aktiv Grotesk" stylename="Cd Medium Italic" filename="../instances/AktivGroteskCd_MdIt.ufo" postscriptfontname="AktivGrotesk-CdMediumItalic" stylemapfamilyname="Aktiv Grotesk Cd Medium" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Medium Italic" familyname="Aktiv Grotesk" stylename="Medium Italic" filename="../instances/AktivGrotesk_MdIt.ufo" postscriptfontname="AktivGrotesk-MediumItalic" stylemapfamilyname="Aktiv Grotesk Medium" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Medium Italic" familyname="Aktiv Grotesk" stylename="Ex Medium Italic" filename="../instances/AktivGroteskEx_MdIt.ufo" postscriptfontname="AktivGrotesk-ExMediumItalic" stylemapfamilyname="Aktiv Grotesk Ex Medium" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd SemiBold Italic" familyname="Aktiv Grotesk" stylename="Cd SemiBold Italic" filename="../../build/instances/AktivGroteskCd_SBdIt.ufo" postscriptfontname="AktivGrotesk-CdSemiBoldItalic" stylemapfamilyname="Aktiv Grotesk Cd SemiBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk SemiBold Italic" familyname="Aktiv Grotesk" stylename="SemiBold Italic" filename="../../build/instances/AktivGrotesk_SBdIt.ufo" postscriptfontname="AktivGrotesk-SemiBoldItalic" stylemapfamilyname="Aktiv Grotesk SemiBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex SemiBold Italic" familyname="Aktiv Grotesk" stylename="Ex SemiBold Italic" filename="../../build/instances/AktivGroteskEx_SBdIt.ufo" postscriptfontname="AktivGrotesk-ExSemiBoldItalic" stylemapfamilyname="Aktiv Grotesk Ex SemiBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Bold Italic" familyname="Aktiv Grotesk" stylename="Cd Bold Italic" filename="../instances/AktivGroteskCd_BdIt.ufo" postscriptfontname="AktivGrotesk-CdBoldItalic" stylemapfamilyname="Aktiv Grotesk Cd" stylemapstylename="bold italic">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Bold Italic" familyname="Aktiv Grotesk" stylename="Bold Italic" filename="../instances/AktivGrotesk_BdIt.ufo" postscriptfontname="AktivGrotesk-BoldItalic" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="bold italic">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Bold Italic" familyname="Aktiv Grotesk" stylename="Ex Bold Italic" filename="../instances/AktivGroteskEx_BdIt.ufo" postscriptfontname="AktivGrotesk-ExBoldItalic" stylemapfamilyname="Aktiv Grotesk Ex" stylemapstylename="bold italic">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd XBold Italic" familyname="Aktiv Grotesk" stylename="Cd XBold Italic" filename="../instances/AktivGroteskCd_XBdIt.ufo" postscriptfontname="AktivGrotesk-CdXBoldItalic" stylemapfamilyname="Aktiv Grotesk Cd XBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk XBold Italic" familyname="Aktiv Grotesk" stylename="XBold Italic" filename="../instances/AktivGrotesk_XBdIt.ufo" postscriptfontname="AktivGrotesk-XBoldItalic" stylemapfamilyname="Aktiv Grotesk XBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex XBold Italic" familyname="Aktiv Grotesk" stylename="Ex XBold Italic" filename="../instances/AktivGroteskEx_XBdIt.ufo" postscriptfontname="AktivGrotesk-ExXBoldItalic" stylemapfamilyname="Aktiv Grotesk Ex XBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Black Italic" familyname="Aktiv Grotesk" stylename="Cd Black Italic" filename="../instances/AktivGroteskCd_BlkIt.ufo" postscriptfontname="AktivGrotesk-CdBlackItalic" stylemapfamilyname="Aktiv Grotesk Cd Black" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Black Italic" familyname="Aktiv Grotesk" stylename="Black Italic" filename="../instances/AktivGrotesk_BlkIt.ufo" postscriptfontname="AktivGrotesk-BlackItalic" stylemapfamilyname="Aktiv Grotesk Black" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Black Italic" familyname="Aktiv Grotesk" stylename="Ex Black Italic" filename="../instances/AktivGroteskEx_BlkIt.ufo" postscriptfontname="AktivGrotesk-ExBlackItalic" stylemapfamilyname="Aktiv Grotesk Ex Black" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_Wght.designspace b/Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_Wght.designspace
new file mode 100644
index 00000000..2ae35f77
--- /dev/null
+++ b/Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_Wght.designspace
@@ -0,0 +1,103 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="4.1">
+ <axes>
+ <axis tag="wght" name="Weight" minimum="100" maximum="900" default="400">
+ <map input="100" output="22"/>
+ <map input="200" output="38"/>
+ <map input="300" output="57"/>
+ <map input="400" output="84"/>
+ <map input="500" output="98"/>
+ <map input="600" output="115"/>
+ <map input="700" output="133"/>
+ <map input="800" output="158"/>
+ <map input="900" output="185"/>
+ </axis>
+ </axes>
+ <sources>
+ <source filename="../AktivGrotesk_Hair.ufo" name="Aktiv Grotesk Hair">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Rg.ufo" name="Aktiv Grotesk Regular" familyname="Aktiv Grotesk">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Rg.ufo" name="Aktiv Grotesk Regular" layer="{126,100,0}">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Blk.ufo" name="Aktiv Grotesk Black">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance name="Aktiv Grotesk Hair" familyname="Aktiv Grotesk" stylename="Hair" filename="../instances/AktivGrotesk_Hair.ufo" postscriptfontname="AktivGrotesk-Hair" stylemapfamilyname="Aktiv Grotesk Hair" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Thin" familyname="Aktiv Grotesk" stylename="Thin" filename="../instances/AktivGrotesk_Th.ufo" postscriptfontname="AktivGrotesk-Thin" stylemapfamilyname="Aktiv Grotesk Thin" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Light" familyname="Aktiv Grotesk" stylename="Light" filename="../instances/AktivGrotesk_Lt.ufo" postscriptfontname="AktivGrotesk-Light" stylemapfamilyname="Aktiv Grotesk Light" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk " familyname="Aktiv Grotesk" stylename="" filename="../instances/AktivGrotesk_Rg.ufo" postscriptfontname="AktivGrotesk-" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Medium" familyname="Aktiv Grotesk" stylename="Medium" filename="../instances/AktivGrotesk_Md.ufo" postscriptfontname="AktivGrotesk-Medium" stylemapfamilyname="Aktiv Grotesk Medium" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk SemiBold" familyname="Aktiv Grotesk" stylename="SemiBold" filename="../../build/instances/AktivGrotesk_SBd.ufo" postscriptfontname="AktivGrotesk-SemiBold" stylemapfamilyname="Aktiv Grotesk SemiBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Bold" familyname="Aktiv Grotesk" stylename="Bold" filename="../instances/AktivGrotesk_Bd.ufo" postscriptfontname="AktivGrotesk-Bold" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="bold">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk XBold" familyname="Aktiv Grotesk" stylename="XBold" filename="../instances/AktivGrotesk_XBd.ufo" postscriptfontname="AktivGrotesk-XBold" stylemapfamilyname="Aktiv Grotesk XBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Black" familyname="Aktiv Grotesk" stylename="Black" filename="../instances/AktivGrotesk_Blk.ufo" postscriptfontname="AktivGrotesk-Black" stylemapfamilyname="Aktiv Grotesk Black" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_WghtWdth.designspace b/Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_WghtWdth.designspace
new file mode 100644
index 00000000..219d2262
--- /dev/null
+++ b/Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_WghtWdth.designspace
@@ -0,0 +1,307 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="4.1">
+ <axes>
+ <axis tag="wght" name="Weight" minimum="100" maximum="900" default="400">
+ <map input="100" output="22"/>
+ <map input="200" output="38"/>
+ <map input="300" output="57"/>
+ <map input="400" output="84"/>
+ <map input="500" output="98"/>
+ <map input="600" output="115"/>
+ <map input="700" output="133"/>
+ <map input="800" output="158"/>
+ <map input="900" output="185"/>
+ </axis>
+ <axis tag="wdth" name="Width" minimum="75" maximum="125" default="100"/>
+ </axes>
+ <rules processing="last">
+ <rule name="BRACKET.116.185">
+ <conditionset>
+ <condition name="Weight" minimum="116" maximum="185"/>
+ <condition name="Width" minimum="75" maximum="97.5"/>
+ </conditionset>
+ <sub name="cent" with="cent.BRACKET.130"/>
+ <sub name="dollar" with="dollar.BRACKET.130"/>
+ </rule>
+ </rules>
+ <sources>
+ <source filename="../AktivGroteskCd_Hair.ufo" name="Aktiv Grotesk Cd Hair">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Hair.ufo" name="Aktiv Grotesk Hair">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_Hair.ufo" name="Aktiv Grotesk Ex Hair">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskCd_Rg.ufo" name="Aktiv Grotesk Cd">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Rg.ufo" name="Aktiv Grotesk Regular" familyname="Aktiv Grotesk">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Rg.ufo" name="Aktiv Grotesk Regular" layer="{126,100,0}">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_Rg.ufo" name="Aktiv Grotesk Ex">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskCd_Blk.ufo" name="Aktiv Grotesk Cd Black">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Blk.ufo" name="Aktiv Grotesk Black">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_Blk.ufo" name="Aktiv Grotesk Ex Black">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance name="Aktiv Grotesk Cd Hair" familyname="Aktiv Grotesk" stylename="Cd Hair" filename="../instances/AktivGroteskCd_Hair.ufo" postscriptfontname="AktivGrotesk-CdHair" stylemapfamilyname="Aktiv Grotesk Cd Hair" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Hair" familyname="Aktiv Grotesk" stylename="Hair" filename="../instances/AktivGrotesk_Hair.ufo" postscriptfontname="AktivGrotesk-Hair" stylemapfamilyname="Aktiv Grotesk Hair" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Hair" familyname="Aktiv Grotesk" stylename="Ex Hair" filename="../instances/AktivGroteskEx_Hair.ufo" postscriptfontname="AktivGrotesk-ExHair" stylemapfamilyname="Aktiv Grotesk Ex Hair" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Thin" familyname="Aktiv Grotesk" stylename="Cd Thin" filename="../instances/AktivGroteskCd_Th.ufo" postscriptfontname="AktivGrotesk-CdThin" stylemapfamilyname="Aktiv Grotesk Cd Thin" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Thin" familyname="Aktiv Grotesk" stylename="Thin" filename="../instances/AktivGrotesk_Th.ufo" postscriptfontname="AktivGrotesk-Thin" stylemapfamilyname="Aktiv Grotesk Thin" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Thin" familyname="Aktiv Grotesk" stylename="Ex Thin" filename="../instances/AktivGroteskEx_Th.ufo" postscriptfontname="AktivGrotesk-ExThin" stylemapfamilyname="Aktiv Grotesk Ex Thin" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Light" familyname="Aktiv Grotesk" stylename="Cd Light" filename="../instances/AktivGroteskCd_Lt.ufo" postscriptfontname="AktivGrotesk-CdLight" stylemapfamilyname="Aktiv Grotesk Cd Light" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Light" familyname="Aktiv Grotesk" stylename="Light" filename="../instances/AktivGrotesk_Lt.ufo" postscriptfontname="AktivGrotesk-Light" stylemapfamilyname="Aktiv Grotesk Light" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Light" familyname="Aktiv Grotesk" stylename="Ex Light" filename="../instances/AktivGroteskEx_Lt.ufo" postscriptfontname="AktivGrotesk-ExLight" stylemapfamilyname="Aktiv Grotesk Ex Light" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd" familyname="Aktiv Grotesk" stylename="Cd" filename="../instances/AktivGroteskCd_Rg.ufo" postscriptfontname="AktivGrotesk-Cd" stylemapfamilyname="Aktiv Grotesk Cd" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk " familyname="Aktiv Grotesk" stylename="" filename="../instances/AktivGrotesk_Rg.ufo" postscriptfontname="AktivGrotesk-" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex" familyname="Aktiv Grotesk" stylename="Ex" filename="../instances/AktivGroteskEx_Rg.ufo" postscriptfontname="AktivGrotesk-Ex" stylemapfamilyname="Aktiv Grotesk Ex" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Medium" familyname="Aktiv Grotesk" stylename="Cd Medium" filename="../instances/AktivGroteskCd_Md.ufo" postscriptfontname="AktivGrotesk-CdMedium" stylemapfamilyname="Aktiv Grotesk Cd Medium" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Medium" familyname="Aktiv Grotesk" stylename="Medium" filename="../instances/AktivGrotesk_Md.ufo" postscriptfontname="AktivGrotesk-Medium" stylemapfamilyname="Aktiv Grotesk Medium" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Medium" familyname="Aktiv Grotesk" stylename="Ex Medium" filename="../instances/AktivGroteskEx_Md.ufo" postscriptfontname="AktivGrotesk-ExMedium" stylemapfamilyname="Aktiv Grotesk Ex Medium" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd SemiBold" familyname="Aktiv Grotesk" stylename="Cd SemiBold" filename="../../build/instances/AktivGroteskCd_SBd.ufo" postscriptfontname="AktivGrotesk-CdSemiBold" stylemapfamilyname="Aktiv Grotesk Cd SemiBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk SemiBold" familyname="Aktiv Grotesk" stylename="SemiBold" filename="../../build/instances/AktivGrotesk_SBd.ufo" postscriptfontname="AktivGrotesk-SemiBold" stylemapfamilyname="Aktiv Grotesk SemiBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex SemiBold" familyname="Aktiv Grotesk" stylename="Ex SemiBold" filename="../../build/instances/AktivGroteskEx_SBd.ufo" postscriptfontname="AktivGrotesk-ExSemiBold" stylemapfamilyname="Aktiv Grotesk Ex SemiBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Bold" familyname="Aktiv Grotesk" stylename="Cd Bold" filename="../instances/AktivGroteskCd_Bd.ufo" postscriptfontname="AktivGrotesk-CdBold" stylemapfamilyname="Aktiv Grotesk Cd" stylemapstylename="bold">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Bold" familyname="Aktiv Grotesk" stylename="Bold" filename="../instances/AktivGrotesk_Bd.ufo" postscriptfontname="AktivGrotesk-Bold" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="bold">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Bold" familyname="Aktiv Grotesk" stylename="Ex Bold" filename="../instances/AktivGroteskEx_Bd.ufo" postscriptfontname="AktivGrotesk-ExBold" stylemapfamilyname="Aktiv Grotesk Ex" stylemapstylename="bold">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd XBold" familyname="Aktiv Grotesk" stylename="Cd XBold" filename="../instances/AktivGroteskCd_XBd.ufo" postscriptfontname="AktivGrotesk-CdXBold" stylemapfamilyname="Aktiv Grotesk Cd XBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk XBold" familyname="Aktiv Grotesk" stylename="XBold" filename="../instances/AktivGrotesk_XBd.ufo" postscriptfontname="AktivGrotesk-XBold" stylemapfamilyname="Aktiv Grotesk XBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex XBold" familyname="Aktiv Grotesk" stylename="Ex XBold" filename="../instances/AktivGroteskEx_XBd.ufo" postscriptfontname="AktivGrotesk-ExXBold" stylemapfamilyname="Aktiv Grotesk Ex XBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Black" familyname="Aktiv Grotesk" stylename="Cd Black" filename="../instances/AktivGroteskCd_Blk.ufo" postscriptfontname="AktivGrotesk-CdBlack" stylemapfamilyname="Aktiv Grotesk Cd Black" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Black" familyname="Aktiv Grotesk" stylename="Black" filename="../instances/AktivGrotesk_Blk.ufo" postscriptfontname="AktivGrotesk-Black" stylemapfamilyname="Aktiv Grotesk Black" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Black" familyname="Aktiv Grotesk" stylename="Ex Black" filename="../instances/AktivGroteskEx_Blk.ufo" postscriptfontname="AktivGrotesk-ExBlack" stylemapfamilyname="Aktiv Grotesk Ex Black" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_WghtWdthItal.designspace b/Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_WghtWdthItal.designspace
new file mode 100644
index 00000000..5b419bad
--- /dev/null
+++ b/Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_WghtWdthItal.designspace
@@ -0,0 +1,670 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="4.1">
+ <axes>
+ <axis tag="wght" name="Weight" minimum="100" maximum="900" default="400">
+ <map input="100" output="22"/>
+ <map input="200" output="38"/>
+ <map input="300" output="57"/>
+ <map input="400" output="84"/>
+ <map input="500" output="98"/>
+ <map input="600" output="115"/>
+ <map input="700" output="133"/>
+ <map input="800" output="158"/>
+ <map input="900" output="185"/>
+ </axis>
+ <axis tag="wdth" name="Width" minimum="75" maximum="125" default="100"/>
+ <axis tag="ital" name="Italic" minimum="0" maximum="1" default="0"/>
+ </axes>
+ <rules processing="last">
+ <rule name="BRACKET.CYR">
+ <conditionset>
+ <condition name="Italic" minimum="0.1" maximum="1"/>
+ </conditionset>
+ <sub name="ghe.loclSRB" with="ghe.ital.loclSRB"/>
+ <sub name="ghe.loclMKD" with="ghe.ital.loclMKD"/>
+ <sub name="de.loclMKDSRB" with="de.ital.loclMKDSRB"/>
+ <sub name="pe.loclMKDSRB" with="pe.ital.loclMKDSRB"/>
+ <sub name="te.loclMKDSRB" with="te.ital.loclMKDSRB"/>
+ <sub name="gje.loclMKD" with="gje.ital.loclMKD"/>
+ <sub name="sha.loclMKDSRB" with="sha.ital.loclMKDSRB"/>
+ </rule>
+ <rule name="BRACKET.116.185">
+ <conditionset>
+ <condition name="Weight" minimum="116" maximum="185"/>
+ <condition name="Width" minimum="75" maximum="97.5"/>
+ </conditionset>
+ <sub name="cent" with="cent.BRACKET.130"/>
+ <sub name="dollar" with="dollar.BRACKET.130"/>
+ </rule>
+ </rules>
+ <sources>
+ <source filename="../AktivGroteskCd_Hair.ufo" name="Aktiv Grotesk Cd Hair">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskCd_HairIt.ufo" name="Aktiv Grotesk Cd Hair Italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Hair.ufo" name="Aktiv Grotesk Hair">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_HairIt.ufo" name="Aktiv Grotesk Hair Italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_Hair.ufo" name="Aktiv Grotesk Ex Hair">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_HairIt.ufo" name="Aktiv Grotesk Ex Hair Italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskCd_Rg.ufo" name="Aktiv Grotesk Cd">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskCd_It.ufo" name="Aktiv Grotesk Cd Italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Rg.ufo" name="Aktiv Grotesk Regular" familyname="Aktiv Grotesk">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Rg.ufo" name="Aktiv Grotesk Regular" layer="{126,100,0}">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_It.ufo" name="Aktiv Grotesk Italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_It.ufo" name="Aktiv Grotesk Italic {126,100,1}" layer="{126,100,1}">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_Rg.ufo" name="Aktiv Grotesk Ex">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_It.ufo" name="Aktiv Grotesk Ex Italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskCd_Blk.ufo" name="Aktiv Grotesk Cd Black">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskCd_BlkIt.ufo" name="Aktiv Grotesk Cd Black Italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Blk.ufo" name="Aktiv Grotesk Black">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_BlkIt.ufo" name="Aktiv Grotesk Black Italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_Blk.ufo" name="Aktiv Grotesk Ex Black">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_BlkIt.ufo" name="Aktiv Grotesk Ex Black Italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance name="Aktiv Grotesk Cd Hair" familyname="Aktiv Grotesk" stylename="Cd Hair" filename="../instances/AktivGroteskCd_Hair.ufo" postscriptfontname="AktivGrotesk-CdHair" stylemapfamilyname="Aktiv Grotesk Cd Hair" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Hair Italic" familyname="Aktiv Grotesk" stylename="Cd Hair Italic" filename="../instances/AktivGroteskCd_HairIt.ufo" postscriptfontname="AktivGrotesk-CdHairItalic" stylemapfamilyname="Aktiv Grotesk Cd Hair" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Hair" familyname="Aktiv Grotesk" stylename="Hair" filename="../instances/AktivGrotesk_Hair.ufo" postscriptfontname="AktivGrotesk-Hair" stylemapfamilyname="Aktiv Grotesk Hair" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Hair Italic" familyname="Aktiv Grotesk" stylename="Hair Italic" filename="../instances/AktivGrotesk_HairIt.ufo" postscriptfontname="AktivGrotesk-HairItalic" stylemapfamilyname="Aktiv Grotesk Hair" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Hair" familyname="Aktiv Grotesk" stylename="Ex Hair" filename="../instances/AktivGroteskEx_Hair.ufo" postscriptfontname="AktivGrotesk-ExHair" stylemapfamilyname="Aktiv Grotesk Ex Hair" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Hair Italic" familyname="Aktiv Grotesk" stylename="Ex Hair Italic" filename="../instances/AktivGroteskEx_HairIt.ufo" postscriptfontname="AktivGrotesk-ExHairItalic" stylemapfamilyname="Aktiv Grotesk Ex Hair" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Thin" familyname="Aktiv Grotesk" stylename="Cd Thin" filename="../instances/AktivGroteskCd_Th.ufo" postscriptfontname="AktivGrotesk-CdThin" stylemapfamilyname="Aktiv Grotesk Cd Thin" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Thin Italic" familyname="Aktiv Grotesk" stylename="Cd Thin Italic" filename="../instances/AktivGroteskCd_ThIt.ufo" postscriptfontname="AktivGrotesk-CdThinItalic" stylemapfamilyname="Aktiv Grotesk Cd Thin" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Thin" familyname="Aktiv Grotesk" stylename="Thin" filename="../instances/AktivGrotesk_Th.ufo" postscriptfontname="AktivGrotesk-Thin" stylemapfamilyname="Aktiv Grotesk Thin" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Thin Italic" familyname="Aktiv Grotesk" stylename="Thin Italic" filename="../instances/AktivGrotesk_ThIt.ufo" postscriptfontname="AktivGrotesk-ThinItalic" stylemapfamilyname="Aktiv Grotesk Thin" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Thin" familyname="Aktiv Grotesk" stylename="Ex Thin" filename="../instances/AktivGroteskEx_Th.ufo" postscriptfontname="AktivGrotesk-ExThin" stylemapfamilyname="Aktiv Grotesk Ex Thin" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Thin Italic" familyname="Aktiv Grotesk" stylename="Ex Thin Italic" filename="../instances/AktivGroteskEx_ThIt.ufo" postscriptfontname="AktivGrotesk-ExThinItalic" stylemapfamilyname="Aktiv Grotesk Ex Thin" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Light" familyname="Aktiv Grotesk" stylename="Cd Light" filename="../instances/AktivGroteskCd_Lt.ufo" postscriptfontname="AktivGrotesk-CdLight" stylemapfamilyname="Aktiv Grotesk Cd Light" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Light Italic" familyname="Aktiv Grotesk" stylename="Cd Light Italic" filename="../instances/AktivGroteskCd_LtIt.ufo" postscriptfontname="AktivGrotesk-CdLightItalic" stylemapfamilyname="Aktiv Grotesk Cd Light" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Light" familyname="Aktiv Grotesk" stylename="Light" filename="../instances/AktivGrotesk_Lt.ufo" postscriptfontname="AktivGrotesk-Light" stylemapfamilyname="Aktiv Grotesk Light" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Light Italic" familyname="Aktiv Grotesk" stylename="Light Italic" filename="../instances/AktivGrotesk_LtIt.ufo" postscriptfontname="AktivGrotesk-LightItalic" stylemapfamilyname="Aktiv Grotesk Light" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Light" familyname="Aktiv Grotesk" stylename="Ex Light" filename="../instances/AktivGroteskEx_Lt.ufo" postscriptfontname="AktivGrotesk-ExLight" stylemapfamilyname="Aktiv Grotesk Ex Light" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Light Italic" familyname="Aktiv Grotesk" stylename="Ex Light Italic" filename="../instances/AktivGroteskEx_LtIt.ufo" postscriptfontname="AktivGrotesk-ExLightItalic" stylemapfamilyname="Aktiv Grotesk Ex Light" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd" familyname="Aktiv Grotesk" stylename="Cd" filename="../instances/AktivGroteskCd_Rg.ufo" postscriptfontname="AktivGrotesk-Cd" stylemapfamilyname="Aktiv Grotesk Cd" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Italic" familyname="Aktiv Grotesk" stylename="Cd Italic" filename="../instances/AktivGroteskCd_It.ufo" postscriptfontname="AktivGrotesk-CdItalic" stylemapfamilyname="Aktiv Grotesk Cd" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk " familyname="Aktiv Grotesk" stylename="" filename="../instances/AktivGrotesk_Rg.ufo" postscriptfontname="AktivGrotesk-" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Italic" familyname="Aktiv Grotesk" stylename="Italic" filename="../instances/AktivGrotesk_It.ufo" postscriptfontname="AktivGrotesk-Italic" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex" familyname="Aktiv Grotesk" stylename="Ex" filename="../instances/AktivGroteskEx_Rg.ufo" postscriptfontname="AktivGrotesk-Ex" stylemapfamilyname="Aktiv Grotesk Ex" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Italic" familyname="Aktiv Grotesk" stylename="Ex Italic" filename="../instances/AktivGroteskEx_It.ufo" postscriptfontname="AktivGrotesk-ExItalic" stylemapfamilyname="Aktiv Grotesk Ex" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Medium" familyname="Aktiv Grotesk" stylename="Cd Medium" filename="../instances/AktivGroteskCd_Md.ufo" postscriptfontname="AktivGrotesk-CdMedium" stylemapfamilyname="Aktiv Grotesk Cd Medium" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Medium Italic" familyname="Aktiv Grotesk" stylename="Cd Medium Italic" filename="../instances/AktivGroteskCd_MdIt.ufo" postscriptfontname="AktivGrotesk-CdMediumItalic" stylemapfamilyname="Aktiv Grotesk Cd Medium" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Medium" familyname="Aktiv Grotesk" stylename="Medium" filename="../instances/AktivGrotesk_Md.ufo" postscriptfontname="AktivGrotesk-Medium" stylemapfamilyname="Aktiv Grotesk Medium" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Medium Italic" familyname="Aktiv Grotesk" stylename="Medium Italic" filename="../instances/AktivGrotesk_MdIt.ufo" postscriptfontname="AktivGrotesk-MediumItalic" stylemapfamilyname="Aktiv Grotesk Medium" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Medium" familyname="Aktiv Grotesk" stylename="Ex Medium" filename="../instances/AktivGroteskEx_Md.ufo" postscriptfontname="AktivGrotesk-ExMedium" stylemapfamilyname="Aktiv Grotesk Ex Medium" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Medium Italic" familyname="Aktiv Grotesk" stylename="Ex Medium Italic" filename="../instances/AktivGroteskEx_MdIt.ufo" postscriptfontname="AktivGrotesk-ExMediumItalic" stylemapfamilyname="Aktiv Grotesk Ex Medium" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd SemiBold" familyname="Aktiv Grotesk" stylename="Cd SemiBold" filename="../../build/instances/AktivGroteskCd_SBd.ufo" postscriptfontname="AktivGrotesk-CdSemiBold" stylemapfamilyname="Aktiv Grotesk Cd SemiBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd SemiBold Italic" familyname="Aktiv Grotesk" stylename="Cd SemiBold Italic" filename="../../build/instances/AktivGroteskCd_SBdIt.ufo" postscriptfontname="AktivGrotesk-CdSemiBoldItalic" stylemapfamilyname="Aktiv Grotesk Cd SemiBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk SemiBold" familyname="Aktiv Grotesk" stylename="SemiBold" filename="../../build/instances/AktivGrotesk_SBd.ufo" postscriptfontname="AktivGrotesk-SemiBold" stylemapfamilyname="Aktiv Grotesk SemiBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk SemiBold Italic" familyname="Aktiv Grotesk" stylename="SemiBold Italic" filename="../../build/instances/AktivGrotesk_SBdIt.ufo" postscriptfontname="AktivGrotesk-SemiBoldItalic" stylemapfamilyname="Aktiv Grotesk SemiBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex SemiBold" familyname="Aktiv Grotesk" stylename="Ex SemiBold" filename="../../build/instances/AktivGroteskEx_SBd.ufo" postscriptfontname="AktivGrotesk-ExSemiBold" stylemapfamilyname="Aktiv Grotesk Ex SemiBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex SemiBold Italic" familyname="Aktiv Grotesk" stylename="Ex SemiBold Italic" filename="../../build/instances/AktivGroteskEx_SBdIt.ufo" postscriptfontname="AktivGrotesk-ExSemiBoldItalic" stylemapfamilyname="Aktiv Grotesk Ex SemiBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Bold" familyname="Aktiv Grotesk" stylename="Cd Bold" filename="../instances/AktivGroteskCd_Bd.ufo" postscriptfontname="AktivGrotesk-CdBold" stylemapfamilyname="Aktiv Grotesk Cd" stylemapstylename="bold">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Bold Italic" familyname="Aktiv Grotesk" stylename="Cd Bold Italic" filename="../instances/AktivGroteskCd_BdIt.ufo" postscriptfontname="AktivGrotesk-CdBoldItalic" stylemapfamilyname="Aktiv Grotesk Cd" stylemapstylename="bold italic">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Bold" familyname="Aktiv Grotesk" stylename="Bold" filename="../instances/AktivGrotesk_Bd.ufo" postscriptfontname="AktivGrotesk-Bold" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="bold">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Bold Italic" familyname="Aktiv Grotesk" stylename="Bold Italic" filename="../instances/AktivGrotesk_BdIt.ufo" postscriptfontname="AktivGrotesk-BoldItalic" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="bold italic">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Bold" familyname="Aktiv Grotesk" stylename="Ex Bold" filename="../instances/AktivGroteskEx_Bd.ufo" postscriptfontname="AktivGrotesk-ExBold" stylemapfamilyname="Aktiv Grotesk Ex" stylemapstylename="bold">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Bold Italic" familyname="Aktiv Grotesk" stylename="Ex Bold Italic" filename="../instances/AktivGroteskEx_BdIt.ufo" postscriptfontname="AktivGrotesk-ExBoldItalic" stylemapfamilyname="Aktiv Grotesk Ex" stylemapstylename="bold italic">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd XBold" familyname="Aktiv Grotesk" stylename="Cd XBold" filename="../instances/AktivGroteskCd_XBd.ufo" postscriptfontname="AktivGrotesk-CdXBold" stylemapfamilyname="Aktiv Grotesk Cd XBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd XBold Italic" familyname="Aktiv Grotesk" stylename="Cd XBold Italic" filename="../instances/AktivGroteskCd_XBdIt.ufo" postscriptfontname="AktivGrotesk-CdXBoldItalic" stylemapfamilyname="Aktiv Grotesk Cd XBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk XBold" familyname="Aktiv Grotesk" stylename="XBold" filename="../instances/AktivGrotesk_XBd.ufo" postscriptfontname="AktivGrotesk-XBold" stylemapfamilyname="Aktiv Grotesk XBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk XBold Italic" familyname="Aktiv Grotesk" stylename="XBold Italic" filename="../instances/AktivGrotesk_XBdIt.ufo" postscriptfontname="AktivGrotesk-XBoldItalic" stylemapfamilyname="Aktiv Grotesk XBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex XBold" familyname="Aktiv Grotesk" stylename="Ex XBold" filename="../instances/AktivGroteskEx_XBd.ufo" postscriptfontname="AktivGrotesk-ExXBold" stylemapfamilyname="Aktiv Grotesk Ex XBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex XBold Italic" familyname="Aktiv Grotesk" stylename="Ex XBold Italic" filename="../instances/AktivGroteskEx_XBdIt.ufo" postscriptfontname="AktivGrotesk-ExXBoldItalic" stylemapfamilyname="Aktiv Grotesk Ex XBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Black" familyname="Aktiv Grotesk" stylename="Cd Black" filename="../instances/AktivGroteskCd_Blk.ufo" postscriptfontname="AktivGrotesk-CdBlack" stylemapfamilyname="Aktiv Grotesk Cd Black" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Black Italic" familyname="Aktiv Grotesk" stylename="Cd Black Italic" filename="../instances/AktivGroteskCd_BlkIt.ufo" postscriptfontname="AktivGrotesk-CdBlackItalic" stylemapfamilyname="Aktiv Grotesk Cd Black" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Black" familyname="Aktiv Grotesk" stylename="Black" filename="../instances/AktivGrotesk_Blk.ufo" postscriptfontname="AktivGrotesk-Black" stylemapfamilyname="Aktiv Grotesk Black" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Black Italic" familyname="Aktiv Grotesk" stylename="Black Italic" filename="../instances/AktivGrotesk_BlkIt.ufo" postscriptfontname="AktivGrotesk-BlackItalic" stylemapfamilyname="Aktiv Grotesk Black" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Black" familyname="Aktiv Grotesk" stylename="Ex Black" filename="../instances/AktivGroteskEx_Blk.ufo" postscriptfontname="AktivGrotesk-ExBlack" stylemapfamilyname="Aktiv Grotesk Ex Black" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Black Italic" familyname="Aktiv Grotesk" stylename="Ex Black Italic" filename="../instances/AktivGroteskEx_BlkIt.ufo" postscriptfontname="AktivGrotesk-ExBlackItalic" stylemapfamilyname="Aktiv Grotesk Ex Black" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/data/convert5to4_output/SourceSerif4Variable-Italic.designspace b/Tests/designspaceLib/data/convert5to4_output/SourceSerif4Variable-Italic.designspace
new file mode 100644
index 00000000..adfe245d
--- /dev/null
+++ b/Tests/designspaceLib/data/convert5to4_output/SourceSerif4Variable-Italic.designspace
@@ -0,0 +1,326 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="4.1">
+ <axes>
+ <axis tag="wght" name="weight" minimum="200" maximum="900" default="400">
+ <map input="200" output="0"/>
+ <map input="300" output="145"/>
+ <map input="400" output="394"/>
+ <map input="600" output="594"/>
+ <map input="700" output="823"/>
+ <map input="900" output="1000"/>
+ </axis>
+ <axis tag="opsz" name="optical" minimum="8" maximum="60" default="20"/>
+ </axes>
+ <sources>
+ <source filename="../caption/master_0/SourceSerif-Italic_c0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </source>
+ <source filename="../caption/master_1/SourceSerif-Italic_c1.ufo">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </source>
+ <source filename="../caption/master_2/SourceSerif-Italic_c2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </source>
+ <source filename="../text/master_0/SourceSerif-Italic_0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </source>
+ <source filename="../text/master_1/SourceSerif-Italic_1.ufo">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </source>
+ <source filename="../text/master_2/SourceSerif-Italic_2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </source>
+ <source filename="../display/master_0/SourceSerif-Italic_d0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </source>
+ <source filename="../display/master_1/SourceSerif-Italic_d1.ufo">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </source>
+ <source filename="../display/master_2/SourceSerif-Italic_d2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance name="Source Serif 4 Caption ExtraLight Italic" familyname="Source Serif 4" stylename="Caption ExtraLight Italic" filename="Source Serif 4-Caption ExtraLight Italic.ttf" postscriptfontname="SourceSerif4Italic-CaptionExtraLight" stylemapfamilyname="Source Serif 4 Caption ExtraLight Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Caption Light Italic" familyname="Source Serif 4" stylename="Caption Light Italic" filename="Source Serif 4-Caption Light Italic.ttf" postscriptfontname="SourceSerif4Italic-CaptionLight" stylemapfamilyname="Source Serif 4 Caption Light Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Caption Italic" familyname="Source Serif 4" stylename="Caption Italic" filename="Source Serif 4-Caption Italic.ttf" postscriptfontname="SourceSerif4Italic-CaptionRegular" stylemapfamilyname="Source Serif 4 Caption Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Caption Semibold Italic" familyname="Source Serif 4" stylename="Caption Semibold Italic" filename="Source Serif 4-Caption Semibold Italic.ttf" postscriptfontname="SourceSerif4Italic-CaptionSemibold" stylemapfamilyname="Source Serif 4 Caption Semibold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Caption Bold Italic" familyname="Source Serif 4" stylename="Caption Bold Italic" filename="Source Serif 4-Caption Bold Italic.ttf" postscriptfontname="SourceSerif4Italic-CaptionBold" stylemapfamilyname="Source Serif 4 Caption Bold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Caption Black Italic" familyname="Source Serif 4" stylename="Caption Black Italic" filename="Source Serif 4-Caption Black Italic.ttf" postscriptfontname="SourceSerif4Italic-CaptionBlack" stylemapfamilyname="Source Serif 4 Caption Black Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 SmText ExtraLight Italic" familyname="Source Serif 4" stylename="SmText ExtraLight Italic" filename="Source Serif 4-SmText ExtraLight Italic.ttf" postscriptfontname="SourceSerif4Italic-SmTextExtraLight" stylemapfamilyname="Source Serif 4 SmallText ExtraLight Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 SmText Light Italic" familyname="Source Serif 4" stylename="SmText Light Italic" filename="Source Serif 4-SmText Light Italic.ttf" postscriptfontname="SourceSerif4Italic-SmTextLight" stylemapfamilyname="Source Serif 4 SmallText Light Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 SmText Italic" familyname="Source Serif 4" stylename="SmText Italic" filename="Source Serif 4-SmText Italic.ttf" postscriptfontname="SourceSerif4Italic-SmTextRegular" stylemapfamilyname="Source Serif 4 SmallText Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 SmText Semibold Italic" familyname="Source Serif 4" stylename="SmText Semibold Italic" filename="Source Serif 4-SmText Semibold Italic.ttf" postscriptfontname="SourceSerif4Italic-SmTextSemibold" stylemapfamilyname="Source Serif 4 SmallText Semibold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 SmText Bold Italic" familyname="Source Serif 4" stylename="SmText Bold Italic" filename="Source Serif 4-SmText Bold Italic.ttf" postscriptfontname="SourceSerif4Italic-SmTextBold" stylemapfamilyname="Source Serif 4 SmallText Bold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 SmText Black Italic" familyname="Source Serif 4" stylename="SmText Black Italic" filename="Source Serif 4-SmText Black Italic.ttf" postscriptfontname="SourceSerif4Italic-SmTextBlack" stylemapfamilyname="Source Serif 4 SmallText Black Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 ExtraLight Italic" familyname="Source Serif 4" stylename="ExtraLight Italic" filename="Source Serif 4-ExtraLight Italic.ttf" postscriptfontname="SourceSerif4Italic-ExtraLight" stylemapfamilyname="Source Serif 4 ExtraLight Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Light Italic" familyname="Source Serif 4" stylename="Light Italic" filename="Source Serif 4-Light Italic.ttf" postscriptfontname="SourceSerif4Italic-Light" stylemapfamilyname="Source Serif 4 Light Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Italic" familyname="Source Serif 4" stylename="Italic" filename="Source Serif 4-Italic.ttf" postscriptfontname="SourceSerif4Italic-Regular" stylemapfamilyname="Source Serif 4 Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Semibold Italic" familyname="Source Serif 4" stylename="Semibold Italic" filename="Source Serif 4-Semibold Italic.ttf" postscriptfontname="SourceSerif4Italic-Semibold" stylemapfamilyname="Source Serif 4 Semibold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Bold Italic" familyname="Source Serif 4" stylename="Bold Italic" filename="Source Serif 4-Bold Italic.ttf" postscriptfontname="SourceSerif4Italic-Bold" stylemapfamilyname="Source Serif 4 Bold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Black Italic" familyname="Source Serif 4" stylename="Black Italic" filename="Source Serif 4-Black Italic.ttf" postscriptfontname="SourceSerif4Italic-Black" stylemapfamilyname="Source Serif 4 Black Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Subhead ExtraLight Italic" familyname="Source Serif 4" stylename="Subhead ExtraLight Italic" filename="Source Serif 4-Subhead ExtraLight Italic.ttf" postscriptfontname="SourceSerif4Italic-SubheadExtraLight" stylemapfamilyname="Source Serif 4 Subhead ExtraLight Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Subhead Light Italic" familyname="Source Serif 4" stylename="Subhead Light Italic" filename="Source Serif 4-Subhead Light Italic.ttf" postscriptfontname="SourceSerif4Italic-SubheadLight" stylemapfamilyname="Source Serif 4 Subhead Light Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Subhead Italic" familyname="Source Serif 4" stylename="Subhead Italic" filename="Source Serif 4-Subhead Italic.ttf" postscriptfontname="SourceSerif4Italic-SubheadRegular" stylemapfamilyname="Source Serif 4 Subhead Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Subhead Semibold Italic" familyname="Source Serif 4" stylename="Subhead Semibold Italic" filename="Source Serif 4-Subhead Semibold Italic.ttf" postscriptfontname="SourceSerif4Italic-SubheadSemibold" stylemapfamilyname="Source Serif 4 Subhead Semibold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Subhead Bold Italic" familyname="Source Serif 4" stylename="Subhead Bold Italic" filename="Source Serif 4-Subhead Bold Italic.ttf" postscriptfontname="SourceSerif4Italic-SubheadBold" stylemapfamilyname="Source Serif 4 Subhead Bold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Subhead Black Italic" familyname="Source Serif 4" stylename="Subhead Black Italic" filename="Source Serif 4-Subhead Black Italic.ttf" postscriptfontname="SourceSerif4Italic-SubheadBlack" stylemapfamilyname="Source Serif 4 Subhead Black Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Display ExtraLight Italic" familyname="Source Serif 4" stylename="Display ExtraLight Italic" filename="Source Serif 4-Display ExtraLight Italic.ttf" postscriptfontname="SourceSerif4Italic-DisplayExtraLight" stylemapfamilyname="Source Serif 4 Display ExtraLight Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Display Light Italic" familyname="Source Serif 4" stylename="Display Light Italic" filename="Source Serif 4-Display Light Italic.ttf" postscriptfontname="SourceSerif4Italic-DisplayLight" stylemapfamilyname="Source Serif 4 Display Light Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Display Italic" familyname="Source Serif 4" stylename="Display Italic" filename="Source Serif 4-Display Italic.ttf" postscriptfontname="SourceSerif4Italic-DisplayRegular" stylemapfamilyname="Source Serif 4 Display Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Display Semibold Italic" familyname="Source Serif 4" stylename="Display Semibold Italic" filename="Source Serif 4-Display Semibold Italic.ttf" postscriptfontname="SourceSerif4Italic-DisplaySemibold" stylemapfamilyname="Source Serif 4 Display Semibold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Display Bold Italic" familyname="Source Serif 4" stylename="Display Bold Italic" filename="Source Serif 4-Display Bold Italic.ttf" postscriptfontname="SourceSerif4Italic-DisplayBold" stylemapfamilyname="Source Serif 4 Display Bold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Display Black Italic" familyname="Source Serif 4" stylename="Display Black Italic" filename="Source Serif 4-Display Black Italic.ttf" postscriptfontname="SourceSerif4Italic-DisplayBlack" stylemapfamilyname="Source Serif 4 Display Black Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ </instances>
+ <lib>
+ <dict>
+ <key>public.skipExportGlyphs</key>
+ <array>
+ <string>caron.alt</string>
+ <string>commabelowcmb.alt</string>
+ <string>f.liga</string>
+ <string>f.ligalong</string>
+ <string>tonos.cap</string>
+ <string>dieresiscmb.tight</string>
+ <string>turkicdsccmb</string>
+ </array>
+ </dict>
+ </lib>
+</designspace>
diff --git a/Tests/designspaceLib/data/convert5to4_output/SourceSerif4Variable-Roman.designspace b/Tests/designspaceLib/data/convert5to4_output/SourceSerif4Variable-Roman.designspace
new file mode 100644
index 00000000..30f181e3
--- /dev/null
+++ b/Tests/designspaceLib/data/convert5to4_output/SourceSerif4Variable-Roman.designspace
@@ -0,0 +1,334 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="4.1">
+ <axes>
+ <axis tag="wght" name="weight" minimum="200" maximum="900" default="400">
+ <map input="200" output="0"/>
+ <map input="300" output="145"/>
+ <map input="400" output="394"/>
+ <map input="600" output="594"/>
+ <map input="700" output="823"/>
+ <map input="900" output="1000"/>
+ </axis>
+ <axis tag="opsz" name="optical" minimum="8" maximum="60" default="20"/>
+ </axes>
+ <sources>
+ <source filename="../caption/master_0/SourceSerif_c0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </source>
+ <source filename="../caption/master_1/SourceSerif_c1.ufo">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </source>
+ <source filename="../caption/master_2/SourceSerif_c2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </source>
+ <source filename="../text/master_0/SourceSerif_0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </source>
+ <source filename="../text/master_1/SourceSerif_1.ufo" familyname="Source Serif 4">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </source>
+ <source filename="../text/master_2/SourceSerif_2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </source>
+ <source filename="../display/master_0/SourceSerif_d0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </source>
+ <source filename="../display/master_1/SourceSerif_d1.ufo">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </source>
+ <source filename="../display/master_2/SourceSerif_d2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance name="Source Serif 4 Caption ExtraLight" familyname="Source Serif 4" stylename="Caption ExtraLight" filename="Source Serif 4-Caption ExtraLight.ttf" postscriptfontname="SourceSerif4Roman-CaptionExtraLight" stylemapfamilyname="Source Serif 4 Caption ExtraLight" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Caption Light" familyname="Source Serif 4" stylename="Caption Light" filename="Source Serif 4-Caption Light.ttf" postscriptfontname="SourceSerif4Roman-CaptionLight" stylemapfamilyname="Source Serif 4 Caption Light" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Caption" familyname="Source Serif 4" stylename="Caption" filename="Source Serif 4-Caption.ttf" postscriptfontname="SourceSerif4Roman-CaptionRegular" stylemapfamilyname="Source Serif 4 Caption" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Caption Semibold" familyname="Source Serif 4" stylename="Caption Semibold" filename="Source Serif 4-Caption Semibold.ttf" postscriptfontname="SourceSerif4Roman-CaptionSemibold" stylemapfamilyname="Source Serif 4 Caption Semibold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Caption Bold" familyname="Source Serif 4" stylename="Caption Bold" filename="Source Serif 4-Caption Bold.ttf" postscriptfontname="SourceSerif4Roman-CaptionBold" stylemapfamilyname="Source Serif 4 Caption Bold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Caption Black" familyname="Source Serif 4" stylename="Caption Black" filename="Source Serif 4-Caption Black.ttf" postscriptfontname="SourceSerif4Roman-CaptionBlack" stylemapfamilyname="Source Serif 4 Caption Black" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 SmText ExtraLight" familyname="Source Serif 4" stylename="SmText ExtraLight" filename="Source Serif 4-SmText ExtraLight.ttf" postscriptfontname="SourceSerif4Roman-SmTextExtraLight" stylemapfamilyname="Source Serif 4 SmallText ExtraLight" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 SmText Light" familyname="Source Serif 4" stylename="SmText Light" filename="Source Serif 4-SmText Light.ttf" postscriptfontname="SourceSerif4Roman-SmTextLight" stylemapfamilyname="Source Serif 4 SmallText Light" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 SmText" familyname="Source Serif 4" stylename="SmText" filename="Source Serif 4-SmText.ttf" postscriptfontname="SourceSerif4Roman-SmTextRegular" stylemapfamilyname="Source Serif 4 SmallText" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 SmText Semibold" familyname="Source Serif 4" stylename="SmText Semibold" filename="Source Serif 4-SmText Semibold.ttf" postscriptfontname="SourceSerif4Roman-SmTextSemibold" stylemapfamilyname="Source Serif 4 SmallText Semibold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 SmText Bold" familyname="Source Serif 4" stylename="SmText Bold" filename="Source Serif 4-SmText Bold.ttf" postscriptfontname="SourceSerif4Roman-SmTextBold" stylemapfamilyname="Source Serif 4 SmallText Bold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 SmText Black" familyname="Source Serif 4" stylename="SmText Black" filename="Source Serif 4-SmText Black.ttf" postscriptfontname="SourceSerif4Roman-SmTextBlack" stylemapfamilyname="Source Serif 4 SmallText Black" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 ExtraLight" familyname="Source Serif 4" stylename="ExtraLight" filename="Source Serif 4-ExtraLight.ttf" postscriptfontname="SourceSerif4Roman-ExtraLight" stylemapfamilyname="Source Serif 4 ExtraLight" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Light" familyname="Source Serif 4" stylename="Light" filename="Source Serif 4-Light.ttf" postscriptfontname="SourceSerif4Roman-Light" stylemapfamilyname="Source Serif 4 Light" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Regular" familyname="Source Serif 4" stylename="Regular" filename="Source Serif 4-Regular.ttf" postscriptfontname="SourceSerif4Roman-Regular" stylemapfamilyname="Source Serif 4" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Semibold" familyname="Source Serif 4" stylename="Semibold" filename="Source Serif 4-Semibold.ttf" postscriptfontname="SourceSerif4Roman-Semibold" stylemapfamilyname="Source Serif 4 Semibold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Bold" familyname="Source Serif 4" stylename="Bold" filename="Source Serif 4-Bold.ttf" postscriptfontname="SourceSerif4Roman-Bold" stylemapfamilyname="Source Serif 4 Bold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Black" familyname="Source Serif 4" stylename="Black" filename="Source Serif 4-Black.ttf" postscriptfontname="SourceSerif4Roman-Black" stylemapfamilyname="Source Serif 4 Black" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Subhead ExtraLight" familyname="Source Serif 4" stylename="Subhead ExtraLight" filename="Source Serif 4-Subhead ExtraLight.ttf" postscriptfontname="SourceSerif4Roman-SubheadExtraLight" stylemapfamilyname="Source Serif 4 Subhead ExtraLight" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Subhead Light" familyname="Source Serif 4" stylename="Subhead Light" filename="Source Serif 4-Subhead Light.ttf" postscriptfontname="SourceSerif4Roman-SubheadLight" stylemapfamilyname="Source Serif 4 Subhead Light" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Subhead" familyname="Source Serif 4" stylename="Subhead" filename="Source Serif 4-Subhead.ttf" postscriptfontname="SourceSerif4Roman-SubheadRegular" stylemapfamilyname="Source Serif 4 Subhead" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Subhead Semibold" familyname="Source Serif 4" stylename="Subhead Semibold" filename="Source Serif 4-Subhead Semibold.ttf" postscriptfontname="SourceSerif4Roman-SubheadSemibold" stylemapfamilyname="Source Serif 4 Subhead Semibold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Subhead Bold" familyname="Source Serif 4" stylename="Subhead Bold" filename="Source Serif 4-Subhead Bold.ttf" postscriptfontname="SourceSerif4Roman-SubheadBold" stylemapfamilyname="Source Serif 4 Subhead Bold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Subhead Black" familyname="Source Serif 4" stylename="Subhead Black" filename="Source Serif 4-Subhead Black.ttf" postscriptfontname="SourceSerif4Roman-SubheadBlack" stylemapfamilyname="Source Serif 4 Subhead Black" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Display ExtraLight" familyname="Source Serif 4" stylename="Display ExtraLight" filename="Source Serif 4-Display ExtraLight.ttf" postscriptfontname="SourceSerif4Roman-DisplayExtraLight" stylemapfamilyname="Source Serif 4 Display ExtraLight" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Display Light" familyname="Source Serif 4" stylename="Display Light" filename="Source Serif 4-Display Light.ttf" postscriptfontname="SourceSerif4Roman-DisplayLight" stylemapfamilyname="Source Serif 4 Display Light" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Display" familyname="Source Serif 4" stylename="Display" filename="Source Serif 4-Display.ttf" postscriptfontname="SourceSerif4Roman-DisplayRegular" stylemapfamilyname="Source Serif 4 Display" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Display Semibold" familyname="Source Serif 4" stylename="Display Semibold" filename="Source Serif 4-Display Semibold.ttf" postscriptfontname="SourceSerif4Roman-DisplaySemibold" stylemapfamilyname="Source Serif 4 Display Semibold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Display Bold" familyname="Source Serif 4" stylename="Display Bold" filename="Source Serif 4-Display Bold.ttf" postscriptfontname="SourceSerif4Roman-DisplayBold" stylemapfamilyname="Source Serif 4 Display Bold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ <instance name="Source Serif 4 Display Black" familyname="Source Serif 4" stylename="Display Black" filename="Source Serif 4-Display Black.ttf" postscriptfontname="SourceSerif4Roman-DisplayBlack" stylemapfamilyname="Source Serif 4 Display Black" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ <kerning/>
+ <info/>
+ </instance>
+ </instances>
+ <lib>
+ <dict>
+ <key>public.skipExportGlyphs</key>
+ <array>
+ <string>caron.alt</string>
+ <string>commabelowcmb.alt</string>
+ <string>tonos.cap</string>
+ <string>f.ligalong</string>
+ <string>dieresiscmb.tight</string>
+ <string>IJ</string>
+ <string>Tbar</string>
+ <string>colontriangularmod</string>
+ <string>crossmark</string>
+ <string>ij</string>
+ <string>overline</string>
+ <string>similar</string>
+ <string>tbar</string>
+ <string>triangularbullet</string>
+ <string>turkicdsccmb</string>
+ </array>
+ </dict>
+ </lib>
+</designspace>
diff --git a/Tests/designspaceLib/data/split_output/AktivGroteskVF_Italics_Wght.designspace b/Tests/designspaceLib/data/split_output/AktivGroteskVF_Italics_Wght.designspace
new file mode 100644
index 00000000..dc38cd7d
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/AktivGroteskVF_Italics_Wght.designspace
@@ -0,0 +1,96 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes>
+ <axis tag="wght" name="Weight" minimum="100" maximum="900" default="400">
+ <map input="100" output="22"/>
+ <map input="200" output="38"/>
+ <map input="300" output="57"/>
+ <map input="400" output="84"/>
+ <map input="500" output="98"/>
+ <map input="600" output="115"/>
+ <map input="700" output="133"/>
+ <map input="800" output="158"/>
+ <map input="900" output="185"/>
+ </axis>
+ </axes>
+ <rules processing="last">
+ <rule name="BRACKET.CYR">
+ <sub name="ghe.loclSRB" with="ghe.ital.loclSRB"/>
+ <sub name="ghe.loclMKD" with="ghe.ital.loclMKD"/>
+ <sub name="de.loclMKDSRB" with="de.ital.loclMKDSRB"/>
+ <sub name="pe.loclMKDSRB" with="pe.ital.loclMKDSRB"/>
+ <sub name="te.loclMKDSRB" with="te.ital.loclMKDSRB"/>
+ <sub name="gje.loclMKD" with="gje.ital.loclMKD"/>
+ <sub name="sha.loclMKDSRB" with="sha.ital.loclMKDSRB"/>
+ </rule>
+ </rules>
+ <sources>
+ <source filename="../AktivGrotesk_HairIt.ufo" name="Aktiv Grotesk Hair Italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_It.ufo" name="Aktiv Grotesk Italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_It.ufo" name="Aktiv Grotesk Italic {126,100,1}" layer="{126,100,1}">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_BlkIt.ufo" name="Aktiv Grotesk Black Italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance name="Aktiv Grotesk Hair Italic" familyname="Aktiv Grotesk" stylename="Hair Italic" filename="../instances/AktivGrotesk_HairIt.ufo" postscriptfontname="AktivGrotesk-HairItalic" stylemapfamilyname="Aktiv Grotesk Hair" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Thin Italic" familyname="Aktiv Grotesk" stylename="Thin Italic" filename="../instances/AktivGrotesk_ThIt.ufo" postscriptfontname="AktivGrotesk-ThinItalic" stylemapfamilyname="Aktiv Grotesk Thin" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Light Italic" familyname="Aktiv Grotesk" stylename="Light Italic" filename="../instances/AktivGrotesk_LtIt.ufo" postscriptfontname="AktivGrotesk-LightItalic" stylemapfamilyname="Aktiv Grotesk Light" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Italic" familyname="Aktiv Grotesk" stylename="Italic" filename="../instances/AktivGrotesk_It.ufo" postscriptfontname="AktivGrotesk-Italic" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Medium Italic" familyname="Aktiv Grotesk" stylename="Medium Italic" filename="../instances/AktivGrotesk_MdIt.ufo" postscriptfontname="AktivGrotesk-MediumItalic" stylemapfamilyname="Aktiv Grotesk Medium" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk SemiBold Italic" familyname="Aktiv Grotesk" stylename="SemiBold Italic" filename="../../build/instances/AktivGrotesk_SBdIt.ufo" postscriptfontname="AktivGrotesk-SemiBoldItalic" stylemapfamilyname="Aktiv Grotesk SemiBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Bold Italic" familyname="Aktiv Grotesk" stylename="Bold Italic" filename="../instances/AktivGrotesk_BdIt.ufo" postscriptfontname="AktivGrotesk-BoldItalic" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="bold italic">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk XBold Italic" familyname="Aktiv Grotesk" stylename="XBold Italic" filename="../instances/AktivGrotesk_XBdIt.ufo" postscriptfontname="AktivGrotesk-XBoldItalic" stylemapfamilyname="Aktiv Grotesk XBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Black Italic" familyname="Aktiv Grotesk" stylename="Black Italic" filename="../instances/AktivGrotesk_BlkIt.ufo" postscriptfontname="AktivGrotesk-BlackItalic" stylemapfamilyname="Aktiv Grotesk Black" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ </location>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/data/split_output/AktivGroteskVF_Italics_WghtWdth.designspace b/Tests/designspaceLib/data/split_output/AktivGroteskVF_Italics_WghtWdth.designspace
new file mode 100644
index 00000000..8cda9140
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/AktivGroteskVF_Italics_WghtWdth.designspace
@@ -0,0 +1,262 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes>
+ <axis tag="wght" name="Weight" minimum="100" maximum="900" default="400">
+ <map input="100" output="22"/>
+ <map input="200" output="38"/>
+ <map input="300" output="57"/>
+ <map input="400" output="84"/>
+ <map input="500" output="98"/>
+ <map input="600" output="115"/>
+ <map input="700" output="133"/>
+ <map input="800" output="158"/>
+ <map input="900" output="185"/>
+ </axis>
+ <axis tag="wdth" name="Width" minimum="75" maximum="125" default="100"/>
+ </axes>
+ <rules processing="last">
+ <rule name="BRACKET.CYR">
+ <sub name="ghe.loclSRB" with="ghe.ital.loclSRB"/>
+ <sub name="ghe.loclMKD" with="ghe.ital.loclMKD"/>
+ <sub name="de.loclMKDSRB" with="de.ital.loclMKDSRB"/>
+ <sub name="pe.loclMKDSRB" with="pe.ital.loclMKDSRB"/>
+ <sub name="te.loclMKDSRB" with="te.ital.loclMKDSRB"/>
+ <sub name="gje.loclMKD" with="gje.ital.loclMKD"/>
+ <sub name="sha.loclMKDSRB" with="sha.ital.loclMKDSRB"/>
+ </rule>
+ <rule name="BRACKET.116.185">
+ <conditionset>
+ <condition name="Weight" minimum="116" maximum="185"/>
+ <condition name="Width" minimum="75" maximum="97.5"/>
+ </conditionset>
+ <sub name="cent" with="cent.BRACKET.130"/>
+ <sub name="dollar" with="dollar.BRACKET.130"/>
+ </rule>
+ </rules>
+ <sources>
+ <source filename="../AktivGroteskCd_HairIt.ufo" name="Aktiv Grotesk Cd Hair Italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_HairIt.ufo" name="Aktiv Grotesk Hair Italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_HairIt.ufo" name="Aktiv Grotesk Ex Hair Italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskCd_It.ufo" name="Aktiv Grotesk Cd Italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_It.ufo" name="Aktiv Grotesk Italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_It.ufo" name="Aktiv Grotesk Italic {126,100,1}" layer="{126,100,1}">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_It.ufo" name="Aktiv Grotesk Ex Italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskCd_BlkIt.ufo" name="Aktiv Grotesk Cd Black Italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_BlkIt.ufo" name="Aktiv Grotesk Black Italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_BlkIt.ufo" name="Aktiv Grotesk Ex Black Italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance name="Aktiv Grotesk Cd Hair Italic" familyname="Aktiv Grotesk" stylename="Cd Hair Italic" filename="../instances/AktivGroteskCd_HairIt.ufo" postscriptfontname="AktivGrotesk-CdHairItalic" stylemapfamilyname="Aktiv Grotesk Cd Hair" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Hair Italic" familyname="Aktiv Grotesk" stylename="Hair Italic" filename="../instances/AktivGrotesk_HairIt.ufo" postscriptfontname="AktivGrotesk-HairItalic" stylemapfamilyname="Aktiv Grotesk Hair" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Hair Italic" familyname="Aktiv Grotesk" stylename="Ex Hair Italic" filename="../instances/AktivGroteskEx_HairIt.ufo" postscriptfontname="AktivGrotesk-ExHairItalic" stylemapfamilyname="Aktiv Grotesk Ex Hair" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Thin Italic" familyname="Aktiv Grotesk" stylename="Cd Thin Italic" filename="../instances/AktivGroteskCd_ThIt.ufo" postscriptfontname="AktivGrotesk-CdThinItalic" stylemapfamilyname="Aktiv Grotesk Cd Thin" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Thin Italic" familyname="Aktiv Grotesk" stylename="Thin Italic" filename="../instances/AktivGrotesk_ThIt.ufo" postscriptfontname="AktivGrotesk-ThinItalic" stylemapfamilyname="Aktiv Grotesk Thin" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Thin Italic" familyname="Aktiv Grotesk" stylename="Ex Thin Italic" filename="../instances/AktivGroteskEx_ThIt.ufo" postscriptfontname="AktivGrotesk-ExThinItalic" stylemapfamilyname="Aktiv Grotesk Ex Thin" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Light Italic" familyname="Aktiv Grotesk" stylename="Cd Light Italic" filename="../instances/AktivGroteskCd_LtIt.ufo" postscriptfontname="AktivGrotesk-CdLightItalic" stylemapfamilyname="Aktiv Grotesk Cd Light" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Light Italic" familyname="Aktiv Grotesk" stylename="Light Italic" filename="../instances/AktivGrotesk_LtIt.ufo" postscriptfontname="AktivGrotesk-LightItalic" stylemapfamilyname="Aktiv Grotesk Light" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Light Italic" familyname="Aktiv Grotesk" stylename="Ex Light Italic" filename="../instances/AktivGroteskEx_LtIt.ufo" postscriptfontname="AktivGrotesk-ExLightItalic" stylemapfamilyname="Aktiv Grotesk Ex Light" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Italic" familyname="Aktiv Grotesk" stylename="Cd Italic" filename="../instances/AktivGroteskCd_It.ufo" postscriptfontname="AktivGrotesk-CdItalic" stylemapfamilyname="Aktiv Grotesk Cd" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Italic" familyname="Aktiv Grotesk" stylename="Italic" filename="../instances/AktivGrotesk_It.ufo" postscriptfontname="AktivGrotesk-Italic" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Italic" familyname="Aktiv Grotesk" stylename="Ex Italic" filename="../instances/AktivGroteskEx_It.ufo" postscriptfontname="AktivGrotesk-ExItalic" stylemapfamilyname="Aktiv Grotesk Ex" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Medium Italic" familyname="Aktiv Grotesk" stylename="Cd Medium Italic" filename="../instances/AktivGroteskCd_MdIt.ufo" postscriptfontname="AktivGrotesk-CdMediumItalic" stylemapfamilyname="Aktiv Grotesk Cd Medium" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Medium Italic" familyname="Aktiv Grotesk" stylename="Medium Italic" filename="../instances/AktivGrotesk_MdIt.ufo" postscriptfontname="AktivGrotesk-MediumItalic" stylemapfamilyname="Aktiv Grotesk Medium" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Medium Italic" familyname="Aktiv Grotesk" stylename="Ex Medium Italic" filename="../instances/AktivGroteskEx_MdIt.ufo" postscriptfontname="AktivGrotesk-ExMediumItalic" stylemapfamilyname="Aktiv Grotesk Ex Medium" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd SemiBold Italic" familyname="Aktiv Grotesk" stylename="Cd SemiBold Italic" filename="../../build/instances/AktivGroteskCd_SBdIt.ufo" postscriptfontname="AktivGrotesk-CdSemiBoldItalic" stylemapfamilyname="Aktiv Grotesk Cd SemiBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk SemiBold Italic" familyname="Aktiv Grotesk" stylename="SemiBold Italic" filename="../../build/instances/AktivGrotesk_SBdIt.ufo" postscriptfontname="AktivGrotesk-SemiBoldItalic" stylemapfamilyname="Aktiv Grotesk SemiBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex SemiBold Italic" familyname="Aktiv Grotesk" stylename="Ex SemiBold Italic" filename="../../build/instances/AktivGroteskEx_SBdIt.ufo" postscriptfontname="AktivGrotesk-ExSemiBoldItalic" stylemapfamilyname="Aktiv Grotesk Ex SemiBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Bold Italic" familyname="Aktiv Grotesk" stylename="Cd Bold Italic" filename="../instances/AktivGroteskCd_BdIt.ufo" postscriptfontname="AktivGrotesk-CdBoldItalic" stylemapfamilyname="Aktiv Grotesk Cd" stylemapstylename="bold italic">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Bold Italic" familyname="Aktiv Grotesk" stylename="Bold Italic" filename="../instances/AktivGrotesk_BdIt.ufo" postscriptfontname="AktivGrotesk-BoldItalic" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="bold italic">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Bold Italic" familyname="Aktiv Grotesk" stylename="Ex Bold Italic" filename="../instances/AktivGroteskEx_BdIt.ufo" postscriptfontname="AktivGrotesk-ExBoldItalic" stylemapfamilyname="Aktiv Grotesk Ex" stylemapstylename="bold italic">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd XBold Italic" familyname="Aktiv Grotesk" stylename="Cd XBold Italic" filename="../instances/AktivGroteskCd_XBdIt.ufo" postscriptfontname="AktivGrotesk-CdXBoldItalic" stylemapfamilyname="Aktiv Grotesk Cd XBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk XBold Italic" familyname="Aktiv Grotesk" stylename="XBold Italic" filename="../instances/AktivGrotesk_XBdIt.ufo" postscriptfontname="AktivGrotesk-XBoldItalic" stylemapfamilyname="Aktiv Grotesk XBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex XBold Italic" familyname="Aktiv Grotesk" stylename="Ex XBold Italic" filename="../instances/AktivGroteskEx_XBdIt.ufo" postscriptfontname="AktivGrotesk-ExXBoldItalic" stylemapfamilyname="Aktiv Grotesk Ex XBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Black Italic" familyname="Aktiv Grotesk" stylename="Cd Black Italic" filename="../instances/AktivGroteskCd_BlkIt.ufo" postscriptfontname="AktivGrotesk-CdBlackItalic" stylemapfamilyname="Aktiv Grotesk Cd Black" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Black Italic" familyname="Aktiv Grotesk" stylename="Black Italic" filename="../instances/AktivGrotesk_BlkIt.ufo" postscriptfontname="AktivGrotesk-BlackItalic" stylemapfamilyname="Aktiv Grotesk Black" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Black Italic" familyname="Aktiv Grotesk" stylename="Ex Black Italic" filename="../instances/AktivGroteskEx_BlkIt.ufo" postscriptfontname="AktivGrotesk-ExBlackItalic" stylemapfamilyname="Aktiv Grotesk Ex Black" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/data/split_output/AktivGroteskVF_Wght.designspace b/Tests/designspaceLib/data/split_output/AktivGroteskVF_Wght.designspace
new file mode 100644
index 00000000..db621655
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/AktivGroteskVF_Wght.designspace
@@ -0,0 +1,85 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes>
+ <axis tag="wght" name="Weight" minimum="100" maximum="900" default="400">
+ <map input="100" output="22"/>
+ <map input="200" output="38"/>
+ <map input="300" output="57"/>
+ <map input="400" output="84"/>
+ <map input="500" output="98"/>
+ <map input="600" output="115"/>
+ <map input="700" output="133"/>
+ <map input="800" output="158"/>
+ <map input="900" output="185"/>
+ </axis>
+ </axes>
+ <sources>
+ <source filename="../AktivGrotesk_Hair.ufo" name="Aktiv Grotesk Hair">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Rg.ufo" name="Aktiv Grotesk Regular" familyname="Aktiv Grotesk">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Rg.ufo" name="Aktiv Grotesk Regular" layer="{126,100,0}">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Blk.ufo" name="Aktiv Grotesk Black">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance name="Aktiv Grotesk Hair" familyname="Aktiv Grotesk" stylename="Hair" filename="../instances/AktivGrotesk_Hair.ufo" postscriptfontname="AktivGrotesk-Hair" stylemapfamilyname="Aktiv Grotesk Hair" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Thin" familyname="Aktiv Grotesk" stylename="Thin" filename="../instances/AktivGrotesk_Th.ufo" postscriptfontname="AktivGrotesk-Thin" stylemapfamilyname="Aktiv Grotesk Thin" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Light" familyname="Aktiv Grotesk" stylename="Light" filename="../instances/AktivGrotesk_Lt.ufo" postscriptfontname="AktivGrotesk-Light" stylemapfamilyname="Aktiv Grotesk Light" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk " familyname="Aktiv Grotesk" stylename="" filename="../instances/AktivGrotesk_Rg.ufo" postscriptfontname="AktivGrotesk-" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Medium" familyname="Aktiv Grotesk" stylename="Medium" filename="../instances/AktivGrotesk_Md.ufo" postscriptfontname="AktivGrotesk-Medium" stylemapfamilyname="Aktiv Grotesk Medium" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk SemiBold" familyname="Aktiv Grotesk" stylename="SemiBold" filename="../../build/instances/AktivGrotesk_SBd.ufo" postscriptfontname="AktivGrotesk-SemiBold" stylemapfamilyname="Aktiv Grotesk SemiBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Bold" familyname="Aktiv Grotesk" stylename="Bold" filename="../instances/AktivGrotesk_Bd.ufo" postscriptfontname="AktivGrotesk-Bold" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="bold">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk XBold" familyname="Aktiv Grotesk" stylename="XBold" filename="../instances/AktivGrotesk_XBd.ufo" postscriptfontname="AktivGrotesk-XBold" stylemapfamilyname="Aktiv Grotesk XBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Black" familyname="Aktiv Grotesk" stylename="Black" filename="../instances/AktivGrotesk_Blk.ufo" postscriptfontname="AktivGrotesk-Black" stylemapfamilyname="Aktiv Grotesk Black" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ </location>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/data/split_output/AktivGroteskVF_WghtWdth.designspace b/Tests/designspaceLib/data/split_output/AktivGroteskVF_WghtWdth.designspace
new file mode 100644
index 00000000..113c6897
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/AktivGroteskVF_WghtWdth.designspace
@@ -0,0 +1,253 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes>
+ <axis tag="wght" name="Weight" minimum="100" maximum="900" default="400">
+ <map input="100" output="22"/>
+ <map input="200" output="38"/>
+ <map input="300" output="57"/>
+ <map input="400" output="84"/>
+ <map input="500" output="98"/>
+ <map input="600" output="115"/>
+ <map input="700" output="133"/>
+ <map input="800" output="158"/>
+ <map input="900" output="185"/>
+ </axis>
+ <axis tag="wdth" name="Width" minimum="75" maximum="125" default="100"/>
+ </axes>
+ <rules processing="last">
+ <rule name="BRACKET.116.185">
+ <conditionset>
+ <condition name="Weight" minimum="116" maximum="185"/>
+ <condition name="Width" minimum="75" maximum="97.5"/>
+ </conditionset>
+ <sub name="cent" with="cent.BRACKET.130"/>
+ <sub name="dollar" with="dollar.BRACKET.130"/>
+ </rule>
+ </rules>
+ <sources>
+ <source filename="../AktivGroteskCd_Hair.ufo" name="Aktiv Grotesk Cd Hair">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Hair.ufo" name="Aktiv Grotesk Hair">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_Hair.ufo" name="Aktiv Grotesk Ex Hair">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskCd_Rg.ufo" name="Aktiv Grotesk Cd">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Rg.ufo" name="Aktiv Grotesk Regular" familyname="Aktiv Grotesk">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Rg.ufo" name="Aktiv Grotesk Regular" layer="{126,100,0}">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_Rg.ufo" name="Aktiv Grotesk Ex">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskCd_Blk.ufo" name="Aktiv Grotesk Cd Black">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Blk.ufo" name="Aktiv Grotesk Black">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_Blk.ufo" name="Aktiv Grotesk Ex Black">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance name="Aktiv Grotesk Cd Hair" familyname="Aktiv Grotesk" stylename="Cd Hair" filename="../instances/AktivGroteskCd_Hair.ufo" postscriptfontname="AktivGrotesk-CdHair" stylemapfamilyname="Aktiv Grotesk Cd Hair" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Hair" familyname="Aktiv Grotesk" stylename="Hair" filename="../instances/AktivGrotesk_Hair.ufo" postscriptfontname="AktivGrotesk-Hair" stylemapfamilyname="Aktiv Grotesk Hair" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Hair" familyname="Aktiv Grotesk" stylename="Ex Hair" filename="../instances/AktivGroteskEx_Hair.ufo" postscriptfontname="AktivGrotesk-ExHair" stylemapfamilyname="Aktiv Grotesk Ex Hair" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Thin" familyname="Aktiv Grotesk" stylename="Cd Thin" filename="../instances/AktivGroteskCd_Th.ufo" postscriptfontname="AktivGrotesk-CdThin" stylemapfamilyname="Aktiv Grotesk Cd Thin" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Thin" familyname="Aktiv Grotesk" stylename="Thin" filename="../instances/AktivGrotesk_Th.ufo" postscriptfontname="AktivGrotesk-Thin" stylemapfamilyname="Aktiv Grotesk Thin" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Thin" familyname="Aktiv Grotesk" stylename="Ex Thin" filename="../instances/AktivGroteskEx_Th.ufo" postscriptfontname="AktivGrotesk-ExThin" stylemapfamilyname="Aktiv Grotesk Ex Thin" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Light" familyname="Aktiv Grotesk" stylename="Cd Light" filename="../instances/AktivGroteskCd_Lt.ufo" postscriptfontname="AktivGrotesk-CdLight" stylemapfamilyname="Aktiv Grotesk Cd Light" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Light" familyname="Aktiv Grotesk" stylename="Light" filename="../instances/AktivGrotesk_Lt.ufo" postscriptfontname="AktivGrotesk-Light" stylemapfamilyname="Aktiv Grotesk Light" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Light" familyname="Aktiv Grotesk" stylename="Ex Light" filename="../instances/AktivGroteskEx_Lt.ufo" postscriptfontname="AktivGrotesk-ExLight" stylemapfamilyname="Aktiv Grotesk Ex Light" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd" familyname="Aktiv Grotesk" stylename="Cd" filename="../instances/AktivGroteskCd_Rg.ufo" postscriptfontname="AktivGrotesk-Cd" stylemapfamilyname="Aktiv Grotesk Cd" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk " familyname="Aktiv Grotesk" stylename="" filename="../instances/AktivGrotesk_Rg.ufo" postscriptfontname="AktivGrotesk-" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex" familyname="Aktiv Grotesk" stylename="Ex" filename="../instances/AktivGroteskEx_Rg.ufo" postscriptfontname="AktivGrotesk-Ex" stylemapfamilyname="Aktiv Grotesk Ex" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Medium" familyname="Aktiv Grotesk" stylename="Cd Medium" filename="../instances/AktivGroteskCd_Md.ufo" postscriptfontname="AktivGrotesk-CdMedium" stylemapfamilyname="Aktiv Grotesk Cd Medium" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Medium" familyname="Aktiv Grotesk" stylename="Medium" filename="../instances/AktivGrotesk_Md.ufo" postscriptfontname="AktivGrotesk-Medium" stylemapfamilyname="Aktiv Grotesk Medium" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Medium" familyname="Aktiv Grotesk" stylename="Ex Medium" filename="../instances/AktivGroteskEx_Md.ufo" postscriptfontname="AktivGrotesk-ExMedium" stylemapfamilyname="Aktiv Grotesk Ex Medium" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd SemiBold" familyname="Aktiv Grotesk" stylename="Cd SemiBold" filename="../../build/instances/AktivGroteskCd_SBd.ufo" postscriptfontname="AktivGrotesk-CdSemiBold" stylemapfamilyname="Aktiv Grotesk Cd SemiBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk SemiBold" familyname="Aktiv Grotesk" stylename="SemiBold" filename="../../build/instances/AktivGrotesk_SBd.ufo" postscriptfontname="AktivGrotesk-SemiBold" stylemapfamilyname="Aktiv Grotesk SemiBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex SemiBold" familyname="Aktiv Grotesk" stylename="Ex SemiBold" filename="../../build/instances/AktivGroteskEx_SBd.ufo" postscriptfontname="AktivGrotesk-ExSemiBold" stylemapfamilyname="Aktiv Grotesk Ex SemiBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Bold" familyname="Aktiv Grotesk" stylename="Cd Bold" filename="../instances/AktivGroteskCd_Bd.ufo" postscriptfontname="AktivGrotesk-CdBold" stylemapfamilyname="Aktiv Grotesk Cd" stylemapstylename="bold">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Bold" familyname="Aktiv Grotesk" stylename="Bold" filename="../instances/AktivGrotesk_Bd.ufo" postscriptfontname="AktivGrotesk-Bold" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="bold">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Bold" familyname="Aktiv Grotesk" stylename="Ex Bold" filename="../instances/AktivGroteskEx_Bd.ufo" postscriptfontname="AktivGrotesk-ExBold" stylemapfamilyname="Aktiv Grotesk Ex" stylemapstylename="bold">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd XBold" familyname="Aktiv Grotesk" stylename="Cd XBold" filename="../instances/AktivGroteskCd_XBd.ufo" postscriptfontname="AktivGrotesk-CdXBold" stylemapfamilyname="Aktiv Grotesk Cd XBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk XBold" familyname="Aktiv Grotesk" stylename="XBold" filename="../instances/AktivGrotesk_XBd.ufo" postscriptfontname="AktivGrotesk-XBold" stylemapfamilyname="Aktiv Grotesk XBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex XBold" familyname="Aktiv Grotesk" stylename="Ex XBold" filename="../instances/AktivGroteskEx_XBd.ufo" postscriptfontname="AktivGrotesk-ExXBold" stylemapfamilyname="Aktiv Grotesk Ex XBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Black" familyname="Aktiv Grotesk" stylename="Cd Black" filename="../instances/AktivGroteskCd_Blk.ufo" postscriptfontname="AktivGrotesk-CdBlack" stylemapfamilyname="Aktiv Grotesk Cd Black" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Black" familyname="Aktiv Grotesk" stylename="Black" filename="../instances/AktivGrotesk_Blk.ufo" postscriptfontname="AktivGrotesk-Black" stylemapfamilyname="Aktiv Grotesk Black" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Black" familyname="Aktiv Grotesk" stylename="Ex Black" filename="../instances/AktivGroteskEx_Blk.ufo" postscriptfontname="AktivGrotesk-ExBlack" stylemapfamilyname="Aktiv Grotesk Ex Black" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ </location>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/data/split_output/AktivGroteskVF_WghtWdthItal.designspace b/Tests/designspaceLib/data/split_output/AktivGroteskVF_WghtWdthItal.designspace
new file mode 100644
index 00000000..4a1ef48f
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/AktivGroteskVF_WghtWdthItal.designspace
@@ -0,0 +1,562 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes>
+ <axis tag="wght" name="Weight" minimum="100" maximum="900" default="400">
+ <map input="100" output="22"/>
+ <map input="200" output="38"/>
+ <map input="300" output="57"/>
+ <map input="400" output="84"/>
+ <map input="500" output="98"/>
+ <map input="600" output="115"/>
+ <map input="700" output="133"/>
+ <map input="800" output="158"/>
+ <map input="900" output="185"/>
+ </axis>
+ <axis tag="wdth" name="Width" minimum="75" maximum="125" default="100"/>
+ <axis tag="ital" name="Italic" minimum="0" maximum="1" default="0"/>
+ </axes>
+ <rules processing="last">
+ <rule name="BRACKET.CYR">
+ <conditionset>
+ <condition name="Italic" minimum="0.1" maximum="1"/>
+ </conditionset>
+ <sub name="ghe.loclSRB" with="ghe.ital.loclSRB"/>
+ <sub name="ghe.loclMKD" with="ghe.ital.loclMKD"/>
+ <sub name="de.loclMKDSRB" with="de.ital.loclMKDSRB"/>
+ <sub name="pe.loclMKDSRB" with="pe.ital.loclMKDSRB"/>
+ <sub name="te.loclMKDSRB" with="te.ital.loclMKDSRB"/>
+ <sub name="gje.loclMKD" with="gje.ital.loclMKD"/>
+ <sub name="sha.loclMKDSRB" with="sha.ital.loclMKDSRB"/>
+ </rule>
+ <rule name="BRACKET.116.185">
+ <conditionset>
+ <condition name="Weight" minimum="116" maximum="185"/>
+ <condition name="Width" minimum="75" maximum="97.5"/>
+ </conditionset>
+ <sub name="cent" with="cent.BRACKET.130"/>
+ <sub name="dollar" with="dollar.BRACKET.130"/>
+ </rule>
+ </rules>
+ <sources>
+ <source filename="../AktivGroteskCd_Hair.ufo" name="Aktiv Grotesk Cd Hair">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskCd_HairIt.ufo" name="Aktiv Grotesk Cd Hair Italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Hair.ufo" name="Aktiv Grotesk Hair">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_HairIt.ufo" name="Aktiv Grotesk Hair Italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_Hair.ufo" name="Aktiv Grotesk Ex Hair">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_HairIt.ufo" name="Aktiv Grotesk Ex Hair Italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskCd_Rg.ufo" name="Aktiv Grotesk Cd">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskCd_It.ufo" name="Aktiv Grotesk Cd Italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Rg.ufo" name="Aktiv Grotesk Regular" familyname="Aktiv Grotesk">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Rg.ufo" name="Aktiv Grotesk Regular" layer="{126,100,0}">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_It.ufo" name="Aktiv Grotesk Italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_It.ufo" name="Aktiv Grotesk Italic {126,100,1}" layer="{126,100,1}">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_Rg.ufo" name="Aktiv Grotesk Ex">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_It.ufo" name="Aktiv Grotesk Ex Italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskCd_Blk.ufo" name="Aktiv Grotesk Cd Black">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskCd_BlkIt.ufo" name="Aktiv Grotesk Cd Black Italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Blk.ufo" name="Aktiv Grotesk Black">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_BlkIt.ufo" name="Aktiv Grotesk Black Italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_Blk.ufo" name="Aktiv Grotesk Ex Black">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_BlkIt.ufo" name="Aktiv Grotesk Ex Black Italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance name="Aktiv Grotesk Cd Hair" familyname="Aktiv Grotesk" stylename="Cd Hair" filename="../instances/AktivGroteskCd_Hair.ufo" postscriptfontname="AktivGrotesk-CdHair" stylemapfamilyname="Aktiv Grotesk Cd Hair" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Hair Italic" familyname="Aktiv Grotesk" stylename="Cd Hair Italic" filename="../instances/AktivGroteskCd_HairIt.ufo" postscriptfontname="AktivGrotesk-CdHairItalic" stylemapfamilyname="Aktiv Grotesk Cd Hair" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Hair" familyname="Aktiv Grotesk" stylename="Hair" filename="../instances/AktivGrotesk_Hair.ufo" postscriptfontname="AktivGrotesk-Hair" stylemapfamilyname="Aktiv Grotesk Hair" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Hair Italic" familyname="Aktiv Grotesk" stylename="Hair Italic" filename="../instances/AktivGrotesk_HairIt.ufo" postscriptfontname="AktivGrotesk-HairItalic" stylemapfamilyname="Aktiv Grotesk Hair" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Hair" familyname="Aktiv Grotesk" stylename="Ex Hair" filename="../instances/AktivGroteskEx_Hair.ufo" postscriptfontname="AktivGrotesk-ExHair" stylemapfamilyname="Aktiv Grotesk Ex Hair" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Hair Italic" familyname="Aktiv Grotesk" stylename="Ex Hair Italic" filename="../instances/AktivGroteskEx_HairIt.ufo" postscriptfontname="AktivGrotesk-ExHairItalic" stylemapfamilyname="Aktiv Grotesk Ex Hair" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Thin" familyname="Aktiv Grotesk" stylename="Cd Thin" filename="../instances/AktivGroteskCd_Th.ufo" postscriptfontname="AktivGrotesk-CdThin" stylemapfamilyname="Aktiv Grotesk Cd Thin" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Thin Italic" familyname="Aktiv Grotesk" stylename="Cd Thin Italic" filename="../instances/AktivGroteskCd_ThIt.ufo" postscriptfontname="AktivGrotesk-CdThinItalic" stylemapfamilyname="Aktiv Grotesk Cd Thin" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Thin" familyname="Aktiv Grotesk" stylename="Thin" filename="../instances/AktivGrotesk_Th.ufo" postscriptfontname="AktivGrotesk-Thin" stylemapfamilyname="Aktiv Grotesk Thin" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Thin Italic" familyname="Aktiv Grotesk" stylename="Thin Italic" filename="../instances/AktivGrotesk_ThIt.ufo" postscriptfontname="AktivGrotesk-ThinItalic" stylemapfamilyname="Aktiv Grotesk Thin" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Thin" familyname="Aktiv Grotesk" stylename="Ex Thin" filename="../instances/AktivGroteskEx_Th.ufo" postscriptfontname="AktivGrotesk-ExThin" stylemapfamilyname="Aktiv Grotesk Ex Thin" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Thin Italic" familyname="Aktiv Grotesk" stylename="Ex Thin Italic" filename="../instances/AktivGroteskEx_ThIt.ufo" postscriptfontname="AktivGrotesk-ExThinItalic" stylemapfamilyname="Aktiv Grotesk Ex Thin" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Light" familyname="Aktiv Grotesk" stylename="Cd Light" filename="../instances/AktivGroteskCd_Lt.ufo" postscriptfontname="AktivGrotesk-CdLight" stylemapfamilyname="Aktiv Grotesk Cd Light" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Light Italic" familyname="Aktiv Grotesk" stylename="Cd Light Italic" filename="../instances/AktivGroteskCd_LtIt.ufo" postscriptfontname="AktivGrotesk-CdLightItalic" stylemapfamilyname="Aktiv Grotesk Cd Light" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Light" familyname="Aktiv Grotesk" stylename="Light" filename="../instances/AktivGrotesk_Lt.ufo" postscriptfontname="AktivGrotesk-Light" stylemapfamilyname="Aktiv Grotesk Light" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Light Italic" familyname="Aktiv Grotesk" stylename="Light Italic" filename="../instances/AktivGrotesk_LtIt.ufo" postscriptfontname="AktivGrotesk-LightItalic" stylemapfamilyname="Aktiv Grotesk Light" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Light" familyname="Aktiv Grotesk" stylename="Ex Light" filename="../instances/AktivGroteskEx_Lt.ufo" postscriptfontname="AktivGrotesk-ExLight" stylemapfamilyname="Aktiv Grotesk Ex Light" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Light Italic" familyname="Aktiv Grotesk" stylename="Ex Light Italic" filename="../instances/AktivGroteskEx_LtIt.ufo" postscriptfontname="AktivGrotesk-ExLightItalic" stylemapfamilyname="Aktiv Grotesk Ex Light" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd" familyname="Aktiv Grotesk" stylename="Cd" filename="../instances/AktivGroteskCd_Rg.ufo" postscriptfontname="AktivGrotesk-Cd" stylemapfamilyname="Aktiv Grotesk Cd" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Italic" familyname="Aktiv Grotesk" stylename="Cd Italic" filename="../instances/AktivGroteskCd_It.ufo" postscriptfontname="AktivGrotesk-CdItalic" stylemapfamilyname="Aktiv Grotesk Cd" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk " familyname="Aktiv Grotesk" stylename="" filename="../instances/AktivGrotesk_Rg.ufo" postscriptfontname="AktivGrotesk-" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Italic" familyname="Aktiv Grotesk" stylename="Italic" filename="../instances/AktivGrotesk_It.ufo" postscriptfontname="AktivGrotesk-Italic" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex" familyname="Aktiv Grotesk" stylename="Ex" filename="../instances/AktivGroteskEx_Rg.ufo" postscriptfontname="AktivGrotesk-Ex" stylemapfamilyname="Aktiv Grotesk Ex" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Italic" familyname="Aktiv Grotesk" stylename="Ex Italic" filename="../instances/AktivGroteskEx_It.ufo" postscriptfontname="AktivGrotesk-ExItalic" stylemapfamilyname="Aktiv Grotesk Ex" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Medium" familyname="Aktiv Grotesk" stylename="Cd Medium" filename="../instances/AktivGroteskCd_Md.ufo" postscriptfontname="AktivGrotesk-CdMedium" stylemapfamilyname="Aktiv Grotesk Cd Medium" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Medium Italic" familyname="Aktiv Grotesk" stylename="Cd Medium Italic" filename="../instances/AktivGroteskCd_MdIt.ufo" postscriptfontname="AktivGrotesk-CdMediumItalic" stylemapfamilyname="Aktiv Grotesk Cd Medium" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Medium" familyname="Aktiv Grotesk" stylename="Medium" filename="../instances/AktivGrotesk_Md.ufo" postscriptfontname="AktivGrotesk-Medium" stylemapfamilyname="Aktiv Grotesk Medium" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Medium Italic" familyname="Aktiv Grotesk" stylename="Medium Italic" filename="../instances/AktivGrotesk_MdIt.ufo" postscriptfontname="AktivGrotesk-MediumItalic" stylemapfamilyname="Aktiv Grotesk Medium" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Medium" familyname="Aktiv Grotesk" stylename="Ex Medium" filename="../instances/AktivGroteskEx_Md.ufo" postscriptfontname="AktivGrotesk-ExMedium" stylemapfamilyname="Aktiv Grotesk Ex Medium" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Medium Italic" familyname="Aktiv Grotesk" stylename="Ex Medium Italic" filename="../instances/AktivGroteskEx_MdIt.ufo" postscriptfontname="AktivGrotesk-ExMediumItalic" stylemapfamilyname="Aktiv Grotesk Ex Medium" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd SemiBold" familyname="Aktiv Grotesk" stylename="Cd SemiBold" filename="../../build/instances/AktivGroteskCd_SBd.ufo" postscriptfontname="AktivGrotesk-CdSemiBold" stylemapfamilyname="Aktiv Grotesk Cd SemiBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd SemiBold Italic" familyname="Aktiv Grotesk" stylename="Cd SemiBold Italic" filename="../../build/instances/AktivGroteskCd_SBdIt.ufo" postscriptfontname="AktivGrotesk-CdSemiBoldItalic" stylemapfamilyname="Aktiv Grotesk Cd SemiBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk SemiBold" familyname="Aktiv Grotesk" stylename="SemiBold" filename="../../build/instances/AktivGrotesk_SBd.ufo" postscriptfontname="AktivGrotesk-SemiBold" stylemapfamilyname="Aktiv Grotesk SemiBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk SemiBold Italic" familyname="Aktiv Grotesk" stylename="SemiBold Italic" filename="../../build/instances/AktivGrotesk_SBdIt.ufo" postscriptfontname="AktivGrotesk-SemiBoldItalic" stylemapfamilyname="Aktiv Grotesk SemiBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex SemiBold" familyname="Aktiv Grotesk" stylename="Ex SemiBold" filename="../../build/instances/AktivGroteskEx_SBd.ufo" postscriptfontname="AktivGrotesk-ExSemiBold" stylemapfamilyname="Aktiv Grotesk Ex SemiBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex SemiBold Italic" familyname="Aktiv Grotesk" stylename="Ex SemiBold Italic" filename="../../build/instances/AktivGroteskEx_SBdIt.ufo" postscriptfontname="AktivGrotesk-ExSemiBoldItalic" stylemapfamilyname="Aktiv Grotesk Ex SemiBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Bold" familyname="Aktiv Grotesk" stylename="Cd Bold" filename="../instances/AktivGroteskCd_Bd.ufo" postscriptfontname="AktivGrotesk-CdBold" stylemapfamilyname="Aktiv Grotesk Cd" stylemapstylename="bold">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Bold Italic" familyname="Aktiv Grotesk" stylename="Cd Bold Italic" filename="../instances/AktivGroteskCd_BdIt.ufo" postscriptfontname="AktivGrotesk-CdBoldItalic" stylemapfamilyname="Aktiv Grotesk Cd" stylemapstylename="bold italic">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Bold" familyname="Aktiv Grotesk" stylename="Bold" filename="../instances/AktivGrotesk_Bd.ufo" postscriptfontname="AktivGrotesk-Bold" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="bold">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Bold Italic" familyname="Aktiv Grotesk" stylename="Bold Italic" filename="../instances/AktivGrotesk_BdIt.ufo" postscriptfontname="AktivGrotesk-BoldItalic" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="bold italic">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Bold" familyname="Aktiv Grotesk" stylename="Ex Bold" filename="../instances/AktivGroteskEx_Bd.ufo" postscriptfontname="AktivGrotesk-ExBold" stylemapfamilyname="Aktiv Grotesk Ex" stylemapstylename="bold">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Bold Italic" familyname="Aktiv Grotesk" stylename="Ex Bold Italic" filename="../instances/AktivGroteskEx_BdIt.ufo" postscriptfontname="AktivGrotesk-ExBoldItalic" stylemapfamilyname="Aktiv Grotesk Ex" stylemapstylename="bold italic">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd XBold" familyname="Aktiv Grotesk" stylename="Cd XBold" filename="../instances/AktivGroteskCd_XBd.ufo" postscriptfontname="AktivGrotesk-CdXBold" stylemapfamilyname="Aktiv Grotesk Cd XBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd XBold Italic" familyname="Aktiv Grotesk" stylename="Cd XBold Italic" filename="../instances/AktivGroteskCd_XBdIt.ufo" postscriptfontname="AktivGrotesk-CdXBoldItalic" stylemapfamilyname="Aktiv Grotesk Cd XBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk XBold" familyname="Aktiv Grotesk" stylename="XBold" filename="../instances/AktivGrotesk_XBd.ufo" postscriptfontname="AktivGrotesk-XBold" stylemapfamilyname="Aktiv Grotesk XBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk XBold Italic" familyname="Aktiv Grotesk" stylename="XBold Italic" filename="../instances/AktivGrotesk_XBdIt.ufo" postscriptfontname="AktivGrotesk-XBoldItalic" stylemapfamilyname="Aktiv Grotesk XBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex XBold" familyname="Aktiv Grotesk" stylename="Ex XBold" filename="../instances/AktivGroteskEx_XBd.ufo" postscriptfontname="AktivGrotesk-ExXBold" stylemapfamilyname="Aktiv Grotesk Ex XBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex XBold Italic" familyname="Aktiv Grotesk" stylename="Ex XBold Italic" filename="../instances/AktivGroteskEx_XBdIt.ufo" postscriptfontname="AktivGrotesk-ExXBoldItalic" stylemapfamilyname="Aktiv Grotesk Ex XBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Black" familyname="Aktiv Grotesk" stylename="Cd Black" filename="../instances/AktivGroteskCd_Blk.ufo" postscriptfontname="AktivGrotesk-CdBlack" stylemapfamilyname="Aktiv Grotesk Cd Black" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Black Italic" familyname="Aktiv Grotesk" stylename="Cd Black Italic" filename="../instances/AktivGroteskCd_BlkIt.ufo" postscriptfontname="AktivGrotesk-CdBlackItalic" stylemapfamilyname="Aktiv Grotesk Cd Black" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Black" familyname="Aktiv Grotesk" stylename="Black" filename="../instances/AktivGrotesk_Blk.ufo" postscriptfontname="AktivGrotesk-Black" stylemapfamilyname="Aktiv Grotesk Black" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Black Italic" familyname="Aktiv Grotesk" stylename="Black Italic" filename="../instances/AktivGrotesk_BlkIt.ufo" postscriptfontname="AktivGrotesk-BlackItalic" stylemapfamilyname="Aktiv Grotesk Black" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Black" familyname="Aktiv Grotesk" stylename="Ex Black" filename="../instances/AktivGroteskEx_Blk.ufo" postscriptfontname="AktivGrotesk-ExBlack" stylemapfamilyname="Aktiv Grotesk Ex Black" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Black Italic" familyname="Aktiv Grotesk" stylename="Ex Black Italic" filename="../instances/AktivGroteskEx_BlkIt.ufo" postscriptfontname="AktivGrotesk-ExBlackItalic" stylemapfamilyname="Aktiv Grotesk Ex Black" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/data/split_output/MutatorSansVariable_Weight.designspace b/Tests/designspaceLib/data/split_output/MutatorSansVariable_Weight.designspace
new file mode 100644
index 00000000..6dcc6002
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/MutatorSansVariable_Weight.designspace
@@ -0,0 +1,55 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes>
+ <axis tag="wght" name="weight" minimum="300" maximum="700" default="300">
+ <map input="300" output="0"/>
+ <map input="500" output="500"/>
+ <map input="700" output="1000"/>
+ </axis>
+ </axes>
+ <rules>
+ <rule name="fold_I_serifs">
+ <sub name="I" with="I.narrow"/>
+ </rule>
+ <rule name="fold_S_terminals">
+ <conditionset>
+ <condition name="weight" minimum="0" maximum="500"/>
+ </conditionset>
+ <sub name="S" with="S.closed"/>
+ </rule>
+ </rules>
+ <sources>
+ <source filename="../MutatorSansLightCondensed.ufo" name="master.MutatorMathTest.LightCondensed.0" familyname="MutatorMathTest" stylename="LightCondensed">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../MutatorSansBoldCondensed.ufo" name="master.MutatorMathTest.BoldCondensed.1" familyname="MutatorMathTest" stylename="BoldCondensed">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="../MutatorSansLightCondensed.ufo" name="support.crossbar" layer="support.crossbar">
+ <location>
+ <dimension name="weight" xvalue="700"/>
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance name="MutatorMathTest Sans Light Condensed" familyname="MutatorMathTest" stylename="Sans Light Condensed" filename="MutatorMathTest-Sans Light Condensed.ttf" postscriptfontname="MutatorMathTest-SansLightCondensed" stylemapfamilyname="MutatorMathTest Sans Light Condensed" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="MutatorMathTest Sans Bold Condensed" familyname="MutatorMathTest" stylename="Sans Bold Condensed" filename="MutatorMathTest-Sans Bold Condensed.ttf" postscriptfontname="MutatorMathTest-SansBoldCondensed" stylemapfamilyname="MutatorMathTest Sans Bold Condensed" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ </location>
+ </instance>
+ <instance name="MutatorMathTest Sans Light Condensed" familyname="MutatorMathTest" stylename="Sans Light Condensed" filename="MutatorMathTest-Sans Light Condensed.ttf" postscriptfontname="MutatorMathTest-SansLightCondensed" stylemapfamilyname="MutatorMathTest Sans Light Condensed" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ </location>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/data/split_output/MutatorSansVariable_Weight_Width.designspace b/Tests/designspaceLib/data/split_output/MutatorSansVariable_Weight_Width.designspace
new file mode 100644
index 00000000..e83be97f
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/MutatorSansVariable_Weight_Width.designspace
@@ -0,0 +1,129 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes>
+ <axis tag="wght" name="weight" minimum="300" maximum="700" default="300">
+ <map input="300" output="0"/>
+ <map input="500" output="500"/>
+ <map input="700" output="1000"/>
+ </axis>
+ <axis tag="wdth" name="width" minimum="50" maximum="200" default="50">
+ <map input="50" output="0"/>
+ <map input="100" output="500"/>
+ <map input="200" output="1000"/>
+ </axis>
+ </axes>
+ <rules>
+ <rule name="fold_I_serifs">
+ <conditionset>
+ <condition name="width" minimum="0" maximum="328"/>
+ </conditionset>
+ <sub name="I" with="I.narrow"/>
+ </rule>
+ <rule name="fold_S_terminals">
+ <conditionset>
+ <condition name="weight" minimum="0" maximum="500"/>
+ </conditionset>
+ <sub name="S" with="S.closed"/>
+ </rule>
+ </rules>
+ <sources>
+ <source filename="../MutatorSansLightCondensed.ufo" name="master.MutatorMathTest.LightCondensed.0" familyname="MutatorMathTest" stylename="LightCondensed">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="width" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../MutatorSansBoldCondensed.ufo" name="master.MutatorMathTest.BoldCondensed.1" familyname="MutatorMathTest" stylename="BoldCondensed">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="width" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../MutatorSansLightWide.ufo" name="master.MutatorMathTest.LightWide.2" familyname="MutatorMathTest" stylename="LightWide">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="width" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="../MutatorSansBoldWide.ufo" name="master.MutatorMathTest.BoldWide.3" familyname="MutatorMathTest" stylename="BoldWide">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="width" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="../MutatorSansLightCondensed.ufo" name="support.crossbar" layer="support.crossbar">
+ <location>
+ <dimension name="weight" xvalue="700"/>
+ <dimension name="width" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../MutatorSansLightCondensed.ufo" name="support.S.wide" layer="support.S.wide">
+ <location>
+ <dimension name="weight" xvalue="700"/>
+ <dimension name="width" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="../MutatorSansLightCondensed.ufo" name="support.S.middle" layer="support.S.middle">
+ <location>
+ <dimension name="weight" xvalue="700"/>
+ <dimension name="width" xvalue="569.078"/>
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance name="MutatorMathTest Sans Light Condensed" familyname="MutatorMathTest" stylename="Sans Light Condensed" filename="MutatorMathTest-Sans Light Condensed.ttf" postscriptfontname="MutatorMathTest-SansLightCondensed" stylemapfamilyname="MutatorMathTest Sans Light Condensed" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="width" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="MutatorMathTest Sans Bold Condensed" familyname="MutatorMathTest" stylename="Sans Bold Condensed" filename="MutatorMathTest-Sans Bold Condensed.ttf" postscriptfontname="MutatorMathTest-SansBoldCondensed" stylemapfamilyname="MutatorMathTest Sans Bold Condensed" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="width" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="MutatorMathTest Sans Light Extended" familyname="MutatorMathTest" stylename="Sans Light Extended" filename="MutatorMathTest-Sans Light Extended.ttf" postscriptfontname="MutatorMathTest-SansLightExtended" stylemapfamilyname="MutatorMathTest Sans Light Extended" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="width" xvalue="1000"/>
+ </location>
+ </instance>
+ <instance name="MutatorMathTest Sans Bold Extended" familyname="MutatorMathTest" stylename="Sans Bold Extended" filename="MutatorMathTest-Sans Bold Extended.ttf" postscriptfontname="MutatorMathTest-SansBoldExtended" stylemapfamilyname="MutatorMathTest Sans Bold Extended" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="width" xvalue="1000"/>
+ </location>
+ </instance>
+ <instance name="MutatorMathTest Sans Medium" familyname="MutatorMathTest" stylename="Sans Medium" filename="MutatorMathTest-Sans Medium.ttf" postscriptfontname="MutatorMathTest-SansMedium" stylemapfamilyname="MutatorMathTest Sans Medium" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="500"/>
+ <dimension name="width" xvalue="327"/>
+ </location>
+ </instance>
+ <instance name="MutatorMathTest Sans Medium" familyname="MutatorMathTest" stylename="Sans Medium" filename="MutatorMathTest-Sans Medium.ttf" postscriptfontname="MutatorMathTest-SansMedium" stylemapfamilyname="MutatorMathTest Sans Medium" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="500"/>
+ <dimension name="width" xvalue="327"/>
+ </location>
+ </instance>
+ <instance name="MutatorMathTest Sans Bold" familyname="MutatorMathTest" stylename="Sans Bold" filename="MutatorMathTest-Sans Bold.ttf" postscriptfontname="MutatorMathTest-SansBold" stylemapfamilyname="MutatorMathTest Sans Bold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="width" xvalue="569.078"/>
+ </location>
+ </instance>
+ <instance name="MutatorMathTest Sans Medium Extended" familyname="MutatorMathTest" stylename="Sans Medium Extended" filename="MutatorMathTest-Sans Medium Extended.ttf" postscriptfontname="MutatorMathTest-SansMediumExtended" stylemapfamilyname="MutatorMathTest Sans Medium Extended" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="500"/>
+ <dimension name="width" xvalue="1000"/>
+ </location>
+ </instance>
+ <instance name="MutatorMathTest Sans Light Condensed" familyname="MutatorMathTest" stylename="Sans Light Condensed" filename="MutatorMathTest-Sans Light Condensed.ttf" postscriptfontname="MutatorMathTest-SansLightCondensed" stylemapfamilyname="MutatorMathTest Sans Light Condensed" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="width" xvalue="0"/>
+ </location>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/data/split_output/MutatorSansVariable_Width.designspace b/Tests/designspaceLib/data/split_output/MutatorSansVariable_Width.designspace
new file mode 100644
index 00000000..9384c73c
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/MutatorSansVariable_Width.designspace
@@ -0,0 +1,50 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes>
+ <axis tag="wdth" name="width" minimum="50" maximum="200" default="50">
+ <map input="50" output="0"/>
+ <map input="100" output="500"/>
+ <map input="200" output="1000"/>
+ </axis>
+ </axes>
+ <rules>
+ <rule name="fold_I_serifs">
+ <conditionset>
+ <condition name="width" minimum="0" maximum="328"/>
+ </conditionset>
+ <sub name="I" with="I.narrow"/>
+ </rule>
+ <rule name="fold_S_terminals">
+ <sub name="S" with="S.closed"/>
+ </rule>
+ </rules>
+ <sources>
+ <source filename="../MutatorSansLightCondensed.ufo" name="master.MutatorMathTest.LightCondensed.0" familyname="MutatorMathTest" stylename="LightCondensed">
+ <location>
+ <dimension name="width" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../MutatorSansLightWide.ufo" name="master.MutatorMathTest.LightWide.2" familyname="MutatorMathTest" stylename="LightWide">
+ <location>
+ <dimension name="width" xvalue="1000"/>
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance name="MutatorMathTest Sans Light Condensed" familyname="MutatorMathTest" stylename="Sans Light Condensed" filename="MutatorMathTest-Sans Light Condensed.ttf" postscriptfontname="MutatorMathTest-SansLightCondensed" stylemapfamilyname="MutatorMathTest Sans Light Condensed" stylemapstylename="regular">
+ <location>
+ <dimension name="width" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="MutatorMathTest Sans Light Extended" familyname="MutatorMathTest" stylename="Sans Light Extended" filename="MutatorMathTest-Sans Light Extended.ttf" postscriptfontname="MutatorMathTest-SansLightExtended" stylemapfamilyname="MutatorMathTest Sans Light Extended" stylemapstylename="regular">
+ <location>
+ <dimension name="width" xvalue="1000"/>
+ </location>
+ </instance>
+ <instance name="MutatorMathTest Sans Light Condensed" familyname="MutatorMathTest" stylename="Sans Light Condensed" filename="MutatorMathTest-Sans Light Condensed.ttf" postscriptfontname="MutatorMathTest-SansLightCondensed" stylemapfamilyname="MutatorMathTest Sans Light Condensed" stylemapstylename="regular">
+ <location>
+ <dimension name="width" xvalue="0"/>
+ </location>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/data/split_output/MutatorSerifVariable_Width.designspace b/Tests/designspaceLib/data/split_output/MutatorSerifVariable_Width.designspace
new file mode 100644
index 00000000..b71025a2
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/MutatorSerifVariable_Width.designspace
@@ -0,0 +1,29 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes>
+ <axis tag="wdth" name="width" minimum="50" maximum="200" default="50">
+ <map input="50" output="0"/>
+ <map input="100" output="500"/>
+ <map input="200" output="1000"/>
+ </axis>
+ </axes>
+ <sources>
+ <source filename="../MutatorSerifLightCondensed.ufo" familyname="MutatorMathTest" stylename="SerifLightCondensed">
+ <location>
+ <dimension name="width" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../MutatorSerifLightWide.ufo" familyname="MutatorMathTest" stylename="SerifLightWide">
+ <location>
+ <dimension name="width" xvalue="1000"/>
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance name="MutatorMathTest Serif Light Condensed" familyname="MutatorMathTest" stylename="Serif Light Condensed" filename="MutatorMathTest-Serif Light Condensed.ttf" postscriptfontname="MutatorMathTest-SerifLightCondensed" stylemapfamilyname="MutatorMathTest Serif Light Condensed" stylemapstylename="regular">
+ <location>
+ <dimension name="width" xvalue="0"/>
+ </location>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/data/split_output/SourceSerif4Variable-Italic.designspace b/Tests/designspaceLib/data/split_output/SourceSerif4Variable-Italic.designspace
new file mode 100644
index 00000000..c1eb95d9
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/SourceSerif4Variable-Italic.designspace
@@ -0,0 +1,266 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes>
+ <axis tag="wght" name="weight" minimum="200" maximum="900" default="400">
+ <map input="200" output="0"/>
+ <map input="300" output="145"/>
+ <map input="400" output="394"/>
+ <map input="600" output="594"/>
+ <map input="700" output="823"/>
+ <map input="900" output="1000"/>
+ </axis>
+ <axis tag="opsz" name="optical" minimum="8" maximum="60" default="20"/>
+ </axes>
+ <sources>
+ <source filename="../caption/master_0/SourceSerif-Italic_c0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </source>
+ <source filename="../caption/master_1/SourceSerif-Italic_c1.ufo">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </source>
+ <source filename="../caption/master_2/SourceSerif-Italic_c2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </source>
+ <source filename="../text/master_0/SourceSerif-Italic_0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </source>
+ <source filename="../text/master_1/SourceSerif-Italic_1.ufo">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </source>
+ <source filename="../text/master_2/SourceSerif-Italic_2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </source>
+ <source filename="../display/master_0/SourceSerif-Italic_d0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </source>
+ <source filename="../display/master_1/SourceSerif-Italic_d1.ufo">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </source>
+ <source filename="../display/master_2/SourceSerif-Italic_d2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance name="Source Serif 4 Caption ExtraLight Italic" familyname="Source Serif 4" stylename="Caption ExtraLight Italic" filename="Source Serif 4-Caption ExtraLight Italic.ttf" postscriptfontname="SourceSerif4Italic-CaptionExtraLight" stylemapfamilyname="Source Serif 4 Caption ExtraLight Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Caption Light Italic" familyname="Source Serif 4" stylename="Caption Light Italic" filename="Source Serif 4-Caption Light Italic.ttf" postscriptfontname="SourceSerif4Italic-CaptionLight" stylemapfamilyname="Source Serif 4 Caption Light Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Caption Italic" familyname="Source Serif 4" stylename="Caption Italic" filename="Source Serif 4-Caption Italic.ttf" postscriptfontname="SourceSerif4Italic-CaptionRegular" stylemapfamilyname="Source Serif 4 Caption Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Caption Semibold Italic" familyname="Source Serif 4" stylename="Caption Semibold Italic" filename="Source Serif 4-Caption Semibold Italic.ttf" postscriptfontname="SourceSerif4Italic-CaptionSemibold" stylemapfamilyname="Source Serif 4 Caption Semibold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Caption Bold Italic" familyname="Source Serif 4" stylename="Caption Bold Italic" filename="Source Serif 4-Caption Bold Italic.ttf" postscriptfontname="SourceSerif4Italic-CaptionBold" stylemapfamilyname="Source Serif 4 Caption Bold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Caption Black Italic" familyname="Source Serif 4" stylename="Caption Black Italic" filename="Source Serif 4-Caption Black Italic.ttf" postscriptfontname="SourceSerif4Italic-CaptionBlack" stylemapfamilyname="Source Serif 4 Caption Black Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText ExtraLight Italic" familyname="Source Serif 4" stylename="SmText ExtraLight Italic" filename="Source Serif 4-SmText ExtraLight Italic.ttf" postscriptfontname="SourceSerif4Italic-SmTextExtraLight" stylemapfamilyname="Source Serif 4 SmallText ExtraLight Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText Light Italic" familyname="Source Serif 4" stylename="SmText Light Italic" filename="Source Serif 4-SmText Light Italic.ttf" postscriptfontname="SourceSerif4Italic-SmTextLight" stylemapfamilyname="Source Serif 4 SmallText Light Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText Italic" familyname="Source Serif 4" stylename="SmText Italic" filename="Source Serif 4-SmText Italic.ttf" postscriptfontname="SourceSerif4Italic-SmTextRegular" stylemapfamilyname="Source Serif 4 SmallText Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText Semibold Italic" familyname="Source Serif 4" stylename="SmText Semibold Italic" filename="Source Serif 4-SmText Semibold Italic.ttf" postscriptfontname="SourceSerif4Italic-SmTextSemibold" stylemapfamilyname="Source Serif 4 SmallText Semibold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText Bold Italic" familyname="Source Serif 4" stylename="SmText Bold Italic" filename="Source Serif 4-SmText Bold Italic.ttf" postscriptfontname="SourceSerif4Italic-SmTextBold" stylemapfamilyname="Source Serif 4 SmallText Bold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText Black Italic" familyname="Source Serif 4" stylename="SmText Black Italic" filename="Source Serif 4-SmText Black Italic.ttf" postscriptfontname="SourceSerif4Italic-SmTextBlack" stylemapfamilyname="Source Serif 4 SmallText Black Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 ExtraLight Italic" familyname="Source Serif 4" stylename="ExtraLight Italic" filename="Source Serif 4-ExtraLight Italic.ttf" postscriptfontname="SourceSerif4Italic-ExtraLight" stylemapfamilyname="Source Serif 4 ExtraLight Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Light Italic" familyname="Source Serif 4" stylename="Light Italic" filename="Source Serif 4-Light Italic.ttf" postscriptfontname="SourceSerif4Italic-Light" stylemapfamilyname="Source Serif 4 Light Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Italic" familyname="Source Serif 4" stylename="Italic" filename="Source Serif 4-Italic.ttf" postscriptfontname="SourceSerif4Italic-Regular" stylemapfamilyname="Source Serif 4 Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Semibold Italic" familyname="Source Serif 4" stylename="Semibold Italic" filename="Source Serif 4-Semibold Italic.ttf" postscriptfontname="SourceSerif4Italic-Semibold" stylemapfamilyname="Source Serif 4 Semibold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Bold Italic" familyname="Source Serif 4" stylename="Bold Italic" filename="Source Serif 4-Bold Italic.ttf" postscriptfontname="SourceSerif4Italic-Bold" stylemapfamilyname="Source Serif 4 Bold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Black Italic" familyname="Source Serif 4" stylename="Black Italic" filename="Source Serif 4-Black Italic.ttf" postscriptfontname="SourceSerif4Italic-Black" stylemapfamilyname="Source Serif 4 Black Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead ExtraLight Italic" familyname="Source Serif 4" stylename="Subhead ExtraLight Italic" filename="Source Serif 4-Subhead ExtraLight Italic.ttf" postscriptfontname="SourceSerif4Italic-SubheadExtraLight" stylemapfamilyname="Source Serif 4 Subhead ExtraLight Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead Light Italic" familyname="Source Serif 4" stylename="Subhead Light Italic" filename="Source Serif 4-Subhead Light Italic.ttf" postscriptfontname="SourceSerif4Italic-SubheadLight" stylemapfamilyname="Source Serif 4 Subhead Light Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead Italic" familyname="Source Serif 4" stylename="Subhead Italic" filename="Source Serif 4-Subhead Italic.ttf" postscriptfontname="SourceSerif4Italic-SubheadRegular" stylemapfamilyname="Source Serif 4 Subhead Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead Semibold Italic" familyname="Source Serif 4" stylename="Subhead Semibold Italic" filename="Source Serif 4-Subhead Semibold Italic.ttf" postscriptfontname="SourceSerif4Italic-SubheadSemibold" stylemapfamilyname="Source Serif 4 Subhead Semibold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead Bold Italic" familyname="Source Serif 4" stylename="Subhead Bold Italic" filename="Source Serif 4-Subhead Bold Italic.ttf" postscriptfontname="SourceSerif4Italic-SubheadBold" stylemapfamilyname="Source Serif 4 Subhead Bold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead Black Italic" familyname="Source Serif 4" stylename="Subhead Black Italic" filename="Source Serif 4-Subhead Black Italic.ttf" postscriptfontname="SourceSerif4Italic-SubheadBlack" stylemapfamilyname="Source Serif 4 Subhead Black Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display ExtraLight Italic" familyname="Source Serif 4" stylename="Display ExtraLight Italic" filename="Source Serif 4-Display ExtraLight Italic.ttf" postscriptfontname="SourceSerif4Italic-DisplayExtraLight" stylemapfamilyname="Source Serif 4 Display ExtraLight Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display Light Italic" familyname="Source Serif 4" stylename="Display Light Italic" filename="Source Serif 4-Display Light Italic.ttf" postscriptfontname="SourceSerif4Italic-DisplayLight" stylemapfamilyname="Source Serif 4 Display Light Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display Italic" familyname="Source Serif 4" stylename="Display Italic" filename="Source Serif 4-Display Italic.ttf" postscriptfontname="SourceSerif4Italic-DisplayRegular" stylemapfamilyname="Source Serif 4 Display Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display Semibold Italic" familyname="Source Serif 4" stylename="Display Semibold Italic" filename="Source Serif 4-Display Semibold Italic.ttf" postscriptfontname="SourceSerif4Italic-DisplaySemibold" stylemapfamilyname="Source Serif 4 Display Semibold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display Bold Italic" familyname="Source Serif 4" stylename="Display Bold Italic" filename="Source Serif 4-Display Bold Italic.ttf" postscriptfontname="SourceSerif4Italic-DisplayBold" stylemapfamilyname="Source Serif 4 Display Bold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display Black Italic" familyname="Source Serif 4" stylename="Display Black Italic" filename="Source Serif 4-Display Black Italic.ttf" postscriptfontname="SourceSerif4Italic-DisplayBlack" stylemapfamilyname="Source Serif 4 Display Black Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ </instances>
+ <lib>
+ <dict>
+ <key>public.skipExportGlyphs</key>
+ <array>
+ <string>caron.alt</string>
+ <string>commabelowcmb.alt</string>
+ <string>f.liga</string>
+ <string>f.ligalong</string>
+ <string>tonos.cap</string>
+ <string>dieresiscmb.tight</string>
+ <string>turkicdsccmb</string>
+ </array>
+ </dict>
+ </lib>
+</designspace>
diff --git a/Tests/designspaceLib/data/split_output/SourceSerif4Variable-Roman.designspace b/Tests/designspaceLib/data/split_output/SourceSerif4Variable-Roman.designspace
new file mode 100644
index 00000000..1eee53e0
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/SourceSerif4Variable-Roman.designspace
@@ -0,0 +1,274 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes>
+ <axis tag="wght" name="weight" minimum="200" maximum="900" default="400">
+ <map input="200" output="0"/>
+ <map input="300" output="145"/>
+ <map input="400" output="394"/>
+ <map input="600" output="594"/>
+ <map input="700" output="823"/>
+ <map input="900" output="1000"/>
+ </axis>
+ <axis tag="opsz" name="optical" minimum="8" maximum="60" default="20"/>
+ </axes>
+ <sources>
+ <source filename="../caption/master_0/SourceSerif_c0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </source>
+ <source filename="../caption/master_1/SourceSerif_c1.ufo">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </source>
+ <source filename="../caption/master_2/SourceSerif_c2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </source>
+ <source filename="../text/master_0/SourceSerif_0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </source>
+ <source filename="../text/master_1/SourceSerif_1.ufo" familyname="Source Serif 4">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </source>
+ <source filename="../text/master_2/SourceSerif_2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </source>
+ <source filename="../display/master_0/SourceSerif_d0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </source>
+ <source filename="../display/master_1/SourceSerif_d1.ufo">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </source>
+ <source filename="../display/master_2/SourceSerif_d2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance name="Source Serif 4 Caption ExtraLight" familyname="Source Serif 4" stylename="Caption ExtraLight" filename="Source Serif 4-Caption ExtraLight.ttf" postscriptfontname="SourceSerif4Roman-CaptionExtraLight" stylemapfamilyname="Source Serif 4 Caption ExtraLight" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Caption Light" familyname="Source Serif 4" stylename="Caption Light" filename="Source Serif 4-Caption Light.ttf" postscriptfontname="SourceSerif4Roman-CaptionLight" stylemapfamilyname="Source Serif 4 Caption Light" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Caption" familyname="Source Serif 4" stylename="Caption" filename="Source Serif 4-Caption.ttf" postscriptfontname="SourceSerif4Roman-CaptionRegular" stylemapfamilyname="Source Serif 4 Caption" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Caption Semibold" familyname="Source Serif 4" stylename="Caption Semibold" filename="Source Serif 4-Caption Semibold.ttf" postscriptfontname="SourceSerif4Roman-CaptionSemibold" stylemapfamilyname="Source Serif 4 Caption Semibold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Caption Bold" familyname="Source Serif 4" stylename="Caption Bold" filename="Source Serif 4-Caption Bold.ttf" postscriptfontname="SourceSerif4Roman-CaptionBold" stylemapfamilyname="Source Serif 4 Caption Bold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Caption Black" familyname="Source Serif 4" stylename="Caption Black" filename="Source Serif 4-Caption Black.ttf" postscriptfontname="SourceSerif4Roman-CaptionBlack" stylemapfamilyname="Source Serif 4 Caption Black" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText ExtraLight" familyname="Source Serif 4" stylename="SmText ExtraLight" filename="Source Serif 4-SmText ExtraLight.ttf" postscriptfontname="SourceSerif4Roman-SmTextExtraLight" stylemapfamilyname="Source Serif 4 SmallText ExtraLight" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText Light" familyname="Source Serif 4" stylename="SmText Light" filename="Source Serif 4-SmText Light.ttf" postscriptfontname="SourceSerif4Roman-SmTextLight" stylemapfamilyname="Source Serif 4 SmallText Light" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText" familyname="Source Serif 4" stylename="SmText" filename="Source Serif 4-SmText.ttf" postscriptfontname="SourceSerif4Roman-SmTextRegular" stylemapfamilyname="Source Serif 4 SmallText" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText Semibold" familyname="Source Serif 4" stylename="SmText Semibold" filename="Source Serif 4-SmText Semibold.ttf" postscriptfontname="SourceSerif4Roman-SmTextSemibold" stylemapfamilyname="Source Serif 4 SmallText Semibold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText Bold" familyname="Source Serif 4" stylename="SmText Bold" filename="Source Serif 4-SmText Bold.ttf" postscriptfontname="SourceSerif4Roman-SmTextBold" stylemapfamilyname="Source Serif 4 SmallText Bold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText Black" familyname="Source Serif 4" stylename="SmText Black" filename="Source Serif 4-SmText Black.ttf" postscriptfontname="SourceSerif4Roman-SmTextBlack" stylemapfamilyname="Source Serif 4 SmallText Black" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 ExtraLight" familyname="Source Serif 4" stylename="ExtraLight" filename="Source Serif 4-ExtraLight.ttf" postscriptfontname="SourceSerif4Roman-ExtraLight" stylemapfamilyname="Source Serif 4 ExtraLight" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Light" familyname="Source Serif 4" stylename="Light" filename="Source Serif 4-Light.ttf" postscriptfontname="SourceSerif4Roman-Light" stylemapfamilyname="Source Serif 4 Light" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Regular" familyname="Source Serif 4" stylename="Regular" filename="Source Serif 4-Regular.ttf" postscriptfontname="SourceSerif4Roman-Regular" stylemapfamilyname="Source Serif 4" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Semibold" familyname="Source Serif 4" stylename="Semibold" filename="Source Serif 4-Semibold.ttf" postscriptfontname="SourceSerif4Roman-Semibold" stylemapfamilyname="Source Serif 4 Semibold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Bold" familyname="Source Serif 4" stylename="Bold" filename="Source Serif 4-Bold.ttf" postscriptfontname="SourceSerif4Roman-Bold" stylemapfamilyname="Source Serif 4 Bold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Black" familyname="Source Serif 4" stylename="Black" filename="Source Serif 4-Black.ttf" postscriptfontname="SourceSerif4Roman-Black" stylemapfamilyname="Source Serif 4 Black" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead ExtraLight" familyname="Source Serif 4" stylename="Subhead ExtraLight" filename="Source Serif 4-Subhead ExtraLight.ttf" postscriptfontname="SourceSerif4Roman-SubheadExtraLight" stylemapfamilyname="Source Serif 4 Subhead ExtraLight" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead Light" familyname="Source Serif 4" stylename="Subhead Light" filename="Source Serif 4-Subhead Light.ttf" postscriptfontname="SourceSerif4Roman-SubheadLight" stylemapfamilyname="Source Serif 4 Subhead Light" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead" familyname="Source Serif 4" stylename="Subhead" filename="Source Serif 4-Subhead.ttf" postscriptfontname="SourceSerif4Roman-SubheadRegular" stylemapfamilyname="Source Serif 4 Subhead" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead Semibold" familyname="Source Serif 4" stylename="Subhead Semibold" filename="Source Serif 4-Subhead Semibold.ttf" postscriptfontname="SourceSerif4Roman-SubheadSemibold" stylemapfamilyname="Source Serif 4 Subhead Semibold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead Bold" familyname="Source Serif 4" stylename="Subhead Bold" filename="Source Serif 4-Subhead Bold.ttf" postscriptfontname="SourceSerif4Roman-SubheadBold" stylemapfamilyname="Source Serif 4 Subhead Bold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead Black" familyname="Source Serif 4" stylename="Subhead Black" filename="Source Serif 4-Subhead Black.ttf" postscriptfontname="SourceSerif4Roman-SubheadBlack" stylemapfamilyname="Source Serif 4 Subhead Black" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display ExtraLight" familyname="Source Serif 4" stylename="Display ExtraLight" filename="Source Serif 4-Display ExtraLight.ttf" postscriptfontname="SourceSerif4Roman-DisplayExtraLight" stylemapfamilyname="Source Serif 4 Display ExtraLight" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display Light" familyname="Source Serif 4" stylename="Display Light" filename="Source Serif 4-Display Light.ttf" postscriptfontname="SourceSerif4Roman-DisplayLight" stylemapfamilyname="Source Serif 4 Display Light" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display" familyname="Source Serif 4" stylename="Display" filename="Source Serif 4-Display.ttf" postscriptfontname="SourceSerif4Roman-DisplayRegular" stylemapfamilyname="Source Serif 4 Display" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display Semibold" familyname="Source Serif 4" stylename="Display Semibold" filename="Source Serif 4-Display Semibold.ttf" postscriptfontname="SourceSerif4Roman-DisplaySemibold" stylemapfamilyname="Source Serif 4 Display Semibold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display Bold" familyname="Source Serif 4" stylename="Display Bold" filename="Source Serif 4-Display Bold.ttf" postscriptfontname="SourceSerif4Roman-DisplayBold" stylemapfamilyname="Source Serif 4 Display Bold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display Black" familyname="Source Serif 4" stylename="Display Black" filename="Source Serif 4-Display Black.ttf" postscriptfontname="SourceSerif4Roman-DisplayBlack" stylemapfamilyname="Source Serif 4 Display Black" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ </instances>
+ <lib>
+ <dict>
+ <key>public.skipExportGlyphs</key>
+ <array>
+ <string>caron.alt</string>
+ <string>commabelowcmb.alt</string>
+ <string>tonos.cap</string>
+ <string>f.ligalong</string>
+ <string>dieresiscmb.tight</string>
+ <string>IJ</string>
+ <string>Tbar</string>
+ <string>colontriangularmod</string>
+ <string>crossmark</string>
+ <string>ij</string>
+ <string>overline</string>
+ <string>similar</string>
+ <string>tbar</string>
+ <string>triangularbullet</string>
+ <string>turkicdsccmb</string>
+ </array>
+ </dict>
+ </lib>
+</designspace>
diff --git a/Tests/designspaceLib/data/split_output/test_v5_MutatorSans_and_Serif_serif_0.0.designspace b/Tests/designspaceLib/data/split_output/test_v5_MutatorSans_and_Serif_serif_0.0.designspace
new file mode 100644
index 00000000..d811ffa0
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/test_v5_MutatorSans_and_Serif_serif_0.0.designspace
@@ -0,0 +1,147 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes>
+ <axis tag="wght" name="weight" minimum="300" maximum="700" default="300">
+ <map input="300" output="0"/>
+ <map input="500" output="500"/>
+ <map input="700" output="1000"/>
+ </axis>
+ <axis tag="wdth" name="width" minimum="50" maximum="200" default="50">
+ <map input="50" output="0"/>
+ <map input="100" output="500"/>
+ <map input="200" output="1000"/>
+ </axis>
+ </axes>
+ <rules>
+ <rule name="fold_I_serifs">
+ <conditionset>
+ <condition name="width" minimum="0" maximum="328"/>
+ </conditionset>
+ <sub name="I" with="I.narrow"/>
+ </rule>
+ <rule name="fold_S_terminals">
+ <conditionset>
+ <condition name="weight" minimum="0" maximum="500"/>
+ </conditionset>
+ <sub name="S" with="S.closed"/>
+ </rule>
+ </rules>
+ <sources>
+ <source filename="../MutatorSansLightCondensed.ufo" name="master.MutatorMathTest.LightCondensed.0" familyname="MutatorMathTest" stylename="LightCondensed">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="width" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../MutatorSansBoldCondensed.ufo" name="master.MutatorMathTest.BoldCondensed.1" familyname="MutatorMathTest" stylename="BoldCondensed">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="width" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../MutatorSansLightWide.ufo" name="master.MutatorMathTest.LightWide.2" familyname="MutatorMathTest" stylename="LightWide">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="width" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="../MutatorSansBoldWide.ufo" name="master.MutatorMathTest.BoldWide.3" familyname="MutatorMathTest" stylename="BoldWide">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="width" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="../MutatorSansLightCondensed.ufo" name="support.crossbar" layer="support.crossbar">
+ <location>
+ <dimension name="weight" xvalue="700"/>
+ <dimension name="width" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../MutatorSansLightCondensed.ufo" name="support.S.wide" layer="support.S.wide">
+ <location>
+ <dimension name="weight" xvalue="700"/>
+ <dimension name="width" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="../MutatorSansLightCondensed.ufo" name="support.S.middle" layer="support.S.middle">
+ <location>
+ <dimension name="weight" xvalue="700"/>
+ <dimension name="width" xvalue="569.078"/>
+ </location>
+ </source>
+ </sources>
+ <variable-fonts>
+ <variable-font name="MutatorSansVariable_Weight_Width">
+ <axis-subsets>
+ <axis-subset name="weight"/>
+ <axis-subset name="width"/>
+ </axis-subsets>
+ </variable-font>
+ <variable-font name="MutatorSansVariable_Weight">
+ <axis-subsets>
+ <axis-subset name="weight"/>
+ </axis-subsets>
+ </variable-font>
+ <variable-font name="MutatorSansVariable_Width">
+ <axis-subsets>
+ <axis-subset name="width"/>
+ </axis-subsets>
+ </variable-font>
+ </variable-fonts>
+ <instances>
+ <instance name="MutatorMathTest Sans Light Condensed" familyname="MutatorMathTest" stylename="Sans Light Condensed" filename="MutatorMathTest-Sans Light Condensed.ttf" postscriptfontname="MutatorMathTest-SansLightCondensed" stylemapfamilyname="MutatorMathTest Sans Light Condensed" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="width" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="MutatorMathTest Sans Bold Condensed" familyname="MutatorMathTest" stylename="Sans Bold Condensed" filename="MutatorMathTest-Sans Bold Condensed.ttf" postscriptfontname="MutatorMathTest-SansBoldCondensed" stylemapfamilyname="MutatorMathTest Sans Bold Condensed" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="width" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="MutatorMathTest Sans Light Extended" familyname="MutatorMathTest" stylename="Sans Light Extended" filename="MutatorMathTest-Sans Light Extended.ttf" postscriptfontname="MutatorMathTest-SansLightExtended" stylemapfamilyname="MutatorMathTest Sans Light Extended" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="width" xvalue="1000"/>
+ </location>
+ </instance>
+ <instance name="MutatorMathTest Sans Bold Extended" familyname="MutatorMathTest" stylename="Sans Bold Extended" filename="MutatorMathTest-Sans Bold Extended.ttf" postscriptfontname="MutatorMathTest-SansBoldExtended" stylemapfamilyname="MutatorMathTest Sans Bold Extended" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="width" xvalue="1000"/>
+ </location>
+ </instance>
+ <instance name="MutatorMathTest Sans Medium" familyname="MutatorMathTest" stylename="Sans Medium" filename="MutatorMathTest-Sans Medium.ttf" postscriptfontname="MutatorMathTest-SansMedium" stylemapfamilyname="MutatorMathTest Sans Medium" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="500"/>
+ <dimension name="width" xvalue="327"/>
+ </location>
+ </instance>
+ <instance name="MutatorMathTest Sans Medium" familyname="MutatorMathTest" stylename="Sans Medium" filename="MutatorMathTest-Sans Medium.ttf" postscriptfontname="MutatorMathTest-SansMedium" stylemapfamilyname="MutatorMathTest Sans Medium" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="500"/>
+ <dimension name="width" xvalue="327"/>
+ </location>
+ </instance>
+ <instance name="MutatorMathTest Sans Bold" familyname="MutatorMathTest" stylename="Sans Bold" filename="MutatorMathTest-Sans Bold.ttf" postscriptfontname="MutatorMathTest-SansBold" stylemapfamilyname="MutatorMathTest Sans Bold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="width" xvalue="569.078"/>
+ </location>
+ </instance>
+ <instance name="MutatorMathTest Sans Medium Extended" familyname="MutatorMathTest" stylename="Sans Medium Extended" filename="MutatorMathTest-Sans Medium Extended.ttf" postscriptfontname="MutatorMathTest-SansMediumExtended" stylemapfamilyname="MutatorMathTest Sans Medium Extended" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="500"/>
+ <dimension name="width" xvalue="1000"/>
+ </location>
+ </instance>
+ <instance name="MutatorMathTest Sans Light Condensed" familyname="MutatorMathTest" stylename="Sans Light Condensed" filename="MutatorMathTest-Sans Light Condensed.ttf" postscriptfontname="MutatorMathTest-SansLightCondensed" stylemapfamilyname="MutatorMathTest Sans Light Condensed" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="width" xvalue="0"/>
+ </location>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/data/split_output/test_v5_MutatorSans_and_Serif_serif_1.0.designspace b/Tests/designspaceLib/data/split_output/test_v5_MutatorSans_and_Serif_serif_1.0.designspace
new file mode 100644
index 00000000..5e3a6f52
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/test_v5_MutatorSans_and_Serif_serif_1.0.designspace
@@ -0,0 +1,44 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes>
+ <axis tag="wght" name="weight" minimum="300" maximum="700" default="300">
+ <map input="300" output="0"/>
+ <map input="500" output="500"/>
+ <map input="700" output="1000"/>
+ </axis>
+ <axis tag="wdth" name="width" minimum="50" maximum="200" default="50">
+ <map input="50" output="0"/>
+ <map input="100" output="500"/>
+ <map input="200" output="1000"/>
+ </axis>
+ </axes>
+ <sources>
+ <source filename="../MutatorSerifLightCondensed.ufo" familyname="MutatorMathTest" stylename="SerifLightCondensed">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="width" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../MutatorSerifLightWide.ufo" familyname="MutatorMathTest" stylename="SerifLightWide">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="width" xvalue="1000"/>
+ </location>
+ </source>
+ </sources>
+ <variable-fonts>
+ <variable-font name="MutatorSerifVariable_Width">
+ <axis-subsets>
+ <axis-subset name="width"/>
+ </axis-subsets>
+ </variable-font>
+ </variable-fonts>
+ <instances>
+ <instance name="MutatorMathTest Serif Light Condensed" familyname="MutatorMathTest" stylename="Serif Light Condensed" filename="MutatorMathTest-Serif Light Condensed.ttf" postscriptfontname="MutatorMathTest-SerifLightCondensed" stylemapfamilyname="MutatorMathTest Serif Light Condensed" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="width" xvalue="0"/>
+ </location>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/data/split_output/test_v5_aktiv_.designspace b/Tests/designspaceLib/data/split_output/test_v5_aktiv_.designspace
new file mode 100644
index 00000000..4451e6e6
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/test_v5_aktiv_.designspace
@@ -0,0 +1,595 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes>
+ <axis tag="wght" name="Weight" minimum="100" maximum="900" default="400">
+ <map input="100" output="22"/>
+ <map input="200" output="38"/>
+ <map input="300" output="57"/>
+ <map input="400" output="84"/>
+ <map input="500" output="98"/>
+ <map input="600" output="115"/>
+ <map input="700" output="133"/>
+ <map input="800" output="158"/>
+ <map input="900" output="185"/>
+ </axis>
+ <axis tag="wdth" name="Width" minimum="75" maximum="125" default="100"/>
+ <axis tag="ital" name="Italic" minimum="0" maximum="1" default="0"/>
+ </axes>
+ <rules processing="last">
+ <rule name="BRACKET.CYR">
+ <conditionset>
+ <condition name="Italic" minimum="0.1" maximum="1"/>
+ </conditionset>
+ <sub name="ghe.loclSRB" with="ghe.ital.loclSRB"/>
+ <sub name="ghe.loclMKD" with="ghe.ital.loclMKD"/>
+ <sub name="de.loclMKDSRB" with="de.ital.loclMKDSRB"/>
+ <sub name="pe.loclMKDSRB" with="pe.ital.loclMKDSRB"/>
+ <sub name="te.loclMKDSRB" with="te.ital.loclMKDSRB"/>
+ <sub name="gje.loclMKD" with="gje.ital.loclMKD"/>
+ <sub name="sha.loclMKDSRB" with="sha.ital.loclMKDSRB"/>
+ </rule>
+ <rule name="BRACKET.116.185">
+ <conditionset>
+ <condition name="Weight" minimum="116" maximum="185"/>
+ <condition name="Width" minimum="75" maximum="97.5"/>
+ </conditionset>
+ <sub name="cent" with="cent.BRACKET.130"/>
+ <sub name="dollar" with="dollar.BRACKET.130"/>
+ </rule>
+ </rules>
+ <sources>
+ <source filename="../AktivGroteskCd_Hair.ufo" name="Aktiv Grotesk Cd Hair">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskCd_HairIt.ufo" name="Aktiv Grotesk Cd Hair Italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Hair.ufo" name="Aktiv Grotesk Hair">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_HairIt.ufo" name="Aktiv Grotesk Hair Italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_Hair.ufo" name="Aktiv Grotesk Ex Hair">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_HairIt.ufo" name="Aktiv Grotesk Ex Hair Italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskCd_Rg.ufo" name="Aktiv Grotesk Cd">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskCd_It.ufo" name="Aktiv Grotesk Cd Italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Rg.ufo" name="Aktiv Grotesk Regular" familyname="Aktiv Grotesk">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Rg.ufo" name="Aktiv Grotesk Regular" layer="{126,100,0}">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_It.ufo" name="Aktiv Grotesk Italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_It.ufo" name="Aktiv Grotesk Italic {126,100,1}" layer="{126,100,1}">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_Rg.ufo" name="Aktiv Grotesk Ex">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_It.ufo" name="Aktiv Grotesk Ex Italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskCd_Blk.ufo" name="Aktiv Grotesk Cd Black">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskCd_BlkIt.ufo" name="Aktiv Grotesk Cd Black Italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_Blk.ufo" name="Aktiv Grotesk Black">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGrotesk_BlkIt.ufo" name="Aktiv Grotesk Black Italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_Blk.ufo" name="Aktiv Grotesk Ex Black">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="../AktivGroteskEx_BlkIt.ufo" name="Aktiv Grotesk Ex Black Italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ </sources>
+ <variable-fonts>
+ <variable-font name="AktivGroteskVF_WghtWdthItal">
+ <axis-subsets>
+ <axis-subset name="Weight"/>
+ <axis-subset name="Width"/>
+ <axis-subset name="Italic"/>
+ </axis-subsets>
+ </variable-font>
+ <variable-font name="AktivGroteskVF_WghtWdth">
+ <axis-subsets>
+ <axis-subset name="Weight"/>
+ <axis-subset name="Width"/>
+ </axis-subsets>
+ </variable-font>
+ <variable-font name="AktivGroteskVF_Wght">
+ <axis-subsets>
+ <axis-subset name="Weight"/>
+ </axis-subsets>
+ </variable-font>
+ <variable-font name="AktivGroteskVF_Italics_WghtWdth">
+ <axis-subsets>
+ <axis-subset name="Weight"/>
+ <axis-subset name="Width"/>
+ <axis-subset name="Italic" uservalue="1"/>
+ </axis-subsets>
+ </variable-font>
+ <variable-font name="AktivGroteskVF_Italics_Wght">
+ <axis-subsets>
+ <axis-subset name="Weight"/>
+ <axis-subset name="Italic" uservalue="1"/>
+ </axis-subsets>
+ </variable-font>
+ </variable-fonts>
+ <instances>
+ <instance name="Aktiv Grotesk Cd Hair" familyname="Aktiv Grotesk" stylename="Cd Hair" filename="../instances/AktivGroteskCd_Hair.ufo" postscriptfontname="AktivGrotesk-CdHair" stylemapfamilyname="Aktiv Grotesk Cd Hair" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Hair Italic" familyname="Aktiv Grotesk" stylename="Cd Hair Italic" filename="../instances/AktivGroteskCd_HairIt.ufo" postscriptfontname="AktivGrotesk-CdHairItalic" stylemapfamilyname="Aktiv Grotesk Cd Hair" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Hair" familyname="Aktiv Grotesk" stylename="Hair" filename="../instances/AktivGrotesk_Hair.ufo" postscriptfontname="AktivGrotesk-Hair" stylemapfamilyname="Aktiv Grotesk Hair" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Hair Italic" familyname="Aktiv Grotesk" stylename="Hair Italic" filename="../instances/AktivGrotesk_HairIt.ufo" postscriptfontname="AktivGrotesk-HairItalic" stylemapfamilyname="Aktiv Grotesk Hair" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Hair" familyname="Aktiv Grotesk" stylename="Ex Hair" filename="../instances/AktivGroteskEx_Hair.ufo" postscriptfontname="AktivGrotesk-ExHair" stylemapfamilyname="Aktiv Grotesk Ex Hair" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Hair Italic" familyname="Aktiv Grotesk" stylename="Ex Hair Italic" filename="../instances/AktivGroteskEx_HairIt.ufo" postscriptfontname="AktivGrotesk-ExHairItalic" stylemapfamilyname="Aktiv Grotesk Ex Hair" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Thin" familyname="Aktiv Grotesk" stylename="Cd Thin" filename="../instances/AktivGroteskCd_Th.ufo" postscriptfontname="AktivGrotesk-CdThin" stylemapfamilyname="Aktiv Grotesk Cd Thin" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Thin Italic" familyname="Aktiv Grotesk" stylename="Cd Thin Italic" filename="../instances/AktivGroteskCd_ThIt.ufo" postscriptfontname="AktivGrotesk-CdThinItalic" stylemapfamilyname="Aktiv Grotesk Cd Thin" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Thin" familyname="Aktiv Grotesk" stylename="Thin" filename="../instances/AktivGrotesk_Th.ufo" postscriptfontname="AktivGrotesk-Thin" stylemapfamilyname="Aktiv Grotesk Thin" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Thin Italic" familyname="Aktiv Grotesk" stylename="Thin Italic" filename="../instances/AktivGrotesk_ThIt.ufo" postscriptfontname="AktivGrotesk-ThinItalic" stylemapfamilyname="Aktiv Grotesk Thin" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Thin" familyname="Aktiv Grotesk" stylename="Ex Thin" filename="../instances/AktivGroteskEx_Th.ufo" postscriptfontname="AktivGrotesk-ExThin" stylemapfamilyname="Aktiv Grotesk Ex Thin" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Thin Italic" familyname="Aktiv Grotesk" stylename="Ex Thin Italic" filename="../instances/AktivGroteskEx_ThIt.ufo" postscriptfontname="AktivGrotesk-ExThinItalic" stylemapfamilyname="Aktiv Grotesk Ex Thin" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Light" familyname="Aktiv Grotesk" stylename="Cd Light" filename="../instances/AktivGroteskCd_Lt.ufo" postscriptfontname="AktivGrotesk-CdLight" stylemapfamilyname="Aktiv Grotesk Cd Light" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Light Italic" familyname="Aktiv Grotesk" stylename="Cd Light Italic" filename="../instances/AktivGroteskCd_LtIt.ufo" postscriptfontname="AktivGrotesk-CdLightItalic" stylemapfamilyname="Aktiv Grotesk Cd Light" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Light" familyname="Aktiv Grotesk" stylename="Light" filename="../instances/AktivGrotesk_Lt.ufo" postscriptfontname="AktivGrotesk-Light" stylemapfamilyname="Aktiv Grotesk Light" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Light Italic" familyname="Aktiv Grotesk" stylename="Light Italic" filename="../instances/AktivGrotesk_LtIt.ufo" postscriptfontname="AktivGrotesk-LightItalic" stylemapfamilyname="Aktiv Grotesk Light" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Light" familyname="Aktiv Grotesk" stylename="Ex Light" filename="../instances/AktivGroteskEx_Lt.ufo" postscriptfontname="AktivGrotesk-ExLight" stylemapfamilyname="Aktiv Grotesk Ex Light" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Light Italic" familyname="Aktiv Grotesk" stylename="Ex Light Italic" filename="../instances/AktivGroteskEx_LtIt.ufo" postscriptfontname="AktivGrotesk-ExLightItalic" stylemapfamilyname="Aktiv Grotesk Ex Light" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd" familyname="Aktiv Grotesk" stylename="Cd" filename="../instances/AktivGroteskCd_Rg.ufo" postscriptfontname="AktivGrotesk-Cd" stylemapfamilyname="Aktiv Grotesk Cd" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Italic" familyname="Aktiv Grotesk" stylename="Cd Italic" filename="../instances/AktivGroteskCd_It.ufo" postscriptfontname="AktivGrotesk-CdItalic" stylemapfamilyname="Aktiv Grotesk Cd" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk " familyname="Aktiv Grotesk" stylename="" filename="../instances/AktivGrotesk_Rg.ufo" postscriptfontname="AktivGrotesk-" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Italic" familyname="Aktiv Grotesk" stylename="Italic" filename="../instances/AktivGrotesk_It.ufo" postscriptfontname="AktivGrotesk-Italic" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex" familyname="Aktiv Grotesk" stylename="Ex" filename="../instances/AktivGroteskEx_Rg.ufo" postscriptfontname="AktivGrotesk-Ex" stylemapfamilyname="Aktiv Grotesk Ex" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Italic" familyname="Aktiv Grotesk" stylename="Ex Italic" filename="../instances/AktivGroteskEx_It.ufo" postscriptfontname="AktivGrotesk-ExItalic" stylemapfamilyname="Aktiv Grotesk Ex" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Medium" familyname="Aktiv Grotesk" stylename="Cd Medium" filename="../instances/AktivGroteskCd_Md.ufo" postscriptfontname="AktivGrotesk-CdMedium" stylemapfamilyname="Aktiv Grotesk Cd Medium" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Medium Italic" familyname="Aktiv Grotesk" stylename="Cd Medium Italic" filename="../instances/AktivGroteskCd_MdIt.ufo" postscriptfontname="AktivGrotesk-CdMediumItalic" stylemapfamilyname="Aktiv Grotesk Cd Medium" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Medium" familyname="Aktiv Grotesk" stylename="Medium" filename="../instances/AktivGrotesk_Md.ufo" postscriptfontname="AktivGrotesk-Medium" stylemapfamilyname="Aktiv Grotesk Medium" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Medium Italic" familyname="Aktiv Grotesk" stylename="Medium Italic" filename="../instances/AktivGrotesk_MdIt.ufo" postscriptfontname="AktivGrotesk-MediumItalic" stylemapfamilyname="Aktiv Grotesk Medium" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Medium" familyname="Aktiv Grotesk" stylename="Ex Medium" filename="../instances/AktivGroteskEx_Md.ufo" postscriptfontname="AktivGrotesk-ExMedium" stylemapfamilyname="Aktiv Grotesk Ex Medium" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Medium Italic" familyname="Aktiv Grotesk" stylename="Ex Medium Italic" filename="../instances/AktivGroteskEx_MdIt.ufo" postscriptfontname="AktivGrotesk-ExMediumItalic" stylemapfamilyname="Aktiv Grotesk Ex Medium" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd SemiBold" familyname="Aktiv Grotesk" stylename="Cd SemiBold" filename="../../build/instances/AktivGroteskCd_SBd.ufo" postscriptfontname="AktivGrotesk-CdSemiBold" stylemapfamilyname="Aktiv Grotesk Cd SemiBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd SemiBold Italic" familyname="Aktiv Grotesk" stylename="Cd SemiBold Italic" filename="../../build/instances/AktivGroteskCd_SBdIt.ufo" postscriptfontname="AktivGrotesk-CdSemiBoldItalic" stylemapfamilyname="Aktiv Grotesk Cd SemiBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk SemiBold" familyname="Aktiv Grotesk" stylename="SemiBold" filename="../../build/instances/AktivGrotesk_SBd.ufo" postscriptfontname="AktivGrotesk-SemiBold" stylemapfamilyname="Aktiv Grotesk SemiBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk SemiBold Italic" familyname="Aktiv Grotesk" stylename="SemiBold Italic" filename="../../build/instances/AktivGrotesk_SBdIt.ufo" postscriptfontname="AktivGrotesk-SemiBoldItalic" stylemapfamilyname="Aktiv Grotesk SemiBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex SemiBold" familyname="Aktiv Grotesk" stylename="Ex SemiBold" filename="../../build/instances/AktivGroteskEx_SBd.ufo" postscriptfontname="AktivGrotesk-ExSemiBold" stylemapfamilyname="Aktiv Grotesk Ex SemiBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex SemiBold Italic" familyname="Aktiv Grotesk" stylename="Ex SemiBold Italic" filename="../../build/instances/AktivGroteskEx_SBdIt.ufo" postscriptfontname="AktivGrotesk-ExSemiBoldItalic" stylemapfamilyname="Aktiv Grotesk Ex SemiBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Bold" familyname="Aktiv Grotesk" stylename="Cd Bold" filename="../instances/AktivGroteskCd_Bd.ufo" postscriptfontname="AktivGrotesk-CdBold" stylemapfamilyname="Aktiv Grotesk Cd" stylemapstylename="bold">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Bold Italic" familyname="Aktiv Grotesk" stylename="Cd Bold Italic" filename="../instances/AktivGroteskCd_BdIt.ufo" postscriptfontname="AktivGrotesk-CdBoldItalic" stylemapfamilyname="Aktiv Grotesk Cd" stylemapstylename="bold italic">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Bold" familyname="Aktiv Grotesk" stylename="Bold" filename="../instances/AktivGrotesk_Bd.ufo" postscriptfontname="AktivGrotesk-Bold" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="bold">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Bold Italic" familyname="Aktiv Grotesk" stylename="Bold Italic" filename="../instances/AktivGrotesk_BdIt.ufo" postscriptfontname="AktivGrotesk-BoldItalic" stylemapfamilyname="Aktiv Grotesk" stylemapstylename="bold italic">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Bold" familyname="Aktiv Grotesk" stylename="Ex Bold" filename="../instances/AktivGroteskEx_Bd.ufo" postscriptfontname="AktivGrotesk-ExBold" stylemapfamilyname="Aktiv Grotesk Ex" stylemapstylename="bold">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Bold Italic" familyname="Aktiv Grotesk" stylename="Ex Bold Italic" filename="../instances/AktivGroteskEx_BdIt.ufo" postscriptfontname="AktivGrotesk-ExBoldItalic" stylemapfamilyname="Aktiv Grotesk Ex" stylemapstylename="bold italic">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd XBold" familyname="Aktiv Grotesk" stylename="Cd XBold" filename="../instances/AktivGroteskCd_XBd.ufo" postscriptfontname="AktivGrotesk-CdXBold" stylemapfamilyname="Aktiv Grotesk Cd XBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd XBold Italic" familyname="Aktiv Grotesk" stylename="Cd XBold Italic" filename="../instances/AktivGroteskCd_XBdIt.ufo" postscriptfontname="AktivGrotesk-CdXBoldItalic" stylemapfamilyname="Aktiv Grotesk Cd XBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk XBold" familyname="Aktiv Grotesk" stylename="XBold" filename="../instances/AktivGrotesk_XBd.ufo" postscriptfontname="AktivGrotesk-XBold" stylemapfamilyname="Aktiv Grotesk XBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk XBold Italic" familyname="Aktiv Grotesk" stylename="XBold Italic" filename="../instances/AktivGrotesk_XBdIt.ufo" postscriptfontname="AktivGrotesk-XBoldItalic" stylemapfamilyname="Aktiv Grotesk XBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex XBold" familyname="Aktiv Grotesk" stylename="Ex XBold" filename="../instances/AktivGroteskEx_XBd.ufo" postscriptfontname="AktivGrotesk-ExXBold" stylemapfamilyname="Aktiv Grotesk Ex XBold" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex XBold Italic" familyname="Aktiv Grotesk" stylename="Ex XBold Italic" filename="../instances/AktivGroteskEx_XBdIt.ufo" postscriptfontname="AktivGrotesk-ExXBoldItalic" stylemapfamilyname="Aktiv Grotesk Ex XBold" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Black" familyname="Aktiv Grotesk" stylename="Cd Black" filename="../instances/AktivGroteskCd_Blk.ufo" postscriptfontname="AktivGrotesk-CdBlack" stylemapfamilyname="Aktiv Grotesk Cd Black" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Cd Black Italic" familyname="Aktiv Grotesk" stylename="Cd Black Italic" filename="../instances/AktivGroteskCd_BlkIt.ufo" postscriptfontname="AktivGrotesk-CdBlackItalic" stylemapfamilyname="Aktiv Grotesk Cd Black" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Black" familyname="Aktiv Grotesk" stylename="Black" filename="../instances/AktivGrotesk_Blk.ufo" postscriptfontname="AktivGrotesk-Black" stylemapfamilyname="Aktiv Grotesk Black" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Black Italic" familyname="Aktiv Grotesk" stylename="Black Italic" filename="../instances/AktivGrotesk_BlkIt.ufo" postscriptfontname="AktivGrotesk-BlackItalic" stylemapfamilyname="Aktiv Grotesk Black" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Black" familyname="Aktiv Grotesk" stylename="Ex Black" filename="../instances/AktivGroteskEx_Blk.ufo" postscriptfontname="AktivGrotesk-ExBlack" stylemapfamilyname="Aktiv Grotesk Ex Black" stylemapstylename="regular">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance name="Aktiv Grotesk Ex Black Italic" familyname="Aktiv Grotesk" stylename="Ex Black Italic" filename="../instances/AktivGroteskEx_BlkIt.ufo" postscriptfontname="AktivGrotesk-ExBlackItalic" stylemapfamilyname="Aktiv Grotesk Ex Black" stylemapstylename="italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/data/split_output/test_v5_sourceserif_italic_0.0.designspace b/Tests/designspaceLib/data/split_output/test_v5_sourceserif_italic_0.0.designspace
new file mode 100644
index 00000000..447461fb
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/test_v5_sourceserif_italic_0.0.designspace
@@ -0,0 +1,282 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes>
+ <axis tag="wght" name="weight" minimum="200" maximum="900" default="400">
+ <map input="200" output="0"/>
+ <map input="300" output="145"/>
+ <map input="400" output="394"/>
+ <map input="600" output="594"/>
+ <map input="700" output="823"/>
+ <map input="900" output="1000"/>
+ </axis>
+ <axis tag="opsz" name="optical" minimum="8" maximum="60" default="20"/>
+ </axes>
+ <sources>
+ <source filename="../caption/master_0/SourceSerif_c0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </source>
+ <source filename="../caption/master_1/SourceSerif_c1.ufo">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </source>
+ <source filename="../caption/master_2/SourceSerif_c2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </source>
+ <source filename="../text/master_0/SourceSerif_0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </source>
+ <source filename="../text/master_1/SourceSerif_1.ufo" familyname="Source Serif 4">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </source>
+ <source filename="../text/master_2/SourceSerif_2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </source>
+ <source filename="../display/master_0/SourceSerif_d0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </source>
+ <source filename="../display/master_1/SourceSerif_d1.ufo">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </source>
+ <source filename="../display/master_2/SourceSerif_d2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </source>
+ </sources>
+ <variable-fonts>
+ <variable-font name="SourceSerif4Variable-Roman">
+ <axis-subsets>
+ <axis-subset name="weight"/>
+ <axis-subset name="optical"/>
+ </axis-subsets>
+ <lib>
+ <dict>
+ <key>public.skipExportGlyphs</key>
+ <array>
+ <string>caron.alt</string>
+ <string>commabelowcmb.alt</string>
+ <string>tonos.cap</string>
+ <string>f.ligalong</string>
+ <string>dieresiscmb.tight</string>
+ <string>IJ</string>
+ <string>Tbar</string>
+ <string>colontriangularmod</string>
+ <string>crossmark</string>
+ <string>ij</string>
+ <string>overline</string>
+ <string>similar</string>
+ <string>tbar</string>
+ <string>triangularbullet</string>
+ <string>turkicdsccmb</string>
+ </array>
+ </dict>
+ </lib>
+ </variable-font>
+ </variable-fonts>
+ <instances>
+ <instance name="Source Serif 4 Caption ExtraLight" familyname="Source Serif 4" stylename="Caption ExtraLight" filename="Source Serif 4-Caption ExtraLight.ttf" postscriptfontname="SourceSerif4Roman-CaptionExtraLight" stylemapfamilyname="Source Serif 4 Caption ExtraLight" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Caption Light" familyname="Source Serif 4" stylename="Caption Light" filename="Source Serif 4-Caption Light.ttf" postscriptfontname="SourceSerif4Roman-CaptionLight" stylemapfamilyname="Source Serif 4 Caption Light" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Caption" familyname="Source Serif 4" stylename="Caption" filename="Source Serif 4-Caption.ttf" postscriptfontname="SourceSerif4Roman-CaptionRegular" stylemapfamilyname="Source Serif 4 Caption" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Caption Semibold" familyname="Source Serif 4" stylename="Caption Semibold" filename="Source Serif 4-Caption Semibold.ttf" postscriptfontname="SourceSerif4Roman-CaptionSemibold" stylemapfamilyname="Source Serif 4 Caption Semibold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Caption Bold" familyname="Source Serif 4" stylename="Caption Bold" filename="Source Serif 4-Caption Bold.ttf" postscriptfontname="SourceSerif4Roman-CaptionBold" stylemapfamilyname="Source Serif 4 Caption Bold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Caption Black" familyname="Source Serif 4" stylename="Caption Black" filename="Source Serif 4-Caption Black.ttf" postscriptfontname="SourceSerif4Roman-CaptionBlack" stylemapfamilyname="Source Serif 4 Caption Black" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText ExtraLight" familyname="Source Serif 4" stylename="SmText ExtraLight" filename="Source Serif 4-SmText ExtraLight.ttf" postscriptfontname="SourceSerif4Roman-SmTextExtraLight" stylemapfamilyname="Source Serif 4 SmallText ExtraLight" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText Light" familyname="Source Serif 4" stylename="SmText Light" filename="Source Serif 4-SmText Light.ttf" postscriptfontname="SourceSerif4Roman-SmTextLight" stylemapfamilyname="Source Serif 4 SmallText Light" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText" familyname="Source Serif 4" stylename="SmText" filename="Source Serif 4-SmText.ttf" postscriptfontname="SourceSerif4Roman-SmTextRegular" stylemapfamilyname="Source Serif 4 SmallText" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText Semibold" familyname="Source Serif 4" stylename="SmText Semibold" filename="Source Serif 4-SmText Semibold.ttf" postscriptfontname="SourceSerif4Roman-SmTextSemibold" stylemapfamilyname="Source Serif 4 SmallText Semibold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText Bold" familyname="Source Serif 4" stylename="SmText Bold" filename="Source Serif 4-SmText Bold.ttf" postscriptfontname="SourceSerif4Roman-SmTextBold" stylemapfamilyname="Source Serif 4 SmallText Bold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText Black" familyname="Source Serif 4" stylename="SmText Black" filename="Source Serif 4-SmText Black.ttf" postscriptfontname="SourceSerif4Roman-SmTextBlack" stylemapfamilyname="Source Serif 4 SmallText Black" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 ExtraLight" familyname="Source Serif 4" stylename="ExtraLight" filename="Source Serif 4-ExtraLight.ttf" postscriptfontname="SourceSerif4Roman-ExtraLight" stylemapfamilyname="Source Serif 4 ExtraLight" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Light" familyname="Source Serif 4" stylename="Light" filename="Source Serif 4-Light.ttf" postscriptfontname="SourceSerif4Roman-Light" stylemapfamilyname="Source Serif 4 Light" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Regular" familyname="Source Serif 4" stylename="Regular" filename="Source Serif 4-Regular.ttf" postscriptfontname="SourceSerif4Roman-Regular" stylemapfamilyname="Source Serif 4" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Semibold" familyname="Source Serif 4" stylename="Semibold" filename="Source Serif 4-Semibold.ttf" postscriptfontname="SourceSerif4Roman-Semibold" stylemapfamilyname="Source Serif 4 Semibold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Bold" familyname="Source Serif 4" stylename="Bold" filename="Source Serif 4-Bold.ttf" postscriptfontname="SourceSerif4Roman-Bold" stylemapfamilyname="Source Serif 4 Bold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Black" familyname="Source Serif 4" stylename="Black" filename="Source Serif 4-Black.ttf" postscriptfontname="SourceSerif4Roman-Black" stylemapfamilyname="Source Serif 4 Black" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead ExtraLight" familyname="Source Serif 4" stylename="Subhead ExtraLight" filename="Source Serif 4-Subhead ExtraLight.ttf" postscriptfontname="SourceSerif4Roman-SubheadExtraLight" stylemapfamilyname="Source Serif 4 Subhead ExtraLight" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead Light" familyname="Source Serif 4" stylename="Subhead Light" filename="Source Serif 4-Subhead Light.ttf" postscriptfontname="SourceSerif4Roman-SubheadLight" stylemapfamilyname="Source Serif 4 Subhead Light" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead" familyname="Source Serif 4" stylename="Subhead" filename="Source Serif 4-Subhead.ttf" postscriptfontname="SourceSerif4Roman-SubheadRegular" stylemapfamilyname="Source Serif 4 Subhead" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead Semibold" familyname="Source Serif 4" stylename="Subhead Semibold" filename="Source Serif 4-Subhead Semibold.ttf" postscriptfontname="SourceSerif4Roman-SubheadSemibold" stylemapfamilyname="Source Serif 4 Subhead Semibold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead Bold" familyname="Source Serif 4" stylename="Subhead Bold" filename="Source Serif 4-Subhead Bold.ttf" postscriptfontname="SourceSerif4Roman-SubheadBold" stylemapfamilyname="Source Serif 4 Subhead Bold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead Black" familyname="Source Serif 4" stylename="Subhead Black" filename="Source Serif 4-Subhead Black.ttf" postscriptfontname="SourceSerif4Roman-SubheadBlack" stylemapfamilyname="Source Serif 4 Subhead Black" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display ExtraLight" familyname="Source Serif 4" stylename="Display ExtraLight" filename="Source Serif 4-Display ExtraLight.ttf" postscriptfontname="SourceSerif4Roman-DisplayExtraLight" stylemapfamilyname="Source Serif 4 Display ExtraLight" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display Light" familyname="Source Serif 4" stylename="Display Light" filename="Source Serif 4-Display Light.ttf" postscriptfontname="SourceSerif4Roman-DisplayLight" stylemapfamilyname="Source Serif 4 Display Light" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display" familyname="Source Serif 4" stylename="Display" filename="Source Serif 4-Display.ttf" postscriptfontname="SourceSerif4Roman-DisplayRegular" stylemapfamilyname="Source Serif 4 Display" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display Semibold" familyname="Source Serif 4" stylename="Display Semibold" filename="Source Serif 4-Display Semibold.ttf" postscriptfontname="SourceSerif4Roman-DisplaySemibold" stylemapfamilyname="Source Serif 4 Display Semibold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display Bold" familyname="Source Serif 4" stylename="Display Bold" filename="Source Serif 4-Display Bold.ttf" postscriptfontname="SourceSerif4Roman-DisplayBold" stylemapfamilyname="Source Serif 4 Display Bold" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display Black" familyname="Source Serif 4" stylename="Display Black" filename="Source Serif 4-Display Black.ttf" postscriptfontname="SourceSerif4Roman-DisplayBlack" stylemapfamilyname="Source Serif 4 Display Black" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/data/split_output/test_v5_sourceserif_italic_1.0.designspace b/Tests/designspaceLib/data/split_output/test_v5_sourceserif_italic_1.0.designspace
new file mode 100644
index 00000000..e1ef12f6
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/test_v5_sourceserif_italic_1.0.designspace
@@ -0,0 +1,274 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes>
+ <axis tag="wght" name="weight" minimum="200" maximum="900" default="400">
+ <map input="200" output="0"/>
+ <map input="300" output="145"/>
+ <map input="400" output="394"/>
+ <map input="600" output="594"/>
+ <map input="700" output="823"/>
+ <map input="900" output="1000"/>
+ </axis>
+ <axis tag="opsz" name="optical" minimum="8" maximum="60" default="20"/>
+ </axes>
+ <sources>
+ <source filename="../caption/master_0/SourceSerif-Italic_c0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </source>
+ <source filename="../caption/master_1/SourceSerif-Italic_c1.ufo">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </source>
+ <source filename="../caption/master_2/SourceSerif-Italic_c2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </source>
+ <source filename="../text/master_0/SourceSerif-Italic_0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </source>
+ <source filename="../text/master_1/SourceSerif-Italic_1.ufo">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </source>
+ <source filename="../text/master_2/SourceSerif-Italic_2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </source>
+ <source filename="../display/master_0/SourceSerif-Italic_d0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </source>
+ <source filename="../display/master_1/SourceSerif-Italic_d1.ufo">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </source>
+ <source filename="../display/master_2/SourceSerif-Italic_d2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </source>
+ </sources>
+ <variable-fonts>
+ <variable-font name="SourceSerif4Variable-Italic">
+ <axis-subsets>
+ <axis-subset name="weight"/>
+ <axis-subset name="optical"/>
+ </axis-subsets>
+ <lib>
+ <dict>
+ <key>public.skipExportGlyphs</key>
+ <array>
+ <string>caron.alt</string>
+ <string>commabelowcmb.alt</string>
+ <string>f.liga</string>
+ <string>f.ligalong</string>
+ <string>tonos.cap</string>
+ <string>dieresiscmb.tight</string>
+ <string>turkicdsccmb</string>
+ </array>
+ </dict>
+ </lib>
+ </variable-font>
+ </variable-fonts>
+ <instances>
+ <instance name="Source Serif 4 Caption ExtraLight Italic" familyname="Source Serif 4" stylename="Caption ExtraLight Italic" filename="Source Serif 4-Caption ExtraLight Italic.ttf" postscriptfontname="SourceSerif4Italic-CaptionExtraLight" stylemapfamilyname="Source Serif 4 Caption ExtraLight Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Caption Light Italic" familyname="Source Serif 4" stylename="Caption Light Italic" filename="Source Serif 4-Caption Light Italic.ttf" postscriptfontname="SourceSerif4Italic-CaptionLight" stylemapfamilyname="Source Serif 4 Caption Light Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Caption Italic" familyname="Source Serif 4" stylename="Caption Italic" filename="Source Serif 4-Caption Italic.ttf" postscriptfontname="SourceSerif4Italic-CaptionRegular" stylemapfamilyname="Source Serif 4 Caption Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Caption Semibold Italic" familyname="Source Serif 4" stylename="Caption Semibold Italic" filename="Source Serif 4-Caption Semibold Italic.ttf" postscriptfontname="SourceSerif4Italic-CaptionSemibold" stylemapfamilyname="Source Serif 4 Caption Semibold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Caption Bold Italic" familyname="Source Serif 4" stylename="Caption Bold Italic" filename="Source Serif 4-Caption Bold Italic.ttf" postscriptfontname="SourceSerif4Italic-CaptionBold" stylemapfamilyname="Source Serif 4 Caption Bold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Caption Black Italic" familyname="Source Serif 4" stylename="Caption Black Italic" filename="Source Serif 4-Caption Black Italic.ttf" postscriptfontname="SourceSerif4Italic-CaptionBlack" stylemapfamilyname="Source Serif 4 Caption Black Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="8"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText ExtraLight Italic" familyname="Source Serif 4" stylename="SmText ExtraLight Italic" filename="Source Serif 4-SmText ExtraLight Italic.ttf" postscriptfontname="SourceSerif4Italic-SmTextExtraLight" stylemapfamilyname="Source Serif 4 SmallText ExtraLight Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText Light Italic" familyname="Source Serif 4" stylename="SmText Light Italic" filename="Source Serif 4-SmText Light Italic.ttf" postscriptfontname="SourceSerif4Italic-SmTextLight" stylemapfamilyname="Source Serif 4 SmallText Light Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText Italic" familyname="Source Serif 4" stylename="SmText Italic" filename="Source Serif 4-SmText Italic.ttf" postscriptfontname="SourceSerif4Italic-SmTextRegular" stylemapfamilyname="Source Serif 4 SmallText Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText Semibold Italic" familyname="Source Serif 4" stylename="SmText Semibold Italic" filename="Source Serif 4-SmText Semibold Italic.ttf" postscriptfontname="SourceSerif4Italic-SmTextSemibold" stylemapfamilyname="Source Serif 4 SmallText Semibold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText Bold Italic" familyname="Source Serif 4" stylename="SmText Bold Italic" filename="Source Serif 4-SmText Bold Italic.ttf" postscriptfontname="SourceSerif4Italic-SmTextBold" stylemapfamilyname="Source Serif 4 SmallText Bold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 SmText Black Italic" familyname="Source Serif 4" stylename="SmText Black Italic" filename="Source Serif 4-SmText Black Italic.ttf" postscriptfontname="SourceSerif4Italic-SmTextBlack" stylemapfamilyname="Source Serif 4 SmallText Black Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="16"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 ExtraLight Italic" familyname="Source Serif 4" stylename="ExtraLight Italic" filename="Source Serif 4-ExtraLight Italic.ttf" postscriptfontname="SourceSerif4Italic-ExtraLight" stylemapfamilyname="Source Serif 4 ExtraLight Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Light Italic" familyname="Source Serif 4" stylename="Light Italic" filename="Source Serif 4-Light Italic.ttf" postscriptfontname="SourceSerif4Italic-Light" stylemapfamilyname="Source Serif 4 Light Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Italic" familyname="Source Serif 4" stylename="Italic" filename="Source Serif 4-Italic.ttf" postscriptfontname="SourceSerif4Italic-Regular" stylemapfamilyname="Source Serif 4 Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Semibold Italic" familyname="Source Serif 4" stylename="Semibold Italic" filename="Source Serif 4-Semibold Italic.ttf" postscriptfontname="SourceSerif4Italic-Semibold" stylemapfamilyname="Source Serif 4 Semibold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Bold Italic" familyname="Source Serif 4" stylename="Bold Italic" filename="Source Serif 4-Bold Italic.ttf" postscriptfontname="SourceSerif4Italic-Bold" stylemapfamilyname="Source Serif 4 Bold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Black Italic" familyname="Source Serif 4" stylename="Black Italic" filename="Source Serif 4-Black Italic.ttf" postscriptfontname="SourceSerif4Italic-Black" stylemapfamilyname="Source Serif 4 Black Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="20"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead ExtraLight Italic" familyname="Source Serif 4" stylename="Subhead ExtraLight Italic" filename="Source Serif 4-Subhead ExtraLight Italic.ttf" postscriptfontname="SourceSerif4Italic-SubheadExtraLight" stylemapfamilyname="Source Serif 4 Subhead ExtraLight Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead Light Italic" familyname="Source Serif 4" stylename="Subhead Light Italic" filename="Source Serif 4-Subhead Light Italic.ttf" postscriptfontname="SourceSerif4Italic-SubheadLight" stylemapfamilyname="Source Serif 4 Subhead Light Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead Italic" familyname="Source Serif 4" stylename="Subhead Italic" filename="Source Serif 4-Subhead Italic.ttf" postscriptfontname="SourceSerif4Italic-SubheadRegular" stylemapfamilyname="Source Serif 4 Subhead Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead Semibold Italic" familyname="Source Serif 4" stylename="Subhead Semibold Italic" filename="Source Serif 4-Subhead Semibold Italic.ttf" postscriptfontname="SourceSerif4Italic-SubheadSemibold" stylemapfamilyname="Source Serif 4 Subhead Semibold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead Bold Italic" familyname="Source Serif 4" stylename="Subhead Bold Italic" filename="Source Serif 4-Subhead Bold Italic.ttf" postscriptfontname="SourceSerif4Italic-SubheadBold" stylemapfamilyname="Source Serif 4 Subhead Bold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Subhead Black Italic" familyname="Source Serif 4" stylename="Subhead Black Italic" filename="Source Serif 4-Subhead Black Italic.ttf" postscriptfontname="SourceSerif4Italic-SubheadBlack" stylemapfamilyname="Source Serif 4 Subhead Black Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="32"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display ExtraLight Italic" familyname="Source Serif 4" stylename="Display ExtraLight Italic" filename="Source Serif 4-Display ExtraLight Italic.ttf" postscriptfontname="SourceSerif4Italic-DisplayExtraLight" stylemapfamilyname="Source Serif 4 Display ExtraLight Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display Light Italic" familyname="Source Serif 4" stylename="Display Light Italic" filename="Source Serif 4-Display Light Italic.ttf" postscriptfontname="SourceSerif4Italic-DisplayLight" stylemapfamilyname="Source Serif 4 Display Light Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display Italic" familyname="Source Serif 4" stylename="Display Italic" filename="Source Serif 4-Display Italic.ttf" postscriptfontname="SourceSerif4Italic-DisplayRegular" stylemapfamilyname="Source Serif 4 Display Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display Semibold Italic" familyname="Source Serif 4" stylename="Display Semibold Italic" filename="Source Serif 4-Display Semibold Italic.ttf" postscriptfontname="SourceSerif4Italic-DisplaySemibold" stylemapfamilyname="Source Serif 4 Display Semibold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display Bold Italic" familyname="Source Serif 4" stylename="Display Bold Italic" filename="Source Serif 4-Display Bold Italic.ttf" postscriptfontname="SourceSerif4Italic-DisplayBold" stylemapfamilyname="Source Serif 4 Display Bold Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ <instance name="Source Serif 4 Display Black Italic" familyname="Source Serif 4" stylename="Display Black Italic" filename="Source Serif 4-Display Black Italic.ttf" postscriptfontname="SourceSerif4Italic-DisplayBlack" stylemapfamilyname="Source Serif 4 Display Black Italic" stylemapstylename="regular">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="60"/>
+ </location>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/data/test.designspace b/Tests/designspaceLib/data/test_v4_original.designspace
index e12f1568..d3b46c7f 100644
--- a/Tests/designspaceLib/data/test.designspace
+++ b/Tests/designspaceLib/data/test_v4_original.designspace
@@ -1,5 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?>
<designspace format="4.1">
+ <!-- NOTE: this file is kept at format 4, to check that round-tripping it
+ doesn't upgrade the format. -->
<axes>
<axis tag="wght" name="weight" minimum="0" maximum="1000" default="0">
<labelname xml:lang="en">Wéíght</labelname>
@@ -50,6 +52,13 @@
</sources>
<instances>
<instance name="instance.ufo1" familyname="InstanceFamilyName" stylename="InstanceStyleName" filename="instances/instanceTest1.ufo" postscriptfontname="InstancePostscriptName" stylemapfamilyname="InstanceStyleMapFamilyName" stylemapstylename="InstanceStyleMapStyleName">
+ <stylename xml:lang="fr">Demigras</stylename>
+ <stylename xml:lang="ja">半ば</stylename>
+ <familyname xml:lang="fr">Montserrat</familyname>
+ <familyname xml:lang="ja">モンセラート</familyname>
+ <stylemapstylename xml:lang="de">Standard</stylemapstylename>
+ <stylemapfamilyname xml:lang="de">Montserrat Halbfett</stylemapfamilyname>
+ <stylemapfamilyname xml:lang="ja">モンセラート SemiBold</stylemapfamilyname>
<location>
<dimension name="weight" xvalue="500"/>
<dimension name="width" xvalue="20"/>
diff --git a/Tests/designspaceLib/data/test_v5.designspace b/Tests/designspaceLib/data/test_v5.designspace
new file mode 100644
index 00000000..2f611b49
--- /dev/null
+++ b/Tests/designspaceLib/data/test_v5.designspace
@@ -0,0 +1,294 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes elidedfallbackname="Regular">
+ <axis tag="wght" name="weight" minimum="200" maximum="1000" default="200">
+ <labelname xml:lang="en">Wéíght</labelname>
+ <labelname xml:lang="fa-IR">قطر</labelname>
+ <map input="200" output="0"/>
+ <map input="300" output="100"/>
+ <map input="400" output="368"/>
+ <map input="600" output="600"/>
+ <map input="700" output="824"/>
+ <map input="900" output="1000"/>
+ <!-- All axes provide STAT information with the "labels" element. -->
+ <labels>
+ <label uservalue="200" userminimum="200" usermaximum="250" name="Extra Light">
+ <labelname xml:lang="de">Extraleicht</labelname>
+ <labelname xml:lang="fr">Extra léger</labelname>
+ </label>
+ <label uservalue="300" userminimum="250" usermaximum="350" name="Light"/>
+ <label uservalue="400" userminimum="350" usermaximum="450" name="Regular" elidable="true"/>
+ <label uservalue="600" userminimum="450" usermaximum="650" name="Semi Bold"/>
+ <label uservalue="700" userminimum="650" usermaximum="850" name="Bold"/>
+ <label uservalue="900" userminimum="850" usermaximum="900" name="Black"/>
+ </labels>
+ </axis>
+
+ <axis tag="wdth" name="width" minimum="50" maximum="150" default="100" hidden="1">
+ <labelname xml:lang="fr">Chasse</labelname>
+ <map input="50" output="10"/>
+ <map input="100" output="20"/>
+ <map input="125" output="66"/>
+ <map input="150" output="990"/>
+ <labels ordering="1">
+ <label uservalue="50" name="Condensed"/>
+ <label uservalue="100" name="Normal" elidable="true" oldersibling="true"/>
+ <label uservalue="125" name="Wide"/>
+ <!-- Allow specifying only one end of the range, the other is assumed to
+ be infinity as does otlLib buildStatTable -->
+ <label uservalue="150" userminimum="150" name="Extra Wide"/>
+ </labels>
+ </axis>
+
+ <!--
+ Discrete axes provide a list of discrete values.
+ No interpolation is allowed between these.
+ -->
+ <axis tag="ital" name="Italic" values="0 1" default="0">
+ <labels>
+ <!-- Discrete axes also provide STAT information. -->
+ <label uservalue="0" name="Roman" elidable="true" linkeduservalue="1"/>
+ <label uservalue="1" name="Italic"/>
+ </labels>
+ </axis>
+ </axes>
+
+ <!-- Freestanding labels are analogues of STAT format 4 entries.
+ They give names to freestyle locations. -->
+ <labels>
+ <label name="Some Style">
+ <labelname xml:lang="fr">Un Style</labelname>
+ <location>
+ <dimension name="weight" uservalue="300"/>
+ <dimension name="width" uservalue="50"/>
+ <dimension name="Italic" uservalue="0"/>
+ </location>
+ </label>
+ <label name="Other">
+ <location>
+ <dimension name="weight" uservalue="700"/>
+ <dimension name="width" uservalue="100"/>
+ <dimension name="Italic" uservalue="1"/>
+ </location>
+ </label>
+ </labels>
+
+ <rules processing="last">
+ <rule name="named.rule.1">
+ <conditionset>
+ <condition name="axisName_a" minimum="0" maximum="1"/>
+ <condition name="axisName_b" minimum="2" maximum="3"/>
+ </conditionset>
+ <sub name="a" with="a.alt"/>
+ </rule>
+ </rules>
+
+ <sources>
+ <source filename="masters/masterTest1.ufo" name="master.ufo1" familyname="MasterFamilyName" stylename="MasterStyleNameOne">
+ <familyname xml:lang="fr">Montserrat</familyname>
+ <familyname xml:lang="ja">モンセラート</familyname>
+ <lib copy="1"/>
+ <features copy="1"/>
+ <info copy="1"/>
+ <glyph name="A" mute="1"/>
+ <glyph name="Z" mute="1"/>
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="width" xvalue="20"/>
+ </location>
+ </source>
+ <source filename="masters/masterTest2.ufo" name="master.ufo2" familyname="MasterFamilyName" stylename="MasterStyleNameTwo">
+ <kerning mute="1"/>
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="width" xvalue="20"/>
+ </location>
+ </source>
+ <source filename="masters/masterTest2.ufo" name="master.ufo2" familyname="MasterFamilyName" stylename="Supports" layer="supports">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="width" xvalue="20"/>
+ </location>
+ </source>
+ </sources>
+
+ <variable-fonts>
+ <!--
+ If this element is present, all output targets must be listed within it.
+ If it is not present, the full Designspace is the output, like in version 4.x.
+
+ Continuous axes can be included either:
+ * in full,
+ * or only on a reduced interval (different minimum-maximum),
+ * or only at 1 discrete location
+ Dicrete axes cannot be included in full, and we must specify a value
+ (or the compiler should assume the default value).
+ -->
+ <variable-font name="Test_WghtWdth" filename="Test_WghtWdth_different_from_name.ttf">
+ <!-- This one is the Roman (default location along ital),
+ with full range for the Weight axis. -->
+ <axis-subsets>
+ <axis-subset name="Weight"/>
+ <axis-subset name="Width"/>
+ </axis-subsets>
+ <lib>
+ <dict>
+ <key>com.vtt.source</key>
+ <string>sources/vtt/Test_WghtWdth.vtt</string>
+ </dict>
+ </lib>
+ </variable-font>
+ <variable-font name="Test_Wght">
+ <!-- This one is the Roman (default location along ital),
+ with full range for the Weight axis. -->
+ <axis-subsets>
+ <axis-subset name="Weight"/>
+ </axis-subsets>
+ <lib>
+ <dict>
+ <key>com.vtt.source</key>
+ <string>sources/vtt/Test_Wght.vtt</string>
+ </dict>
+ </lib>
+ </variable-font>
+ <variable-font name="TestCd_Wght">
+ <!-- This one is the Roman (default location along ital),
+ with full range for the Weight axis. -->
+ <axis-subsets>
+ <axis-subset name="Weight"/>
+ <axis-subset name="Width" uservalue="0"/>
+ </axis-subsets>
+ </variable-font>
+ <variable-font name="TestWd_Wght">
+ <!-- This one is the Roman (default location along ital),
+ with full range for the Weight axis. -->
+ <axis-subsets>
+ <axis-subset name="Weight"/>
+ <axis-subset name="Width" uservalue="1000"/>
+ </axis-subsets>
+ </variable-font>
+ <variable-font name="TestItalic_Wght">
+ <!-- This one is the Italic, with full range for the Weight axis. -->
+ <axis-subsets>
+ <axis-subset name="Weight"/>
+ <axis-subset name="Italic" uservalue="1"/>
+ </axis-subsets>
+ </variable-font>
+ <variable-font name="TestRB_Wght">
+ <!-- As an example, this would be the Roman with a reduced range. -->
+ <axis-subsets>
+ <axis-subset name="Weight" userminimum="400" usermaximum="700" userdefault="400"/>
+ <axis-subset name="Italic" uservalue="0"/>
+ </axis-subsets>
+ </variable-font>
+ </variable-fonts>
+
+ <instances>
+ <instance name="instance.ufo1" familyname="InstanceFamilyName" stylename="InstanceStyleName" filename="instances/instanceTest1.ufo" postscriptfontname="InstancePostscriptName" stylemapfamilyname="InstanceStyleMapFamilyName" stylemapstylename="InstanceStyleMapStyleName">
+ <stylename xml:lang="fr">Demigras</stylename>
+ <stylename xml:lang="ja">半ば</stylename>
+ <familyname xml:lang="fr">Montserrat</familyname>
+ <familyname xml:lang="ja">モンセラート</familyname>
+ <stylemapstylename xml:lang="de">Standard</stylemapstylename>
+ <stylemapfamilyname xml:lang="de">Montserrat Halbfett</stylemapfamilyname>
+ <stylemapfamilyname xml:lang="ja">モンセラート SemiBold</stylemapfamilyname>
+ <location>
+ <dimension name="weight" xvalue="500"/>
+ <dimension name="width" xvalue="20"/>
+ </location>
+
+ <!-- The following elements are deprecated in v5.0. They can still be
+ read, but they won't be written out again (they don't roundtrip). -->
+ <!-- ROUNDTRIP_TEST_REMOVE_ME_BEGIN -->
+ <glyphs>
+ <glyph mute="1" unicode="0x123 0x124 0x125" name="arrow"/>
+ </glyphs>
+ <kerning/>
+ <info/>
+ <!-- ROUNDTRIP_TEST_REMOVE_ME_END -->
+
+ <lib>
+ <dict>
+ <key>com.coolDesignspaceApp.binaryData</key>
+ <data>
+ PGJpbmFyeSBndW5rPg==
+ </data>
+ <key>com.coolDesignspaceApp.specimenText</key>
+ <string>Hamburgerwhatever</string>
+ </dict>
+ </lib>
+ </instance>
+ <instance name="instance.ufo2" familyname="InstanceFamilyName" stylename="InstanceStyleName" filename="instances/instanceTest2.ufo" postscriptfontname="InstancePostscriptName" stylemapfamilyname="InstanceStyleMapFamilyName" stylemapstylename="InstanceStyleMapStyleName">
+ <location>
+ <dimension name="weight" xvalue="500"/>
+ <dimension name="width" xvalue="400" yvalue="300"/>
+ </location>
+ <!-- ROUNDTRIP_TEST_REMOVE_ME_BEGIN -->
+ <glyphs>
+ <glyph unicode="0x65 0xc9 0x12d" name="arrow">
+ <location>
+ <dimension name="weight" xvalue="120"/>
+ <dimension name="width" xvalue="100"/>
+ </location>
+ <note>A note about this glyph</note>
+ <masters>
+ <master glyphname="BB" source="master.ufo1">
+ <location>
+ <dimension name="weight" xvalue="20"/>
+ <dimension name="width" xvalue="20"/>
+ </location>
+ </master>
+ <master glyphname="CC" source="master.ufo2">
+ <location>
+ <dimension name="weight" xvalue="900"/>
+ <dimension name="width" xvalue="900"/>
+ </location>
+ </master>
+ </masters>
+ </glyph>
+ <glyph name="arrow2"/>
+ </glyphs>
+ <kerning/>
+ <info/>
+ <!-- ROUNDTRIP_TEST_REMOVE_ME_END -->
+ </instance>
+
+ <!--
+ These instances will derive all their data from the data above.
+
+ Instances can specify their location either:
+ - using the name of a location label
+ - with design coordinates (xvalue="")
+ - with user coordinates (uservalue="")
+ - with a mix of both coordinate systems
+ -->
+ <instance location="asdf"/>
+ <instance>
+ <location>
+ <dimension name="weight" xvalue="600"/>
+ <dimension name="width" xvalue="401" yvalue="420"/>
+ </location>
+ </instance>
+ <instance>
+ <location>
+ <dimension name="weight" xvalue="10"/>
+ <dimension name="width" uservalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance>
+ <location>
+ <dimension name="weight" uservalue="300"/>
+ <dimension name="width" uservalue="130"/>
+ <dimension name="Italic" uservalue="1"/>
+ </location>
+ </instance>
+ </instances>
+
+ <lib>
+ <dict>
+ <key>com.coolDesignspaceApp.previewSize</key>
+ <integer>30</integer>
+ </dict>
+ </lib>
+</designspace>
diff --git a/Tests/designspaceLib/data/test_v5_MutatorSans_and_Serif.designspace b/Tests/designspaceLib/data/test_v5_MutatorSans_and_Serif.designspace
new file mode 100644
index 00000000..d0fbb3d2
--- /dev/null
+++ b/Tests/designspaceLib/data/test_v5_MutatorSans_and_Serif.designspace
@@ -0,0 +1,206 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes elidedfallbackname="Regular">
+ <axis tag="SRIF" name="serif" values="0 1" default="0">
+ <labels>
+ <label uservalue="0" name="Sans"/>
+ <label uservalue="1" name="Serif"/>
+ </labels>
+ </axis>
+ <axis tag="wght" name="weight" minimum="300" maximum="700" default="300">
+ <map input="300" output="0"/>
+ <map input="500" output="500"/>
+ <map input="700" output="1000"/>
+ <labels>
+ <label uservalue="300" userminimum="300" usermaximum="400" name="Light"/>
+ <label uservalue="500" userminimum="400" usermaximum="600" name="Medium"/>
+ <label uservalue="700" userminimum="600" usermaximum="700" name="Bold"/>
+ </labels>
+ </axis>
+ <axis tag="wdth" name="width" minimum="50" maximum="200" default="50">
+ <map input="50" output="0"/>
+ <map input="100" output="500"/>
+ <map input="200" output="1000"/>
+ <labels>
+ <label uservalue="50" userminimum="50" usermaximum="75" name="Condensed"/>
+ <label uservalue="100" userminimum="75" usermaximum="125" name="Normal" elidable="true"/>
+ <label uservalue="200" userminimum="125" usermaximum="200" name="Extended"/>
+ </labels>
+ </axis>
+ </axes>
+ <labels>
+ <label name="S1">
+ <location>
+ <dimension name="width" uservalue="158.9044"/>
+ <dimension name="weight" uservalue="610.2436"/>
+ </location>
+ </label>
+ <label name="S2">
+ <location>
+ <dimension name="width" uservalue="159.1956"/>
+ <dimension name="weight" uservalue="642.2196"/>
+ </location>
+ </label>
+ </labels>
+ <rules>
+ <rule name="fold_I_serifs">
+ <conditionset>
+ <condition name="width" minimum="0" maximum="328"/>
+ <condition name="serif" minimum="0" maximum="0"/>
+ </conditionset>
+ <sub name="I" with="I.narrow"/>
+ </rule>
+ <rule name="fold_S_terminals">
+ <conditionset>
+ <condition name="width" minimum="0" maximum="1000"/>
+ <condition name="weight" minimum="0" maximum="500"/>
+ <condition name="serif" minimum="0" maximum="0.5"/>
+ </conditionset>
+ <sub name="S" with="S.closed"/>
+ </rule>
+ </rules>
+ <sources>
+ <source filename="MutatorSansLightCondensed.ufo" name="master.MutatorMathTest.LightCondensed.0" familyname="MutatorMathTest" stylename="LightCondensed">
+ <location>
+ <dimension name="width" xvalue="0"/>
+ <dimension name="weight" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="MutatorSansBoldCondensed.ufo" name="master.MutatorMathTest.BoldCondensed.1" familyname="MutatorMathTest" stylename="BoldCondensed">
+ <location>
+ <dimension name="width" xvalue="0"/>
+ <dimension name="weight" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="MutatorSansLightWide.ufo" name="master.MutatorMathTest.LightWide.2" familyname="MutatorMathTest" stylename="LightWide">
+ <location>
+ <dimension name="width" xvalue="1000"/>
+ <dimension name="weight" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="MutatorSansBoldWide.ufo" name="master.MutatorMathTest.BoldWide.3" familyname="MutatorMathTest" stylename="BoldWide">
+ <location>
+ <dimension name="width" xvalue="1000"/>
+ <dimension name="weight" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="MutatorSansLightCondensed.ufo" name="support.crossbar" layer="support.crossbar">
+ <location>
+ <dimension name="width" xvalue="0"/>
+ <dimension name="weight" xvalue="700"/>
+ </location>
+ </source>
+ <source filename="MutatorSansLightCondensed.ufo" name="support.S.wide" layer="support.S.wide">
+ <location>
+ <dimension name="width" xvalue="1000"/>
+ <dimension name="weight" xvalue="700"/>
+ </location>
+ </source>
+ <source filename="MutatorSansLightCondensed.ufo" name="support.S.middle" layer="support.S.middle">
+ <location>
+ <dimension name="width" xvalue="569.078000"/>
+ <dimension name="weight" xvalue="700"/>
+ </location>
+ </source>
+ <source filename="MutatorSerifLightCondensed.ufo" familyname="MutatorMathTest" stylename="SerifLightCondensed">
+ <location>
+ <dimension name="width" xvalue="0"/>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="serif" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="MutatorSerifLightWide.ufo" familyname="MutatorMathTest" stylename="SerifLightWide">
+ <location>
+ <dimension name="width" xvalue="1000"/>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="serif" xvalue="1"/>
+ </location>
+ </source>
+ </sources>
+ <variable-fonts>
+ <variable-font name="MutatorSansVariable_Weight_Width">
+ <axis-subsets>
+ <axis-subset name="weight"/>
+ <axis-subset name="width"/>
+ </axis-subsets>
+ </variable-font>
+ <variable-font name="MutatorSansVariable_Weight">
+ <axis-subsets>
+ <axis-subset name="weight"/>
+ </axis-subsets>
+ </variable-font>
+ <variable-font name="MutatorSansVariable_Width">
+ <axis-subsets>
+ <axis-subset name="width"/>
+ </axis-subsets>
+ </variable-font>
+ <variable-font name="MutatorSerifVariable_Width">
+ <axis-subsets>
+ <axis-subset name="serif" uservalue="1"/>
+ <axis-subset name="width"/>
+ </axis-subsets>
+ </variable-font>
+ </variable-fonts>
+ <instances>
+ <instance>
+ <location>
+ <dimension name="width" xvalue="0"/>
+ <dimension name="weight" xvalue="0"/>
+ </location>
+ </instance>
+ <instance>
+ <location>
+ <dimension name="width" xvalue="0"/>
+ <dimension name="weight" xvalue="1000"/>
+ </location>
+ </instance>
+ <instance>
+ <location>
+ <dimension name="width" xvalue="1000"/>
+ <dimension name="weight" xvalue="0"/>
+ </location>
+ </instance>
+ <instance>
+ <location>
+ <dimension name="width" xvalue="1000"/>
+ <dimension name="weight" xvalue="1000"/>
+ </location>
+ </instance>
+ <instance>
+ <location>
+ <dimension name="width" xvalue="327"/>
+ <dimension name="weight" xvalue="500"/>
+ </location>
+ </instance>
+ <instance>
+ <location>
+ <dimension name="width" xvalue="327"/>
+ <dimension name="weight" xvalue="500"/>
+ </location>
+ </instance>
+ <instance>
+ <location>
+ <dimension name="width" xvalue="569.078"/>
+ <dimension name="weight" xvalue="1000"/>
+ </location>
+ </instance>
+ <instance>
+ <location>
+ <dimension name="width" xvalue="1000"/>
+ <dimension name="weight" xvalue="500"/>
+ </location>
+ </instance>
+ <instance>
+ <location>
+ <dimension name="width" xvalue="0"/>
+ <dimension name="serif" xvalue="0"/>
+ </location>
+ </instance>
+ <instance>
+ <location>
+ <dimension name="width" xvalue="0"/>
+ <dimension name="serif" xvalue="1"/>
+ </location>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/data/test_v5_aktiv.designspace b/Tests/designspaceLib/data/test_v5_aktiv.designspace
new file mode 100644
index 00000000..32dfd1ae
--- /dev/null
+++ b/Tests/designspaceLib/data/test_v5_aktiv.designspace
@@ -0,0 +1,621 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes>
+ <axis tag="wght" name="Weight" minimum="100" maximum="900" default="400">
+ <map input="100" output="22"/>
+ <map input="200" output="38"/>
+ <map input="300" output="57"/>
+ <map input="400" output="84"/>
+ <map input="500" output="98"/>
+ <map input="600" output="115"/>
+ <map input="700" output="133"/>
+ <map input="800" output="158"/>
+ <map input="900" output="185"/>
+ <labels ordering="1">
+ <label uservalue="100" name="Hair"/>
+ <label uservalue="200" name="Thin"/>
+ <label uservalue="300" name="Light"/>
+ <label uservalue="400" name="Regular" elidable="true" linkeduservalue="700"/>
+ <label uservalue="500" name="Medium"/>
+ <label uservalue="600" name="SemiBold"/>
+ <label uservalue="700" name="Bold"/>
+ <label uservalue="800" name="XBold"/>
+ <label uservalue="900" name="Black"/>
+ </labels>
+ </axis>
+ <axis tag="wdth" name="Width" minimum="75" maximum="125" default="100">
+ <labels ordering="0">
+ <label uservalue="75" name="Cd"/>
+ <label uservalue="100" name="Normal" elidable="true"/>
+ <label uservalue="125" name="Ex"/>
+ </labels>
+ </axis>
+ <axis tag="ital" name="Italic" minimum="0" maximum="1" default="0">
+ <labels ordering="2">
+ <label uservalue="0" name="Upright" elidable="true" linkeduservalue="1"/>
+ <label uservalue="1" name="Italic"/>
+ </labels>
+ </axis>
+ </axes>
+
+ <rules processing="last">
+ <rule name="BRACKET.CYR">
+ <conditionset>
+ <condition name="Italic" minimum="0.1" maximum="1"/>
+ </conditionset>
+ <sub name="ghe.loclSRB" with="ghe.ital.loclSRB"/>
+ <sub name="ghe.loclMKD" with="ghe.ital.loclMKD"/>
+ <sub name="de.loclMKDSRB" with="de.ital.loclMKDSRB"/>
+ <sub name="pe.loclMKDSRB" with="pe.ital.loclMKDSRB"/>
+ <sub name="te.loclMKDSRB" with="te.ital.loclMKDSRB"/>
+ <sub name="gje.loclMKD" with="gje.ital.loclMKD"/>
+ <sub name="sha.loclMKDSRB" with="sha.ital.loclMKDSRB"/>
+ </rule>
+ <rule name="BRACKET.116.185">
+ <conditionset>
+ <condition name="Weight" minimum="116" maximum="185"/>
+ <condition name="Width" minimum="75" maximum="97.5"/>
+ </conditionset>
+ <sub name="cent" with="cent.BRACKET.130"/>
+ <sub name="dollar" with="dollar.BRACKET.130"/>
+ </rule>
+ </rules>
+
+ <sources>
+ <source filename="AktivGroteskCd_Hair.ufo" name="Aktiv Grotesk Cd Hair">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="AktivGroteskCd_HairIt.ufo" name="Aktiv Grotesk Cd Hair Italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="AktivGrotesk_Hair.ufo" name="Aktiv Grotesk Hair">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="AktivGrotesk_HairIt.ufo" name="Aktiv Grotesk Hair Italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="AktivGroteskEx_Hair.ufo" name="Aktiv Grotesk Ex Hair">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="AktivGroteskEx_HairIt.ufo" name="Aktiv Grotesk Ex Hair Italic">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="AktivGroteskCd_Rg.ufo" name="Aktiv Grotesk Cd">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="AktivGroteskCd_It.ufo" name="Aktiv Grotesk Cd Italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="AktivGrotesk_Rg.ufo" name="Aktiv Grotesk Regular" familyname="Aktiv Grotesk">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="AktivGrotesk_Rg.ufo" name="Aktiv Grotesk Regular" layer="{126,100,0}">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="AktivGrotesk_It.ufo" name="Aktiv Grotesk Italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="AktivGrotesk_It.ufo" name="Aktiv Grotesk Italic {126,100,1}" layer="{126,100,1}">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="AktivGroteskEx_Rg.ufo" name="Aktiv Grotesk Ex">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="AktivGroteskEx_It.ufo" name="Aktiv Grotesk Ex Italic">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="AktivGroteskCd_Blk.ufo" name="Aktiv Grotesk Cd Black">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="AktivGroteskCd_BlkIt.ufo" name="Aktiv Grotesk Cd Black Italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="AktivGrotesk_Blk.ufo" name="Aktiv Grotesk Black">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="AktivGrotesk_BlkIt.ufo" name="Aktiv Grotesk Black Italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="AktivGroteskEx_Blk.ufo" name="Aktiv Grotesk Ex Black">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="AktivGroteskEx_BlkIt.ufo" name="Aktiv Grotesk Ex Black Italic">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ </sources>
+
+ <variable-fonts>
+ <variable-font name="AktivGroteskVF_WghtWdthItal">
+ <axis-subsets>
+ <axis-subset name="Weight"/>
+ <axis-subset name="Width"/>
+ <axis-subset name="Italic"/>
+ </axis-subsets>
+ </variable-font>
+ <variable-font name="AktivGroteskVF_WghtWdth">
+ <axis-subsets>
+ <axis-subset name="Weight"/>
+ <axis-subset name="Width"/>
+ </axis-subsets>
+ </variable-font>
+ <variable-font name="AktivGroteskVF_Wght">
+ <axis-subsets>
+ <axis-subset name="Weight"/>
+ </axis-subsets>
+ </variable-font>
+ <variable-font name="AktivGroteskVF_Italics_WghtWdth">
+ <axis-subsets>
+ <axis-subset name="Weight"/>
+ <axis-subset name="Width"/>
+ <axis-subset name="Italic" uservalue="1"/>
+ </axis-subsets>
+ </variable-font>
+ <variable-font name="AktivGroteskVF_Italics_Wght">
+ <axis-subsets>
+ <axis-subset name="Weight"/>
+ <axis-subset name="Italic" uservalue="1"/>
+ </axis-subsets>
+ </variable-font>
+ </variable-fonts>
+
+ <instances>
+ <instance filename="instances/AktivGroteskCd_Hair.ufo">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskCd_HairIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGrotesk_Hair.ufo">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGrotesk_HairIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskEx_Hair.ufo">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskEx_HairIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="22"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskCd_Th.ufo">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskCd_ThIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGrotesk_Th.ufo">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGrotesk_ThIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskEx_Th.ufo">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskEx_ThIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="38"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskCd_Lt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskCd_LtIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGrotesk_Lt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGrotesk_LtIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskEx_Lt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskEx_LtIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="57"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskCd_Rg.ufo">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskCd_It.ufo">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGrotesk_Rg.ufo">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGrotesk_It.ufo">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskEx_Rg.ufo">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskEx_It.ufo">
+ <location>
+ <dimension name="Weight" xvalue="84"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskCd_Md.ufo">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskCd_MdIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGrotesk_Md.ufo">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGrotesk_MdIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskEx_Md.ufo">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskEx_MdIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="98"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="../build/instances/AktivGroteskCd_SBd.ufo">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="../build/instances/AktivGroteskCd_SBdIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="../build/instances/AktivGrotesk_SBd.ufo">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="../build/instances/AktivGrotesk_SBdIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="../build/instances/AktivGroteskEx_SBd.ufo">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="../build/instances/AktivGroteskEx_SBdIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="115"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskCd_Bd.ufo">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskCd_BdIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGrotesk_Bd.ufo">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGrotesk_BdIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskEx_Bd.ufo">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskEx_BdIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="133"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskCd_XBd.ufo">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskCd_XBdIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGrotesk_XBd.ufo">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGrotesk_XBdIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskEx_XBd.ufo">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskEx_XBdIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="158"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskCd_Blk.ufo">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskCd_BlkIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGrotesk_Blk.ufo">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGrotesk_BlkIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="100"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskEx_Blk.ufo">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance filename="instances/AktivGroteskEx_BlkIt.ufo">
+ <location>
+ <dimension name="Weight" xvalue="185"/>
+ <dimension name="Width" xvalue="125"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/data/test_v5_decovar.designspace b/Tests/designspaceLib/data/test_v5_decovar.designspace
new file mode 100644
index 00000000..fd31626e
--- /dev/null
+++ b/Tests/designspaceLib/data/test_v5_decovar.designspace
@@ -0,0 +1,242 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes>
+ <axis tag="BLDA" name="Inline" minimum="0" maximum="1000" default="0"/>
+ <axis tag="TRMD" name="Shearded" minimum="0" maximum="1000" default="0"/>
+ <axis tag="TRMC" name="Rounded Slab" minimum="0" maximum="1000" default="0"/>
+ <axis tag="SKLD" name="Stripes" minimum="0" maximum="1000" default="0"/>
+ <axis tag="TRML" name="Worm Terminal" minimum="0" maximum="1000" default="0"/>
+ <axis tag="SKLA" name="Inline Skeleton" minimum="0" maximum="1000" default="0"/>
+ <axis tag="TRMF" name="Open Inline Terminal" minimum="0" maximum="1000" default="0"/>
+ <axis tag="TRMK" name="Inline Terminal" minimum="0" maximum="1000" default="0"/>
+ <axis tag="BLDB" name="Worm" minimum="0" maximum="1000" default="0"/>
+ <axis tag="WMX2" name="Weight" minimum="0" maximum="1000" default="0"/>
+ <axis tag="TRMB" name="Flared" minimum="0" maximum="1000" default="0"/>
+ <axis tag="TRMA" name="Rounded" minimum="0" maximum="1000" default="0"/>
+ <axis tag="SKLB" name="Worm Skeleton" minimum="0" maximum="1000" default="0"/>
+ <axis tag="TRMG" name="Slab" minimum="0" maximum="1000" default="0"/>
+ <axis tag="TRME" name="Bifurcated" minimum="0" maximum="1000" default="0"/>
+ </axes>
+
+ <!-- Labels without location or just partly specified location are at the default location plus the explicitly given sub-locations. -->
+ <labels>
+ <label name="Default" elidable="true"/>
+ <label name="Open">
+ <labelname xml:lang="de">Offen</labelname>
+ <location>
+ <dimension name="Inline" uservalue="1000"/>
+ </location>
+ </label>
+ <label name="Worm">
+ <location>
+ <dimension name="Worm" uservalue="1000"/>
+ </location>
+ </label>
+ <label name="Checkered">
+ <location>
+ <dimension name="Inline Skeleton" uservalue="1000"/>
+ </location>
+ </label>
+ <label name="Checkered Reverse">
+ <location>
+ <dimension name="Inline Terminal" uservalue="1000"/>
+ </location>
+ </label>
+ <label name="Striped">
+ <location>
+ <dimension name="Stripes" uservalue="500"/>
+ </location>
+ </label>
+ <label name="Rounded">
+ <location>
+ <dimension name="Rounded" uservalue="1000"/>
+ </location>
+ </label>
+ <label name="Flared">
+ <location>
+ <dimension name="Flared" uservalue="1000"/>
+ </location>
+ </label>
+ <label name="Flared Open">
+ <location>
+ <dimension name="Inline Skeleton" uservalue="1000"/>
+ <dimension name="Flared" uservalue="1000"/>
+ </location>
+ </label>
+ <label name="Rounded Slab">
+ <location>
+ <dimension name="Rounded Slab" uservalue="1000"/>
+ </location>
+ </label>
+ <label name="Sheared">
+ <location>
+ <dimension name="Shearded" uservalue="1000"/>
+ </location>
+ </label>
+ <label name="Bifurcated">
+ <location>
+ <dimension name="Bifurcated" uservalue="1000"/>
+ </location>
+ </label>
+ <label name="Inline">
+ <location>
+ <dimension name="Inline Skeleton" uservalue="500"/>
+ <dimension name="Open Inline Terminal" uservalue="500"/>
+ </location>
+ </label>
+ <label name="Slab">
+ <location>
+ <dimension name="Slab" uservalue="1000"/>
+ </location>
+ </label>
+ <label name="Contrast">
+ <location>
+ <dimension name="Weight" uservalue="1000"/>
+ </location>
+ </label>
+ <label name="Fancy">
+ <location>
+ <dimension name="Inline Skeleton" uservalue="1000"/>
+ <dimension name="Weight" uservalue="1000"/>
+ <dimension name="Flared" uservalue="1000"/>
+ </location>
+ </label>
+ <label name="Mayhem">
+ <location>
+ <dimension name="Rounded Slab" uservalue="750"/>
+ <dimension name="Worm Terminal" uservalue="250"/>
+ <dimension name="Inline Skeleton" uservalue="1000"/>
+ <dimension name="Open Inline Terminal" uservalue="250"/>
+ <dimension name="Inline Terminal" uservalue="250"/>
+ <dimension name="Worm" uservalue="1000"/>
+ <dimension name="Weight" uservalue="750"/>
+ <dimension name="Flared" uservalue="500"/>
+ <dimension name="Rounded" uservalue="500"/>
+ <dimension name="Worm Skeleton" uservalue="1000"/>
+ <dimension name="Slab" uservalue="750"/>
+ <dimension name="Bifurcated" uservalue="500"/>
+ </location>
+ </label>
+ </labels>
+
+ <sources>
+ <source filename="DecovarAlpha-Regular24.ufo" name="master_Regular"/>
+ <source filename="DecovarAlpha-Regular24SkelA.ufo" name="master_sklA">
+ <location>
+ <dimension name="Inline Skeleton" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="DecovarAlpha-Regular24SkelD2.ufo" name="master_sklD2">
+ <location>
+ <dimension name="Stripes" xvalue="500"/>
+ </location>
+ </source>
+ <source filename="DecovarAlpha-Regular24SkelD4.ufo" name="master_sklD4">
+ <location>
+ <dimension name="Stripes" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="DecovarAlpha-Regular24SkelB2.ufo" name="master_sklB2">
+ <location>
+ <dimension name="Worm Skeleton" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="DecovarAlpha-Regular24TermA.ufo" name="master_trmA">
+ <location>
+ <dimension name="Rounded" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="DecovarAlpha-Regular24TermB.ufo" name="master_trmB">
+ <location>
+ <dimension name="Flared" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="DecovarAlpha-Regular24TermC.ufo" name="master_trmC">
+ <location>
+ <dimension name="Rounded Slab" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="DecovarAlpha-Regular24TermD.ufo" name="master_trmD">
+ <location>
+ <dimension name="Shearded" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="DecovarAlpha-Regular24TermE.ufo" name="master_trmE">
+ <location>
+ <dimension name="Bifurcated" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="DecovarAlpha-Regular24TermF.ufo" name="master_trmF">
+ <location>
+ <dimension name="Open Inline Terminal" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="DecovarAlpha-Regular24TermG.ufo" name="master_trmG">
+ <location>
+ <dimension name="Slab" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="DecovarAlpha-Regular24TermSkelA.ufo" name="master_trmK">
+ <location>
+ <dimension name="Inline Terminal" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="DecovarAlpha-Regular24TermSkelB.ufo" name="master_trmL">
+ <location>
+ <dimension name="Worm Terminal" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="DecovarAlpha-Regular24WeightMax1.ufo" name="master_wmx1">
+ <location>
+ <dimension name="Weight" xvalue="5"/>
+ </location>
+ </source>
+ <source filename="DecovarAlpha-Regular24WeightMax1.5.ufo" name="master_wmx1.5">
+ <location>
+ <dimension name="Weight" xvalue="507.806"/>
+ </location>
+ </source>
+ <source filename="DecovarAlpha-Regular24WeightMax2.ufo" name="master_wmx2">
+ <location>
+ <dimension name="Weight" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="DecovarAlpha-Regular24bldA.ufo" name="master_bldA">
+ <location>
+ <dimension name="Inline" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="DecovarAlpha-Regular24bldB.ufo" name="master_bldB">
+ <location>
+ <dimension name="Worm" xvalue="1000"/>
+ </location>
+ </source>
+ </sources>
+
+ <instances>
+ <!--
+ This designspace defines LocationLabels (STAT format 4) for various
+ points within the design space. In order to define instances at these
+ locations, DS version 5 provides the `location="..."` attribute which
+ means that the instance is in the same location as the LocationLabel
+ with the given name.
+ -->
+ <instance location="Default" filename="DecovarAlpha-Default.ufo"/>
+ <instance location="Open" filename="DecovarAlpha-Open.ufo"/>
+ <instance location="Worm" filename="DecovarAlpha-Worm.ufo"/>
+ <instance location="Checkered" filename="DecovarAlpha-Checkered.ufo"/>
+ <instance location="Checkered Reverse" filename="DecovarAlpha-CheckeredReverse.ufo"/>
+ <instance location="Striped" filename="DecovarAlpha-Striped.ufo"/>
+ <instance location="Rounded" filename="DecovarAlpha-Rounded.ufo"/>
+ <instance location="Flared" filename="DecovarAlpha-Flared.ufo"/>
+ <instance location="Flared Open" filename="DecovarAlpha-FlaredOpen.ufo"/>
+ <instance location="Rounded Slab" filename="DecovarAlpha-RoundedSlab.ufo"/>
+ <instance location="Sheared" filename="DecovarAlpha-Sheared.ufo"/>
+ <instance location="Bifurcated" filename="DecovarAlpha-Bifurcated.ufo"/>
+ <instance location="Inline" filename="DecovarAlpha-Inline.ufo"/>
+ <instance location="Slab" filename="DecovarAlpha-Slab.ufo"/>
+ <instance location="Contrast" filename="DecovarAlpha-Contrast.ufo"/>
+ <instance location="Fancy" filename="DecovarAlpha-Fancy.ufo"/>
+ <instance location="Mayhem" filename="DecovarAlpha-Mayhem.ufo"/>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/data/test_v5_discrete.designspace b/Tests/designspaceLib/data/test_v5_discrete.designspace
new file mode 100644
index 00000000..f42f2dc9
--- /dev/null
+++ b/Tests/designspaceLib/data/test_v5_discrete.designspace
@@ -0,0 +1,139 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes>
+ <axis tag="wght" name="Weight" values="400 700 900" default="400">
+ <labels>
+ <label uservalue="400" name="Regular" elidable="true" linkeduservalue="700"/>
+ <label uservalue="700" name="Bold"/>
+ <label uservalue="900" name="Black"/>
+ </labels>
+ </axis>
+ <axis tag="wdth" name="Width" values="75 100" default="100">
+ <labels>
+ <label uservalue="75" name="Narrow"/>
+ <label uservalue="100" name="Normal" elidable="true"/>
+ </labels>
+ </axis>
+ <axis tag="ital" name="Italic" values="0 1" default="0">
+ <labels>
+ <label uservalue="0" name="Roman" elidable="true" linkeduservalue="1"/>
+ <label uservalue="1" name="Italic"/>
+ </labels>
+ </axis>
+ </axes>
+
+ <sources>
+ <source filename="arial.ufo" name="Arial Regular">
+ <location>
+ <dimension name="Weight" xvalue="400"/>
+ </location>
+ </source>
+ <source filename="arialbd.ufo" name="Arial Bold">
+ <location>
+ <dimension name="Weight" xvalue="700"/>
+ </location>
+ </source>
+ <source filename="ariblk.ufo" name="Arial Black">
+ <location>
+ <dimension name="Weight" xvalue="900"/>
+ </location>
+ </source>
+ <source filename="ariali.ufo" name="Arial Italic">
+ <location>
+ <dimension name="Weight" xvalue="400"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="arialbi.ufo" name="Arial Bold Italic">
+ <location>
+ <dimension name="Weight" xvalue="700"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="ARIALN.ufo" name="Arial Narrow">
+ <location>
+ <dimension name="Weight" xvalue="400"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </source>
+ <source filename="ARIALNB.ufo" name="Arial Narrow Bold">
+ <location>
+ <dimension name="Weight" xvalue="700"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </source>
+ <source filename="ARIALNBI.ufo" name="Arial Narrow Bold Italic">
+ <location>
+ <dimension name="Weight" xvalue="700"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="ARIALNI.ufo" name="Arial Narrow Italic">
+ <location>
+ <dimension name="Weight" xvalue="400"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </source>
+ </sources>
+
+ <instances>
+ <instance name="Arial Regular">
+ <location>
+ <dimension name="Weight" xvalue="400"/>
+ </location>
+ </instance>
+ <instance name="Arial Bold">
+ <!-- Should be stylemapstylename="bold" -->
+ <location>
+ <dimension name="Weight" xvalue="700"/>
+ </location>
+ </instance>
+ <instance name="Arial Black">
+ <location>
+ <dimension name="Weight" xvalue="900"/>
+ </location>
+ </instance>
+ <instance name="Arial Italic">
+ <!-- Should be stylemapstylename="italic" -->
+ <location>
+ <dimension name="Weight" xvalue="400"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Arial Bold Italic">
+ <!-- Should be stylemapstylename="bold italic" -->
+ <location>
+ <dimension name="Weight" xvalue="700"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Arial Narrow">
+ <location>
+ <dimension name="Weight" xvalue="400"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </instance>
+ <instance name="Arial Narrow Bold">
+ <location>
+ <dimension name="Weight" xvalue="700"/>
+ <dimension name="Width" xvalue="75"/>
+ </location>
+ </instance>
+ <instance name="Arial Narrow Bold Italic">
+ <location>
+ <dimension name="Weight" xvalue="700"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance name="Arial Narrow Italic">
+ <location>
+ <dimension name="Weight" xvalue="400"/>
+ <dimension name="Width" xvalue="75"/>
+ <dimension name="Italic" xvalue="1"/>
+ </location>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/data/test_v5_original.designspace b/Tests/designspaceLib/data/test_v5_original.designspace
new file mode 100644
index 00000000..d144a073
--- /dev/null
+++ b/Tests/designspaceLib/data/test_v5_original.designspace
@@ -0,0 +1,90 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <!--
+ NOTE: this file is the same as test_v4_original, except:
+ - the format is 5,
+ - source location width = 20 are not written out because it's the default
+ - instance <glyphs>, <kerning>, <info> are removed because deprecated
+ -->
+ <axes>
+ <axis tag="wght" name="weight" minimum="0" maximum="1000" default="0">
+ <labelname xml:lang="en">Wéíght</labelname>
+ <labelname xml:lang="fa-IR">قطر</labelname>
+ </axis>
+ <axis tag="wdth" name="width" minimum="0" maximum="1000" default="15" hidden="1">
+ <labelname xml:lang="fr">Chasse</labelname>
+ <map input="0" output="10"/>
+ <map input="15" output="20"/>
+ <map input="401" output="66"/>
+ <map input="1000" output="990"/>
+ </axis>
+ </axes>
+ <rules processing="last">
+ <rule name="named.rule.1">
+ <conditionset>
+ <condition name="axisName_a" minimum="0" maximum="1"/>
+ <condition name="axisName_b" minimum="2" maximum="3"/>
+ </conditionset>
+ <sub name="a" with="a.alt"/>
+ </rule>
+ </rules>
+ <sources>
+ <source filename="masters/masterTest1.ufo" name="master.ufo1" familyname="MasterFamilyName" stylename="MasterStyleNameOne">
+ <lib copy="1"/>
+ <features copy="1"/>
+ <info copy="1"/>
+ <glyph name="A" mute="1"/>
+ <glyph name="Z" mute="1"/>
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="masters/masterTest2.ufo" name="master.ufo2" familyname="MasterFamilyName" stylename="MasterStyleNameTwo">
+ <kerning mute="1"/>
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ </location>
+ </source>
+ <source filename="masters/masterTest2.ufo" name="master.ufo2" familyname="MasterFamilyName" stylename="Supports" layer="supports">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ </location>
+ </source>
+ </sources>
+ <instances>
+ <instance name="instance.ufo1" familyname="InstanceFamilyName" stylename="InstanceStyleName" filename="instances/instanceTest1.ufo" postscriptfontname="InstancePostscriptName" stylemapfamilyname="InstanceStyleMapFamilyName" stylemapstylename="InstanceStyleMapStyleName">
+ <stylename xml:lang="fr">Demigras</stylename>
+ <stylename xml:lang="ja">半ば</stylename>
+ <familyname xml:lang="fr">Montserrat</familyname>
+ <familyname xml:lang="ja">モンセラート</familyname>
+ <stylemapstylename xml:lang="de">Standard</stylemapstylename>
+ <stylemapfamilyname xml:lang="de">Montserrat Halbfett</stylemapfamilyname>
+ <stylemapfamilyname xml:lang="ja">モンセラート SemiBold</stylemapfamilyname>
+ <location>
+ <dimension name="weight" xvalue="500"/>
+ </location>
+ <lib>
+ <dict>
+ <key>com.coolDesignspaceApp.binaryData</key>
+ <data>
+ PGJpbmFyeSBndW5rPg==
+ </data>
+ <key>com.coolDesignspaceApp.specimenText</key>
+ <string>Hamburgerwhatever</string>
+ </dict>
+ </lib>
+ </instance>
+ <instance name="instance.ufo2" familyname="InstanceFamilyName" stylename="InstanceStyleName" filename="instances/instanceTest2.ufo" postscriptfontname="InstancePostscriptName" stylemapfamilyname="InstanceStyleMapFamilyName" stylemapstylename="InstanceStyleMapStyleName">
+ <location>
+ <dimension name="weight" xvalue="500"/>
+ <dimension name="width" xvalue="400" yvalue="300"/>
+ </location>
+ </instance>
+ </instances>
+ <lib>
+ <dict>
+ <key>com.coolDesignspaceApp.previewSize</key>
+ <integer>30</integer>
+ </dict>
+ </lib>
+</designspace>
diff --git a/Tests/designspaceLib/data/test_v5_sourceserif.designspace b/Tests/designspaceLib/data/test_v5_sourceserif.designspace
new file mode 100644
index 00000000..d3a32177
--- /dev/null
+++ b/Tests/designspaceLib/data/test_v5_sourceserif.designspace
@@ -0,0 +1,646 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes>
+ <axis tag="wght" name="weight" minimum="200" maximum="900" default="400">
+ <map input="200" output="0"/>
+ <map input="300" output="145"/>
+ <map input="400" output="394"/>
+ <map input="600" output="594"/>
+ <map input="700" output="823"/>
+ <map input="900" output="1000"/>
+ <labels ordering="1">
+ <label uservalue="200" userminimum="200" usermaximum="250" name="ExtraLight"/>
+ <label uservalue="300" userminimum="250" usermaximum="350" name="Light"/>
+ <label uservalue="400" userminimum="350" usermaximum="450" name="Regular" elidable="true"/>
+ <label uservalue="600" userminimum="550" usermaximum="650" name="Semibold"/>
+ <label uservalue="700" userminimum="650" usermaximum="750" name="Bold"/>
+ <label uservalue="775" userminimum="750" usermaximum="800" name="ExtraBold"/>
+ <label uservalue="900" userminimum="800" usermaximum="900" name="Black"/>
+ </labels>
+ </axis>
+ <axis tag="opsz" name="optical" minimum="8" maximum="60" default="20">
+ <labels ordering="0">
+ <label uservalue="8" userminimum="8" usermaximum="12" name="Caption"/>
+ <label uservalue="16" userminimum="12" usermaximum="18" name="SmallText"/>
+ <label uservalue="20" userminimum="18" usermaximum="26" name="Text" elidable="true"/>
+ <label uservalue="32" userminimum="26" usermaximum="48" name="Subhead"/>
+ <label uservalue="60" userminimum="48" usermaximum="60" name="Display"/>
+ </labels>
+ </axis>
+ <axis tag="ital" name="italic" values="0 1" default="0">
+ <labels ordering="2">
+ <label uservalue="0" name="Roman" elidable="true"/>
+ <label uservalue="1" name="Italic"/>
+ </labels>
+ </axis>
+ </axes>
+
+ <sources>
+ <source filename="caption/master_0/SourceSerif_c0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="8"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="caption/master_1/SourceSerif_c1.ufo">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="8"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="caption/master_2/SourceSerif_c2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="8"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="text/master_0/SourceSerif_0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="20"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="text/master_1/SourceSerif_1.ufo" familyname="Source Serif 4">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="20"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="text/master_2/SourceSerif_2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="20"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="display/master_0/SourceSerif_d0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="60"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="display/master_1/SourceSerif_d1.ufo">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="60"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </source>
+ <source filename="display/master_2/SourceSerif_d2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="60"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </source>
+
+ <source filename="caption/master_0/SourceSerif-Italic_c0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="8"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="caption/master_1/SourceSerif-Italic_c1.ufo">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="8"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="caption/master_2/SourceSerif-Italic_c2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="8"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="text/master_0/SourceSerif-Italic_0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="20"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="text/master_1/SourceSerif-Italic_1.ufo">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="20"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="text/master_2/SourceSerif-Italic_2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="20"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="display/master_0/SourceSerif-Italic_d0.ufo">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="60"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="display/master_1/SourceSerif-Italic_d1.ufo">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="60"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </source>
+ <source filename="display/master_2/SourceSerif-Italic_d2.ufo">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="60"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </source>
+ </sources>
+
+ <variable-fonts>
+ <variable-font name="SourceSerif4Variable-Roman">
+ <axis-subsets>
+ <axis-subset name="weight"/>
+ <axis-subset name="optical"/>
+ <axis-subset name="italic" uservalue="0"/>
+ </axis-subsets>
+ <!-- The per-variable-font lib is to be merged into the global lib on
+ conversion to a DS v4, overwriting keys in the global lib. -->
+ <lib>
+ <dict>
+ <key>public.skipExportGlyphs</key>
+ <array>
+ <string>caron.alt</string>
+ <string>commabelowcmb.alt</string>
+ <string>tonos.cap</string>
+ <string>f.ligalong</string>
+ <string>dieresiscmb.tight</string>
+ <string>IJ</string>
+ <string>Tbar</string>
+ <string>colontriangularmod</string>
+ <string>crossmark</string>
+ <string>ij</string>
+ <string>overline</string>
+ <string>similar</string>
+ <string>tbar</string>
+ <string>triangularbullet</string>
+ <string>turkicdsccmb</string>
+ </array>
+ </dict>
+ </lib>
+ </variable-font>
+ <variable-font name="SourceSerif4Variable-Italic">
+ <axis-subsets>
+ <axis-subset name="weight"/>
+ <axis-subset name="optical"/>
+ <axis-subset name="italic" uservalue="1"/>
+ </axis-subsets>
+ <lib>
+ <dict>
+ <key>public.skipExportGlyphs</key>
+ <array>
+ <string>caron.alt</string>
+ <string>commabelowcmb.alt</string>
+ <string>f.liga</string>
+ <string>f.ligalong</string>
+ <string>tonos.cap</string>
+ <string>dieresiscmb.tight</string>
+ <string>turkicdsccmb</string>
+ </array>
+ </dict>
+ </lib>
+ </variable-font>
+ </variable-fonts>
+
+ <instances>
+ <instance postscriptfontname="SourceSerif4Roman-CaptionExtraLight">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="8"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Roman-CaptionLight">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="8"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Roman-CaptionRegular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="8"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Roman-CaptionSemibold">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="8"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Roman-CaptionBold">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="8"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Roman-CaptionBlack">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="8"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance stylename="SmText ExtraLight" postscriptfontname="SourceSerif4Roman-SmTextExtraLight">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="16"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance stylename="SmText Light" postscriptfontname="SourceSerif4Roman-SmTextLight">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="16"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance stylename="SmText" postscriptfontname="SourceSerif4Roman-SmTextRegular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="16"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance stylename="SmText Semibold" postscriptfontname="SourceSerif4Roman-SmTextSemibold">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="16"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance stylename="SmText Bold" postscriptfontname="SourceSerif4Roman-SmTextBold">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="16"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance stylename="SmText Black" postscriptfontname="SourceSerif4Roman-SmTextBlack">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="16"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Roman-ExtraLight">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="20"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Roman-Light">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="20"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance stylename="Regular" postscriptfontname="SourceSerif4Roman-Regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="20"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Roman-Semibold">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="20"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Roman-Bold">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="20"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Roman-Black">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="20"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Roman-SubheadExtraLight">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="32"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Roman-SubheadLight">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="32"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Roman-SubheadRegular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="32"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Roman-SubheadSemibold">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="32"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Roman-SubheadBold">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="32"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Roman-SubheadBlack">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="32"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Roman-DisplayExtraLight">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="60"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Roman-DisplayLight">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="60"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Roman-DisplayRegular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="60"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Roman-DisplaySemibold">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="60"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Roman-DisplayBold">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="60"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Roman-DisplayBlack">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="60"/>
+ <dimension name="italic" xvalue="0"/>
+ </location>
+ </instance>
+
+ <instance postscriptfontname="SourceSerif4Italic-CaptionExtraLight">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="8"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Italic-CaptionLight">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="8"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Italic-CaptionRegular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="8"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Italic-CaptionSemibold">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="8"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Italic-CaptionBold">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="8"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Italic-CaptionBlack">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="8"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance stylename="SmText ExtraLight Italic" postscriptfontname="SourceSerif4Italic-SmTextExtraLight">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="16"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance stylename="SmText Light Italic" postscriptfontname="SourceSerif4Italic-SmTextLight">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="16"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance stylename="SmText Italic" postscriptfontname="SourceSerif4Italic-SmTextRegular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="16"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance stylename="SmText Semibold Italic" postscriptfontname="SourceSerif4Italic-SmTextSemibold">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="16"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance stylename="SmText Bold Italic" postscriptfontname="SourceSerif4Italic-SmTextBold">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="16"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance stylename="SmText Black Italic" postscriptfontname="SourceSerif4Italic-SmTextBlack">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="16"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Italic-ExtraLight">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="20"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Italic-Light">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="20"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Italic-Regular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="20"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Italic-Semibold">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="20"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Italic-Bold">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="20"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Italic-Black">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="20"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Italic-SubheadExtraLight">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="32"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Italic-SubheadLight">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="32"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Italic-SubheadRegular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="32"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Italic-SubheadSemibold">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="32"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Italic-SubheadBold">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="32"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Italic-SubheadBlack">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="32"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Italic-DisplayExtraLight">
+ <location>
+ <dimension name="weight" xvalue="0"/>
+ <dimension name="optical" xvalue="60"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Italic-DisplayLight">
+ <location>
+ <dimension name="weight" xvalue="145"/>
+ <dimension name="optical" xvalue="60"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Italic-DisplayRegular">
+ <location>
+ <dimension name="weight" xvalue="394"/>
+ <dimension name="optical" xvalue="60"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Italic-DisplaySemibold">
+ <location>
+ <dimension name="weight" xvalue="594"/>
+ <dimension name="optical" xvalue="60"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Italic-DisplayBold">
+ <location>
+ <dimension name="weight" xvalue="823"/>
+ <dimension name="optical" xvalue="60"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ <instance postscriptfontname="SourceSerif4Italic-DisplayBlack">
+ <location>
+ <dimension name="weight" xvalue="1000"/>
+ <dimension name="optical" xvalue="60"/>
+ <dimension name="italic" xvalue="1"/>
+ </location>
+ </instance>
+ </instances>
+</designspace>
diff --git a/Tests/designspaceLib/designspace_test.py b/Tests/designspaceLib/designspace_test.py
index b6ee9d65..ee2d19e6 100644
--- a/Tests/designspaceLib/designspace_test.py
+++ b/Tests/designspaceLib/designspace_test.py
@@ -1,15 +1,26 @@
# coding=utf-8
import os
-import sys
-import pytest
-import warnings
+import re
-from fontTools.misc import plistlib
-from fontTools.designspaceLib import (
- DesignSpaceDocument, SourceDescriptor, AxisDescriptor, RuleDescriptor,
- InstanceDescriptor, evaluateRule, processRules, posix, DesignSpaceDocumentError)
+import pytest
from fontTools import ttLib
+from fontTools.designspaceLib import (
+ AxisDescriptor,
+ AxisLabelDescriptor,
+ DesignSpaceDocument,
+ DesignSpaceDocumentError,
+ DiscreteAxisDescriptor,
+ InstanceDescriptor,
+ RuleDescriptor,
+ SourceDescriptor,
+ evaluateRule,
+ posix,
+ processRules,
+)
+from fontTools.designspaceLib.types import Range
+from fontTools.misc import plistlib
+
def _axesAsDict(axes):
"""
@@ -30,19 +41,22 @@ def _axesAsDict(axes):
def assert_equals_test_file(path, test_filename):
- with open(path) as fp:
+ with open(path, encoding="utf-8") as fp:
actual = fp.read()
test_path = os.path.join(os.path.dirname(__file__), test_filename)
- with open(test_path) as fp:
+ with open(test_path, encoding="utf-8") as fp:
expected = fp.read()
+ expected = re.sub(r"<!--(.|\n)*?-->", "", expected)
+ expected = re.sub(r"\s*\n+", "\n", expected)
assert actual == expected
def test_fill_document(tmpdir):
tmpdir = str(tmpdir)
- testDocPath = os.path.join(tmpdir, "test.designspace")
+ testDocPath = os.path.join(tmpdir, "test_v4.designspace")
+ testDocPath5 = os.path.join(tmpdir, "test_v5.designspace")
masterPath1 = os.path.join(tmpdir, "masters", "masterTest1.ufo")
masterPath2 = os.path.join(tmpdir, "masters", "masterTest2.ufo")
instancePath1 = os.path.join(tmpdir, "instances", "instanceTest1.ufo")
@@ -121,6 +135,10 @@ def test_fill_document(tmpdir):
i1.postScriptFontName = "InstancePostscriptName"
i1.styleMapFamilyName = "InstanceStyleMapFamilyName"
i1.styleMapStyleName = "InstanceStyleMapStyleName"
+ i1.localisedStyleName = dict(fr="Demigras", ja="半ば")
+ i1.localisedFamilyName = dict(fr="Montserrat", ja="モンセラート")
+ i1.localisedStyleMapStyleName = dict(de="Standard")
+ i1.localisedStyleMapFamilyName = dict(de="Montserrat Halbfett", ja="モンセラート SemiBold")
glyphData = dict(name="arrow", mute=True, unicodes=[0x123, 0x124, 0x125])
i1.glyphs['arrow'] = glyphData
i1.lib['com.coolDesignspaceApp.binaryData'] = plistlib.Data(b'<binary gunk>')
@@ -158,16 +176,21 @@ def test_fill_document(tmpdir):
])
r1.subs.append(("a", "a.alt"))
doc.addRule(r1)
- # write the document
+ # write the document; without an explicit format it will be 5.0 by default
+ doc.write(testDocPath5)
+ assert os.path.exists(testDocPath5)
+ assert_equals_test_file(testDocPath5, 'data/test_v5_original.designspace')
+ # write again with an explicit format = 4.1
+ doc.formatVersion = "4.1"
doc.write(testDocPath)
assert os.path.exists(testDocPath)
- assert_equals_test_file(testDocPath, 'data/test.designspace')
+ assert_equals_test_file(testDocPath, 'data/test_v4_original.designspace')
# import it again
new = DesignSpaceDocument()
new.read(testDocPath)
assert new.default.location == {'width': 20.0, 'weight': 0.0}
- assert new.filename == 'test.designspace'
+ assert new.filename == 'test_v4.designspace'
assert new.lib == doc.lib
assert new.instances[0].lib == doc.instances[0].lib
@@ -197,6 +220,7 @@ def test_unicodes(tmpdir):
instancePath1 = os.path.join(tmpdir, "instances", "instanceTest1.ufo")
instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo")
doc = DesignSpaceDocument()
+ doc.formatVersion = "4.1" # This test about instance glyphs is deprecated in v5
# add master 1
s1 = SourceDescriptor()
s1.filename = os.path.relpath(masterPath1, os.path.dirname(testDocPath))
@@ -832,7 +856,7 @@ def test_updatePaths(tmpdir):
def test_read_with_path_object():
import pathlib
- source = (pathlib.Path(__file__) / "../data/test.designspace").resolve()
+ source = (pathlib.Path(__file__) / "../data/test_v4_original.designspace").resolve()
assert source.exists()
doc = DesignSpaceDocument()
doc.read(source)
@@ -841,7 +865,7 @@ def test_read_with_path_object():
def test_with_with_path_object(tmpdir):
import pathlib
tmpdir = str(tmpdir)
- dest = pathlib.Path(tmpdir) / "test.designspace"
+ dest = pathlib.Path(tmpdir) / "test_v4_original.designspace"
doc = DesignSpaceDocument()
doc.write(dest)
assert dest.exists()
@@ -1020,3 +1044,23 @@ def test_addRuleDescriptor(tmp_path):
# Test it doesn't crash.
ds.write(tmp_path / "test.designspace")
+
+
+def test_deepcopyExceptFonts():
+ ds = DesignSpaceDocument()
+ ds.addSourceDescriptor(font=object())
+ ds.addSourceDescriptor(font=object())
+
+ ds_copy = ds.deepcopyExceptFonts()
+
+ assert ds.tostring() == ds_copy.tostring()
+ assert ds.sources[0].font is ds_copy.sources[0].font
+ assert ds.sources[1].font is ds_copy.sources[1].font
+
+
+def test_Range_post_init():
+ # test min and max are sorted and default is clamped to either min/max
+ r = Range(minimum=2, maximum=-1, default=-2)
+ assert r.minimum == -1
+ assert r.maximum == 2
+ assert r.default == -1
diff --git a/Tests/designspaceLib/designspace_v5_test.py b/Tests/designspaceLib/designspace_v5_test.py
new file mode 100644
index 00000000..9e803340
--- /dev/null
+++ b/Tests/designspaceLib/designspace_v5_test.py
@@ -0,0 +1,888 @@
+import re
+import shutil
+from pathlib import Path
+
+import pytest
+from fontTools.designspaceLib import (
+ AxisDescriptor,
+ AxisLabelDescriptor,
+ DesignSpaceDocument,
+ DiscreteAxisDescriptor,
+ InstanceDescriptor,
+ LocationLabelDescriptor,
+ RangeAxisSubsetDescriptor,
+ SourceDescriptor,
+ ValueAxisSubsetDescriptor,
+ VariableFontDescriptor,
+ posix,
+)
+
+from .fixtures import datadir
+
+
+def assert_descriptors_equal(actual, expected):
+ assert len(actual) == len(expected)
+ for a, e in zip(actual, expected):
+ assert a.asdict() == e.asdict()
+
+
+def test_read_v5_document_simple(datadir):
+ doc = DesignSpaceDocument.fromfile(datadir / "test_v5.designspace")
+
+ assert_descriptors_equal(
+ doc.axes,
+ [
+ AxisDescriptor(
+ tag="wght",
+ name="weight",
+ minimum=200,
+ maximum=1000,
+ default=200,
+ labelNames={"en": "Wéíght", "fa-IR": "قطر"},
+ map=[
+ (200, 0),
+ (300, 100),
+ (400, 368),
+ (600, 600),
+ (700, 824),
+ (900, 1000),
+ ],
+ axisOrdering=None,
+ axisLabels=[
+ AxisLabelDescriptor(
+ name="Extra Light",
+ userMinimum=200,
+ userValue=200,
+ userMaximum=250,
+ labelNames={"de": "Extraleicht", "fr": "Extra léger"},
+ ),
+ AxisLabelDescriptor(
+ name="Light", userMinimum=250, userValue=300, userMaximum=350
+ ),
+ AxisLabelDescriptor(
+ name="Regular",
+ userMinimum=350,
+ userValue=400,
+ userMaximum=450,
+ elidable=True,
+ ),
+ AxisLabelDescriptor(
+ name="Semi Bold",
+ userMinimum=450,
+ userValue=600,
+ userMaximum=650,
+ ),
+ AxisLabelDescriptor(
+ name="Bold", userMinimum=650, userValue=700, userMaximum=850
+ ),
+ AxisLabelDescriptor(
+ name="Black", userMinimum=850, userValue=900, userMaximum=900
+ ),
+ ],
+ ),
+ AxisDescriptor(
+ tag="wdth",
+ name="width",
+ minimum=50,
+ maximum=150,
+ default=100,
+ hidden=True,
+ labelNames={"fr": "Chasse"},
+ map=[(50, 10), (100, 20), (125, 66), (150, 990)],
+ axisOrdering=1,
+ axisLabels=[
+ AxisLabelDescriptor(name="Condensed", userValue=50),
+ AxisLabelDescriptor(
+ name="Normal", elidable=True, olderSibling=True, userValue=100
+ ),
+ AxisLabelDescriptor(name="Wide", userValue=125),
+ AxisLabelDescriptor(
+ name="Extra Wide", userValue=150, userMinimum=150
+ ),
+ ],
+ ),
+ DiscreteAxisDescriptor(
+ tag="ital",
+ name="Italic",
+ values=[0, 1],
+ default=0,
+ axisOrdering=None,
+ axisLabels=[
+ AxisLabelDescriptor(
+ name="Roman", userValue=0, elidable=True, linkedUserValue=1
+ ),
+ AxisLabelDescriptor(name="Italic", userValue=1),
+ ],
+ ),
+ ],
+ )
+
+ assert_descriptors_equal(
+ doc.locationLabels,
+ [
+ LocationLabelDescriptor(
+ name="Some Style",
+ labelNames={"fr": "Un Style"},
+ userLocation={"weight": 300, "width": 50, "Italic": 0},
+ ),
+ LocationLabelDescriptor(
+ name="Other", userLocation={"weight": 700, "width": 100, "Italic": 1}
+ ),
+ ],
+ )
+
+ assert_descriptors_equal(
+ doc.sources,
+ [
+ SourceDescriptor(
+ filename="masters/masterTest1.ufo",
+ path=posix(str((datadir / "masters/masterTest1.ufo").resolve())),
+ name="master.ufo1",
+ layerName=None,
+ location={"weight": 0.0, "width": 20.0},
+ copyLib=True,
+ copyInfo=True,
+ copyGroups=False,
+ copyFeatures=True,
+ muteKerning=False,
+ muteInfo=False,
+ mutedGlyphNames=["A", "Z"],
+ familyName="MasterFamilyName",
+ styleName="MasterStyleNameOne",
+ localisedFamilyName={"fr": "Montserrat", "ja": "モンセラート"},
+ ),
+ SourceDescriptor(
+ filename="masters/masterTest2.ufo",
+ path=posix(str((datadir / "masters/masterTest2.ufo").resolve())),
+ name="master.ufo2",
+ layerName=None,
+ location={"weight": 1000.0, "width": 20.0},
+ copyLib=False,
+ copyInfo=False,
+ copyGroups=False,
+ copyFeatures=False,
+ muteKerning=True,
+ muteInfo=False,
+ mutedGlyphNames=[],
+ familyName="MasterFamilyName",
+ styleName="MasterStyleNameTwo",
+ localisedFamilyName={},
+ ),
+ SourceDescriptor(
+ filename="masters/masterTest2.ufo",
+ path=posix(str((datadir / "masters/masterTest2.ufo").resolve())),
+ name="master.ufo2",
+ layerName="supports",
+ location={"weight": 1000.0, "width": 20.0},
+ copyLib=False,
+ copyInfo=False,
+ copyGroups=False,
+ copyFeatures=False,
+ muteKerning=False,
+ muteInfo=False,
+ mutedGlyphNames=[],
+ familyName="MasterFamilyName",
+ styleName="Supports",
+ localisedFamilyName={},
+ ),
+ ],
+ )
+
+ assert_descriptors_equal(
+ doc.variableFonts,
+ [
+ VariableFontDescriptor(
+ name="Test_WghtWdth",
+ filename="Test_WghtWdth_different_from_name.ttf",
+ axisSubsets=[
+ RangeAxisSubsetDescriptor(name="Weight"),
+ RangeAxisSubsetDescriptor(name="Width"),
+ ],
+ lib={"com.vtt.source": "sources/vtt/Test_WghtWdth.vtt"},
+ ),
+ VariableFontDescriptor(
+ name="Test_Wght",
+ axisSubsets=[RangeAxisSubsetDescriptor(name="Weight")],
+ lib={"com.vtt.source": "sources/vtt/Test_Wght.vtt"},
+ ),
+ VariableFontDescriptor(
+ name="TestCd_Wght",
+ axisSubsets=[
+ RangeAxisSubsetDescriptor(name="Weight"),
+ ValueAxisSubsetDescriptor(name="Width", userValue=0),
+ ],
+ ),
+ VariableFontDescriptor(
+ name="TestWd_Wght",
+ axisSubsets=[
+ RangeAxisSubsetDescriptor(name="Weight"),
+ ValueAxisSubsetDescriptor(name="Width", userValue=1000),
+ ],
+ ),
+ VariableFontDescriptor(
+ name="TestItalic_Wght",
+ axisSubsets=[
+ RangeAxisSubsetDescriptor(name="Weight"),
+ ValueAxisSubsetDescriptor(name="Italic", userValue=1),
+ ],
+ ),
+ VariableFontDescriptor(
+ name="TestRB_Wght",
+ axisSubsets=[
+ RangeAxisSubsetDescriptor(
+ name="Weight", userMinimum=400, userDefault=400, userMaximum=700
+ ),
+ ValueAxisSubsetDescriptor(name="Italic", userValue=0),
+ ],
+ ),
+ ],
+ )
+
+ assert_descriptors_equal(
+ doc.instances,
+ [
+ InstanceDescriptor(
+ filename="instances/instanceTest1.ufo",
+ path=posix(str((datadir / "instances/instanceTest1.ufo").resolve())),
+ name="instance.ufo1",
+ designLocation={"weight": 500.0, "width": 20.0},
+ familyName="InstanceFamilyName",
+ styleName="InstanceStyleName",
+ postScriptFontName="InstancePostscriptName",
+ styleMapFamilyName="InstanceStyleMapFamilyName",
+ styleMapStyleName="InstanceStyleMapStyleName",
+ localisedFamilyName={"fr": "Montserrat", "ja": "モンセラート"},
+ localisedStyleName={"fr": "Demigras", "ja": "半ば"},
+ localisedStyleMapFamilyName={
+ "de": "Montserrat Halbfett",
+ "ja": "モンセラート SemiBold",
+ },
+ localisedStyleMapStyleName={"de": "Standard"},
+ glyphs={"arrow": {"mute": True, "unicodes": [291, 292, 293]}},
+ lib={
+ "com.coolDesignspaceApp.binaryData": b"<binary gunk>",
+ "com.coolDesignspaceApp.specimenText": "Hamburgerwhatever",
+ },
+ ),
+ InstanceDescriptor(
+ filename="instances/instanceTest2.ufo",
+ path=posix(str((datadir / "instances/instanceTest2.ufo").resolve())),
+ name="instance.ufo2",
+ designLocation={"weight": 500.0, "width": (400.0, 300.0)},
+ familyName="InstanceFamilyName",
+ styleName="InstanceStyleName",
+ postScriptFontName="InstancePostscriptName",
+ styleMapFamilyName="InstanceStyleMapFamilyName",
+ styleMapStyleName="InstanceStyleMapStyleName",
+ glyphs={
+ "arrow": {
+ "unicodes": [101, 201, 301],
+ "note": "A note about this glyph",
+ "instanceLocation": {"weight": 120.0, "width": 100.0},
+ "masters": [
+ {
+ "font": "master.ufo1",
+ "location": {"weight": 20.0, "width": 20.0},
+ "glyphName": "BB",
+ },
+ {
+ "font": "master.ufo2",
+ "location": {"weight": 900.0, "width": 900.0},
+ "glyphName": "CC",
+ },
+ ],
+ },
+ "arrow2": {},
+ },
+ ),
+ InstanceDescriptor(
+ locationLabel="asdf",
+ ),
+ InstanceDescriptor(
+ designLocation={"weight": 600.0, "width": (401.0, 420.0)},
+ ),
+ InstanceDescriptor(
+ designLocation={"weight": 10.0, "Italic": 0.0},
+ userLocation={"width": 100.0},
+ ),
+ InstanceDescriptor(
+ userLocation={"weight": 300.0, "width": 130.0, "Italic": 1.0},
+ ),
+ ],
+ )
+
+
+def test_read_v5_document_decovar(datadir):
+ doc = DesignSpaceDocument.fromfile(datadir / "test_v5_decovar.designspace")
+
+ assert not doc.variableFonts
+
+ assert_descriptors_equal(
+ doc.axes,
+ [
+ AxisDescriptor(
+ default=0, maximum=1000, minimum=0, name="Inline", tag="BLDA"
+ ),
+ AxisDescriptor(
+ default=0, maximum=1000, minimum=0, name="Shearded", tag="TRMD"
+ ),
+ AxisDescriptor(
+ default=0, maximum=1000, minimum=0, name="Rounded Slab", tag="TRMC"
+ ),
+ AxisDescriptor(
+ default=0, maximum=1000, minimum=0, name="Stripes", tag="SKLD"
+ ),
+ AxisDescriptor(
+ default=0, maximum=1000, minimum=0, name="Worm Terminal", tag="TRML"
+ ),
+ AxisDescriptor(
+ default=0, maximum=1000, minimum=0, name="Inline Skeleton", tag="SKLA"
+ ),
+ AxisDescriptor(
+ default=0,
+ maximum=1000,
+ minimum=0,
+ name="Open Inline Terminal",
+ tag="TRMF",
+ ),
+ AxisDescriptor(
+ default=0, maximum=1000, minimum=0, name="Inline Terminal", tag="TRMK"
+ ),
+ AxisDescriptor(default=0, maximum=1000, minimum=0, name="Worm", tag="BLDB"),
+ AxisDescriptor(
+ default=0, maximum=1000, minimum=0, name="Weight", tag="WMX2"
+ ),
+ AxisDescriptor(
+ default=0, maximum=1000, minimum=0, name="Flared", tag="TRMB"
+ ),
+ AxisDescriptor(
+ default=0, maximum=1000, minimum=0, name="Rounded", tag="TRMA"
+ ),
+ AxisDescriptor(
+ default=0, maximum=1000, minimum=0, name="Worm Skeleton", tag="SKLB"
+ ),
+ AxisDescriptor(default=0, maximum=1000, minimum=0, name="Slab", tag="TRMG"),
+ AxisDescriptor(
+ default=0, maximum=1000, minimum=0, name="Bifurcated", tag="TRME"
+ ),
+ ],
+ )
+
+ assert_descriptors_equal(
+ doc.locationLabels,
+ [
+ LocationLabelDescriptor(name="Default", elidable=True, userLocation={}),
+ LocationLabelDescriptor(
+ name="Open", userLocation={"Inline": 1000}, labelNames={"de": "Offen"}
+ ),
+ LocationLabelDescriptor(name="Worm", userLocation={"Worm": 1000}),
+ LocationLabelDescriptor(
+ name="Checkered", userLocation={"Inline Skeleton": 1000}
+ ),
+ LocationLabelDescriptor(
+ name="Checkered Reverse", userLocation={"Inline Terminal": 1000}
+ ),
+ LocationLabelDescriptor(name="Striped", userLocation={"Stripes": 500}),
+ LocationLabelDescriptor(name="Rounded", userLocation={"Rounded": 1000}),
+ LocationLabelDescriptor(name="Flared", userLocation={"Flared": 1000}),
+ LocationLabelDescriptor(
+ name="Flared Open",
+ userLocation={"Inline Skeleton": 1000, "Flared": 1000},
+ ),
+ LocationLabelDescriptor(
+ name="Rounded Slab", userLocation={"Rounded Slab": 1000}
+ ),
+ LocationLabelDescriptor(name="Sheared", userLocation={"Shearded": 1000}),
+ LocationLabelDescriptor(
+ name="Bifurcated", userLocation={"Bifurcated": 1000}
+ ),
+ LocationLabelDescriptor(
+ name="Inline",
+ userLocation={"Inline Skeleton": 500, "Open Inline Terminal": 500},
+ ),
+ LocationLabelDescriptor(name="Slab", userLocation={"Slab": 1000}),
+ LocationLabelDescriptor(name="Contrast", userLocation={"Weight": 1000}),
+ LocationLabelDescriptor(
+ name="Fancy",
+ userLocation={"Inline Skeleton": 1000, "Flared": 1000, "Weight": 1000},
+ ),
+ LocationLabelDescriptor(
+ name="Mayhem",
+ userLocation={
+ "Inline Skeleton": 1000,
+ "Worm Skeleton": 1000,
+ "Rounded": 500,
+ "Flared": 500,
+ "Rounded Slab": 750,
+ "Bifurcated": 500,
+ "Open Inline Terminal": 250,
+ "Slab": 750,
+ "Inline Terminal": 250,
+ "Worm Terminal": 250,
+ "Weight": 750,
+ "Worm": 1000,
+ },
+ ),
+ ],
+ )
+
+ assert [i.locationLabel for i in doc.instances] == [
+ "Default",
+ "Open",
+ "Worm",
+ "Checkered",
+ "Checkered Reverse",
+ "Striped",
+ "Rounded",
+ "Flared",
+ "Flared Open",
+ "Rounded Slab",
+ "Sheared",
+ "Bifurcated",
+ "Inline",
+ "Slab",
+ "Contrast",
+ "Fancy",
+ "Mayhem",
+ ]
+
+
+def test_read_v5_document_discrete(datadir):
+ doc = DesignSpaceDocument.fromfile(datadir / "test_v5_discrete.designspace")
+
+ assert not doc.locationLabels
+ assert not doc.variableFonts
+
+ assert_descriptors_equal(
+ doc.axes,
+ [
+ DiscreteAxisDescriptor(
+ default=400,
+ values=[400, 700, 900],
+ name="Weight",
+ tag="wght",
+ axisLabels=[
+ AxisLabelDescriptor(
+ name="Regular",
+ userValue=400,
+ elidable=True,
+ linkedUserValue=700,
+ ),
+ AxisLabelDescriptor(name="Bold", userValue=700),
+ AxisLabelDescriptor(name="Black", userValue=900),
+ ],
+ ),
+ DiscreteAxisDescriptor(
+ default=100,
+ values=[75, 100],
+ name="Width",
+ tag="wdth",
+ axisLabels=[
+ AxisLabelDescriptor(name="Narrow", userValue=75),
+ AxisLabelDescriptor(name="Normal", userValue=100, elidable=True),
+ ],
+ ),
+ DiscreteAxisDescriptor(
+ default=0,
+ values=[0, 1],
+ name="Italic",
+ tag="ital",
+ axisLabels=[
+ AxisLabelDescriptor(
+ name="Roman", userValue=0, elidable=True, linkedUserValue=1
+ ),
+ AxisLabelDescriptor(name="Italic", userValue=1),
+ ],
+ ),
+ ],
+ )
+
+
+def test_read_v5_document_aktiv(datadir):
+ doc = DesignSpaceDocument.fromfile(datadir / "test_v5_aktiv.designspace")
+
+ assert not doc.locationLabels
+
+ assert_descriptors_equal(
+ doc.axes,
+ [
+ AxisDescriptor(
+ tag="wght",
+ name="Weight",
+ minimum=100,
+ default=400,
+ maximum=900,
+ map=[
+ (100, 22),
+ (200, 38),
+ (300, 57),
+ (400, 84),
+ (500, 98),
+ (600, 115),
+ (700, 133),
+ (800, 158),
+ (900, 185),
+ ],
+ axisOrdering=1,
+ axisLabels=[
+ AxisLabelDescriptor(name="Hair", userValue=100),
+ AxisLabelDescriptor(userValue=200, name="Thin"),
+ AxisLabelDescriptor(userValue=300, name="Light"),
+ AxisLabelDescriptor(
+ userValue=400,
+ name="Regular",
+ elidable=True,
+ linkedUserValue=700,
+ ),
+ AxisLabelDescriptor(userValue=500, name="Medium"),
+ AxisLabelDescriptor(userValue=600, name="SemiBold"),
+ AxisLabelDescriptor(userValue=700, name="Bold"),
+ AxisLabelDescriptor(userValue=800, name="XBold"),
+ AxisLabelDescriptor(userValue=900, name="Black"),
+ ],
+ ),
+ AxisDescriptor(
+ tag="wdth",
+ name="Width",
+ minimum=75,
+ default=100,
+ maximum=125,
+ axisOrdering=0,
+ axisLabels=[
+ AxisLabelDescriptor(name="Cd", userValue=75),
+ AxisLabelDescriptor(name="Normal", elidable=True, userValue=100),
+ AxisLabelDescriptor(name="Ex", userValue=125),
+ ],
+ ),
+ AxisDescriptor(
+ tag="ital",
+ name="Italic",
+ minimum=0,
+ default=0,
+ maximum=1,
+ axisOrdering=2,
+ axisLabels=[
+ AxisLabelDescriptor(
+ name="Upright", userValue=0, elidable=True, linkedUserValue=1
+ ),
+ AxisLabelDescriptor(name="Italic", userValue=1),
+ ],
+ ),
+ ],
+ )
+
+ assert_descriptors_equal(
+ doc.variableFonts,
+ [
+ VariableFontDescriptor(
+ name="AktivGroteskVF_WghtWdthItal",
+ axisSubsets=[
+ RangeAxisSubsetDescriptor(name="Weight"),
+ RangeAxisSubsetDescriptor(name="Width"),
+ RangeAxisSubsetDescriptor(name="Italic"),
+ ],
+ ),
+ VariableFontDescriptor(
+ name="AktivGroteskVF_WghtWdth",
+ axisSubsets=[
+ RangeAxisSubsetDescriptor(name="Weight"),
+ RangeAxisSubsetDescriptor(name="Width"),
+ ],
+ ),
+ VariableFontDescriptor(
+ name="AktivGroteskVF_Wght",
+ axisSubsets=[
+ RangeAxisSubsetDescriptor(name="Weight"),
+ ],
+ ),
+ VariableFontDescriptor(
+ name="AktivGroteskVF_Italics_WghtWdth",
+ axisSubsets=[
+ RangeAxisSubsetDescriptor(name="Weight"),
+ RangeAxisSubsetDescriptor(name="Width"),
+ ValueAxisSubsetDescriptor(name="Italic", userValue=1),
+ ],
+ ),
+ VariableFontDescriptor(
+ name="AktivGroteskVF_Italics_Wght",
+ axisSubsets=[
+ RangeAxisSubsetDescriptor(name="Weight"),
+ ValueAxisSubsetDescriptor(name="Italic", userValue=1),
+ ],
+ ),
+ ],
+ )
+
+
+@pytest.fixture
+def map_doc():
+ """Generate a document with a few axes to test the mapping functions"""
+ doc = DesignSpaceDocument()
+ doc.addAxis(
+ AxisDescriptor(
+ tag="wght",
+ name="Weight",
+ minimum=100,
+ maximum=900,
+ default=100,
+ map=[(100, 10), (900, 90)],
+ )
+ )
+ doc.addAxis(
+ AxisDescriptor(
+ tag="wdth",
+ name="Width",
+ minimum=75,
+ maximum=200,
+ default=100,
+ map=[(75, 7500), (100, 10000), (200, 20000)],
+ )
+ )
+ doc.addAxis(
+ AxisDescriptor(tag="CUST", name="Custom", minimum=1, maximum=2, default=1.5)
+ )
+ doc.addLocationLabel(
+ LocationLabelDescriptor(
+ name="Wonky", userLocation={"Weight": 800, "Custom": 1.2}
+ )
+ )
+ return doc
+
+
+def test_doc_location_map_forward(map_doc: DesignSpaceDocument):
+ assert map_doc.map_forward({"Weight": 400, "Width": 150, "Custom": 2}) == {
+ "Weight": 40,
+ "Width": 15000,
+ "Custom": 2,
+ }, "The mappings should be used to compute the design locations"
+ assert map_doc.map_forward({"Weight": 400}) == {
+ "Weight": 40,
+ "Width": 10000,
+ "Custom": 1.5,
+ }, "Missing user locations should be assumed equal to the axis's default"
+
+
+def test_doc_location_map_backward(map_doc: DesignSpaceDocument):
+ assert map_doc.map_backward({"Weight": 40, "Width": 15000, "Custom": 2}) == {
+ "Weight": 400,
+ "Width": 150,
+ "Custom": 2,
+ }, "The mappings should be used to compute the user locations"
+ assert map_doc.map_backward({"Weight": 40}) == {
+ "Weight": 400,
+ "Width": 100,
+ "Custom": 1.5,
+ }, "Missing design locations should be assumed equal to the axis's default"
+ assert map_doc.map_backward(
+ {"Weight": (40, 50), "Width": (15000, 100000), "Custom": (2, 1.5)}
+ ) == {
+ "Weight": 400,
+ "Width": 150,
+ "Custom": 2,
+ }, "Only the xvalue of anisotropic locations is used"
+
+
+def test_instance_location_from_label(map_doc):
+ inst = InstanceDescriptor(locationLabel="Wonky")
+ assert inst.getFullUserLocation(map_doc) == {
+ "Weight": 800,
+ "Width": 100,
+ "Custom": 1.2,
+ }, "an instance with a locationLabel uses the user location from that label, empty values on the label use axis defaults"
+ assert inst.getFullDesignLocation(map_doc) == {
+ "Weight": 80,
+ "Width": 10000,
+ "Custom": 1.2,
+ }, "an instance with a locationLabel computes the design location from that label, empty values on the label use axis defaults"
+
+ inst = InstanceDescriptor(locationLabel="Wonky", userLocation={"Width": 200})
+ assert inst.getFullUserLocation(map_doc) == {
+ "Weight": 800,
+ "Width": 100,
+ "Custom": 1.2,
+ }, "an instance with a locationLabel uses the user location from that label, other location values are ignored"
+ assert inst.getFullDesignLocation(map_doc) == {
+ "Weight": 80,
+ "Width": 10000,
+ "Custom": 1.2,
+ }, "an instance with a locationLabel computes the design location from that label, other location values are ignored"
+
+
+def test_instance_location_no_data(map_doc):
+ inst = InstanceDescriptor()
+ assert inst.getFullUserLocation(map_doc) == {
+ "Weight": 100,
+ "Width": 100,
+ "Custom": 1.5,
+ }, "an instance without any location data has the default user location"
+ assert inst.getFullDesignLocation(map_doc) == {
+ "Weight": 10,
+ "Width": 10000,
+ "Custom": 1.5,
+ }, "an instance without any location data has the default design location"
+
+
+def test_instance_location_design_first(map_doc):
+ inst = InstanceDescriptor(
+ designLocation={"Weight": (60, 61), "Width": 11000, "Custom": 1.2},
+ userLocation={"Weight": 700, "Width": 180, "Custom": 1.4},
+ )
+ assert inst.getFullUserLocation(map_doc) == {
+ "Weight": 600,
+ "Width": 110,
+ "Custom": 1.2,
+ }, "when both design and user location data are provided, design wins"
+ assert inst.getFullDesignLocation(map_doc) == {
+ "Weight": (60, 61),
+ "Width": 11000,
+ "Custom": 1.2,
+ }, "when both design and user location data are provided, design wins (incl. anisotropy)"
+
+
+def test_instance_location_mix(map_doc):
+ inst = InstanceDescriptor(
+ designLocation={"Weight": (60, 61)},
+ userLocation={"Width": 180},
+ )
+ assert inst.getFullUserLocation(map_doc) == {
+ "Weight": 600,
+ "Width": 180,
+ "Custom": 1.5,
+ }, "instance location is a mix of design and user locations"
+ assert inst.getFullDesignLocation(map_doc) == {
+ "Weight": (60, 61),
+ "Width": 18000,
+ "Custom": 1.5,
+ }, "instance location is a mix of design and user location"
+
+
+@pytest.mark.parametrize(
+ "filename",
+ [
+ "test_v4_original.designspace",
+ "test_v5_original.designspace",
+ "test_v5_aktiv.designspace",
+ "test_v5_decovar.designspace",
+ "test_v5_discrete.designspace",
+ "test_v5_sourceserif.designspace",
+ "test_v5.designspace",
+ ],
+)
+def test_roundtrip(tmpdir, datadir, filename):
+ test_file = datadir / filename
+ output_path = tmpdir / filename
+ # Move the file to the tmpdir so that the filenames stay the same
+ # (they're relative to the file's path)
+ shutil.copy(test_file, output_path)
+ doc = DesignSpaceDocument.fromfile(output_path)
+ doc.write(output_path)
+ # The input XML has comments and empty lines for documentation purposes
+ xml = test_file.read_text(encoding="utf-8")
+ xml = re.sub(
+ r"<!-- ROUNDTRIP_TEST_REMOVE_ME_BEGIN -->(.|\n)*?<!-- ROUNDTRIP_TEST_REMOVE_ME_END -->",
+ "",
+ xml,
+ )
+ xml = re.sub(r"<!--(.|\n)*?-->", "", xml)
+ xml = re.sub(r"\s*\n+", "\n", xml)
+ assert output_path.read_text(encoding="utf-8") == xml
+
+
+def test_using_v5_features_upgrades_format(tmpdir, datadir):
+ test_file = datadir / "test_v4_original.designspace"
+ output_4_path = tmpdir / "test_v4.designspace"
+ output_5_path = tmpdir / "test_v5.designspace"
+ shutil.copy(test_file, output_4_path)
+ doc = DesignSpaceDocument.fromfile(output_4_path)
+ doc.write(output_4_path)
+ assert 'format="4.1"' in output_4_path.read_text(encoding="utf-8")
+ doc.addVariableFont(VariableFontDescriptor(name="TestVF"))
+ doc.write(output_5_path)
+ assert 'format="5.0"' in output_5_path.read_text(encoding="utf-8")
+
+
+def test_addAxisDescriptor_discrete():
+ ds = DesignSpaceDocument()
+
+ axis = ds.addAxisDescriptor(
+ name="Italic",
+ tag="ital",
+ values=[0, 1],
+ default=0,
+ hidden=True,
+ map=[(0, -12), (1, 0)],
+ axisOrdering=3,
+ axisLabels=[
+ AxisLabelDescriptor(
+ name="Roman",
+ userValue=0,
+ elidable=True,
+ olderSibling=True,
+ linkedUserValue=1,
+ labelNames={"fr": "Romain"},
+ )
+ ],
+ )
+
+ assert ds.axes[0] is axis
+ assert_descriptors_equal(
+ [axis],
+ [
+ DiscreteAxisDescriptor(
+ tag="ital",
+ name="Italic",
+ values=[0, 1],
+ default=0,
+ hidden=True,
+ map=[(0, -12), (1, 0)],
+ axisOrdering=3,
+ axisLabels=[
+ AxisLabelDescriptor(
+ name="Roman",
+ userValue=0,
+ elidable=True,
+ olderSibling=True,
+ linkedUserValue=1,
+ labelNames={"fr": "Romain"},
+ )
+ ],
+ )
+ ],
+ )
+
+
+def test_addLocationLabelDescriptor():
+ ds = DesignSpaceDocument()
+
+ label = ds.addLocationLabelDescriptor(
+ name="Somewhere",
+ userLocation={},
+ elidable=True,
+ olderSibling=True,
+ labelNames={"fr": "Quelque part"},
+ )
+
+ assert ds.locationLabels[0] is label
+ assert_descriptors_equal(
+ [label],
+ [
+ LocationLabelDescriptor(
+ name="Somewhere",
+ userLocation={},
+ elidable=True,
+ olderSibling=True,
+ labelNames={"fr": "Quelque part"},
+ )
+ ],
+ )
+
+
+def test_addVariableFontDescriptor():
+ ds = DesignSpaceDocument()
+
+ vf = ds.addVariableFontDescriptor(name="TestVF", filename="TestVF.ttf")
+
+ assert ds.variableFonts[0] is vf
+ assert_descriptors_equal(
+ [vf], [VariableFontDescriptor(name="TestVF", filename="TestVF.ttf")]
+ )
diff --git a/Tests/designspaceLib/fixtures.py b/Tests/designspaceLib/fixtures.py
new file mode 100644
index 00000000..66041bed
--- /dev/null
+++ b/Tests/designspaceLib/fixtures.py
@@ -0,0 +1,8 @@
+from pathlib import Path
+
+import pytest
+
+
+@pytest.fixture
+def datadir():
+ return Path(__file__).parent / "data"
diff --git a/Tests/designspaceLib/split_test.py b/Tests/designspaceLib/split_test.py
new file mode 100644
index 00000000..8708f704
--- /dev/null
+++ b/Tests/designspaceLib/split_test.py
@@ -0,0 +1,150 @@
+import shutil
+from pathlib import Path
+
+import pytest
+from fontTools.designspaceLib import DesignSpaceDocument
+from fontTools.designspaceLib.split import splitInterpolable, splitVariableFonts, convert5to4
+
+from .fixtures import datadir
+
+UPDATE_REFERENCE_OUT_FILES_INSTEAD_OF_TESTING = False
+
+
+@pytest.mark.parametrize(
+ "test_ds,expected_interpolable_spaces",
+ [
+ (
+ "test_v5_aktiv.designspace",
+ [
+ (
+ {},
+ {
+ "AktivGroteskVF_Italics_Wght",
+ "AktivGroteskVF_Italics_WghtWdth",
+ "AktivGroteskVF_Wght",
+ "AktivGroteskVF_WghtWdth",
+ "AktivGroteskVF_WghtWdthItal",
+ },
+ )
+ ],
+ ),
+ (
+ "test_v5_sourceserif.designspace",
+ [
+ (
+ {"italic": 0},
+ {"SourceSerif4Variable-Roman"},
+ ),
+ (
+ {"italic": 1},
+ {"SourceSerif4Variable-Italic"},
+ ),
+ ],
+ ),
+ (
+ "test_v5_MutatorSans_and_Serif.designspace",
+ [
+ (
+ {"serif": 0},
+ {
+ "MutatorSansVariable_Weight_Width",
+ "MutatorSansVariable_Weight",
+ "MutatorSansVariable_Width",
+ },
+ ),
+ (
+ {"serif": 1},
+ {
+ "MutatorSerifVariable_Width",
+ },
+ ),
+ ],
+ ),
+ ],
+)
+def test_split(datadir, tmpdir, test_ds, expected_interpolable_spaces):
+ data_in = datadir / test_ds
+ temp_in = Path(tmpdir) / test_ds
+ shutil.copy(data_in, temp_in)
+ doc = DesignSpaceDocument.fromfile(temp_in)
+
+ for i, (location, sub_doc) in enumerate(splitInterpolable(doc)):
+ expected_location, expected_vf_names = expected_interpolable_spaces[i]
+ assert location == expected_location
+ vfs = list(splitVariableFonts(sub_doc))
+ assert expected_vf_names == set(vf[0] for vf in vfs)
+
+ loc_str = "_".join(f"{name}_{value}"for name, value in sorted(location.items()))
+ data_out = datadir / "split_output" / f"{temp_in.stem}_{loc_str}.designspace"
+ temp_out = Path(tmpdir) / "out" / f"{temp_in.stem}_{loc_str}.designspace"
+ temp_out.parent.mkdir(exist_ok=True)
+ sub_doc.write(temp_out)
+
+ if UPDATE_REFERENCE_OUT_FILES_INSTEAD_OF_TESTING:
+ data_out.write_text(temp_out.read_text(encoding="utf-8"), encoding="utf-8")
+ else:
+ assert data_out.read_text(encoding="utf-8") == temp_out.read_text(
+ encoding="utf-8"
+ )
+
+ for vf_name, vf_doc in vfs:
+ data_out = (datadir / "split_output" / vf_name).with_suffix(".designspace")
+ temp_out = (Path(tmpdir) / "out" / vf_name).with_suffix(".designspace")
+ temp_out.parent.mkdir(exist_ok=True)
+ vf_doc.write(temp_out)
+
+ if UPDATE_REFERENCE_OUT_FILES_INSTEAD_OF_TESTING:
+ data_out.write_text(
+ temp_out.read_text(encoding="utf-8"), encoding="utf-8"
+ )
+ else:
+ assert data_out.read_text(encoding="utf-8") == temp_out.read_text(
+ encoding="utf-8"
+ )
+
+
+
+
+@pytest.mark.parametrize(
+ "test_ds,expected_vfs",
+ [
+ (
+ "test_v5_aktiv.designspace",
+ {
+ "AktivGroteskVF_Italics_Wght",
+ "AktivGroteskVF_Italics_WghtWdth",
+ "AktivGroteskVF_Wght",
+ "AktivGroteskVF_WghtWdth",
+ "AktivGroteskVF_WghtWdthItal",
+ },
+ ),
+ (
+ "test_v5_sourceserif.designspace",
+ {
+ "SourceSerif4Variable-Italic",
+ "SourceSerif4Variable-Roman",
+ },
+ ),
+ ],
+)
+def test_convert5to4(datadir, tmpdir, test_ds, expected_vfs):
+ data_in = datadir / test_ds
+ temp_in = tmpdir / test_ds
+ shutil.copy(data_in, temp_in)
+ doc = DesignSpaceDocument.fromfile(temp_in)
+
+ variable_fonts = convert5to4(doc)
+
+ assert variable_fonts.keys() == expected_vfs
+ for vf_name, vf in variable_fonts.items():
+ data_out = (datadir / "convert5to4_output" / vf_name).with_suffix(".designspace")
+ temp_out = (Path(tmpdir) / "out" / vf_name).with_suffix(".designspace")
+ temp_out.parent.mkdir(exist_ok=True)
+ vf.write(temp_out)
+
+ if UPDATE_REFERENCE_OUT_FILES_INSTEAD_OF_TESTING:
+ data_out.write_text(temp_out.read_text(encoding="utf-8"), encoding="utf-8")
+ else:
+ assert data_out.read_text(encoding="utf-8") == temp_out.read_text(
+ encoding="utf-8"
+ )
diff --git a/Tests/designspaceLib/statNames_test.py b/Tests/designspaceLib/statNames_test.py
new file mode 100644
index 00000000..076abc90
--- /dev/null
+++ b/Tests/designspaceLib/statNames_test.py
@@ -0,0 +1,61 @@
+from fontTools.designspaceLib import DesignSpaceDocument
+from fontTools.designspaceLib.statNames import StatNames, getStatNames
+
+from .fixtures import datadir
+
+
+def test_instance_getStatNames(datadir):
+ doc = DesignSpaceDocument.fromfile(datadir / "test_v5_sourceserif.designspace")
+
+ assert getStatNames(doc, doc.instances[0].getFullUserLocation(doc)) == StatNames(
+ familyNames={"en": "Source Serif 4"},
+ styleNames={"en": "Caption ExtraLight"},
+ postScriptFontName="SourceSerif4-CaptionExtraLight",
+ styleMapFamilyNames={"en": "Source Serif 4 Caption ExtraLight"},
+ styleMapStyleName="regular",
+ )
+
+
+def test_not_all_ordering_specified_and_translations(datadir):
+ doc = DesignSpaceDocument.fromfile(datadir / "test_v5.designspace")
+
+ assert getStatNames(doc, {"weight": 200, "width": 125, "Italic": 1}) == StatNames(
+ familyNames={
+ "en": "MasterFamilyName",
+ "fr": "Montserrat",
+ "ja": "モンセラート",
+ },
+ styleNames={
+ "fr": "Wide Extra léger Italic",
+ "de": "Wide Extraleicht Italic",
+ "en": "Wide Extra Light Italic",
+ },
+ postScriptFontName="MasterFamilyName-WideExtraLightItalic",
+ styleMapFamilyNames={
+ "en": "MasterFamilyName Wide Extra Light",
+ "fr": "Montserrat Wide Extra léger",
+ "de": "MasterFamilyName Wide Extraleicht",
+ "ja": "モンセラート Wide Extra Light",
+ },
+ styleMapStyleName="italic",
+ )
+
+
+def test_detect_ribbi_aktiv(datadir):
+ doc = DesignSpaceDocument.fromfile(datadir / "test_v5_aktiv.designspace")
+
+ assert getStatNames(doc, {"Weight": 600, "Width": 125, "Italic": 1}) == StatNames(
+ familyNames={"en": "Aktiv Grotesk"},
+ styleNames={"en": "Ex SemiBold Italic"},
+ postScriptFontName="AktivGrotesk-ExSemiBoldItalic",
+ styleMapFamilyNames={"en": "Aktiv Grotesk Ex SemiBold"},
+ styleMapStyleName="italic",
+ )
+
+ assert getStatNames(doc, {"Weight": 700, "Width": 75, "Italic": 1}) == StatNames(
+ familyNames={"en": "Aktiv Grotesk"},
+ styleNames={"en": "Cd Bold Italic"},
+ postScriptFontName="AktivGrotesk-CdBoldItalic",
+ styleMapFamilyNames={"en": "Aktiv Grotesk Cd"},
+ styleMapStyleName="bold italic",
+ )
diff --git a/Tests/feaLib/data/spec6h_ii.ttx b/Tests/feaLib/data/spec6h_ii.ttx
index 2f0efc6f..e8ec85f2 100644
--- a/Tests/feaLib/data/spec6h_ii.ttx
+++ b/Tests/feaLib/data/spec6h_ii.ttx
@@ -112,23 +112,25 @@
</MarkBasePos>
</Lookup>
<Lookup index="2">
- <LookupType value="7"/>
+ <LookupType value="8"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <ContextPos index="0" Format="3">
- <!-- GlyphCount=3 -->
- <!-- PosCount=2 -->
- <Coverage index="0">
+ <ChainContextPos index="0" Format="3">
+ <!-- BacktrackGlyphCount=0 -->
+ <!-- InputGlyphCount=3 -->
+ <InputCoverage index="0">
<Glyph value="T"/>
- </Coverage>
- <Coverage index="1">
+ </InputCoverage>
+ <InputCoverage index="1">
<Glyph value="c"/>
<Glyph value="o"/>
- </Coverage>
- <Coverage index="2">
+ </InputCoverage>
+ <InputCoverage index="2">
<Glyph value="grave"/>
<Glyph value="acute"/>
- </Coverage>
+ </InputCoverage>
+ <!-- LookAheadGlyphCount=0 -->
+ <!-- PosCount=2 -->
<PosLookupRecord index="0">
<SequenceIndex value="0"/>
<LookupListIndex value="0"/>
@@ -137,7 +139,7 @@
<SequenceIndex value="2"/>
<LookupListIndex value="1"/>
</PosLookupRecord>
- </ContextPos>
+ </ChainContextPos>
</Lookup>
</LookupList>
</GPOS>
diff --git a/Tests/feaLib/data/spec6h_iii_3d.ttx b/Tests/feaLib/data/spec6h_iii_3d.ttx
index 2335dd0b..a608f0e6 100644
--- a/Tests/feaLib/data/spec6h_iii_3d.ttx
+++ b/Tests/feaLib/data/spec6h_iii_3d.ttx
@@ -30,23 +30,25 @@
<LookupList>
<!-- LookupCount=2 -->
<Lookup index="0">
- <LookupType value="7"/>
+ <LookupType value="8"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <ContextPos index="0" Format="3">
- <!-- GlyphCount=2 -->
- <!-- PosCount=1 -->
- <Coverage index="0">
+ <ChainContextPos index="0" Format="3">
+ <!-- BacktrackGlyphCount=0 -->
+ <!-- InputGlyphCount=2 -->
+ <InputCoverage index="0">
<Glyph value="L"/>
- </Coverage>
- <Coverage index="1">
+ </InputCoverage>
+ <InputCoverage index="1">
<Glyph value="quoteright"/>
- </Coverage>
+ </InputCoverage>
+ <!-- LookAheadGlyphCount=0 -->
+ <!-- PosCount=1 -->
<PosLookupRecord index="0">
<SequenceIndex value="1"/>
<LookupListIndex value="1"/>
</PosLookupRecord>
- </ContextPos>
+ </ChainContextPos>
</Lookup>
<Lookup index="1">
<LookupType value="1"/>
diff --git a/Tests/fontBuilder/fontBuilder_test.py b/Tests/fontBuilder/fontBuilder_test.py
index 6368cb87..775e94d9 100644
--- a/Tests/fontBuilder/fontBuilder_test.py
+++ b/Tests/fontBuilder/fontBuilder_test.py
@@ -1,13 +1,13 @@
import os
import pytest
-import re
from fontTools.ttLib import TTFont
from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.pens.t2CharStringPen import T2CharStringPen
from fontTools.fontBuilder import FontBuilder
from fontTools.ttLib.tables.TupleVariation import TupleVariation
from fontTools.misc.psCharStrings import T2CharString
+from fontTools.misc.testTools import stripVariableItemsFromTTX
def getTestData(fileName, mode="r"):
@@ -16,16 +16,6 @@ def getTestData(fileName, mode="r"):
return f.read()
-def strip_VariableItems(string):
- # ttlib changes with the fontTools version
- string = re.sub(' ttLibVersion=".*"', '', string)
- # head table checksum and creation and mod date changes with each save.
- string = re.sub('<checkSumAdjustment value="[^"]+"/>', '', string)
- string = re.sub('<modified value="[^"]+"/>', '', string)
- string = re.sub('<created value="[^"]+"/>', '', string)
- return string
-
-
def drawTestGlyph(pen):
pen.moveTo((100, 100))
pen.lineTo((100, 1000))
@@ -91,8 +81,8 @@ def _verifyOutput(outPath, tables=None):
f = TTFont(outPath)
f.saveXML(outPath + ".ttx", tables=tables)
with open(outPath + ".ttx") as f:
- testData = strip_VariableItems(f.read())
- refData = strip_VariableItems(getTestData(os.path.basename(outPath) + ".ttx"))
+ testData = stripVariableItemsFromTTX(f.read())
+ refData = stripVariableItemsFromTTX(getTestData(os.path.basename(outPath) + ".ttx"))
assert refData == testData
diff --git a/Tests/merge/data/CFFFont_expected.ttx b/Tests/merge/data/CFFFont_expected.ttx
index 6e0acc22..c8870e44 100644
--- a/Tests/merge/data/CFFFont_expected.ttx
+++ b/Tests/merge/data/CFFFont_expected.ttx
@@ -834,7 +834,7 @@
<!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
will be recalculated by the compiler -->
<version value="4"/>
- <xAvgCharWidth value="505"/>
+ <xAvgCharWidth value="501"/>
<usWeightClass value="400"/>
<usWidthClass value="5"/>
<fsType value="00000000 00000000"/>
diff --git a/Tests/misc/configTools_test.py b/Tests/misc/configTools_test.py
new file mode 100644
index 00000000..94afb233
--- /dev/null
+++ b/Tests/misc/configTools_test.py
@@ -0,0 +1,80 @@
+import dataclasses
+import pytest
+
+from fontTools.misc.configTools import (
+ AbstractConfig,
+ Option,
+ Options,
+ ConfigUnknownOptionError,
+)
+
+
+def test_can_create_custom_config_system():
+ class MyConfig(AbstractConfig):
+ options = Options()
+
+ MyConfig.register_option(
+ "test:option_name",
+ "This is an option",
+ 0,
+ int,
+ lambda v: isinstance(v, int),
+ )
+
+ cfg = MyConfig({"test:option_name": "10"}, parse_values=True)
+
+ assert 10 == cfg["test:option_name"]
+
+ # This config is independent from "the" fontTools config
+ with pytest.raises(ConfigUnknownOptionError):
+ MyConfig({"fontTools.otlLib.optimize.gpos:COMPRESSION_LEVEL": 4})
+
+ # Test the repr()
+ assert repr(cfg) == "MyConfig({'test:option_name': 10})"
+
+ # Test the skip_unknown param: just check that the following does not raise
+ MyConfig({"test:unknown": "whatever"}, skip_unknown=True)
+
+ # Test that it raises on unknown option
+ with pytest.raises(ConfigUnknownOptionError):
+ cfg.get("test:unknown")
+
+
+def test_options_are_unique():
+ class MyConfig(AbstractConfig):
+ options = Options()
+
+ opt1 = MyConfig.register_option("test:OPT_1", "", "foo", str, any)
+ cfg = MyConfig({opt1: "bar"})
+ assert cfg[opt1] == "bar"
+
+ opt2 = Option("test:OPT_1", "", "foo", str, any)
+
+ assert dataclasses.asdict(opt1) == dataclasses.asdict(opt2)
+ assert opt1 != opt2
+
+ with pytest.raises(ConfigUnknownOptionError):
+ cfg.get(opt2)
+ with pytest.raises(ConfigUnknownOptionError):
+ cfg.set(opt2, "bar")
+
+
+def test_optional_bool():
+ for v in ("yes", "YES", "Yes", "1", "True", "true", "TRUE"):
+ assert Option.parse_optional_bool(v) is True
+
+ for v in ("no", "NO", "No", "0", "False", "false", "FALSE"):
+ assert Option.parse_optional_bool(v) is False
+
+ for v in ("auto", "AUTO", "Auto", "None", "none", "NONE"):
+ assert Option.parse_optional_bool(v) is None
+
+ with pytest.raises(ValueError, match="invalid optional bool"):
+ Option.parse_optional_bool("foobar")
+
+ assert Option.validate_optional_bool(True)
+ assert Option.validate_optional_bool(False)
+ assert Option.validate_optional_bool(None)
+ assert not Option.validate_optional_bool(1)
+ assert not Option.validate_optional_bool(0)
+ assert not Option.validate_optional_bool("1")
diff --git a/Tests/misc/psCharStrings_test.py b/Tests/misc/psCharStrings_test.py
index 47ff4fda..5e36fe73 100644
--- a/Tests/misc/psCharStrings_test.py
+++ b/Tests/misc/psCharStrings_test.py
@@ -8,6 +8,7 @@ from fontTools.misc.psCharStrings import (
read_fixed1616,
read_realNumber,
)
+from fontTools.pens.recordingPen import RecordingPen
import unittest
@@ -158,6 +159,14 @@ class T2CharStringTest(unittest.TestCase):
self.assertNotIsInstance(expected_arg, str)
self.assertAlmostEqual(arg, expected_arg)
+ def test_pen_closePath(self):
+ # Test CFF2/T2 charstring: it does NOT end in "endchar"
+ # https://github.com/fonttools/fonttools/issues/2455
+ cs = self.stringToT2CharString("100 100 rmoveto -50 -150 200 0 -50 150 rrcurveto")
+ pen = RecordingPen()
+ cs.draw(pen)
+ self.assertEqual(pen.value[-1], ('closePath', ()))
+
if __name__ == "__main__":
import sys
diff --git a/Tests/otlLib/optimize_test.py b/Tests/otlLib/optimize_test.py
index 40cf389e..a2e43322 100644
--- a/Tests/otlLib/optimize_test.py
+++ b/Tests/otlLib/optimize_test.py
@@ -1,17 +1,15 @@
+import contextlib
import logging
+import os
from pathlib import Path
from subprocess import run
-import contextlib
-import os
from typing import List, Optional, Tuple
-from fontTools.ttLib import TTFont
import pytest
-
from fontTools.feaLib.builder import addOpenTypeFeaturesFromString
from fontTools.fontBuilder import FontBuilder
-
-from fontTools.ttLib.tables.otBase import OTTableWriter, ValueRecord
+from fontTools.ttLib import TTFont
+from fontTools.ttLib.tables.otBase import OTTableWriter
def test_main(tmpdir: Path):
@@ -34,7 +32,7 @@ def test_main(tmpdir: Path):
[
"fonttools",
"otlLib.optimize",
- "--gpos-compact-mode",
+ "--gpos-compression-level",
"5",
str(input),
"-o",
@@ -127,9 +125,9 @@ def get_kerning_by_blocks(blocks: List[Tuple[int, int]]) -> Tuple[List[str], str
@pytest.mark.parametrize(
- ("blocks", "mode", "expected_subtables", "expected_bytes"),
+ ("blocks", "level", "expected_subtables", "expected_bytes"),
[
- # Mode = 0 = no optimization leads to 650 bytes of GPOS
+ # Level = 0 = no optimization leads to 650 bytes of GPOS
([(15, 3), (2, 10)], None, 1, 602),
# Optimization level 1 recognizes the 2 blocks and splits into 2
# subtables = adds 1 subtable leading to a size reduction of
@@ -143,13 +141,13 @@ def get_kerning_by_blocks(blocks: List[Tuple[int, int]]) -> Tuple[List[str], str
([(4, 4) for _ in range(20)], 9, 20, 1886),
# On a fully occupied kerning matrix, even the strategy 9 doesn't
# split anything.
- ([(10, 10)], 9, 1, 304)
+ ([(10, 10)], 9, 1, 304),
],
)
def test_optimization_mode(
caplog,
blocks: List[Tuple[int, int]],
- mode: Optional[int],
+ level: Optional[int],
expected_subtables: int,
expected_bytes: int,
):
@@ -161,15 +159,10 @@ def test_optimization_mode(
glyphs, features = get_kerning_by_blocks(blocks)
glyphs = [".notdef space"] + glyphs
- env = {}
- if mode is not None:
- # NOTE: activating this optimization via the environment variable is
- # experimental and may not be supported once an alternative mechanism
- # is in place. See: https://github.com/fonttools/fonttools/issues/2349
- env["FONTTOOLS_GPOS_COMPACT_MODE"] = str(mode)
- with set_env(**env):
- fb = FontBuilder(1000)
- fb.setupGlyphOrder(glyphs)
- addOpenTypeFeaturesFromString(fb.font, features)
- assert expected_subtables == count_pairpos_subtables(fb.font)
- assert expected_bytes == count_pairpos_bytes(fb.font)
+ fb = FontBuilder(1000)
+ if level is not None:
+ fb.font.cfg["fontTools.otlLib.optimize.gpos:COMPRESSION_LEVEL"] = level
+ fb.setupGlyphOrder(glyphs)
+ addOpenTypeFeaturesFromString(fb.font, features)
+ assert expected_subtables == count_pairpos_subtables(fb.font)
+ assert expected_bytes == count_pairpos_bytes(fb.font)
diff --git a/Tests/subset/data/expect_harfbuzz_repacker.ttx b/Tests/subset/data/expect_harfbuzz_repacker.ttx
new file mode 100644
index 00000000..aeebe7ba
--- /dev/null
+++ b/Tests/subset/data/expect_harfbuzz_repacker.ttx
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="OTTO" ttLibVersion="4.29">
+
+ <GSUB>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=2 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ <ScriptRecord index="1">
+ <ScriptTag value="hang"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="1"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=2 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="aalt"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="1">
+ <FeatureTag value="jp90"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="1"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=2 -->
+ <Lookup index="0">
+ <LookupType value="3"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <AlternateSubst index="0">
+ <AlternateSet glyph="cid12223">
+ <Alternate glyph="cid62031"/>
+ <Alternate glyph="cid62033"/>
+ <Alternate glyph="cid61789"/>
+ </AlternateSet>
+ <AlternateSet glyph="cid61789">
+ <Alternate glyph="cid62031"/>
+ <Alternate glyph="cid62033"/>
+ <Alternate glyph="cid12223"/>
+ </AlternateSet>
+ </AlternateSubst>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="cid12223" out="cid61789"/>
+ </SingleSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+
+</ttFont>
diff --git a/Tests/subset/data/expect_lcar_0.ttx b/Tests/subset/data/expect_lcar_0.ttx
index feb866d7..0edcaa11 100644
--- a/Tests/subset/data/expect_lcar_0.ttx
+++ b/Tests/subset/data/expect_lcar_0.ttx
@@ -12,5 +12,5 @@
</Carets>
</LigatureCarets>
</lcar>
-
+
</ttFont>
diff --git a/Tests/subset/data/expect_lcar_1.ttx b/Tests/subset/data/expect_lcar_1.ttx
index 26b50082..72b4aabc 100644
--- a/Tests/subset/data/expect_lcar_1.ttx
+++ b/Tests/subset/data/expect_lcar_1.ttx
@@ -12,5 +12,5 @@
</Carets>
</LigatureCarets>
</lcar>
-
+
</ttFont>
diff --git a/Tests/subset/data/expect_opbd_0.ttx b/Tests/subset/data/expect_opbd_0.ttx
index 55842a01..3f6c4199 100644
--- a/Tests/subset/data/expect_opbd_0.ttx
+++ b/Tests/subset/data/expect_opbd_0.ttx
@@ -14,5 +14,5 @@
</OpticalBoundsDeltas>
</OpticalBounds>
</opbd>
-
+
</ttFont>
diff --git a/Tests/subset/data/expect_opbd_1.ttx b/Tests/subset/data/expect_opbd_1.ttx
index 080abd91..7b40401b 100644
--- a/Tests/subset/data/expect_opbd_1.ttx
+++ b/Tests/subset/data/expect_opbd_1.ttx
@@ -14,5 +14,5 @@
</OpticalBoundsPoints>
</OpticalBounds>
</opbd>
-
+
</ttFont>
diff --git a/Tests/subset/data/expect_prop_0.ttx b/Tests/subset/data/expect_prop_0.ttx
index f8ca1509..64201e12 100644
--- a/Tests/subset/data/expect_prop_0.ttx
+++ b/Tests/subset/data/expect_prop_0.ttx
@@ -7,5 +7,5 @@
<DefaultProperties value="3"/>
</GlyphProperties>
</prop>
-
+
</ttFont>
diff --git a/Tests/subset/data/expect_prop_1.ttx b/Tests/subset/data/expect_prop_1.ttx
index f7f2d23c..a582ac21 100644
--- a/Tests/subset/data/expect_prop_1.ttx
+++ b/Tests/subset/data/expect_prop_1.ttx
@@ -10,5 +10,5 @@
</Properties>
</GlyphProperties>
</prop>
-
+
</ttFont>
diff --git a/Tests/subset/data/harfbuzz_repacker.ttx b/Tests/subset/data/harfbuzz_repacker.ttx
new file mode 100644
index 00000000..667d8687
--- /dev/null
+++ b/Tests/subset/data/harfbuzz_repacker.ttx
@@ -0,0 +1,1542 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="OTTO" ttLibVersion="4.29">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="cid12223"/>
+ <GlyphID id="2" name="cid12224"/>
+ <GlyphID id="3" name="cid12342"/>
+ <GlyphID id="4" name="cid15450"/>
+ <GlyphID id="5" name="cid15451"/>
+ <GlyphID id="6" name="cid15452"/>
+ <GlyphID id="7" name="cid18153"/>
+ <GlyphID id="8" name="cid18154"/>
+ <GlyphID id="9" name="cid18155"/>
+ <GlyphID id="10" name="cid59592"/>
+ <GlyphID id="11" name="cid59593"/>
+ <GlyphID id="12" name="cid61789"/>
+ <GlyphID id="13" name="cid61811"/>
+ <GlyphID id="14" name="cid62031"/>
+ <GlyphID id="15" name="cid62032"/>
+ <GlyphID id="16" name="cid62033"/>
+ <GlyphID id="17" name="cid62034"/>
+ <GlyphID id="18" name="cid62039"/>
+ <GlyphID id="19" name="cid62104"/>
+ <GlyphID id="20" name="cid62105"/>
+ <GlyphID id="21" name="cid62172"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.004"/>
+ <checkSumAdjustment value="0x96aa8849"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Mon Jun 15 05:06:56 2015"/>
+ <modified value="Sat Mar 5 04:13:07 2022"/>
+ <xMin value="30"/>
+ <yMin value="-82"/>
+ <xMax value="972"/>
+ <yMax value="838"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="3"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1160"/>
+ <descent value="-320"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="1000"/>
+ <minLeftSideBearing value="30"/>
+ <minRightSideBearing value="28"/>
+ <xMaxExtent value="972"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="1"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x5000"/>
+ <numGlyphs value="22"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="979"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="325"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="2"/>
+ <bSerifStyle value="11"/>
+ <bWeight value="5"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange2 value="00001000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="GOOG"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="21417"/>
+ <usLastCharIndex value="24674"/>
+ <sTypoAscender value="880"/>
+ <sTypoDescender value="-120"/>
+ <sTypoLineGap value="0"/>
+ <usWinAscent value="1160"/>
+ <usWinDescent value="320"/>
+ <ulCodePageRange1 value="01100000 00101110 00000001 00000111"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="543"/>
+ <sCapHeight value="733"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="6"/>
+ </OS_2>
+
+ <name>
+ <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+ Copyright © 2014, 2015 Adobe Systems Incorporated (http://www.adobe.com/).
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Noto Sans CJK JP Regular
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 1.004;GOOG;NotoSansCJKjp-Regular;ADOBE
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Noto Sans CJK JP Regular
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 1.004;PS 1.004;hotconv 1.0.82;makeotf.lib2.5.63406
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ NotoSansCJKjp-Regular
+ </namerecord>
+ </name>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x53a9" name="cid12223"/><!-- CJK UNIFIED IDEOGRAPH-53A9 -->
+ <map code="0x53f1" name="cid12342"/><!-- CJK UNIFIED IDEOGRAPH-53F1 -->
+ <map code="0x5abe" name="cid15450"/><!-- CJK UNIFIED IDEOGRAPH-5ABE -->
+ <map code="0x6062" name="cid18153"/><!-- CJK UNIFIED IDEOGRAPH-6062 -->
+ </cmap_format_4>
+ <cmap_format_12 platformID="0" platEncID="4" format="12" reserved="0" length="64" language="0" nGroups="4">
+ <map code="0x53a9" name="cid12223"/><!-- CJK UNIFIED IDEOGRAPH-53A9 -->
+ <map code="0x53f1" name="cid12342"/><!-- CJK UNIFIED IDEOGRAPH-53F1 -->
+ <map code="0x5abe" name="cid15450"/><!-- CJK UNIFIED IDEOGRAPH-5ABE -->
+ <map code="0x6062" name="cid18153"/><!-- CJK UNIFIED IDEOGRAPH-6062 -->
+ </cmap_format_12>
+ <cmap_format_14 platformID="0" platEncID="5">
+ <map uv="0x53f1" uvs="0xe0100"/>
+ <map uv="0x5abe" uvs="0xe0100"/>
+ <map uv="0x53a9" uvs="0xe0100" name="cid61789"/>
+ <map uv="0x6062" uvs="0xe0100" name="cid61811"/>
+ <map uv="0x53a9" uvs="0xe0101" name="cid62031"/>
+ <map uv="0x53f1" uvs="0xe0101" name="cid62039"/>
+ <map uv="0x5abe" uvs="0xe0101" name="cid62104"/>
+ <map uv="0x6062" uvs="0xe0101" name="cid62172"/>
+ <map uv="0x6062" uvs="0xe0102"/>
+ <map uv="0x53a9" uvs="0xe0102" name="cid62032"/>
+ <map uv="0x5abe" uvs="0xe0102" name="cid62105"/>
+ <map uv="0x53a9" uvs="0xe0103" name="cid62033"/>
+ <map uv="0x53a9" uvs="0xe0104" name="cid62034"/>
+ <map uv="0x53a9" uvs="0xe0105"/>
+ </cmap_format_14>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x53a9" name="cid12223"/><!-- CJK UNIFIED IDEOGRAPH-53A9 -->
+ <map code="0x53f1" name="cid12342"/><!-- CJK UNIFIED IDEOGRAPH-53F1 -->
+ <map code="0x5abe" name="cid15450"/><!-- CJK UNIFIED IDEOGRAPH-5ABE -->
+ <map code="0x6062" name="cid18153"/><!-- CJK UNIFIED IDEOGRAPH-6062 -->
+ </cmap_format_4>
+ <cmap_format_12 platformID="3" platEncID="10" format="12" reserved="0" length="64" language="0" nGroups="4">
+ <map code="0x53a9" name="cid12223"/><!-- CJK UNIFIED IDEOGRAPH-53A9 -->
+ <map code="0x53f1" name="cid12342"/><!-- CJK UNIFIED IDEOGRAPH-53F1 -->
+ <map code="0x5abe" name="cid15450"/><!-- CJK UNIFIED IDEOGRAPH-5ABE -->
+ <map code="0x6062" name="cid18153"/><!-- CJK UNIFIED IDEOGRAPH-6062 -->
+ </cmap_format_12>
+ </cmap>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-125"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+ <CFF>
+ <major value="1"/>
+ <minor value="0"/>
+ <CFFFont name="NotoSansCJKjp-Regular">
+ <ROS Registry="Adobe" Order="Identity" Supplement="0"/>
+ <Notice value="Copyright 2014, 2015 Adobe Systems Incorporated (http://www.adobe.com/). Noto is a trademark of Google Inc."/>
+ <FullName value="Noto Sans CJK JP Regular"/>
+ <FamilyName value="Noto Sans CJK JP"/>
+ <Weight value="Regular"/>
+ <isFixedPitch value="0"/>
+ <ItalicAngle value="0"/>
+ <UnderlinePosition value="-150"/>
+ <UnderlineThickness value="50"/>
+ <PaintType value="0"/>
+ <CharstringType value="2"/>
+ <FontMatrix value="0.001 0 0 0.001 0 0"/>
+ <FontBBox value="30 -82 972 838"/>
+ <StrokeWidth value="0"/>
+ <XUID value="1 11 9274312"/>
+ <CIDFontVersion value="1.004"/>
+ <CIDFontRevision value="0"/>
+ <CIDFontType value="0"/>
+ <CIDCount value="65535"/>
+ <!-- charset is dumped separately as the 'GlyphOrder' element -->
+ <FDSelect format="3"/>
+ <FDArray>
+ <FontDict index="0">
+ <FontName value="NotoSansCJKjp-Regular-Generic"/>
+ <Private>
+ <BlueValues value="-250 -250 1100 1100"/>
+ <BlueScale value="0.039625"/>
+ <BlueShift value="7"/>
+ <BlueFuzz value="1"/>
+ <StdHW value="40"/>
+ <StdVW value="40"/>
+ <StemSnapH value="40 120"/>
+ <StemSnapV value="40 120"/>
+ <ForceBold value="0"/>
+ <LanguageGroup value="1"/>
+ <ExpansionFactor value="0.06"/>
+ <initialRandomSeed value="0"/>
+ <defaultWidthX value="1000"/>
+ <nominalWidthX value="107"/>
+ </Private>
+ </FontDict>
+ <FontDict index="1">
+ <FontName value="NotoSansCJKjp-Regular-Ideographs"/>
+ <Private>
+ <BlueValues value="-250 -250 1100 1100"/>
+ <BlueScale value="0.039625"/>
+ <BlueShift value="7"/>
+ <BlueFuzz value="1"/>
+ <StdHW value="58"/>
+ <StdVW value="63"/>
+ <StemSnapH value="58 65 84"/>
+ <StemSnapV value="63 73 89"/>
+ <ForceBold value="0"/>
+ <LanguageGroup value="1"/>
+ <ExpansionFactor value="0.06"/>
+ <initialRandomSeed value="0"/>
+ <defaultWidthX value="1000"/>
+ <nominalWidthX value="0"/>
+ <Subrs>
+ <!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
+ <CharString index="0">
+ -351 rmoveto
+ -8 48 -27 80 -31 60 -101 callsubr
+ return
+ </CharString>
+ <CharString index="1">
+ 68 16 113 -95 callsubr
+ return
+ </CharString>
+ <CharString index="2">
+ 77 -94 callsubr
+ return
+ </CharString>
+ <CharString index="3">
+ 7 -100 callsubr
+ return
+ </CharString>
+ <CharString index="4">
+ hlineto
+ 14 70 12 68 11 61 -68 6 rcurveline
+ -10 -62 -12 -71 -14 -72 rrcurveto
+ -97 -67 84 -85 callsubr
+ return
+ </CharString>
+ <CharString index="5">
+ -59.5 return
+ </CharString>
+ <CharString index="6">
+ -46 -17 rcurveline
+ 28 -62 26 -81 8 -50 rrcurveto
+ return
+ </CharString>
+ <CharString index="7">
+ -92 callsubr
+ -34 return
+ </CharString>
+ <CharString index="8">
+ -103 callsubr
+ 11 44 30 return
+ </CharString>
+ <CharString index="9">
+ -96 callsubr
+ 141 -71 callsubr
+ return
+ </CharString>
+ <CharString index="10">
+ -98 callsubr
+ 190 -107 callsubr
+ return
+ </CharString>
+ <CharString index="11">
+ -106 callsubr
+ -60 rlinecurve
+ return
+ </CharString>
+ <CharString index="12">
+ 90 vvcurveto
+ -50 -104 callsubr
+ return
+ </CharString>
+ <CharString index="13">
+ -89 callsubr
+ 87 return
+ </CharString>
+ <CharString index="14">
+ hlineto
+ hintmask 011000000010101000000000
+ -62 181 vlineto
+ -31 -154 -66 -109 -129 -72 22 17 10 33 4 65 -18 5 -26 9 -14 11 rrcurveto
+ -80 -4 -4 -12 -27 hhcurveto
+ -113 hlineto
+ -33 -5 4 24 hvcurveto
+ 87 vlineto
+ 80 19 87 29 62 32 -54 46 rcurveline
+ -41 -26 -68 -26 -66 -20 rrcurveto
+ 98 -65 -240 vlineto
+ -68 19 -19 77 vhcurveto
+ -96 callgsubr
+ 18 15 2 7 11 hvcurveto
+ 14 -11 27 -24 10 -12 87 54 60 66 41 85 rrcurveto
+ hintmask 100000000000001010000000
+ -92 vlineto
+ -55 5 -16 15 -12 vhcurveto
+ -12 13 23 -4 18 hhcurveto
+ 53 hlineto
+ 18 21 3 6 11 hvcurveto
+ 14 7 9 13 5 18 5 17 3 52 1 44 -16 5 -20 10 -12 11 rrcurveto
+ -48 -2 -37 -2 -16 vhcurveto
+ -3 -15 -4 -9 -5 -3 rrcurveto
+ -3 -5 -12 -2 -9 hhcurveto
+ endchar
+ </CharString>
+ <CharString index="15">
+ -2 -87 -16 -108 return
+ </CharString>
+ <CharString index="16">
+ -86 callsubr
+ -23 31 -26 29 -76 callsubr
+ 72 45 58 63 45 86 37 -37 32 -36 22 -31 45 60 rcurveline
+ return
+ </CharString>
+ <CharString index="17">
+ 16 -78 callsubr
+ return
+ </CharString>
+ <CharString index="18">
+ vlineto
+ -220 vmoveto
+ return
+ </CharString>
+ <CharString index="19">
+ 70 912 -70 return
+ </CharString>
+ <CharString index="20">
+ 135 62 187 36 230 rrcurveto
+ 318 -129 rmoveto
+ -16 -85 -28 -89 -34 -60 15 -5 27 -13 11 -9 34 63 31 95 19 90 rrcurveto
+ -337 5 rmoveto
+ -12 -84 -21 -85 -36 -58 15 -7 24 -15 10 -9 36 62 26 93 15 93 rrcurveto
+ 58 112 rmoveto
+ -13 -323 -42 -198 -222 -79 14 -15 19 -26 9 -20 128 51 72 83 42 122 42 -113 66 -88 92 -48 11 18 20 25 16 13 -112 49 -72 112 -36 137 17 84 8 99 4 114 rrcurveto
+ endchar
+ </CharString>
+ <CharString index="21">
+ -100 callgsubr
+ -120 -99 callsubr
+ return
+ </CharString>
+ <CharString index="22">
+ hlineto
+ -24 -124 -27 -121 -22 -85 59 -35 rcurveline
+ return
+ </CharString>
+ <CharString index="23">
+ 189 721 rmoveto
+ 755 68 -825 -294 hlineto
+ -156 -7 -220 -76 -157 vhcurveto
+ 18 -7 31 -18 14 -11 rrcurveto
+ 79 164 11 241 164 vvcurveto
+ return
+ </CharString>
+ <CharString index="24">
+ 104 54 73 69 50 91 rrcurveto
+ -117 vlineto
+ -45 4 -14 16 -13 vhcurveto
+ -13 14 23 -3 22 hhcurveto
+ 55 hlineto
+ 18 22 3 7 12 hvcurveto
+ 15 8 10 12 6 18 6 17 3 50 1 45 -17 6 -24 13 -12 12 1 -46 -2 -40 -2 -15 -3 -11 -6 -7 -5 -4 rrcurveto
+ -3 -6 -12 -1 -10 hhcurveto
+ endchar
+ </CharString>
+ <CharString index="25">
+ 284 -154 rmoveto
+ -70 -166 70 vlineto
+ -193 vmoveto
+ 71 166 -71 vlineto
+ 70 -316 rmoveto
+ 56 -236 75 230 54 -230 75 227 305 -140 vlineto
+ 10 22 11 26 11 24 -69 16 rcurveline
+ -5 -25 -10 -35 -10 -28 rrcurveto
+ -89 -678 64 -92 callgsubr
+ return
+ </CharString>
+ <CharString index="26">
+ rmoveto
+ 69 913 -69 hlineto
+ return
+ </CharString>
+ <CharString index="27">
+ 61 -31 69 75 53 cntrmask 0110111011111000
+ cntrmask 1001000110100101
+ hintmask 1110110111111101
+ 949 715 rmoveto
+ 68 -827 -281 vlineto
+ -154 -7 -218 -77 -155 vhcurveto
+ 18 -7 30 -18 14 -11 rrcurveto
+ 81 163 10 238 162 vvcurveto
+ 213 vlineto
+ return
+ </CharString>
+ <CharString index="28">
+ 6 -99 callgsubr
+ return
+ </CharString>
+ <CharString index="29">
+ -15 19 -26 10 -18 return
+ </CharString>
+ <CharString index="30">
+ -164 68 164 345 -85 hlineto
+ -13 -69 callsubr
+ 9 -18 9 -25 3 -18 -49 callsubr
+ 26 -107 callgsubr
+ 85 56 vlineto
+ endchar
+ </CharString>
+ <CharString index="31">
+ -27 rlinecurve
+ -43 -85 -56 -59 callsubr
+ return
+ </CharString>
+ <CharString index="32">
+ rmoveto
+ -88 callsubr
+ hlineto
+ return
+ </CharString>
+ <CharString index="33">
+ -14 -1 vhcurveto
+ -13 -1 -48 0 return
+ </CharString>
+ <CharString index="34">
+ -147 69 114 55 -114 60 134 57 -134 82 -68 -82 -154 82 -67 -82 -129 -57 129 -60 -105 -55 105 -69 -144 -56 250 -57 hlineto
+ return
+ </CharString>
+ <CharString index="35">
+ -55 9 -4 -83 -18 -109 -27 -64 rlinecurve
+ 126 -467 -81 callsubr
+ 193 -350 rmoveto
+ -8 48 -27 79 -29 62 -53 -20 rcurveline
+ 28 -63 return
+ </CharString>
+ <CharString index="36">
+ -465 -75 callsubr
+ return
+ </CharString>
+ <CharString index="37">
+ hstemhm
+ 83 50 39 70 return
+ </CharString>
+ <CharString index="38">
+ -60 callsubr
+ -52 2 return
+ </CharString>
+ <CharString index="39">
+ -61.5 return
+ </CharString>
+ <CharString index="40">
+ -38 -90 callsubr
+ return
+ </CharString>
+ <CharString index="41">
+ 68 -102 callsubr
+ return
+ </CharString>
+ <CharString index="42">
+ hstem
+ 83 50 39 70 return
+ </CharString>
+ <CharString index="43">
+ 232 -58 callsubr
+ 762 return
+ </CharString>
+ <CharString index="44">
+ -134 hlineto
+ 134 return
+ </CharString>
+ <CharString index="45">
+ rrcurveto
+ hintmask 101000000000001000000000
+ 85 -380 rmoveto
+ -9 -7 2 4 -4 hvcurveto
+ -5 3 -1 11 19 vvcurveto
+ 275 -6 4 return
+ </CharString>
+ <CharString index="46">
+ 10 18 20 25 16 return
+ </CharString>
+ <CharString index="47">
+ -4 -54 callsubr
+ return
+ </CharString>
+ <CharString index="48">
+ -62 -67 -67 callsubr
+ return
+ </CharString>
+ <CharString index="49">
+ -57 callsubr
+ -50 callsubr
+ return
+ </CharString>
+ <CharString index="50">
+ -53 callsubr
+ -90 -34 29 -35 28 return
+ </CharString>
+ <CharString index="51">
+ rmoveto
+ -11 -131 -23 -110 return
+ </CharString>
+ <CharString index="52">
+ -124 hlineto
+ 124 return
+ </CharString>
+ <CharString index="53">
+ -3 -74 callsubr
+ return
+ </CharString>
+ <CharString index="54">
+ -56 callsubr
+ -33 return
+ </CharString>
+ <CharString index="55">
+ -40 -79 callsubr
+ return
+ </CharString>
+ <CharString index="56">
+ 120 -69 -120 return
+ </CharString>
+ <CharString index="57">
+ -34 25 18 76 18 87 18 86 rrcurveto
+ return
+ </CharString>
+ <CharString index="58">
+ rrcurveto
+ 72 45 0 11 28 hvcurveto
+ return
+ </CharString>
+ <CharString index="59">
+ hvcurveto
+ 25 11 7 19 36 vvcurveto
+ return
+ </CharString>
+ <CharString index="60">
+ -7 31 -18 13 -11 rrcurveto
+ return
+ </CharString>
+ <CharString index="61">
+ -14 19 -25 9 -17 return
+ </CharString>
+ </Subrs>
+ </Private>
+ </FontDict>
+ </FDArray>
+ <CharStrings>
+ <CharString name=".notdef" fdSelectIndex="0">
+ endchar
+ </CharString>
+ <CharString name="cid12223" fdSelectIndex="1">
+ -67 64 55 56 75 54 74 61 -60 56 71 52 70 56 -46 61 83 68 hstemhm
+ 122 69 54 64 166 61 61 61 80 -80 callsubr
+ hintmask 0110111000110000
+ -82 callsubr
+ hintmask 0001000100001010
+ 349 320 rmoveto
+ 199 -95 callgsubr
+ -4 -73 -6 -66 -11 -60 rrcurveto
+ hintmask 1001000000000100
+ 82 -375 rmoveto
+ -10 -7 1 4 -5 hvcurveto
+ -6 3 -2 6 8 vvcurveto
+ 281 vlineto
+ 3 11 rlineto
+ 144 61 hlineto
+ hintmask 0001000100001010
+ -86 callgsubr
+ 11 60 7 66 5 73 rrcurveto
+ 99 61 -339 -260 -40 -61 177 hlineto
+ -41 -161 -80 -107 -154 -69 16 -12 27 -27 9 -11 rrcurveto
+ hintmask 1000000000001101
+ -83 callsubr
+ </CharString>
+ <CharString name="cid12224" fdSelectIndex="1">
+ -58 57 292 65 -54 59 88 56 75 61 80 68 hstemhm
+ 119 71 55 66 155 64 86 63 58 68 -43 66 81 53 cntrmask 1100111100101000
+ cntrmask 0011100110000000
+ hintmask 1011111111101000
+ 946 721 rmoveto
+ 68 -827 -333 vlineto
+ -149 -6 -202 -73 -143 vhcurveto
+ 18 -7 32 -18 13 -13 rrcurveto
+ 76 151 11 225 156 vvcurveto
+ 265 vlineto
+ 276 -139 rmoveto
+ -77 -155 77 vlineto
+ -221 vmoveto
+ 88 155 -88 vlineto
+ 79 -307 rmoveto
+ -16 54 -46 86 -43 64 -53 -21 rcurveline
+ 17 -28 18 -31 16 -30 -127 -47 rcurveline
+ 201 219 340 -285 -514 vlineto
+ -41 -22 -22 -16 -9 vhcurveto
+ 12 -15 16 -28 6 -18 rrcurveto
+ -1 vlineto
+ 17 15 27 13 178 71 11 -24 9 -21 5 -19 rrcurveto
+ 343 -30 rmoveto
+ -8 -7 1 4 -5 hvcurveto
+ -5 3 -1 10 18 vvcurveto
+ hintmask 1101111111101000
+ 220 -34 vlineto
+ 3 12 4 12 4 12 rrcurveto
+ 154 65 hlineto
+ hintmask 1101111111011000
+ -139 hlineto
+ 17 77 9 77 5 70 rrcurveto
+ 104 61 -341 -61 -83 callgsubr
+ -5 -68 -8 -79 -18 -77 rrcurveto
+ -91 hlineto
+ 14 58 16 85 9 39 rrcurveto
+ -63 hlineto
+ -7 -39 -22 -117 -9 -18 -6 -16 -8 -5 -12 -5 7 -13 15 -30 5 -16 rrcurveto
+ -1 vlineto
+ 8 8 29 5 37 hhcurveto
+ 60 hlineto
+ -38 -122 -70 -115 -131 -72 17 -12 20 -21 10 -15 rrcurveto
+ hintmask 1011111111101000
+ 104 59 67 84 45 94 rrcurveto
+ -130 vlineto
+ -55 6 -16 15 -11 vhcurveto
+ -12 14 24 -5 20 hhcurveto
+ 55 hlineto
+ 16 23 3 6 12 hvcurveto
+ 14 7 9 11 6 17 6 17 3 47 2 41 -18 6 -22 10 -13 11 rrcurveto
+ -43 -1 -33 -4 -16 vhcurveto
+ -2 -12 -4 -8 -6 -3 rrcurveto
+ -3 -5 -10 -1 -10 hhcurveto
+ endchar
+ </CharString>
+ <CharString name="cid12342" fdSelectIndex="1">
+ -59 73 170 70 411 69 hstem
+ 77 72 144 73 189 76 255 72 cntrmask 01111100
+ 293 665 rmoveto
+ -411 -144 411 vlineto
+ 217 69 rmoveto
+ -289 -646 72 96 217 hlineto
+ 316 -170 rmoveto
+ -43 -8 10 50 hvcurveto
+ 287 vlineto
+ 112 75 108 90 83 96 -72 49 rcurveline
+ -61 -77 -82 -75 -88 -65 rrcurveto
+ 361 -76 -414 vlineto
+ -60 -39 -60 -35 -58 -27 17 -15 24 -26 12 -16 41 20 42 23 42 26 rrcurveto
+ -236 vlineto
+ -105 27 -30 91 vhcurveto
+ -97 callgsubr
+ 93 20 60 167 10 hvcurveto
+ -22 5 -30 14 -20 16 rrcurveto
+ -150 -5 -7 -39 -43 hhcurveto
+ endchar
+ </CharString>
+ <CharString name="cid15450" fdSelectIndex="1">
+ -75 62 102 61 74 52 71 54 60 55 48 67 -45 53 64 55 hstemhm
+ 311 71 57 68 17 66 41 69 44 67 19 69 cntrmask 1111101101010100
+ cntrmask 0000000000101000
+ hintmask 1111101101010100
+ 700 150 rmoveto
+ 74 130 -74 vlineto
+ -323 74 rmoveto
+ 124 -74 -55 callsubr
+ 197 rmoveto
+ -71 -124 71 vlineto
+ 323 hmoveto
+ -71 -130 71 vlineto
+ hintmask 0000010010000000
+ -389 217 -58 callsubr
+ hintmask 0000101100101000
+ 395 22 rmoveto
+ 154 -70 -154 hlineto
+ 187 vmoveto
+ 154 -64 -154 hlineto
+ hintmask 0101100000010100
+ 367 -489 rmoveto
+ -58 251 -199 60 252 55 hlineto
+ hintmask 0001101100111000
+ -141 70 103 53 -103 64 122 55 -122 78 -67 -78 -154 78 -66 -78 -125 -55 125 -64 -98 -53 98 -70 -138 -55 245 -60 hlineto
+ hintmask 1101010011000100
+ -192 -251 -65 -40 hlineto
+ -21 24 -26 25 -28 25 45 113 27 145 11 185 -43 6 rcurveline
+ -91 callsubr
+ -4 5 -4 6 -5 5 rrcurveto
+ 47 -164 68 164 323 -87 hlineto
+ -11 -4 -3 -11 -1 vhcurveto
+ -13 -1 -40 0 -46 1 8 -17 11 -27 3 -18 rrcurveto
+ 62 41 0 11 26 -48 callsubr
+ 87 58 vlineto
+ endchar
+ </CharString>
+ <CharString name="cid15451" fdSelectIndex="1">
+ -76 61 101 59 74 52 72 56 57 56 51 68 -50 55 60 57 hstemhm
+ 160 67 55 69 66 68 28 67 39 66 49 68 28 69 cntrmask 1111101100101010
+ cntrmask 0000000000010100
+ hintmask 1111101100101010
+ 685 145 rmoveto
+ 74 145 -74 vlineto
+ -345 74 rmoveto
+ 134 -74 -63 callsubr
+ 198 rmoveto
+ -72 -134 72 vlineto
+ 345 hmoveto
+ -72 -145 72 vlineto
+ hintmask 0000010001000000
+ -403 220 rmoveto
+ -10 -120 -18 -101 -29 -86 -29 24 -31 22 -29 21 17 69 18 84 16 87 rrcurveto
+ hintmask 0000101100010100
+ 393 18 rmoveto
+ 154 -69 -154 hlineto
+ 184 vmoveto
+ 154 -60 -154 hlineto
+ hintmask 0101100000001010
+ 375 -491 rmoveto
+ -56 254 -214 57 264 56 hlineto
+ hintmask 0001101100011100
+ -73 callsubr
+ hintmask 1101010011100010
+ -202 -254 -51 -17 hlineto
+ -24 27 -29 28 -34 29 39 107 24 137 9 171 -52 callsubr
+ -100 hlineto
+ 12 68 10 67 6 61 -67 3 rcurveline
+ -6 -61 -9 -68 -11 -70 rrcurveto
+ -90 -68 78 hlineto
+ -19 -102 -22 -98 -21 -71 45 -31 48 -38 44 -38 -42 -93 -56 -66 -69 -40 14 -46 callsubr
+ 73 48 60 67 44 93 38 -36 31 -36 22 -30 47 50 rcurveline
+ 30 -77 callsubr
+ </CharString>
+ <CharString name="cid15452" fdSelectIndex="1">
+ -76 61 101 59 74 52 72 56 57 56 -103 callgsubr
+ 70 -68 callsubr
+ -66 callsubr
+ 66 -102 callgsubr
+ 55 60 57 hstemhm
+ 43 139 -45 68 57 66 41 17 31 68 28 67 39 66 49 68 28 69 cntrmask 111110001100110101000000
+ cntrmask 000000000000001010100000
+ hintmask 000000100000100000000000
+ 262 556 rmoveto
+ -1 -96 -8 -114 -39 -111 -27 25 -29 25 -27 24 14 70 15 84 14 87 rrcurveto
+ -105 callgsubr
+ 211 13 rmoveto
+ hintmask 000000001000110000000000
+ 1 66 -58 -3 rlineto
+ hintmask 000000100010100000000000
+ 69 -66 -72 vlineto
+ -80 -4 rlineto
+ -106 callgsubr
+ 10 74 8 72 5 65 -68 3 rcurveline
+ -4 -66 -8 -76 -10 -75 rrcurveto
+ hintmask 000001000010100000000000
+ -76 -3 8 -70 60 3 -15 -97 -17 -93 -16 -68 rlinecurve
+ 41 -35 45 -42 41 -42 -32 -70 -47 -68 -68 -61 17 -10 24 -22 11 -15 65 59 47 66 34 68 29 -32 26 -29 17 -24 44 55 rcurveline
+ -20 28 -31 33 -35 35 rrcurveto
+ hintmask 011100010000101010100000
+ 51 132 9 135 1 111 rrcurveto
+ 357 -415 rmoveto
+ 74 145 -74 vlineto
+ -345 74 rmoveto
+ 134 -74 -63 callsubr
+ 198 rmoveto
+ -72 -134 72 vlineto
+ 345 hmoveto
+ -72 -145 72 vlineto
+ hintmask 000010001100000101000000
+ -105 238 rmoveto
+ 154 -69 -154 hlineto
+ 184 vmoveto
+ 154 -60 -154 hlineto
+ hintmask 010110000000000010100000
+ 375 -491 rmoveto
+ -56 254 -214 57 264 56 hlineto
+ hintmask 000110001100010111000000
+ -73 callsubr
+ hintmask 110100000000001000100000
+ -202 -254 -51 -59 51 -77 callsubr
+ </CharString>
+ <CharString name="cid18153" fdSelectIndex="1">
+ 656 68 -65 callsubr
+ 161 66 60 55 87 68 cntrmask 01110100
+ 81 367 rmoveto
+ 36 -97 callsubr
+ 157 192 rmoveto
+ 486 68 -486 112 -66 -112 -82 -68 82 -109 hlineto
+ -191 -12 -213 -118 -175 vhcurveto
+ 17 -9 26 -21 13 -13 rrcurveto
+ 127 187 13 228 208 vvcurveto
+ 382 -272 rmoveto
+ 32 56 37 93 31 77 -65 16 rcurveline
+ -19 -69 -39 -99 -30 -60 rrcurveto
+ -274 -33 rmoveto
+ 40 57 12 94 8 84 -55 13 rcurveline
+ -5 -81 -15 -88 -34 -51 rrcurveto
+ 264 344 rmoveto
+ -68 -259 hlineto
+ -71 -36 -210 -209 -108 vhcurveto
+ 13 -15 19 -28 9 -15 165 88 61 159 12 71 12 -70 55 -164 154 -84 10 17 20 29 12 15 rrcurveto
+ -195 105 -34 212 69 vvcurveto
+ endchar
+ </CharString>
+ <CharString name="cid18154" fdSelectIndex="1">
+ 617 68 hstemhm
+ 92 55 22 69 266 70 128 63 hintmask 11000000
+ 99 370 rmoveto
+ 28 72 17 113 3 83 rrcurveto
+ hintmask 10111000
+ -72 callsubr
+ 26 -81 7 -50 rrcurveto
+ 635 -89 callgsubr
+ 68 -384 vlineto
+ 7 47 5 49 5 51 -70 6 rcurveline
+ -5 -53 -5 -51 -7 -49 rrcurveto
+ -139 -68 130 hlineto
+ -33 -210 -57 -173 -109 -118 16 -12 28 -26 11 -13 115 -87 callsubr
+ </CharString>
+ <CharString name="cid18155" fdSelectIndex="1">
+ 617 68 hstemhm
+ 92 55 22 69 240 70 -54 70 -8 60 76 66 cntrmask 00100010
+ hintmask 11000000
+ 99 370 rmoveto
+ 28 72 17 113 3 83 rrcurveto
+ hintmask 10110000
+ -72 callsubr
+ 25 -81 8 -50 rrcurveto
+ 635 -89 callgsubr
+ 68 -393 vlineto
+ hintmask 10001000
+ 6 47 5 49 5 51 -70 6 rcurveline
+ hintmask 10010000
+ -4 -53 -6 -51 -6 -49 rrcurveto
+ -131 -68 -84 callgsubr
+ -32 -213 -56 -176 -109 -120 16 -11 29 -26 10 -14 116 137 60 191 35 232 rrcurveto
+ 332 -129 rmoveto
+ -19 -82 -32 -90 -33 -62 16 -5 26 -13 13 -9 31 64 35 97 22 85 rrcurveto
+ hintmask 10000110
+ -313 -229 rmoveto
+ -4 59 -16 107 -16 84 -56 -9 rcurveline
+ 14 -83 14 -109 4 -64 rrcurveto
+ 136 355 rmoveto
+ -6 -328 -13 -180 -251 -94 14 -13 20 -25 9 -17 138 55 71 80 39 115 42 -112 66 -89 93 -48 -61 callsubr
+ 13 -113 50 -73 115 -36 139 14 84 4 98 2 114 rrcurveto
+ endchar
+ </CharString>
+ <CharString name="cid59592" fdSelectIndex="1">
+ -49 71 205 69 377 70 hstem
+ 79 70 159 71 161 75 281 69 cntrmask 01111100
+ 308 673 rmoveto
+ -377 -159 377 vlineto
+ 230 70 rmoveto
+ -300 -602 70 86 230 hlineto
+ 292 -205 rmoveto
+ -47 -9 10 51 hvcurveto
+ 345 vlineto
+ 345 85 -18 68 -327 -80 rlineto
+ 319 -75 -337 vlineto
+ -158 -39 17 -69 141 35 rlineto
+ -326 vlineto
+ -105 29 -28 95 vhcurveto
+ 178 hlineto
+ 92 21 53 154 10 hvcurveto
+ -21 6 -29 13 -19 13 rrcurveto
+ -134 -6 -7 -34 -45 hhcurveto
+ endchar
+ </CharString>
+ <CharString name="cid59593" fdSelectIndex="1">
+ -50 72 192 70 219 73 97 70 hstem
+ 78 71 159 72 160 75 254 70 cntrmask 10101110
+ cntrmask 01010000
+ 308 673 rmoveto
+ -389 -159 389 vlineto
+ 231 70 rmoveto
+ -302 -626 71 97 231 hlineto
+ 576 289 rmoveto
+ 73 -341 244 -75 -244 -116 -73 116 -421 vlineto
+ -104 27 -28 90 vhcurveto
+ -90 callgsubr
+ 89 20 53 154 10 hvcurveto
+ -22 5 -29 14 -19 14 rrcurveto
+ -134 -6 -6 -34 -41 hhcurveto
+ -85 callgsubr
+ -42 -9 10 48 hvcurveto
+ 423 vlineto
+ endchar
+ </CharString>
+ <CharString name="cid61789" fdSelectIndex="1">
+ -65 59 305 63 87 56 74 63 79 68 hstemhm
+ 119 70 59 65 157 64 59 63 82 68 -33 69 71 52 cntrmask 1111111110000000
+ cntrmask 0000001001010000
+ -87 callgsubr
+ 189 721 rmoveto
+ 755 68 -825 -294 hlineto
+ -157 -7 -219 -81 -157 vhcurveto
+ 18 -47 callsubr
+ 85 164 11 240 165 vvcurveto
+ 281 87 rmoveto
+ -77 -157 -105 callsubr
+ 157 -87 vlineto
+ 97 -316 rmoveto
+ -15 58 -45 88 -46 65 -59 -20 rcurveline
+ 19 -27 19 -32 16 -31 -49 -18 -49 -17 -45 -15 rrcurveto
+ 205 221 340 -286 -567 vlineto
+ -23 -8 -21 -7 -19 -6 24 -67 rcurveline
+ 77 30 99 37 96 38 10 -25 9 -24 5 -20 rrcurveto
+ 151 339 rmoveto
+ 217 vlineto
+ hintmask 0101000000100000
+ -94 callgsubr
+ -3 -75 -5 -73 -13 -69 rrcurveto
+ hintmask 1100000001000000
+ 81 -368 rmoveto
+ -9 -6 1 3 -5 hvcurveto
+ -5 4 -2 11 19 vvcurveto
+ 264 vlineto
+ 1 3 rlineto
+ 144 63 hlineto
+ hintmask 0101000010100000
+ -131 hlineto
+ 12 69 6 74 4 74 rrcurveto
+ 92 63 -341 -280 -38 -63 182 hlineto
+ -39 -140 -82 -117 -184 -68 15 -13 20 -25 8 -16 rrcurveto
+ hintmask 1000000011010000
+ 127 49 82 73 54 87 rrcurveto
+ -92 vlineto
+ -56 6 -16 15 -13 vhcurveto
+ -12 14 25 -5 20 hhcurveto
+ 56 hlineto
+ 17 23 3 6 11 hvcurveto
+ 15 7 10 12 6 18 rrcurveto
+ 6 15 3 48 41 vvcurveto
+ -16 6 -23 11 -13 12 rrcurveto
+ -43 -2 -34 -2 -14 vhcurveto
+ -2 -14 -5 -8 -6 -3 rrcurveto
+ -3 -5 -12 -1 -10 hhcurveto
+ endchar
+ </CharString>
+ <CharString name="cid61811" fdSelectIndex="1">
+ -75 40 751 67 -65 callsubr
+ 133 69 77 58 86 72 cntrmask 00111010
+ 81 367 rmoveto
+ 36 -97 callsubr
+ 132 252 rmoveto
+ 499 67 -568 -300 hlineto
+ -156 -10 -212 -102 -150 vhcurveto
+ 17 -7 32 -18 13 -11 rrcurveto
+ 104 154 15 236 164 vvcurveto
+ 403 -190 rmoveto
+ 31 57 38 92 30 78 -66 15 rcurveline
+ -19 -68 -39 -99 -30 -60 rrcurveto
+ -275 -36 rmoveto
+ 40 56 14 95 8 85 -58 14 rcurveline
+ -7 -81 -16 -87 -35 -52 rrcurveto
+ 274 352 rmoveto
+ -72 -268 hlineto
+ -74 -38 -216 -214 -112 vhcurveto
+ 13 -15 21 -30 9 -16 169 90 63 165 12 73 13 -72 56 -169 158 -87 11 18 21 31 13 15 rrcurveto
+ -200 110 -35 217 72 vvcurveto
+ endchar
+ </CharString>
+ <CharString name="cid62031" fdSelectIndex="1">
+ -65 53 -29 60 287 62 -58 51 78 49 78 51 -45 62 82 63 hstemhm
+ 122 70 48 61 -58 65 165 62 -41 58 53 61 83 63 -43 65 81 48 cntrmask 010111011101010000000000
+ cntrmask 101000110010001010000000
+ hintmask 010110111101011010000000
+ 949 716 rmoveto
+ 63 -827 -297 vlineto
+ -153 -7 -214 -77 -152 vhcurveto
+ 18 -7 31 -19 14 -11 rrcurveto
+ 80 159 11 236 161 vvcurveto
+ 234 vlineto
+ 281 -228 rmoveto
+ -172 hlineto
+ hintmask 000111000101010000000000
+ 78 172 vlineto
+ -205 vmoveto
+ -172 78 172 hlineto
+ 62 178 rmoveto
+ -128 hlineto
+ 10 22 11 23 10 24 -69 16 rcurveline
+ -6 -24 -11 -32 -11 -29 rrcurveto
+ -101 -307 295 hlineto
+ 131 58 rmoveto
+ hintmask 000100100000010100000000
+ 204 103 vlineto
+ -3 -75 -4 -68 -9 -61 -62 callsubr
+ 140 vlineto
+ hintmask 000100100000010100000000
+ 62 -129 vlineto
+ 9 62 5 67 3 75 rrcurveto
+ 104 62 -333 -266 -44 -93 callsubr
+ </CharString>
+ <CharString name="cid62032" fdSelectIndex="1">
+ -67 64 55 56 75 54 70 61 -56 56 71 52 70 56 -47 60 85 68 hstemhm
+ 122 69 54 64 166 61 66 61 75 -80 callsubr
+ hintmask 1110111011111101
+ -82 callsubr
+ 520 -55 rmoveto
+ -10 -7 1 4 -5 hvcurveto
+ -6 3 -2 6 8 vvcurveto
+ hintmask 1111010111111101
+ 281 vlineto
+ 2 7 rlineto
+ 148 61 hlineto
+ hintmask 1111010111111011
+ -91 callgsubr
+ 11 61 8 67 5 74 rrcurveto
+ 101 60 -365 -60 -88 callgsubr
+ -4 -74 -6 -68 -12 -60 rrcurveto
+ -83 167 -61 hlineto
+ hintmask 1110110111111101
+ -167 -44 vlineto
+ hintmask 1111010111111101
+ -61 175 vlineto
+ -41 -159 -80 -106 -153 -68 16 -12 27 -27 9 -11 -83 callsubr
+ </CharString>
+ <CharString name="cid62033" fdSelectIndex="1">
+ -65 58 304 63 -59 59 88 55 76 63 79 68 hstemhm
+ 119 70 58 65 158 63 83 58 63 68 -32 68 71 53 cntrmask 1100111111000000
+ cntrmask 0011100100101000
+ hintmask 1101111111100000
+ -84 callsubr
+ 281 87 rmoveto
+ -79 -158 79 vlineto
+ hintmask 0011100110000000
+ -221 vmoveto
+ 87 158 -87 vlineto
+ 98 -316 rmoveto
+ -15 59 -44 88 -46 65 -59 -20 rcurveline
+ 19 -27 18 -32 16 -31 -50 -17 -49 -17 -46 -16 rrcurveto
+ 204 221 341 -286 -567 vlineto
+ -23 -7 -21 -7 -19 -6 26 -67 rcurveline
+ 78 29 99 39 95 37 11 -26 8 -24 5 -20 rrcurveto
+ hintmask 0100100001010000
+ 168 337 rmoveto
+ 219 99 vlineto
+ -4 -75 -5 -74 -13 -70 rrcurveto
+ hintmask 1100000000100000
+ 81 -367 rmoveto
+ -9 -6 1 3 -5 hvcurveto
+ -6 4 -1 11 18 vvcurveto
+ 264 -1 vlineto
+ 1 3 rlineto
+ 145 63 hlineto
+ hintmask 0100100001010000
+ -86 callgsubr
+ 13 70 6 74 4 75 rrcurveto
+ 96 63 -371 -63 50 -219 -62 -63 182 hlineto
+ -40 -139 -83 -118 -182 -68 15 -13 19 -24 9 -17 rrcurveto
+ hintmask 1000000000101000
+ 127 50 82 74 54 88 rrcurveto
+ -94 vlineto
+ -56 5 -16 16 -13 vhcurveto
+ -12 14 25 -4 20 hhcurveto
+ 56 hlineto
+ 17 23 2 6 11 hvcurveto
+ 15 7 10 12 6 17 6 16 3 48 1 41 -18 5 -23 12 -12 12 rrcurveto
+ -44 -2 -33 -2 -15 vhcurveto
+ -2 -13 -5 -8 -6 -3 rrcurveto
+ -3 -5 -12 -1 -10 hhcurveto
+ endchar
+ </CharString>
+ <CharString name="cid62034" fdSelectIndex="1">
+ -65 53 -29 60 287 62 -58 51 78 49 78 51 -43 60 82 63 hstemhm
+ 122 70 48 61 -58 65 165 62 -41 58 59 59 79 63 -43 66 80 48 cntrmask 010111011101010000000000
+ cntrmask 101000110010001010000000
+ hintmask 010110111101011010000000
+ 949 716 rmoveto
+ 63 -827 -297 vlineto
+ -153 -7 -214 -77 -152 vhcurveto
+ 18 -7 31 -19 14 -11 rrcurveto
+ 80 159 11 236 161 vvcurveto
+ 234 vlineto
+ 281 -228 rmoveto
+ -172 hlineto
+ hintmask 000111000101010000000000
+ 78 172 vlineto
+ -205 vmoveto
+ -172 78 172 hlineto
+ 62 178 rmoveto
+ -128 hlineto
+ 10 22 11 23 10 24 -69 16 rcurveline
+ -6 -24 -11 -32 -11 -29 rrcurveto
+ -101 -307 295 hlineto
+ 135 58 rmoveto
+ hintmask 000100100000010100000000
+ 206 99 vlineto
+ -3 -76 -4 -68 -9 -62 -62 callsubr
+ 141 vlineto
+ hintmask 000100100000010100000000
+ 62 -130 vlineto
+ 9 62 5 69 4 75 rrcurveto
+ 100 60 -367 -60 43 -206 -50 -93 callsubr
+ </CharString>
+ <CharString name="cid62039" fdSelectIndex="1">
+ -52 71 206 69 377 69 hstem
+ 79 70 162 72 100 75 331 70 cntrmask 01111100
+ 311 671 rmoveto
+ -377 -162 377 vlineto
+ 234 69 rmoveto
+ -304 -618 70 103 234 hlineto
+ 240 -206 rmoveto
+ -54 -11 12 55 hvcurveto
+ 261 vlineto
+ 131 76 143 98 93 94 -55 48 rcurveline
+ -74 -77 -120 -88 -118 -70 rrcurveto
+ 407 -75 -748 vlineto
+ -109 32 -30 103 vhcurveto
+ 206 hlineto
+ 102 22 56 157 11 hvcurveto
+ -21 5 -30 13 -19 14 rrcurveto
+ -138 -7 -9 -36 -53 hhcurveto
+ endchar
+ </CharString>
+ <CharString name="cid62104" fdSelectIndex="1">
+ -76 61 94 56 74 54 69 54 80 56 42 67 -43 53 64 55 hstemhm
+ 311 71 61 68 13 66 41 69 44 67 15 69 cntrmask 1111101101010100
+ cntrmask 0000000000101000
+ hintmask 0110101100111000
+ 590 588 rmoveto
+ 154 -66 -154 hlineto
+ 183 vmoveto
+ 154 -64 -154 hlineto
+ 362 -119 rmoveto
+ -141 66 103 53 -103 64 122 55 -122 76 -67 -76 -154 76 -66 -76 -123 -55 123 -64 -98 -53 98 -66 -138 -56 566 hlineto
+ -252 -331 rmoveto
+ 74 vlineto
+ hintmask 1111010011010100
+ 126 -74 hlineto
+ -315 74 rmoveto
+ 120 -74 -120 hlineto
+ 197 vmoveto
+ -51 callsubr
+ hlineto
+ 315 69 rmoveto
+ -69 -126 69 vlineto
+ -389 -64 callsubr
+ -429 rmoveto
+ -62 74 52 54 -52 123 -195 59 -69 -59 -188 -123 -66 -54 66 -74 -71 -23 hlineto
+ -21 23 -25 25 -27 24 45 113 27 145 11 185 -43 6 rcurveline
+ -91 callsubr
+ -5 6 rlineto
+ 43 -155 68 155 315 -79 hlineto
+ -11 -4 -4 -11 vhcurveto
+ -13 -1 -40 0 -46 2 9 -17 10 -27 3 -18 rrcurveto
+ 62 41 1 10 26 hvcurveto
+ 25 11 7 19 35 vvcurveto
+ 79 -101 callgsubr
+ </CharString>
+ <CharString name="cid62105" fdSelectIndex="1">
+ -76 61 94 58 74 52 69 54 80 54 44 67 -45 53 65 54 hstemhm
+ 311 71 57 68 17 66 43 65 46 67 19 69 cntrmask 1111101101010100
+ cntrmask 0000000000101000
+ hintmask 0110101100111000
+ 590 586 rmoveto
+ 154 -66 -154 hlineto
+ 184 vmoveto
+ 154 -65 -154 hlineto
+ 362 -119 rmoveto
+ -141 66 104 53 -104 65 122 54 -122 78 -67 -78 -154 78 -66 -78 -123 -54 123 -65 -98 -53 98 -66 -138 -54 566 hlineto
+ -254 -329 rmoveto
+ 74 vlineto
+ hintmask 1111010011010100
+ 132 -74 hlineto
+ -323 74 rmoveto
+ 126 -74 -126 hlineto
+ 126 195 rmoveto
+ -69 -126 69 vlineto
+ 323 hmoveto
+ -69 -132 69 vlineto
+ -387 -64 callsubr
+ -427 rmoveto
+ -58 249 -201 57 -65 -57 -194 -249 -67 -25 hlineto
+ -21 23 -25 25 -27 24 45 113 27 145 11 185 -43 6 rcurveline
+ -86 callsubr
+ -23 30 -26 30 -76 callsubr
+ 71 45 58 63 45 85 38 -37 32 -36 22 -30 45 60 rcurveline
+ -5 6 rlineto
+ 39 -155 68 155 323 -79 hlineto
+ -11 -4 -4 -11 vhcurveto
+ -13 -1 -40 0 -46 2 8 -18 11 -26 3 -18 rrcurveto
+ 62 41 1 10 26 hvcurveto
+ 25 11 7 19 35 vvcurveto
+ 79 58 vlineto
+ endchar
+ </CharString>
+ <CharString name="cid62172" fdSelectIndex="1">
+ 650 68 -70 callsubr
+ 223 69 6 54 77 66 cntrmask 01100100
+ hintmask 11110100
+ 81 367 rmoveto
+ 36 -97 callsubr
+ 639 186 rmoveto
+ 68 -427 vlineto
+ 4 37 3 39 3 39 -69 4 rcurveline
+ -3 -41 -3 -39 -4 -39 rrcurveto
+ -120 -68 -93 callgsubr
+ -26 -209 -49 -177 -98 -117 17 -8 35 -20 12 -10 99 131 50 184 29 226 rrcurveto
+ 340 -406 rmoveto
+ 27 56 33 88 25 73 -63 15 rcurveline
+ -16 -64 -33 -95 -27 -58 rrcurveto
+ hintmask 10001100
+ -277 -30 rmoveto
+ 43 53 16 90 10 83 -54 14 rcurveline
+ -9 -80 -19 -84 -40 -51 rrcurveto
+ 265 349 rmoveto
+ -66 -247 hlineto
+ -69 -35 -203 -212 -104 vhcurveto
+ 13 -15 19 -26 8 -16 167 86 61 154 12 68 12 -68 53 -158 155 -82 10 16 19 28 12 16 rrcurveto
+ -196 101 -32 206 66 vvcurveto
+ endchar
+ </CharString>
+ </CharStrings>
+ </CFFFont>
+
+ <GlobalSubrs>
+ <!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
+ <CharString index="0">
+ 11 -104 callgsubr
+ return
+ </CharString>
+ <CharString index="1">
+ hintmask 000001000001000000000000
+ return
+ </CharString>
+ <CharString index="2">
+ hintmask 000000010000010000000000
+ return
+ </CharString>
+ <CharString index="3">
+ 8 18 37 vvcurveto
+ return
+ </CharString>
+ <CharString index="4">
+ 32.5 return
+ </CharString>
+ <CharString index="5">
+ -46.5 return
+ </CharString>
+ <CharString index="6">
+ 62 vlineto
+ endchar
+ </CharString>
+ <CharString index="7">
+ -11 -2 rlineto
+ return
+ </CharString>
+ <CharString index="8">
+ rcurveline
+ -98 callgsubr
+ return
+ </CharString>
+ <CharString index="9">
+ -12 -2 rlineto
+ return
+ </CharString>
+ <CharString index="10">
+ 162 hlineto
+ return
+ </CharString>
+ <CharString index="11">
+ 127 hlineto
+ return
+ </CharString>
+ <CharString index="12">
+ 110 vlineto
+ return
+ </CharString>
+ <CharString index="13">
+ 117 hlineto
+ return
+ </CharString>
+ <CharString index="14">
+ 112 hlineto
+ return
+ </CharString>
+ <CharString index="15">
+ 113 hlineto
+ return
+ </CharString>
+ <CharString index="16">
+ -136 hlineto
+ return
+ </CharString>
+ <CharString index="17">
+ 163 hlineto
+ return
+ </CharString>
+ <CharString index="18">
+ 155 rmoveto
+ return
+ </CharString>
+ <CharString index="19">
+ 195 hlineto
+ return
+ </CharString>
+ <CharString index="20">
+ hintmask 1111111111000000
+ return
+ </CharString>
+ <CharString index="21">
+ -132 hlineto
+ return
+ </CharString>
+ <CharString index="22">
+ -150 hlineto
+ return
+ </CharString>
+ <CharString index="23">
+ 122 hlineto
+ return
+ </CharString>
+ <CharString index="24">
+ 171 hlineto
+ return
+ </CharString>
+ </GlobalSubrs>
+ </CFF>
+
+ <GSUB>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=2 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ <ScriptRecord index="1">
+ <ScriptTag value="hang"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="1"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=2 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="aalt"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="1">
+ <FeatureTag value="jp90"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="1"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=2 -->
+ <Lookup index="0">
+ <LookupType value="3"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <AlternateSubst index="0">
+ <AlternateSet glyph="cid12223">
+ <Alternate glyph="cid62031"/>
+ <Alternate glyph="cid62033"/>
+ <Alternate glyph="cid61789"/>
+ </AlternateSet>
+ <AlternateSet glyph="cid12342">
+ <Alternate glyph="cid62039"/>
+ <Alternate glyph="cid59592"/>
+ </AlternateSet>
+ <AlternateSet glyph="cid15450">
+ <Alternate glyph="cid62104"/>
+ <Alternate glyph="cid62105"/>
+ </AlternateSet>
+ <AlternateSet glyph="cid18153">
+ <Alternate glyph="cid62172"/>
+ <Alternate glyph="cid61811"/>
+ </AlternateSet>
+ <AlternateSet glyph="cid61789">
+ <Alternate glyph="cid62031"/>
+ <Alternate glyph="cid62033"/>
+ <Alternate glyph="cid12223"/>
+ </AlternateSet>
+ <AlternateSet glyph="cid61811">
+ <Alternate glyph="cid62172"/>
+ <Alternate glyph="cid18153"/>
+ </AlternateSet>
+ </AlternateSubst>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SingleSubst index="0">
+ <Substitution in="cid12223" out="cid61789"/>
+ <Substitution in="cid18153" out="cid61811"/>
+ </SingleSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+
+ <VORG>
+ <majorVersion value="1"/>
+ <minorVersion value="0"/>
+ <defaultVertOriginY value="880"/>
+ <numVertOriginYMetrics value="0"/>
+ </VORG>
+
+ <hmtx>
+ <mtx name=".notdef" width="1000" lsb="100"/>
+ <mtx name="cid12223" width="1000" lsb="38"/>
+ <mtx name="cid12224" width="1000" lsb="40"/>
+ <mtx name="cid12342" width="1000" lsb="77"/>
+ <mtx name="cid15450" width="1000" lsb="44"/>
+ <mtx name="cid15451" width="1000" lsb="30"/>
+ <mtx name="cid15452" width="1000" lsb="39"/>
+ <mtx name="cid18153" width="1000" lsb="31"/>
+ <mtx name="cid18154" width="1000" lsb="43"/>
+ <mtx name="cid18155" width="1000" lsb="43"/>
+ <mtx name="cid59592" width="1000" lsb="79"/>
+ <mtx name="cid59593" width="1000" lsb="78"/>
+ <mtx name="cid61789" width="1000" lsb="31"/>
+ <mtx name="cid61811" width="1000" lsb="31"/>
+ <mtx name="cid62031" width="1000" lsb="38"/>
+ <mtx name="cid62032" width="1000" lsb="38"/>
+ <mtx name="cid62033" width="1000" lsb="36"/>
+ <mtx name="cid62034" width="1000" lsb="38"/>
+ <mtx name="cid62039" width="1000" lsb="79"/>
+ <mtx name="cid62104" width="1000" lsb="44"/>
+ <mtx name="cid62105" width="1000" lsb="44"/>
+ <mtx name="cid62172" width="1000" lsb="31"/>
+ </hmtx>
+
+ <vhea>
+ <tableVersion value="0x00011000"/>
+ <ascent value="500"/>
+ <descent value="-500"/>
+ <lineGap value="0"/>
+ <advanceHeightMax value="1000"/>
+ <minTopSideBearing value="42"/>
+ <minBottomSideBearing value="38"/>
+ <yMaxExtent value="962"/>
+ <caretSlopeRise value="0"/>
+ <caretSlopeRun value="1"/>
+ <caretOffset value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <reserved4 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfVMetrics value="1"/>
+ </vhea>
+
+ <vmtx>
+ <mtx name=".notdef" height="1000" tsb="0"/>
+ <mtx name="cid12223" height="1000" tsb="97"/>
+ <mtx name="cid12224" height="1000" tsb="91"/>
+ <mtx name="cid12342" height="1000" tsb="65"/>
+ <mtx name="cid15450" height="1000" tsb="44"/>
+ <mtx name="cid15451" height="1000" tsb="45"/>
+ <mtx name="cid15452" height="1000" tsb="45"/>
+ <mtx name="cid18153" height="1000" tsb="43"/>
+ <mtx name="cid18154" height="1000" tsb="42"/>
+ <mtx name="cid18155" height="1000" tsb="42"/>
+ <mtx name="cid59592" height="1000" tsb="60"/>
+ <mtx name="cid59593" height="1000" tsb="60"/>
+ <mtx name="cid61789" height="1000" tsb="91"/>
+ <mtx name="cid61811" height="1000" tsb="43"/>
+ <mtx name="cid62031" height="1000" tsb="101"/>
+ <mtx name="cid62032" height="1000" tsb="97"/>
+ <mtx name="cid62033" height="1000" tsb="91"/>
+ <mtx name="cid62034" height="1000" tsb="101"/>
+ <mtx name="cid62039" height="1000" tsb="45"/>
+ <mtx name="cid62104" height="1000" tsb="44"/>
+ <mtx name="cid62105" height="1000" tsb="44"/>
+ <mtx name="cid62172" height="1000" tsb="43"/>
+ </vmtx>
+
+</ttFont>
diff --git a/Tests/subset/subset_test.py b/Tests/subset/subset_test.py
index d195af0f..facafb2a 100644
--- a/Tests/subset/subset_test.py
+++ b/Tests/subset/subset_test.py
@@ -1,5 +1,6 @@
import io
-from fontTools.misc.testTools import getXML
+import fontTools.ttLib.tables.otBase
+from fontTools.misc.testTools import getXML, stripVariableItemsFromTTX
from fontTools.misc.textTools import tobytes, tostr
from fontTools import subset
from fontTools.fontBuilder import FontBuilder
@@ -19,44 +20,36 @@ import pathlib
import pytest
-class SubsetTest(unittest.TestCase):
- def __init__(self, methodName):
- unittest.TestCase.__init__(self, methodName)
- # Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
- # and fires deprecation warnings if a program uses the old name.
- if not hasattr(self, "assertRaisesRegex"):
- self.assertRaisesRegex = self.assertRaisesRegexp
+class SubsetTest:
+ @classmethod
+ def setup_class(cls):
+ cls.tempdir = None
+ cls.num_tempfiles = 0
- def setUp(self):
- self.tempdir = None
- self.num_tempfiles = 0
-
- def tearDown(self):
- if self.tempdir:
- shutil.rmtree(self.tempdir)
+ @classmethod
+ def teardown_class(cls):
+ if cls.tempdir:
+ shutil.rmtree(cls.tempdir, ignore_errors=True)
@staticmethod
def getpath(testfile):
path, _ = os.path.split(__file__)
return os.path.join(path, "data", testfile)
- def temp_path(self, suffix):
- if not self.tempdir:
- self.tempdir = tempfile.mkdtemp()
- self.num_tempfiles += 1
- return os.path.join(self.tempdir,
- "tmp%d%s" % (self.num_tempfiles, suffix))
-
- def read_ttx(self, path):
- lines = []
- with open(path, "r", encoding="utf-8") as ttx:
- for line in ttx.readlines():
- # Elide ttFont attributes because ttLibVersion may change.
- if line.startswith("<ttFont "):
- lines.append("<ttFont>\n")
- else:
- lines.append(line.rstrip() + "\n")
- return lines
+ @classmethod
+ def temp_path(cls, suffix):
+ if not cls.tempdir:
+ cls.tempdir = tempfile.mkdtemp()
+ cls.num_tempfiles += 1
+ return os.path.join(cls.tempdir,
+ "tmp%d%s" % (cls.num_tempfiles, suffix))
+
+ @staticmethod
+ def read_ttx(path):
+ with open(path, "r", encoding="utf-8") as f:
+ ttx = f.read()
+ # don't care whether TTF or OTF, thus strip sfntVersion as well
+ return stripVariableItemsFromTTX(ttx, sfntVersion=True).splitlines(True)
def expect_ttx(self, font, expected_ttx, tables=None):
path = self.temp_path(suffix=".ttx")
@@ -67,21 +60,21 @@ class SubsetTest(unittest.TestCase):
for line in difflib.unified_diff(
expected, actual, fromfile=expected_ttx, tofile=path):
sys.stdout.write(line)
- self.fail("TTX output is different from expected")
+ pytest.fail("TTX output is different from expected")
def compile_font(self, path, suffix):
savepath = self.temp_path(suffix=suffix)
font = TTFont(recalcBBoxes=False, recalcTimestamp=False)
font.importXML(path)
font.save(savepath, reorderTables=None)
- return font, savepath
+ return savepath
# -----
# Tests
# -----
def test_layout_scripts(self):
- _, fontpath = self.compile_font(self.getpath("layout_scripts.ttx"), ".otf")
+ fontpath = self.compile_font(self.getpath("layout_scripts.ttx"), ".otf")
subsetpath = self.temp_path(".otf")
subset.main([fontpath, "--glyphs=*", "--layout-features=*",
"--layout-scripts=latn,arab.URD,arab.dflt",
@@ -91,41 +84,41 @@ class SubsetTest(unittest.TestCase):
["GPOS", "GSUB"])
def test_no_notdef_outline_otf(self):
- _, fontpath = self.compile_font(self.getpath("TestOTF-Regular.ttx"), ".otf")
+ fontpath = self.compile_font(self.getpath("TestOTF-Regular.ttx"), ".otf")
subsetpath = self.temp_path(".otf")
subset.main([fontpath, "--no-notdef-outline", "--gids=0", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
self.expect_ttx(subsetfont, self.getpath("expect_no_notdef_outline_otf.ttx"), ["CFF "])
def test_no_notdef_outline_cid(self):
- _, fontpath = self.compile_font(self.getpath("TestCID-Regular.ttx"), ".otf")
+ fontpath = self.compile_font(self.getpath("TestCID-Regular.ttx"), ".otf")
subsetpath = self.temp_path(".otf")
subset.main([fontpath, "--no-notdef-outline", "--gids=0", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
self.expect_ttx(subsetfont, self.getpath("expect_no_notdef_outline_cid.ttx"), ["CFF "])
def test_no_notdef_outline_ttf(self):
- _, fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--no-notdef-outline", "--gids=0", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
self.expect_ttx(subsetfont, self.getpath("expect_no_notdef_outline_ttf.ttx"), ["glyf", "hmtx"])
def test_subset_ankr(self):
- _, fontpath = self.compile_font(self.getpath("TestANKR.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestANKR.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--glyphs=one", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
self.expect_ttx(subsetfont, self.getpath("expect_ankr.ttx"), ["ankr"])
def test_subset_ankr_remove(self):
- _, fontpath = self.compile_font(self.getpath("TestANKR.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestANKR.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--glyphs=two", "--output-file=%s" % subsetpath])
- self.assertNotIn("ankr", TTFont(subsetpath))
+ assert "ankr" not in TTFont(subsetpath)
def test_subset_bsln_format_0(self):
- _, fontpath = self.compile_font(self.getpath("TestBSLN-0.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestBSLN-0.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--glyphs=one", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
@@ -138,7 +131,7 @@ class SubsetTest(unittest.TestCase):
# a subsetted font with {zero, one} and the implicit .notdef, all
# glyphs in the resulting font use the Roman baseline. In this case,
# we expect a format 0 'bsln' table because it is the most compact.
- _, fontpath = self.compile_font(self.getpath("TestBSLN-1.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestBSLN-1.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--unicodes=U+0030-0031",
"--output-file=%s" % subsetpath])
@@ -154,7 +147,7 @@ class SubsetTest(unittest.TestCase):
# subsetted font, we expect a format 1 'bsln' table whose default
# is Roman, but with an override that uses the ideographic baseline
# for uni2EA2.
- _, fontpath = self.compile_font(self.getpath("TestBSLN-1.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestBSLN-1.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--unicodes=U+0030-0031,U+2EA2",
"--output-file=%s" % subsetpath])
@@ -165,7 +158,7 @@ class SubsetTest(unittest.TestCase):
# The 'bsln' table in TestBSLN-2 refers to control points in glyph 'P'
# for defining its baselines. Therefore, the subsetted font should
# include this glyph even though it is not requested explicitly.
- _, fontpath = self.compile_font(self.getpath("TestBSLN-2.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestBSLN-2.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--glyphs=one", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
@@ -179,7 +172,7 @@ class SubsetTest(unittest.TestCase):
# baseline measurement, all glyphs in the resulting font use the Roman
# baseline. In this case, we expect a format 2 'bsln' table because it
# is the most compact encoding.
- _, fontpath = self.compile_font(self.getpath("TestBSLN-3.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestBSLN-3.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--unicodes=U+0030",
"--output-file=%s" % subsetpath])
@@ -195,7 +188,7 @@ class SubsetTest(unittest.TestCase):
# subsetted font, we expect a format 1 'bsln' table whose default
# is Roman, but with an override that uses the ideographic baseline
# for uni2EA2.
- _, fontpath = self.compile_font(self.getpath("TestBSLN-3.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestBSLN-3.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--unicodes=U+0030-0031,U+2EA2",
"--output-file=%s" % subsetpath])
@@ -203,35 +196,35 @@ class SubsetTest(unittest.TestCase):
self.expect_ttx(subsetfont, self.getpath("expect_bsln_3.ttx"), ["bsln"])
def test_subset_clr(self):
- _, fontpath = self.compile_font(self.getpath("TestCLR-Regular.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestCLR-Regular.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--glyphs=smileface", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
self.expect_ttx(subsetfont, self.getpath("expect_keep_colr.ttx"), ["GlyphOrder", "hmtx", "glyf", "COLR", "CPAL"])
def test_subset_gvar(self):
- _, fontpath = self.compile_font(self.getpath("TestGVAR.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestGVAR.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--unicodes=U+002B,U+2212", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
self.expect_ttx(subsetfont, self.getpath("expect_keep_gvar.ttx"), ["GlyphOrder", "avar", "fvar", "gvar", "name"])
def test_subset_gvar_notdef_outline(self):
- _, fontpath = self.compile_font(self.getpath("TestGVAR.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestGVAR.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--unicodes=U+0030", "--notdef_outline", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
self.expect_ttx(subsetfont, self.getpath("expect_keep_gvar_notdef_outline.ttx"), ["GlyphOrder", "avar", "fvar", "gvar", "name"])
def test_subset_lcar_remove(self):
- _, fontpath = self.compile_font(self.getpath("TestLCAR-0.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestLCAR-0.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--glyphs=one", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
- self.assertNotIn("lcar", subsetfont)
+ assert "lcar" not in subsetfont
def test_subset_lcar_format_0(self):
- _, fontpath = self.compile_font(self.getpath("TestLCAR-0.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestLCAR-0.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--unicodes=U+FB01",
"--output-file=%s" % subsetpath])
@@ -239,7 +232,7 @@ class SubsetTest(unittest.TestCase):
self.expect_ttx(subsetfont, self.getpath("expect_lcar_0.ttx"), ["lcar"])
def test_subset_lcar_format_1(self):
- _, fontpath = self.compile_font(self.getpath("TestLCAR-1.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestLCAR-1.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--unicodes=U+FB01",
"--output-file=%s" % subsetpath])
@@ -247,14 +240,14 @@ class SubsetTest(unittest.TestCase):
self.expect_ttx(subsetfont, self.getpath("expect_lcar_1.ttx"), ["lcar"])
def test_subset_math(self):
- _, fontpath = self.compile_font(self.getpath("TestMATH-Regular.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestMATH-Regular.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--unicodes=U+0041,U+0028,U+0302,U+1D400,U+1D435", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
self.expect_ttx(subsetfont, self.getpath("expect_keep_math.ttx"), ["GlyphOrder", "CFF ", "MATH", "hmtx"])
def test_subset_math_partial(self):
- _, fontpath = self.compile_font(self.getpath("test_math_partial.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("test_math_partial.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--text=A", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
@@ -265,21 +258,21 @@ class SubsetTest(unittest.TestCase):
# the Optical Bounds table. When subsetting, we do not request any
# of those glyphs. Therefore, the produced subsetted font should
# not contain an 'opbd' table.
- _, fontpath = self.compile_font(self.getpath("TestOPBD-0.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestOPBD-0.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--glyphs=one", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
- self.assertNotIn("opbd", subsetfont)
+ assert "opbd" not in subsetfont
def test_subset_opbd_format_0(self):
- _, fontpath = self.compile_font(self.getpath("TestOPBD-0.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestOPBD-0.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--glyphs=A", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
self.expect_ttx(subsetfont, self.getpath("expect_opbd_0.ttx"), ["opbd"])
def test_subset_opbd_format_1(self):
- _, fontpath = self.compile_font(self.getpath("TestOPBD-1.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestOPBD-1.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--glyphs=A", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
@@ -288,12 +281,12 @@ class SubsetTest(unittest.TestCase):
def test_subset_prop_remove_default_zero(self):
# If all glyphs have an AAT glyph property with value 0,
# the "prop" table should be removed from the subsetted font.
- _, fontpath = self.compile_font(self.getpath("TestPROP.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestPROP.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--unicodes=U+0041",
"--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
- self.assertNotIn("prop", subsetfont)
+ assert "prop" not in subsetfont
def test_subset_prop_0(self):
# If all glyphs share the same AAT glyph properties, the "prop" table
@@ -302,7 +295,7 @@ class SubsetTest(unittest.TestCase):
# Unless the shared value is zero, in which case the subsetted font
# should have no "prop" table at all. But that case has already been
# tested above in test_subset_prop_remove_default_zero().
- _, fontpath = self.compile_font(self.getpath("TestPROP.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestPROP.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--unicodes=U+0030-0032", "--no-notdef-glyph",
"--output-file=%s" % subsetpath])
@@ -313,7 +306,7 @@ class SubsetTest(unittest.TestCase):
# If not all glyphs share the same AAT glyph properties, the subsetted
# font should contain a "prop" table in format 1. To save space, the
# DefaultProperties should be set to the most frequent value.
- _, fontpath = self.compile_font(self.getpath("TestPROP.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestPROP.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--unicodes=U+0030-0032", "--notdef-outline",
"--output-file=%s" % subsetpath])
@@ -323,32 +316,32 @@ class SubsetTest(unittest.TestCase):
def test_options(self):
# https://github.com/fonttools/fonttools/issues/413
opt1 = subset.Options()
- self.assertTrue('Xyz-' not in opt1.layout_features)
+ assert 'Xyz-' not in opt1.layout_features
opt2 = subset.Options()
opt2.layout_features.append('Xyz-')
- self.assertTrue('Xyz-' in opt2.layout_features)
- self.assertTrue('Xyz-' not in opt1.layout_features)
+ assert 'Xyz-' in opt2.layout_features
+ assert 'Xyz-' not in opt1.layout_features
def test_google_color(self):
- _, fontpath = self.compile_font(self.getpath("google_color.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("google_color.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--gids=0,1", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
- self.assertTrue("CBDT" in subsetfont)
- self.assertTrue("CBLC" in subsetfont)
- self.assertTrue("x" in subsetfont['CBDT'].strikeData[0])
- self.assertFalse("y" in subsetfont['CBDT'].strikeData[0])
+ assert "CBDT" in subsetfont
+ assert "CBLC" in subsetfont
+ assert "x" in subsetfont['CBDT'].strikeData[0]
+ assert "y" not in subsetfont['CBDT'].strikeData[0]
def test_google_color_all(self):
- _, fontpath = self.compile_font(self.getpath("google_color.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("google_color.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--unicodes=*", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
- self.assertTrue("x" in subsetfont['CBDT'].strikeData[0])
- self.assertTrue("y" in subsetfont['CBDT'].strikeData[0])
+ assert "x" in subsetfont['CBDT'].strikeData[0]
+ assert "y" in subsetfont['CBDT'].strikeData[0]
def test_sbix(self):
- _, fontpath = self.compile_font(self.getpath("sbix.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("sbix.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--gids=0,1", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
@@ -356,7 +349,7 @@ class SubsetTest(unittest.TestCase):
"expect_sbix.ttx"), ["sbix"])
def test_timing_publishes_parts(self):
- _, fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf")
options = subset.Options()
options.timing = True
@@ -367,15 +360,15 @@ class SubsetTest(unittest.TestCase):
subsetter.subset(font)
logs = captor.records
- self.assertTrue(len(logs) > 5)
- self.assertEqual(len(logs), len([l for l in logs if 'msg' in l.args and 'time' in l.args]))
+ assert len(logs) > 5
+ assert len(logs) == len([l for l in logs if 'msg' in l.args and 'time' in l.args])
# Look for a few things we know should happen
- self.assertTrue(filter(lambda l: l.args['msg'] == "load 'cmap'", logs))
- self.assertTrue(filter(lambda l: l.args['msg'] == "subset 'cmap'", logs))
- self.assertTrue(filter(lambda l: l.args['msg'] == "subset 'glyf'", logs))
+ assert filter(lambda l: l.args['msg'] == "load 'cmap'", logs)
+ assert filter(lambda l: l.args['msg'] == "subset 'cmap'", logs)
+ assert filter(lambda l: l.args['msg'] == "subset 'glyf'", logs)
def test_passthrough_tables(self):
- _, fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf")
font = TTFont(fontpath)
unknown_tag = 'ZZZZ'
unknown_table = newTable(unknown_tag)
@@ -388,17 +381,17 @@ class SubsetTest(unittest.TestCase):
subsetfont = TTFont(subsetpath)
# tables we can't subset are dropped by default
- self.assertFalse(unknown_tag in subsetfont)
+ assert unknown_tag not in subsetfont
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--passthrough-tables", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
# unknown tables are kept if --passthrough-tables option is passed
- self.assertTrue(unknown_tag in subsetfont)
+ assert unknown_tag in subsetfont
def test_non_BMP_text_arg_input(self):
- _, fontpath = self.compile_font(
+ fontpath = self.compile_font(
self.getpath("TestTTF-Regular_non_BMP_char.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
text = tostr(u"A\U0001F6D2", encoding='utf-8')
@@ -406,11 +399,11 @@ class SubsetTest(unittest.TestCase):
subset.main([fontpath, "--text=%s" % text, "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
- self.assertEqual(subsetfont['maxp'].numGlyphs, 3)
- self.assertEqual(subsetfont.getGlyphOrder(), ['.notdef', 'A', 'u1F6D2'])
+ assert subsetfont['maxp'].numGlyphs == 3
+ assert subsetfont.getGlyphOrder() == ['.notdef', 'A', 'u1F6D2']
def test_non_BMP_text_file_input(self):
- _, fontpath = self.compile_font(
+ fontpath = self.compile_font(
self.getpath("TestTTF-Regular_non_BMP_char.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
text = tobytes(u"A\U0001F6D2", encoding='utf-8')
@@ -424,12 +417,12 @@ class SubsetTest(unittest.TestCase):
finally:
os.remove(tmp.name)
- self.assertEqual(subsetfont['maxp'].numGlyphs, 3)
- self.assertEqual(subsetfont.getGlyphOrder(), ['.notdef', 'A', 'u1F6D2'])
+ assert subsetfont['maxp'].numGlyphs == 3
+ assert subsetfont.getGlyphOrder() == ['.notdef', 'A', 'u1F6D2']
def test_no_hinting_CFF(self):
ttxpath = self.getpath("Lobster.subset.ttx")
- _, fontpath = self.compile_font(ttxpath, ".otf")
+ fontpath = self.compile_font(ttxpath, ".otf")
subsetpath = self.temp_path(".otf")
subset.main([fontpath, "--no-hinting", "--notdef-outline",
"--output-file=%s" % subsetpath, "*"])
@@ -439,7 +432,7 @@ class SubsetTest(unittest.TestCase):
def test_desubroutinize_CFF(self):
ttxpath = self.getpath("Lobster.subset.ttx")
- _, fontpath = self.compile_font(ttxpath, ".otf")
+ fontpath = self.compile_font(ttxpath, ".otf")
subsetpath = self.temp_path(".otf")
subset.main([fontpath, "--desubroutinize", "--notdef-outline",
"--output-file=%s" % subsetpath, "*"])
@@ -449,7 +442,7 @@ class SubsetTest(unittest.TestCase):
def test_desubroutinize_hinted_subrs_CFF(self):
ttxpath = self.getpath("test_hinted_subrs_CFF.ttx")
- _, fontpath = self.compile_font(ttxpath, ".otf")
+ fontpath = self.compile_font(ttxpath, ".otf")
subsetpath = self.temp_path(".otf")
subset.main([fontpath, "--desubroutinize", "--notdef-outline",
"--output-file=%s" % subsetpath, "*"])
@@ -459,7 +452,7 @@ class SubsetTest(unittest.TestCase):
def test_desubroutinize_cntrmask_CFF(self):
ttxpath = self.getpath("test_cntrmask_CFF.ttx")
- _, fontpath = self.compile_font(ttxpath, ".otf")
+ fontpath = self.compile_font(ttxpath, ".otf")
subsetpath = self.temp_path(".otf")
subset.main([fontpath, "--desubroutinize", "--notdef-outline",
"--output-file=%s" % subsetpath, "*"])
@@ -469,7 +462,7 @@ class SubsetTest(unittest.TestCase):
def test_no_hinting_desubroutinize_CFF(self):
ttxpath = self.getpath("test_hinted_subrs_CFF.ttx")
- _, fontpath = self.compile_font(ttxpath, ".otf")
+ fontpath = self.compile_font(ttxpath, ".otf")
subsetpath = self.temp_path(".otf")
subset.main([fontpath, "--no-hinting", "--desubroutinize", "--notdef-outline",
"--output-file=%s" % subsetpath, "*"])
@@ -478,7 +471,7 @@ class SubsetTest(unittest.TestCase):
"expect_no_hinting_desubroutinize_CFF.ttx"), ["CFF "])
def test_no_hinting_TTF(self):
- _, fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--no-hinting", "--notdef-outline",
"--output-file=%s" % subsetpath, "*"])
@@ -486,11 +479,11 @@ class SubsetTest(unittest.TestCase):
self.expect_ttx(subsetfont, self.getpath(
"expect_no_hinting_TTF.ttx"), ["glyf", "maxp"])
for tag in subset.Options().hinting_tables:
- self.assertTrue(tag not in subsetfont)
+ assert tag not in subsetfont
def test_notdef_width_cid(self):
# https://github.com/fonttools/fonttools/pull/845
- _, fontpath = self.compile_font(self.getpath("NotdefWidthCID-Regular.ttx"), ".otf")
+ fontpath = self.compile_font(self.getpath("NotdefWidthCID-Regular.ttx"), ".otf")
subsetpath = self.temp_path(".otf")
subset.main([fontpath, "--no-notdef-outline", "--gids=0,1", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
@@ -503,18 +496,18 @@ class SubsetTest(unittest.TestCase):
head = font['head']
bounds = [head.xMin, head.yMin, head.xMax, head.yMax]
- _, fontpath = self.compile_font(ttxpath, ".ttf")
+ fontpath = self.compile_font(ttxpath, ".ttf")
subsetpath = self.temp_path(".ttf")
# by default, the subsetter does not recalculate the bounding box
subset.main([fontpath, "--output-file=%s" % subsetpath, "*"])
head = TTFont(subsetpath)['head']
- self.assertEqual(bounds, [head.xMin, head.yMin, head.xMax, head.yMax])
+ assert bounds == [head.xMin, head.yMin, head.xMax, head.yMax]
subset.main([fontpath, "--recalc-bounds", "--output-file=%s" % subsetpath, "*"])
head = TTFont(subsetpath)['head']
bounds = [132, 304, 365, 567]
- self.assertEqual(bounds, [head.xMin, head.yMin, head.xMax, head.yMax])
+ assert bounds == [head.xMin, head.yMin, head.xMax, head.yMax]
def test_recalc_bounds_otf(self):
ttxpath = self.getpath("TestOTF-Regular.ttx")
@@ -523,76 +516,76 @@ class SubsetTest(unittest.TestCase):
head = font['head']
bounds = [head.xMin, head.yMin, head.xMax, head.yMax]
- _, fontpath = self.compile_font(ttxpath, ".otf")
+ fontpath = self.compile_font(ttxpath, ".otf")
subsetpath = self.temp_path(".otf")
# by default, the subsetter does not recalculate the bounding box
subset.main([fontpath, "--output-file=%s" % subsetpath, "*"])
head = TTFont(subsetpath)['head']
- self.assertEqual(bounds, [head.xMin, head.yMin, head.xMax, head.yMax])
+ assert bounds == [head.xMin, head.yMin, head.xMax, head.yMax]
subset.main([fontpath, "--recalc-bounds", "--output-file=%s" % subsetpath, "*"])
head = TTFont(subsetpath)['head']
bounds = [132, 304, 365, 567]
- self.assertEqual(bounds, [head.xMin, head.yMin, head.xMax, head.yMax])
+ assert bounds == [head.xMin, head.yMin, head.xMax, head.yMax]
def test_recalc_timestamp_ttf(self):
ttxpath = self.getpath("TestTTF-Regular.ttx")
font = TTFont()
font.importXML(ttxpath)
modified = font['head'].modified
- _, fontpath = self.compile_font(ttxpath, ".ttf")
+ fontpath = self.compile_font(ttxpath, ".ttf")
subsetpath = self.temp_path(".ttf")
# by default, the subsetter does not recalculate the modified timestamp
subset.main([fontpath, "--output-file=%s" % subsetpath, "*"])
- self.assertEqual(modified, TTFont(subsetpath)['head'].modified)
+ assert modified == TTFont(subsetpath)['head'].modified
subset.main([fontpath, "--recalc-timestamp", "--output-file=%s" % subsetpath, "*"])
- self.assertLess(modified, TTFont(subsetpath)['head'].modified)
+ assert modified < TTFont(subsetpath)['head'].modified
def test_recalc_timestamp_otf(self):
ttxpath = self.getpath("TestOTF-Regular.ttx")
font = TTFont()
font.importXML(ttxpath)
modified = font['head'].modified
- _, fontpath = self.compile_font(ttxpath, ".otf")
+ fontpath = self.compile_font(ttxpath, ".otf")
subsetpath = self.temp_path(".otf")
# by default, the subsetter does not recalculate the modified timestamp
subset.main([fontpath, "--output-file=%s" % subsetpath, "*"])
- self.assertEqual(modified, TTFont(subsetpath)['head'].modified)
+ assert modified == TTFont(subsetpath)['head'].modified
subset.main([fontpath, "--recalc-timestamp", "--output-file=%s" % subsetpath, "*"])
- self.assertLess(modified, TTFont(subsetpath)['head'].modified)
+ assert modified < TTFont(subsetpath)['head'].modified
def test_recalc_max_context(self):
ttxpath = self.getpath("Lobster.subset.ttx")
font = TTFont()
font.importXML(ttxpath)
max_context = font['OS/2'].usMaxContext
- _, fontpath = self.compile_font(ttxpath, ".otf")
+ fontpath = self.compile_font(ttxpath, ".otf")
subsetpath = self.temp_path(".otf")
# by default, the subsetter does not recalculate the usMaxContext
subset.main([fontpath, "--drop-tables+=GSUB,GPOS",
"--output-file=%s" % subsetpath])
- self.assertEqual(max_context, TTFont(subsetpath)['OS/2'].usMaxContext)
+ assert max_context == TTFont(subsetpath)['OS/2'].usMaxContext
subset.main([fontpath, "--recalc-max-context",
"--drop-tables+=GSUB,GPOS",
"--output-file=%s" % subsetpath])
- self.assertEqual(0, TTFont(subsetpath)['OS/2'].usMaxContext)
+ assert 0 == TTFont(subsetpath)['OS/2'].usMaxContext
def test_retain_gids_ttf(self):
- _, fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf")
font = TTFont(fontpath)
- self.assertEqual(font["hmtx"]["A"], (500, 132))
- self.assertEqual(font["hmtx"]["B"], (400, 132))
+ assert font["hmtx"]["A"] == (500, 132)
+ assert font["hmtx"]["B"] == (400, 132)
- self.assertGreater(font["glyf"]["A"].numberOfContours, 0)
- self.assertGreater(font["glyf"]["B"].numberOfContours, 0)
+ assert font["glyf"]["A"].numberOfContours > 0
+ assert font["glyf"]["B"].numberOfContours > 0
subsetpath = self.temp_path(".ttf")
subset.main(
@@ -606,29 +599,29 @@ class SubsetTest(unittest.TestCase):
)
subsetfont = TTFont(subsetpath)
- self.assertEqual(subsetfont.getGlyphOrder(), font.getGlyphOrder()[0:3])
+ assert subsetfont.getGlyphOrder() == font.getGlyphOrder()[0:3]
hmtx = subsetfont["hmtx"]
- self.assertEqual(hmtx["A"], ( 0, 0))
- self.assertEqual(hmtx["B"], (400, 132))
+ assert hmtx["A"] == (0, 0)
+ assert hmtx["B"] == (400, 132)
glyf = subsetfont["glyf"]
- self.assertEqual(glyf["A"].numberOfContours, 0)
- self.assertGreater(glyf["B"].numberOfContours, 0)
+ assert glyf["A"].numberOfContours == 0
+ assert glyf["B"].numberOfContours > 0
def test_retain_gids_cff(self):
- _, fontpath = self.compile_font(self.getpath("TestOTF-Regular.ttx"), ".otf")
+ fontpath = self.compile_font(self.getpath("TestOTF-Regular.ttx"), ".otf")
font = TTFont(fontpath)
- self.assertEqual(font["hmtx"]["A"], (500, 132))
- self.assertEqual(font["hmtx"]["B"], (400, 132))
- self.assertEqual(font["hmtx"]["C"], (500, 0))
+ assert font["hmtx"]["A"] == (500, 132)
+ assert font["hmtx"]["B"] == (400, 132)
+ assert font["hmtx"]["C"] == (500, 0)
font["CFF "].cff[0].decompileAllCharStrings()
cs = font["CFF "].cff[0].CharStrings
- self.assertGreater(len(cs["A"].program), 0)
- self.assertGreater(len(cs["B"].program), 0)
- self.assertGreater(len(cs["C"].program), 0)
+ assert len(cs["A"].program) > 0
+ assert len(cs["B"].program) > 0
+ assert len(cs["C"].program) > 0
subsetpath = self.temp_path(".otf")
subset.main(
@@ -642,29 +635,30 @@ class SubsetTest(unittest.TestCase):
)
subsetfont = TTFont(subsetpath)
- self.assertEqual(subsetfont.getGlyphOrder(), font.getGlyphOrder()[0:3])
+ assert subsetfont.getGlyphOrder() == font.getGlyphOrder()[0:3]
hmtx = subsetfont["hmtx"]
- self.assertEqual(hmtx["A"], (0, 0))
- self.assertEqual(hmtx["B"], (400, 132))
+ assert hmtx["A"] == (0, 0)
+ assert hmtx["B"] == (400, 132)
subsetfont["CFF "].cff[0].decompileAllCharStrings()
cs = subsetfont["CFF "].cff[0].CharStrings
- self.assertEqual(cs["A"].program, ["endchar"])
- self.assertGreater(len(cs["B"].program), 0)
+ assert cs["A"].program == ["endchar"]
+ assert len(cs["B"].program) > 0
def test_retain_gids_cff2(self):
ttx_path = self.getpath("../../varLib/data/master_ttx_varfont_otf/TestCFF2VF.ttx")
- font, fontpath = self.compile_font(ttx_path, ".otf")
+ fontpath = self.compile_font(ttx_path, ".otf")
+ font = TTFont(fontpath)
- self.assertEqual(font["hmtx"]["A"], (600, 31))
- self.assertEqual(font["hmtx"]["T"], (600, 41))
+ assert font["hmtx"]["A"] == (600, 31)
+ assert font["hmtx"]["T"] == (600, 41)
font["CFF2"].cff[0].decompileAllCharStrings()
cs = font["CFF2"].cff[0].CharStrings
- self.assertGreater(len(cs["A"].program), 0)
- self.assertGreater(len(cs["T"].program), 0)
+ assert len(cs["A"].program) > 0
+ assert len(cs["T"].program) > 0
subsetpath = self.temp_path(".otf")
subset.main(
@@ -677,33 +671,33 @@ class SubsetTest(unittest.TestCase):
)
subsetfont = TTFont(subsetpath)
- self.assertEqual(len(subsetfont.getGlyphOrder()), len(font.getGlyphOrder()[0:3]))
+ assert len(subsetfont.getGlyphOrder()) == len(font.getGlyphOrder()[0:3])
hmtx = subsetfont["hmtx"]
- self.assertEqual(hmtx["glyph00001"], ( 0, 0))
- self.assertEqual(hmtx["T"], (600, 41))
+ assert hmtx["glyph00001"] == (0, 0)
+ assert hmtx["T"] == (600, 41)
subsetfont["CFF2"].cff[0].decompileAllCharStrings()
cs = subsetfont["CFF2"].cff[0].CharStrings
- self.assertEqual(cs["glyph00001"].program, [])
- self.assertGreater(len(cs["T"].program), 0)
+ assert cs["glyph00001"].program == []
+ assert len(cs["T"].program) > 0
def test_HVAR_VVAR(self):
- _, fontpath = self.compile_font(self.getpath("TestHVVAR.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestHVVAR.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--text=BD", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
self.expect_ttx(subsetfont, self.getpath("expect_HVVAR.ttx"), ["GlyphOrder", "HVAR", "VVAR", "avar", "fvar"])
def test_HVAR_VVAR_retain_gids(self):
- _, fontpath = self.compile_font(self.getpath("TestHVVAR.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestHVVAR.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--text=BD", "--retain-gids", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
self.expect_ttx(subsetfont, self.getpath("expect_HVVAR_retain_gids.ttx"), ["GlyphOrder", "HVAR", "VVAR", "avar", "fvar"])
def test_subset_flavor(self):
- _, fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf")
font = TTFont(fontpath)
woff_path = self.temp_path(".woff")
@@ -717,7 +711,7 @@ class SubsetTest(unittest.TestCase):
)
woff = TTFont(woff_path)
- self.assertEqual(woff.flavor, "woff")
+ assert woff.flavor == "woff"
woff2_path = self.temp_path(".woff2")
subset.main(
@@ -730,7 +724,7 @@ class SubsetTest(unittest.TestCase):
)
woff2 = TTFont(woff2_path)
- self.assertEqual(woff2.flavor, "woff2")
+ assert woff2.flavor == "woff2"
ttf_path = self.temp_path(".ttf")
subset.main(
@@ -742,13 +736,13 @@ class SubsetTest(unittest.TestCase):
)
ttf = TTFont(ttf_path)
- self.assertEqual(ttf.flavor, None)
+ assert ttf.flavor is None
def test_subset_context_subst_format_3(self):
# https://github.com/fonttools/fonttools/issues/1879
# Test font contains 'calt' feature with Format 3 ContextSubst lookup subtables
ttx = self.getpath("TestContextSubstFormat3.ttx")
- font, fontpath = self.compile_font(ttx, ".ttf")
+ fontpath = self.compile_font(ttx, ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--unicodes=*", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
@@ -756,13 +750,14 @@ class SubsetTest(unittest.TestCase):
self.expect_ttx(subsetfont, ttx)
def test_cmap_prune_format12(self):
- _, fontpath = self.compile_font(self.getpath("CmapSubsetTest.ttx"), ".ttf")
+ fontpath = self.compile_font(self.getpath("CmapSubsetTest.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--glyphs=a", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
self.expect_ttx(subsetfont, self.getpath("CmapSubsetTest.subset.ttx"), ["cmap"])
- def test_GPOS_PairPos_Format2_useClass0(self):
+ @pytest.mark.parametrize("text, n", [("!", 1), ("#", 2)])
+ def test_GPOS_PairPos_Format2_useClass0(self, text, n):
# Check two things related to class 0 ('every other glyph'):
# 1) that it's reused for ClassDef1 when it becomes empty as the subset glyphset
# is intersected with the table's Coverage
@@ -772,29 +767,27 @@ class SubsetTest(unittest.TestCase):
# The test font (from Harfbuzz test suite) is constructed to trigger these two
# situations depending on the input subset --text.
# https://github.com/fonttools/fonttools/pull/2221
- _, fontpath = self.compile_font(
+ fontpath = self.compile_font(
self.getpath("GPOS_PairPos_Format2_PR_2221.ttx"), ".ttf"
)
subsetpath = self.temp_path(".ttf")
- for n, text in enumerate("!#", start=1):
- expected_ttx = self.getpath(
- f"GPOS_PairPos_Format2_ClassDef{n}_useClass0.subset.ttx"
- )
- with self.subTest(text=text, expected_ttx=expected_ttx):
- subset.main(
- [
- fontpath,
- f"--text='{text}'",
- "--layout-features+=test",
- "--output-file=%s" % subsetpath,
- ]
- )
- subsetfont = TTFont(subsetpath)
- self.expect_ttx(subsetfont, expected_ttx, ["GPOS"])
+ expected_ttx = self.getpath(
+ f"GPOS_PairPos_Format2_ClassDef{n}_useClass0.subset.ttx"
+ )
+ subset.main(
+ [
+ fontpath,
+ f"--text='{text}'",
+ "--layout-features+=test",
+ "--output-file=%s" % subsetpath,
+ ]
+ )
+ subsetfont = TTFont(subsetpath)
+ self.expect_ttx(subsetfont, expected_ttx, ["GPOS"])
def test_GPOS_SinglePos_prune_post_subset_no_value(self):
- _, fontpath = self.compile_font(
+ fontpath = self.compile_font(
self.getpath("GPOS_SinglePos_no_value_issue_2312.ttx"), ".ttf"
)
subsetpath = self.temp_path(".ttf")
@@ -806,6 +799,91 @@ class SubsetTest(unittest.TestCase):
["GlyphOrder", "GPOS"],
)
+ @pytest.mark.parametrize(
+ "installed, enabled, ok",
+ [
+ pytest.param(True, None, True, id="installed-auto-ok"),
+ pytest.param(True, None, False, id="installed-auto-fail"),
+ pytest.param(True, True, True, id="installed-enabled-ok"),
+ pytest.param(True, True, False, id="installed-enabled-fail"),
+ pytest.param(True, False, True, id="installed-disabled"),
+ pytest.param(False, True, True, id="not_installed-enabled"),
+ pytest.param(False, False, True, id="not_installed-disabled"),
+ ],
+ )
+ def test_harfbuzz_repacker(self, caplog, monkeypatch, installed, enabled, ok):
+ # Use a mock to test the pure-python serializer is used when uharfbuzz
+ # returns an error or is not installed
+ have_uharfbuzz = fontTools.ttLib.tables.otBase.have_uharfbuzz
+ if installed:
+ if not have_uharfbuzz:
+ pytest.skip("uharfbuzz is not installed")
+ if not ok:
+ # pretend hb.repack returns an error
+ import uharfbuzz as hb
+
+ def mock_repack(data, obj_list):
+ raise hb.RepackerError("mocking")
+
+ monkeypatch.setattr(hb, "repack", mock_repack)
+ else:
+ if have_uharfbuzz:
+ # pretend uharfbuzz is not installed
+ monkeypatch.setattr(
+ fontTools.ttLib.tables.otBase, "have_uharfbuzz", False
+ )
+
+ fontpath = self.compile_font(self.getpath("harfbuzz_repacker.ttx"), ".otf")
+ subsetpath = self.temp_path(".otf")
+ args = [
+ fontpath,
+ "--unicodes=0x53a9",
+ "--layout-features=*",
+ f"--output-file={subsetpath}",
+ ]
+ if enabled is True:
+ args.append("--harfbuzz-repacker")
+ elif enabled is False:
+ args.append("--no-harfbuzz-repacker")
+ # elif enabled is None: ... is the default
+
+ if enabled is True and not installed:
+ # raise if enabled but not installed
+ with pytest.raises(ImportError, match="uharfbuzz"):
+ subset.main(args)
+ return
+
+ with caplog.at_level(logging.DEBUG, "fontTools.ttLib.tables.otBase"):
+ subset.main(args)
+
+ subsetfont = TTFont(subsetpath)
+ # both hb.repack and pure-python serializer compile to the same ttx
+ self.expect_ttx(
+ subsetfont, self.getpath("expect_harfbuzz_repacker.ttx"), ["GSUB"]
+ )
+
+ if enabled or enabled is None:
+ if installed:
+ assert "serializing 'GSUB' with hb.repack" in caplog.text
+
+ if enabled is None and not installed:
+ assert (
+ "uharfbuzz not found, compiling 'GSUB' with pure-python serializer"
+ ) in caplog.text
+
+ if enabled is False:
+ assert (
+ "hb.repack disabled, compiling 'GSUB' with pure-python serializer"
+ ) in caplog.text
+
+ # test we emit a log.error if hb.repack fails (and we don't if successful)
+ assert (
+ (
+ "hb.repack failed to serialize 'GSUB', reverting to "
+ "pure-python serializer; the error message was: RepackerError: mocking"
+ ) in caplog.text
+ ) ^ ok
+
@pytest.fixture
def featureVarsTestFont():
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_diff.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_8_diff.ttx
index 14e12097..0086ddc3 100644
--- a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_diff.ttx
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_8_diff.ttx
@@ -83,21 +83,23 @@
</MarkBasePos>
</Lookup>
<Lookup index="2">
- <LookupType value="7"/>
+ <LookupType value="8"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <ContextPos index="0" Format="1">
+ <ChainContextPos index="0" Format="1">
<Coverage>
<Glyph value="A"/>
</Coverage>
- <!-- PosRuleSetCount=1 -->
- <PosRuleSet index="0">
- <!-- PosRuleCount=1 -->
- <PosRule index="0">
- <!-- GlyphCount=3 -->
- <!-- PosCount=2 -->
+ <!-- ChainPosRuleSetCount=1 -->
+ <ChainPosRuleSet index="0">
+ <!-- ChainPosRuleCount=1 -->
+ <ChainPosRule index="0">
+ <!-- BacktrackGlyphCount=0 -->
+ <!-- InputGlyphCount=3 -->
<Input index="0" value="a"/>
<Input index="1" value="uni0303"/>
+ <!-- LookAheadGlyphCount=0 -->
+ <!-- PosCount=2 -->
<PosLookupRecord index="0">
<SequenceIndex value="0"/>
<LookupListIndex value="0"/>
@@ -106,9 +108,9 @@
<SequenceIndex value="2"/>
<LookupListIndex value="1"/>
</PosLookupRecord>
- </PosRule>
- </PosRuleSet>
- </ContextPos>
+ </ChainPosRule>
+ </ChainPosRuleSet>
+ </ChainContextPos>
</Lookup>
</LookupList>
</GPOS>
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_same.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_8_same.ttx
index eff24fc3..a32c94d0 100644
--- a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_same.ttx
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_8_same.ttx
@@ -83,21 +83,23 @@
</MarkBasePos>
</Lookup>
<Lookup index="2">
- <LookupType value="7"/>
+ <LookupType value="8"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
- <ContextPos index="0" Format="1">
+ <ChainContextPos index="0" Format="1">
<Coverage>
<Glyph value="A"/>
</Coverage>
- <!-- PosRuleSetCount=1 -->
- <PosRuleSet index="0">
- <!-- PosRuleCount=1 -->
- <PosRule index="0">
- <!-- GlyphCount=3 -->
- <!-- PosCount=2 -->
+ <!-- ChainPosRuleSetCount=1 -->
+ <ChainPosRuleSet index="0">
+ <!-- ChainPosRuleCount=1 -->
+ <ChainPosRule index="0">
+ <!-- BacktrackGlyphCount=0 -->
+ <!-- InputGlyphCount=3 -->
<Input index="0" value="a"/>
<Input index="1" value="uni0303"/>
+ <!-- LookAheadGlyphCount=0 -->
+ <!-- PosCount=2 -->
<PosLookupRecord index="0">
<SequenceIndex value="0"/>
<LookupListIndex value="0"/>
@@ -106,9 +108,9 @@
<SequenceIndex value="2"/>
<LookupListIndex value="1"/>
</PosLookupRecord>
- </PosRule>
- </PosRuleSet>
- </ContextPos>
+ </ChainPosRule>
+ </ChainPosRuleSet>
+ </ChainContextPos>
</Lookup>
</LookupList>
</GPOS>
diff --git a/Tests/varLib/instancer/instancer_test.py b/Tests/varLib/instancer/instancer_test.py
index bb2d1758..b9d4ffe9 100644
--- a/Tests/varLib/instancer/instancer_test.py
+++ b/Tests/varLib/instancer/instancer_test.py
@@ -1,4 +1,5 @@
from fontTools.misc.fixedTools import floatToFixedToFloat
+from fontTools.misc.testTools import stripVariableItemsFromTTX
from fontTools.misc.textTools import Tag
from fontTools import ttLib
from fontTools import designspaceLib
@@ -1386,10 +1387,6 @@ def test_setMacOverlapFlags():
assert b.components[0].flags & flagOverlapCompound != 0
-def _strip_ttLibVersion(string):
- return re.sub(' ttLibVersion=".*"', "", string)
-
-
@pytest.fixture
def varfont2():
f = ttLib.TTFont(recalcTimestamp=False)
@@ -1412,7 +1409,7 @@ def _dump_ttx(ttFont):
ttFont2 = ttLib.TTFont(tmp, recalcBBoxes=False, recalcTimestamp=False)
s = StringIO()
ttFont2.saveXML(s)
- return _strip_ttLibVersion(s.getvalue())
+ return stripVariableItemsFromTTX(s.getvalue())
def _get_expected_instance_ttx(
@@ -1428,7 +1425,7 @@ def _get_expected_instance_ttx(
"r",
encoding="utf-8",
) as fp:
- return _strip_ttLibVersion(fp.read())
+ return stripVariableItemsFromTTX(fp.read())
class InstantiateVariableFontTest(object):
diff --git a/Tests/varLib/interpolate_layout_test.py b/Tests/varLib/interpolate_layout_test.py
index 18dc3a75..219f087f 100644
--- a/Tests/varLib/interpolate_layout_test.py
+++ b/Tests/varLib/interpolate_layout_test.py
@@ -746,8 +746,8 @@ class InterpolateLayoutTest(unittest.TestCase):
self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
- def test_varlib_interpolate_layout_GPOS_only_LookupType_7_same_val_ttf(self):
- """Only GPOS; LookupType 7; same values in all masters.
+ def test_varlib_interpolate_layout_GPOS_only_LookupType_8_same_val_ttf(self):
+ """Only GPOS; LookupType 8; same values in all masters.
"""
suffix = '.ttf'
ds_path = self.get_test_input('InterpolateLayout.designspace')
@@ -779,13 +779,13 @@ class InterpolateLayoutTest(unittest.TestCase):
instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_7_same.ttx')
+ expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_8_same.ttx')
self.expect_ttx(instfont, expected_ttx_path, tables)
self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
- def test_varlib_interpolate_layout_GPOS_only_LookupType_7_diff_val_ttf(self):
- """Only GPOS; LookupType 7; different values in each master.
+ def test_varlib_interpolate_layout_GPOS_only_LookupType_8_diff_val_ttf(self):
+ """Only GPOS; LookupType 8; different values in each master.
"""
suffix = '.ttf'
ds_path = self.get_test_input('InterpolateLayout.designspace')
@@ -831,7 +831,7 @@ class InterpolateLayoutTest(unittest.TestCase):
instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_7_diff.ttx')
+ expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_8_diff.ttx')
self.expect_ttx(instfont, expected_ttx_path, tables)
self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
diff --git a/Tests/varLib/stat_test.py b/Tests/varLib/stat_test.py
new file mode 100644
index 00000000..6aa88a05
--- /dev/null
+++ b/Tests/varLib/stat_test.py
@@ -0,0 +1,167 @@
+from pathlib import Path
+
+import pytest
+from fontTools.designspaceLib import DesignSpaceDocument
+from fontTools.designspaceLib.split import Range
+from fontTools.varLib.stat import getStatAxes, getStatLocations
+
+
+@pytest.fixture
+def datadir():
+ return Path(__file__).parent / "../designspaceLib/data"
+
+
+def test_getStatAxes(datadir):
+ doc = DesignSpaceDocument.fromfile(datadir / "test_v5.designspace")
+
+ assert getStatAxes(
+ doc, {"Italic": 0, "width": Range(50, 150), "weight": Range(200, 900)}
+ ) == [
+ {
+ "values": [
+ {
+ "flags": 0,
+ "name": {
+ "de": "Extraleicht",
+ "en": "Extra Light",
+ "fr": "Extra léger",
+ },
+ "nominalValue": 200.0,
+ "rangeMaxValue": 250.0,
+ "rangeMinValue": 200.0,
+ },
+ {
+ "flags": 0,
+ "name": {"en": "Light"},
+ "nominalValue": 300.0,
+ "rangeMaxValue": 350.0,
+ "rangeMinValue": 250.0,
+ },
+ {
+ "flags": 2,
+ "name": {"en": "Regular"},
+ "nominalValue": 400.0,
+ "rangeMaxValue": 450.0,
+ "rangeMinValue": 350.0,
+ },
+ {
+ "flags": 0,
+ "name": {"en": "Semi Bold"},
+ "nominalValue": 600.0,
+ "rangeMaxValue": 650.0,
+ "rangeMinValue": 450.0,
+ },
+ {
+ "flags": 0,
+ "name": {"en": "Bold"},
+ "nominalValue": 700.0,
+ "rangeMaxValue": 850.0,
+ "rangeMinValue": 650.0,
+ },
+ {
+ "flags": 0,
+ "name": {"en": "Black"},
+ "nominalValue": 900.0,
+ "rangeMaxValue": 900.0,
+ "rangeMinValue": 850.0,
+ },
+ ],
+ "name": {"en": "Wéíght", "fa-IR": "قطر"},
+ "ordering": 2,
+ "tag": "wght",
+ },
+ {
+ "values": [
+ {"flags": 0, "name": {"en": "Condensed"}, "value": 50.0},
+ {"flags": 3, "name": {"en": "Normal"}, "value": 100.0},
+ {"flags": 0, "name": {"en": "Wide"}, "value": 125.0},
+ {
+ "flags": 0,
+ "name": {"en": "Extra Wide"},
+ "nominalValue": 150.0,
+ "rangeMinValue": 150.0,
+ },
+ ],
+ "name": {"en": "width", "fr": "Chasse"},
+ "ordering": 1,
+ "tag": "wdth",
+ },
+ {
+ "values": [
+ {"flags": 2, "linkedValue": 1.0, "name": {"en": "Roman"}, "value": 0.0},
+ ],
+ "name": {"en": "Italic"},
+ "ordering": 3,
+ "tag": "ital",
+ },
+ ]
+
+ assert getStatAxes(doc, {"Italic": 1, "width": 100, "weight": Range(400, 700)}) == [
+ {
+ "values": [
+ {
+ "flags": 2,
+ "name": {"en": "Regular"},
+ "nominalValue": 400.0,
+ "rangeMaxValue": 450.0,
+ "rangeMinValue": 350.0,
+ },
+ {
+ "flags": 0,
+ "name": {"en": "Semi Bold"},
+ "nominalValue": 600.0,
+ "rangeMaxValue": 650.0,
+ "rangeMinValue": 450.0,
+ },
+ {
+ "flags": 0,
+ "name": {"en": "Bold"},
+ "nominalValue": 700.0,
+ "rangeMaxValue": 850.0,
+ "rangeMinValue": 650.0,
+ },
+ ],
+ "name": {"en": "Wéíght", "fa-IR": "قطر"},
+ "ordering": 2,
+ "tag": "wght",
+ },
+ {
+ "values": [
+ {"flags": 3, "name": {"en": "Normal"}, "value": 100.0},
+ ],
+ "name": {"en": "width", "fr": "Chasse"},
+ "ordering": 1,
+ "tag": "wdth",
+ },
+ {
+ "values": [
+ {"flags": 0, "name": {"en": "Italic"}, "value": 1.0},
+ ],
+ "name": {"en": "Italic"},
+ "ordering": 3,
+ "tag": "ital",
+ },
+ ]
+
+
+def test_getStatLocations(datadir):
+ doc = DesignSpaceDocument.fromfile(datadir / "test_v5.designspace")
+
+ assert getStatLocations(
+ doc, {"Italic": 0, "width": Range(50, 150), "weight": Range(200, 900)}
+ ) == [
+ {
+ "flags": 0,
+ "location": {"ital": 0.0, "wdth": 50.0, "wght": 300.0},
+ "name": {"en": "Some Style", "fr": "Un Style"},
+ },
+ ]
+ assert getStatLocations(
+ doc, {"Italic": 1, "width": Range(50, 150), "weight": Range(200, 900)}
+ ) == [
+ {
+ "flags": 0,
+ "location": {"ital": 1.0, "wdth": 100.0, "wght": 700.0},
+ "name": {"en": "Other"},
+ },
+ ]
diff --git a/requirements.txt b/requirements.txt
index d061b779..008bc8c5 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -12,3 +12,4 @@ skia-pathops==0.7.2; platform_python_implementation != "PyPy"
ufoLib2==0.13.0
pyobjc==8.1; sys_platform == "darwin"
freetype-py==2.2.0
+uharfbuzz==0.24.1
diff --git a/setup.cfg b/setup.cfg
index 7c7fc00c..e184ce32 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 4.31.2
+current_version = 4.33.3
commit = True
tag = False
tag_name = {new_version}
diff --git a/setup.py b/setup.py
index 2f30ef4f..6e6d3912 100755
--- a/setup.py
+++ b/setup.py
@@ -124,6 +124,10 @@ extras_require = {
"pathops": [
"skia-pathops >= 0.5.0",
],
+ # for packing GSUB/GPOS tables with Harfbuzz repacker
+ "repacker": [
+ "uharfbuzz >= 0.23.0",
+ ],
}
# use a special 'all' key as shorthand to includes all the extra dependencies
extras_require["all"] = sum(extras_require.values(), [])
@@ -439,7 +443,7 @@ if ext_modules:
setup_params = dict(
name="fonttools",
- version="4.31.2",
+ version="4.33.3",
description="Tools to manipulate font files",
author="Just van Rossum",
author_email="just@letterror.com",