diff --git a/Tests/designspaceLib/__init__.py b/Tests/designspaceLib/__init__.py
new file mode 100644
index 000000000..e69de29bb
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 000000000..aa8e4f9c2
--- /dev/null
+++ b/Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_Italics_Wght.designspace
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 000000000..2e22c0197
--- /dev/null
+++ b/Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_Italics_WghtWdth.designspace
@@ -0,0 +1,316 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 000000000..2ae35f77b
--- /dev/null
+++ b/Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_Wght.designspace
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 000000000..219d2262d
--- /dev/null
+++ b/Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_WghtWdth.designspace
@@ -0,0 +1,307 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 000000000..5b419bad3
--- /dev/null
+++ b/Tests/designspaceLib/data/convert5to4_output/AktivGroteskVF_WghtWdthItal.designspace
@@ -0,0 +1,670 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 000000000..adfe245df
--- /dev/null
+++ b/Tests/designspaceLib/data/convert5to4_output/SourceSerif4Variable-Italic.designspace
@@ -0,0 +1,326 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ public.skipExportGlyphs
+
+ caron.alt
+ commabelowcmb.alt
+ f.liga
+ f.ligalong
+ tonos.cap
+ dieresiscmb.tight
+ turkicdsccmb
+
+
+
+
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 000000000..30f181e37
--- /dev/null
+++ b/Tests/designspaceLib/data/convert5to4_output/SourceSerif4Variable-Roman.designspace
@@ -0,0 +1,334 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ public.skipExportGlyphs
+
+ caron.alt
+ commabelowcmb.alt
+ tonos.cap
+ f.ligalong
+ dieresiscmb.tight
+ IJ
+ Tbar
+ colontriangularmod
+ crossmark
+ ij
+ overline
+ similar
+ tbar
+ triangularbullet
+ turkicdsccmb
+
+
+
+
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 000000000..dc38cd7dd
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/AktivGroteskVF_Italics_Wght.designspace
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 000000000..8cda91403
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/AktivGroteskVF_Italics_WghtWdth.designspace
@@ -0,0 +1,262 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 000000000..db6216558
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/AktivGroteskVF_Wght.designspace
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 000000000..113c68978
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/AktivGroteskVF_WghtWdth.designspace
@@ -0,0 +1,253 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 000000000..4a1ef48f6
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/AktivGroteskVF_WghtWdthItal.designspace
@@ -0,0 +1,562 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 000000000..6dcc60024
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/MutatorSansVariable_Weight.designspace
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 000000000..e83be97f6
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/MutatorSansVariable_Weight_Width.designspace
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 000000000..9384c73c5
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/MutatorSansVariable_Width.designspace
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 000000000..b71025a2b
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/MutatorSerifVariable_Width.designspace
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 000000000..c1eb95d9e
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/SourceSerif4Variable-Italic.designspace
@@ -0,0 +1,266 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ public.skipExportGlyphs
+
+ caron.alt
+ commabelowcmb.alt
+ f.liga
+ f.ligalong
+ tonos.cap
+ dieresiscmb.tight
+ turkicdsccmb
+
+
+
+
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 000000000..1eee53e00
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/SourceSerif4Variable-Roman.designspace
@@ -0,0 +1,274 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ public.skipExportGlyphs
+
+ caron.alt
+ commabelowcmb.alt
+ tonos.cap
+ f.ligalong
+ dieresiscmb.tight
+ IJ
+ Tbar
+ colontriangularmod
+ crossmark
+ ij
+ overline
+ similar
+ tbar
+ triangularbullet
+ turkicdsccmb
+
+
+
+
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 000000000..d811ffa06
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/test_v5_MutatorSans_and_Serif_serif_0.0.designspace
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 000000000..5e3a6f525
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/test_v5_MutatorSans_and_Serif_serif_1.0.designspace
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 000000000..4451e6e63
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/test_v5_aktiv_.designspace
@@ -0,0 +1,595 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 000000000..447461fb6
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/test_v5_sourceserif_italic_0.0.designspace
@@ -0,0 +1,282 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ public.skipExportGlyphs
+
+ caron.alt
+ commabelowcmb.alt
+ tonos.cap
+ f.ligalong
+ dieresiscmb.tight
+ IJ
+ Tbar
+ colontriangularmod
+ crossmark
+ ij
+ overline
+ similar
+ tbar
+ triangularbullet
+ turkicdsccmb
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 000000000..e1ef12f65
--- /dev/null
+++ b/Tests/designspaceLib/data/split_output/test_v5_sourceserif_italic_1.0.designspace
@@ -0,0 +1,274 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ public.skipExportGlyphs
+
+ caron.alt
+ commabelowcmb.alt
+ f.liga
+ f.ligalong
+ tonos.cap
+ dieresiscmb.tight
+ turkicdsccmb
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/designspaceLib/data/test.designspace b/Tests/designspaceLib/data/test_v4_original.designspace
similarity index 87%
rename from Tests/designspaceLib/data/test.designspace
rename to Tests/designspaceLib/data/test_v4_original.designspace
index e12f1568a..d3b46c7f7 100644
--- a/Tests/designspaceLib/data/test.designspace
+++ b/Tests/designspaceLib/data/test_v4_original.designspace
@@ -1,5 +1,7 @@
+
Wéíght
@@ -50,6 +52,13 @@
+ Demigras
+ 半ば
+ Montserrat
+ モンセラート
+ Standard
+ Montserrat Halbfett
+ モンセラート SemiBold
diff --git a/Tests/designspaceLib/data/test_v5.designspace b/Tests/designspaceLib/data/test_v5.designspace
new file mode 100644
index 000000000..2f611b49d
--- /dev/null
+++ b/Tests/designspaceLib/data/test_v5.designspace
@@ -0,0 +1,294 @@
+
+
+
+
+ Wéíght
+ قطر
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Chasse
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Montserrat
+ モンセラート
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ com.vtt.source
+ sources/vtt/Test_WghtWdth.vtt
+
+
+
+
+
+
+
+
+
+
+ com.vtt.source
+ sources/vtt/Test_Wght.vtt
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Demigras
+ 半ば
+ Montserrat
+ モンセラート
+ Standard
+ Montserrat Halbfett
+ モンセラート SemiBold
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ com.coolDesignspaceApp.binaryData
+
+ PGJpbmFyeSBndW5rPg==
+
+ com.coolDesignspaceApp.specimenText
+ Hamburgerwhatever
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A note about this glyph
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ com.coolDesignspaceApp.previewSize
+ 30
+
+
+
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 000000000..d0fbb3d23
--- /dev/null
+++ b/Tests/designspaceLib/data/test_v5_MutatorSans_and_Serif.designspace
@@ -0,0 +1,206 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/designspaceLib/data/test_v5_aktiv.designspace b/Tests/designspaceLib/data/test_v5_aktiv.designspace
new file mode 100644
index 000000000..32dfd1ae5
--- /dev/null
+++ b/Tests/designspaceLib/data/test_v5_aktiv.designspace
@@ -0,0 +1,621 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/designspaceLib/data/test_v5_decovar.designspace b/Tests/designspaceLib/data/test_v5_decovar.designspace
new file mode 100644
index 000000000..fd31626e2
--- /dev/null
+++ b/Tests/designspaceLib/data/test_v5_decovar.designspace
@@ -0,0 +1,242 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/designspaceLib/data/test_v5_discrete.designspace b/Tests/designspaceLib/data/test_v5_discrete.designspace
new file mode 100644
index 000000000..f42f2dc93
--- /dev/null
+++ b/Tests/designspaceLib/data/test_v5_discrete.designspace
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/designspaceLib/data/test_v5_original.designspace b/Tests/designspaceLib/data/test_v5_original.designspace
new file mode 100644
index 000000000..d144a0731
--- /dev/null
+++ b/Tests/designspaceLib/data/test_v5_original.designspace
@@ -0,0 +1,90 @@
+
+
+
+
+
+ Wéíght
+ قطر
+
+
+ Chasse
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Demigras
+ 半ば
+ Montserrat
+ モンセラート
+ Standard
+ Montserrat Halbfett
+ モンセラート SemiBold
+
+
+
+
+
+ com.coolDesignspaceApp.binaryData
+
+ PGJpbmFyeSBndW5rPg==
+
+ com.coolDesignspaceApp.specimenText
+ Hamburgerwhatever
+
+
+
+
+
+
+
+
+
+
+
+
+ com.coolDesignspaceApp.previewSize
+ 30
+
+
+
diff --git a/Tests/designspaceLib/data/test_v5_sourceserif.designspace b/Tests/designspaceLib/data/test_v5_sourceserif.designspace
new file mode 100644
index 000000000..d3a321772
--- /dev/null
+++ b/Tests/designspaceLib/data/test_v5_sourceserif.designspace
@@ -0,0 +1,646 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ public.skipExportGlyphs
+
+ caron.alt
+ commabelowcmb.alt
+ tonos.cap
+ f.ligalong
+ dieresiscmb.tight
+ IJ
+ Tbar
+ colontriangularmod
+ crossmark
+ ij
+ overline
+ similar
+ tbar
+ triangularbullet
+ turkicdsccmb
+
+
+
+
+
+
+
+
+
+
+
+
+ public.skipExportGlyphs
+
+ caron.alt
+ commabelowcmb.alt
+ f.liga
+ f.ligalong
+ tonos.cap
+ dieresiscmb.tight
+ turkicdsccmb
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/designspaceLib/designspace_test.py b/Tests/designspaceLib/designspace_test.py
index b6ee9d655..694bba1e5 100644
--- a/Tests/designspaceLib/designspace_test.py
+++ b/Tests/designspaceLib/designspace_test.py
@@ -1,15 +1,25 @@
# 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.misc import plistlib
+
def _axesAsDict(axes):
"""
@@ -30,19 +40,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"", "", 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 +134,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'')
@@ -158,16 +175,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 +219,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 +855,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 +864,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()
diff --git a/Tests/designspaceLib/designspace_v5_test.py b/Tests/designspaceLib/designspace_v5_test.py
new file mode 100644
index 000000000..9e8033402
--- /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"",
+ "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"(.|\n)*?",
+ "",
+ xml,
+ )
+ xml = re.sub(r"", "", 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 000000000..66041bedb
--- /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 000000000..8708f7048
--- /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 000000000..076abc90a
--- /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",
+ )