xref: /aosp_15_r20/external/fonttools/Tests/designspaceLib/designspace_test.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughes# coding=utf-8
2*e1fe3e4aSElliott Hughes
3*e1fe3e4aSElliott Hughesimport os
4*e1fe3e4aSElliott Hughesfrom pathlib import Path
5*e1fe3e4aSElliott Hughesimport re
6*e1fe3e4aSElliott Hughesimport shutil
7*e1fe3e4aSElliott Hughes
8*e1fe3e4aSElliott Hughesimport pytest
9*e1fe3e4aSElliott Hughesfrom fontTools import ttLib
10*e1fe3e4aSElliott Hughesfrom fontTools.designspaceLib import (
11*e1fe3e4aSElliott Hughes    AxisDescriptor,
12*e1fe3e4aSElliott Hughes    AxisMappingDescriptor,
13*e1fe3e4aSElliott Hughes    AxisLabelDescriptor,
14*e1fe3e4aSElliott Hughes    DesignSpaceDocument,
15*e1fe3e4aSElliott Hughes    DesignSpaceDocumentError,
16*e1fe3e4aSElliott Hughes    DiscreteAxisDescriptor,
17*e1fe3e4aSElliott Hughes    InstanceDescriptor,
18*e1fe3e4aSElliott Hughes    RuleDescriptor,
19*e1fe3e4aSElliott Hughes    SourceDescriptor,
20*e1fe3e4aSElliott Hughes    evaluateRule,
21*e1fe3e4aSElliott Hughes    posix,
22*e1fe3e4aSElliott Hughes    processRules,
23*e1fe3e4aSElliott Hughes)
24*e1fe3e4aSElliott Hughesfrom fontTools.designspaceLib.types import Range
25*e1fe3e4aSElliott Hughesfrom fontTools.misc import plistlib
26*e1fe3e4aSElliott Hughes
27*e1fe3e4aSElliott Hughesfrom .fixtures import datadir
28*e1fe3e4aSElliott Hughes
29*e1fe3e4aSElliott Hughes
30*e1fe3e4aSElliott Hughesdef _axesAsDict(axes):
31*e1fe3e4aSElliott Hughes    """
32*e1fe3e4aSElliott Hughes    Make the axis data we have available in
33*e1fe3e4aSElliott Hughes    """
34*e1fe3e4aSElliott Hughes    axesDict = {}
35*e1fe3e4aSElliott Hughes    for axisDescriptor in axes:
36*e1fe3e4aSElliott Hughes        d = {
37*e1fe3e4aSElliott Hughes            "name": axisDescriptor.name,
38*e1fe3e4aSElliott Hughes            "tag": axisDescriptor.tag,
39*e1fe3e4aSElliott Hughes            "minimum": axisDescriptor.minimum,
40*e1fe3e4aSElliott Hughes            "maximum": axisDescriptor.maximum,
41*e1fe3e4aSElliott Hughes            "default": axisDescriptor.default,
42*e1fe3e4aSElliott Hughes            "map": axisDescriptor.map,
43*e1fe3e4aSElliott Hughes        }
44*e1fe3e4aSElliott Hughes        axesDict[axisDescriptor.name] = d
45*e1fe3e4aSElliott Hughes    return axesDict
46*e1fe3e4aSElliott Hughes
47*e1fe3e4aSElliott Hughes
48*e1fe3e4aSElliott Hughesdef assert_equals_test_file(path, test_filename):
49*e1fe3e4aSElliott Hughes    with open(path, encoding="utf-8") as fp:
50*e1fe3e4aSElliott Hughes        actual = fp.read()
51*e1fe3e4aSElliott Hughes
52*e1fe3e4aSElliott Hughes    test_path = os.path.join(os.path.dirname(__file__), test_filename)
53*e1fe3e4aSElliott Hughes    with open(test_path, encoding="utf-8") as fp:
54*e1fe3e4aSElliott Hughes        expected = fp.read()
55*e1fe3e4aSElliott Hughes        expected = re.sub(r"<!--(.|\n)*?-->", "", expected)
56*e1fe3e4aSElliott Hughes        expected = re.sub(r"\s*\n+", "\n", expected)
57*e1fe3e4aSElliott Hughes
58*e1fe3e4aSElliott Hughes    assert actual == expected
59*e1fe3e4aSElliott Hughes
60*e1fe3e4aSElliott Hughes
61*e1fe3e4aSElliott Hughesdef test_fill_document(tmpdir):
62*e1fe3e4aSElliott Hughes    tmpdir = str(tmpdir)
63*e1fe3e4aSElliott Hughes    testDocPath = os.path.join(tmpdir, "test_v4.designspace")
64*e1fe3e4aSElliott Hughes    testDocPath5 = os.path.join(tmpdir, "test_v5.designspace")
65*e1fe3e4aSElliott Hughes    masterPath1 = os.path.join(tmpdir, "masters", "masterTest1.ufo")
66*e1fe3e4aSElliott Hughes    masterPath2 = os.path.join(tmpdir, "masters", "masterTest2.ufo")
67*e1fe3e4aSElliott Hughes    instancePath1 = os.path.join(tmpdir, "instances", "instanceTest1.ufo")
68*e1fe3e4aSElliott Hughes    instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo")
69*e1fe3e4aSElliott Hughes    doc = DesignSpaceDocument()
70*e1fe3e4aSElliott Hughes    doc.rulesProcessingLast = True
71*e1fe3e4aSElliott Hughes
72*e1fe3e4aSElliott Hughes    # write some axes
73*e1fe3e4aSElliott Hughes    a1 = AxisDescriptor()
74*e1fe3e4aSElliott Hughes    a1.minimum = 0
75*e1fe3e4aSElliott Hughes    a1.maximum = 1000
76*e1fe3e4aSElliott Hughes    a1.default = 0
77*e1fe3e4aSElliott Hughes    a1.name = "weight"
78*e1fe3e4aSElliott Hughes    a1.tag = "wght"
79*e1fe3e4aSElliott Hughes    # note: just to test the element language, not an actual label name recommendations.
80*e1fe3e4aSElliott Hughes    a1.labelNames["fa-IR"] = "قطر"
81*e1fe3e4aSElliott Hughes    a1.labelNames["en"] = "Wéíght"
82*e1fe3e4aSElliott Hughes    doc.addAxis(a1)
83*e1fe3e4aSElliott Hughes    a2 = AxisDescriptor()
84*e1fe3e4aSElliott Hughes    a2.minimum = 0
85*e1fe3e4aSElliott Hughes    a2.maximum = 1000
86*e1fe3e4aSElliott Hughes    a2.default = 15
87*e1fe3e4aSElliott Hughes    a2.name = "width"
88*e1fe3e4aSElliott Hughes    a2.tag = "wdth"
89*e1fe3e4aSElliott Hughes    a2.map = [(0.0, 10.0), (15.0, 20.0), (401.0, 66.0), (1000.0, 990.0)]
90*e1fe3e4aSElliott Hughes    a2.hidden = True
91*e1fe3e4aSElliott Hughes    a2.labelNames["fr"] = "Chasse"
92*e1fe3e4aSElliott Hughes    doc.addAxis(a2)
93*e1fe3e4aSElliott Hughes
94*e1fe3e4aSElliott Hughes    # add master 1
95*e1fe3e4aSElliott Hughes    s1 = SourceDescriptor()
96*e1fe3e4aSElliott Hughes    s1.filename = os.path.relpath(masterPath1, os.path.dirname(testDocPath))
97*e1fe3e4aSElliott Hughes    assert s1.font is None
98*e1fe3e4aSElliott Hughes    s1.name = "master.ufo1"
99*e1fe3e4aSElliott Hughes    s1.copyLib = True
100*e1fe3e4aSElliott Hughes    s1.copyInfo = True
101*e1fe3e4aSElliott Hughes    s1.copyFeatures = True
102*e1fe3e4aSElliott Hughes    s1.location = dict(weight=0)
103*e1fe3e4aSElliott Hughes    s1.familyName = "MasterFamilyName"
104*e1fe3e4aSElliott Hughes    s1.styleName = "MasterStyleNameOne"
105*e1fe3e4aSElliott Hughes    s1.mutedGlyphNames.append("A")
106*e1fe3e4aSElliott Hughes    s1.mutedGlyphNames.append("Z")
107*e1fe3e4aSElliott Hughes    doc.addSource(s1)
108*e1fe3e4aSElliott Hughes    # add master 2
109*e1fe3e4aSElliott Hughes    s2 = SourceDescriptor()
110*e1fe3e4aSElliott Hughes    s2.filename = os.path.relpath(masterPath2, os.path.dirname(testDocPath))
111*e1fe3e4aSElliott Hughes    s2.name = "master.ufo2"
112*e1fe3e4aSElliott Hughes    s2.copyLib = False
113*e1fe3e4aSElliott Hughes    s2.copyInfo = False
114*e1fe3e4aSElliott Hughes    s2.copyFeatures = False
115*e1fe3e4aSElliott Hughes    s2.muteKerning = True
116*e1fe3e4aSElliott Hughes    s2.location = dict(weight=1000)
117*e1fe3e4aSElliott Hughes    s2.familyName = "MasterFamilyName"
118*e1fe3e4aSElliott Hughes    s2.styleName = "MasterStyleNameTwo"
119*e1fe3e4aSElliott Hughes    doc.addSource(s2)
120*e1fe3e4aSElliott Hughes    # add master 3 from a different layer
121*e1fe3e4aSElliott Hughes    s3 = SourceDescriptor()
122*e1fe3e4aSElliott Hughes    s3.filename = os.path.relpath(masterPath2, os.path.dirname(testDocPath))
123*e1fe3e4aSElliott Hughes    s3.name = "master.ufo2"
124*e1fe3e4aSElliott Hughes    s3.copyLib = False
125*e1fe3e4aSElliott Hughes    s3.copyInfo = False
126*e1fe3e4aSElliott Hughes    s3.copyFeatures = False
127*e1fe3e4aSElliott Hughes    s3.muteKerning = False
128*e1fe3e4aSElliott Hughes    s3.layerName = "supports"
129*e1fe3e4aSElliott Hughes    s3.location = dict(weight=1000)
130*e1fe3e4aSElliott Hughes    s3.familyName = "MasterFamilyName"
131*e1fe3e4aSElliott Hughes    s3.styleName = "Supports"
132*e1fe3e4aSElliott Hughes    doc.addSource(s3)
133*e1fe3e4aSElliott Hughes    # add instance 1
134*e1fe3e4aSElliott Hughes    i1 = InstanceDescriptor()
135*e1fe3e4aSElliott Hughes    i1.filename = os.path.relpath(instancePath1, os.path.dirname(testDocPath))
136*e1fe3e4aSElliott Hughes    i1.familyName = "InstanceFamilyName"
137*e1fe3e4aSElliott Hughes    i1.styleName = "InstanceStyleName"
138*e1fe3e4aSElliott Hughes    i1.name = "instance.ufo1"
139*e1fe3e4aSElliott Hughes    i1.location = dict(
140*e1fe3e4aSElliott Hughes        weight=500, spooky=666
141*e1fe3e4aSElliott Hughes    )  # this adds a dimension that is not defined.
142*e1fe3e4aSElliott Hughes    i1.postScriptFontName = "InstancePostscriptName"
143*e1fe3e4aSElliott Hughes    i1.styleMapFamilyName = "InstanceStyleMapFamilyName"
144*e1fe3e4aSElliott Hughes    i1.styleMapStyleName = "InstanceStyleMapStyleName"
145*e1fe3e4aSElliott Hughes    i1.localisedStyleName = dict(fr="Demigras", ja="半ば")
146*e1fe3e4aSElliott Hughes    i1.localisedFamilyName = dict(fr="Montserrat", ja="モンセラート")
147*e1fe3e4aSElliott Hughes    i1.localisedStyleMapStyleName = dict(de="Standard")
148*e1fe3e4aSElliott Hughes    i1.localisedStyleMapFamilyName = dict(
149*e1fe3e4aSElliott Hughes        de="Montserrat Halbfett", ja="モンセラート SemiBold"
150*e1fe3e4aSElliott Hughes    )
151*e1fe3e4aSElliott Hughes    glyphData = dict(name="arrow", mute=True, unicodes=[0x123, 0x124, 0x125])
152*e1fe3e4aSElliott Hughes    i1.glyphs["arrow"] = glyphData
153*e1fe3e4aSElliott Hughes    i1.lib["com.coolDesignspaceApp.binaryData"] = plistlib.Data(b"<binary gunk>")
154*e1fe3e4aSElliott Hughes    i1.lib["com.coolDesignspaceApp.specimenText"] = "Hamburgerwhatever"
155*e1fe3e4aSElliott Hughes    doc.addInstance(i1)
156*e1fe3e4aSElliott Hughes    # add instance 2
157*e1fe3e4aSElliott Hughes    i2 = InstanceDescriptor()
158*e1fe3e4aSElliott Hughes    i2.filename = os.path.relpath(instancePath2, os.path.dirname(testDocPath))
159*e1fe3e4aSElliott Hughes    i2.familyName = "InstanceFamilyName"
160*e1fe3e4aSElliott Hughes    i2.styleName = "InstanceStyleName"
161*e1fe3e4aSElliott Hughes    i2.name = "instance.ufo2"
162*e1fe3e4aSElliott Hughes    # anisotropic location
163*e1fe3e4aSElliott Hughes    i2.location = dict(weight=500, width=(400, 300))
164*e1fe3e4aSElliott Hughes    i2.postScriptFontName = "InstancePostscriptName"
165*e1fe3e4aSElliott Hughes    i2.styleMapFamilyName = "InstanceStyleMapFamilyName"
166*e1fe3e4aSElliott Hughes    i2.styleMapStyleName = "InstanceStyleMapStyleName"
167*e1fe3e4aSElliott Hughes    glyphMasters = [
168*e1fe3e4aSElliott Hughes        dict(font="master.ufo1", glyphName="BB", location=dict(width=20, weight=20)),
169*e1fe3e4aSElliott Hughes        dict(font="master.ufo2", glyphName="CC", location=dict(width=900, weight=900)),
170*e1fe3e4aSElliott Hughes    ]
171*e1fe3e4aSElliott Hughes    glyphData = dict(name="arrow", unicodes=[101, 201, 301])
172*e1fe3e4aSElliott Hughes    glyphData["masters"] = glyphMasters
173*e1fe3e4aSElliott Hughes    glyphData["note"] = "A note about this glyph"
174*e1fe3e4aSElliott Hughes    glyphData["instanceLocation"] = dict(width=100, weight=120)
175*e1fe3e4aSElliott Hughes    i2.glyphs["arrow"] = glyphData
176*e1fe3e4aSElliott Hughes    i2.glyphs["arrow2"] = dict(mute=False)
177*e1fe3e4aSElliott Hughes    doc.addInstance(i2)
178*e1fe3e4aSElliott Hughes
179*e1fe3e4aSElliott Hughes    doc.filename = "suggestedFileName.designspace"
180*e1fe3e4aSElliott Hughes    doc.lib["com.coolDesignspaceApp.previewSize"] = 30
181*e1fe3e4aSElliott Hughes
182*e1fe3e4aSElliott Hughes    # write some rules
183*e1fe3e4aSElliott Hughes    r1 = RuleDescriptor()
184*e1fe3e4aSElliott Hughes    r1.name = "named.rule.1"
185*e1fe3e4aSElliott Hughes    r1.conditionSets.append(
186*e1fe3e4aSElliott Hughes        [
187*e1fe3e4aSElliott Hughes            dict(name="axisName_a", minimum=0, maximum=1),
188*e1fe3e4aSElliott Hughes            dict(name="axisName_b", minimum=2, maximum=3),
189*e1fe3e4aSElliott Hughes        ]
190*e1fe3e4aSElliott Hughes    )
191*e1fe3e4aSElliott Hughes    r1.subs.append(("a", "a.alt"))
192*e1fe3e4aSElliott Hughes    doc.addRule(r1)
193*e1fe3e4aSElliott Hughes    # write the document; without an explicit format it will be 5.0 by default
194*e1fe3e4aSElliott Hughes    doc.write(testDocPath5)
195*e1fe3e4aSElliott Hughes    assert os.path.exists(testDocPath5)
196*e1fe3e4aSElliott Hughes    assert_equals_test_file(testDocPath5, "data/test_v5_original.designspace")
197*e1fe3e4aSElliott Hughes    # write again with an explicit format = 4.1
198*e1fe3e4aSElliott Hughes    doc.formatVersion = "4.1"
199*e1fe3e4aSElliott Hughes    doc.write(testDocPath)
200*e1fe3e4aSElliott Hughes    assert os.path.exists(testDocPath)
201*e1fe3e4aSElliott Hughes    assert_equals_test_file(testDocPath, "data/test_v4_original.designspace")
202*e1fe3e4aSElliott Hughes    # import it again
203*e1fe3e4aSElliott Hughes    new = DesignSpaceDocument()
204*e1fe3e4aSElliott Hughes    new.read(testDocPath)
205*e1fe3e4aSElliott Hughes
206*e1fe3e4aSElliott Hughes    assert new.default.location == {"width": 20.0, "weight": 0.0}
207*e1fe3e4aSElliott Hughes    assert new.filename == "test_v4.designspace"
208*e1fe3e4aSElliott Hughes    assert new.lib == doc.lib
209*e1fe3e4aSElliott Hughes    assert new.instances[0].lib == doc.instances[0].lib
210*e1fe3e4aSElliott Hughes
211*e1fe3e4aSElliott Hughes    # test roundtrip for the axis attributes and data
212*e1fe3e4aSElliott Hughes    axes = {}
213*e1fe3e4aSElliott Hughes    for axis in doc.axes:
214*e1fe3e4aSElliott Hughes        if axis.tag not in axes:
215*e1fe3e4aSElliott Hughes            axes[axis.tag] = []
216*e1fe3e4aSElliott Hughes        axes[axis.tag].append(axis.serialize())
217*e1fe3e4aSElliott Hughes    for axis in new.axes:
218*e1fe3e4aSElliott Hughes        if axis.tag[0] == "_":
219*e1fe3e4aSElliott Hughes            continue
220*e1fe3e4aSElliott Hughes        if axis.tag not in axes:
221*e1fe3e4aSElliott Hughes            axes[axis.tag] = []
222*e1fe3e4aSElliott Hughes        axes[axis.tag].append(axis.serialize())
223*e1fe3e4aSElliott Hughes    for v in axes.values():
224*e1fe3e4aSElliott Hughes        a, b = v
225*e1fe3e4aSElliott Hughes        assert a == b
226*e1fe3e4aSElliott Hughes
227*e1fe3e4aSElliott Hughes
228*e1fe3e4aSElliott Hughesdef test_unicodes(tmpdir):
229*e1fe3e4aSElliott Hughes    tmpdir = str(tmpdir)
230*e1fe3e4aSElliott Hughes    testDocPath = os.path.join(tmpdir, "testUnicodes.designspace")
231*e1fe3e4aSElliott Hughes    testDocPath2 = os.path.join(tmpdir, "testUnicodes_roundtrip.designspace")
232*e1fe3e4aSElliott Hughes    masterPath1 = os.path.join(tmpdir, "masters", "masterTest1.ufo")
233*e1fe3e4aSElliott Hughes    masterPath2 = os.path.join(tmpdir, "masters", "masterTest2.ufo")
234*e1fe3e4aSElliott Hughes    instancePath1 = os.path.join(tmpdir, "instances", "instanceTest1.ufo")
235*e1fe3e4aSElliott Hughes    instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo")
236*e1fe3e4aSElliott Hughes    doc = DesignSpaceDocument()
237*e1fe3e4aSElliott Hughes    doc.formatVersion = "4.1"  # This test about instance glyphs is deprecated in v5
238*e1fe3e4aSElliott Hughes    # add master 1
239*e1fe3e4aSElliott Hughes    s1 = SourceDescriptor()
240*e1fe3e4aSElliott Hughes    s1.filename = os.path.relpath(masterPath1, os.path.dirname(testDocPath))
241*e1fe3e4aSElliott Hughes    s1.name = "master.ufo1"
242*e1fe3e4aSElliott Hughes    s1.copyInfo = True
243*e1fe3e4aSElliott Hughes    s1.location = dict(weight=0)
244*e1fe3e4aSElliott Hughes    doc.addSource(s1)
245*e1fe3e4aSElliott Hughes    # add master 2
246*e1fe3e4aSElliott Hughes    s2 = SourceDescriptor()
247*e1fe3e4aSElliott Hughes    s2.filename = os.path.relpath(masterPath2, os.path.dirname(testDocPath))
248*e1fe3e4aSElliott Hughes    s2.name = "master.ufo2"
249*e1fe3e4aSElliott Hughes    s2.location = dict(weight=1000)
250*e1fe3e4aSElliott Hughes    doc.addSource(s2)
251*e1fe3e4aSElliott Hughes    # add instance 1
252*e1fe3e4aSElliott Hughes    i1 = InstanceDescriptor()
253*e1fe3e4aSElliott Hughes    i1.filename = os.path.relpath(instancePath1, os.path.dirname(testDocPath))
254*e1fe3e4aSElliott Hughes    i1.name = "instance.ufo1"
255*e1fe3e4aSElliott Hughes    i1.location = dict(weight=500)
256*e1fe3e4aSElliott Hughes    glyphData = dict(name="arrow", mute=True, unicodes=[100, 200, 300])
257*e1fe3e4aSElliott Hughes    i1.glyphs["arrow"] = glyphData
258*e1fe3e4aSElliott Hughes    doc.addInstance(i1)
259*e1fe3e4aSElliott Hughes    # now we have sources and instances, but no axes yet.
260*e1fe3e4aSElliott Hughes    doc.axes = []  # clear the axes
261*e1fe3e4aSElliott Hughes    # write some axes
262*e1fe3e4aSElliott Hughes    a1 = AxisDescriptor()
263*e1fe3e4aSElliott Hughes    a1.minimum = 0
264*e1fe3e4aSElliott Hughes    a1.maximum = 1000
265*e1fe3e4aSElliott Hughes    a1.default = 0
266*e1fe3e4aSElliott Hughes    a1.name = "weight"
267*e1fe3e4aSElliott Hughes    a1.tag = "wght"
268*e1fe3e4aSElliott Hughes    doc.addAxis(a1)
269*e1fe3e4aSElliott Hughes    # write the document
270*e1fe3e4aSElliott Hughes    doc.write(testDocPath)
271*e1fe3e4aSElliott Hughes    assert os.path.exists(testDocPath)
272*e1fe3e4aSElliott Hughes    # import it again
273*e1fe3e4aSElliott Hughes    new = DesignSpaceDocument()
274*e1fe3e4aSElliott Hughes    new.read(testDocPath)
275*e1fe3e4aSElliott Hughes    new.write(testDocPath2)
276*e1fe3e4aSElliott Hughes    # compare the file contents
277*e1fe3e4aSElliott Hughes    with open(testDocPath, "r", encoding="utf-8") as f1:
278*e1fe3e4aSElliott Hughes        t1 = f1.read()
279*e1fe3e4aSElliott Hughes    with open(testDocPath2, "r", encoding="utf-8") as f2:
280*e1fe3e4aSElliott Hughes        t2 = f2.read()
281*e1fe3e4aSElliott Hughes    assert t1 == t2
282*e1fe3e4aSElliott Hughes    # check the unicode values read from the document
283*e1fe3e4aSElliott Hughes    assert new.instances[0].glyphs["arrow"]["unicodes"] == [100, 200, 300]
284*e1fe3e4aSElliott Hughes
285*e1fe3e4aSElliott Hughes
286*e1fe3e4aSElliott Hughesdef test_localisedNames(tmpdir):
287*e1fe3e4aSElliott Hughes    tmpdir = str(tmpdir)
288*e1fe3e4aSElliott Hughes    testDocPath = os.path.join(tmpdir, "testLocalisedNames.designspace")
289*e1fe3e4aSElliott Hughes    testDocPath2 = os.path.join(tmpdir, "testLocalisedNames_roundtrip.designspace")
290*e1fe3e4aSElliott Hughes    masterPath1 = os.path.join(tmpdir, "masters", "masterTest1.ufo")
291*e1fe3e4aSElliott Hughes    masterPath2 = os.path.join(tmpdir, "masters", "masterTest2.ufo")
292*e1fe3e4aSElliott Hughes    instancePath1 = os.path.join(tmpdir, "instances", "instanceTest1.ufo")
293*e1fe3e4aSElliott Hughes    instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo")
294*e1fe3e4aSElliott Hughes    doc = DesignSpaceDocument()
295*e1fe3e4aSElliott Hughes    # add master 1
296*e1fe3e4aSElliott Hughes    s1 = SourceDescriptor()
297*e1fe3e4aSElliott Hughes    s1.filename = os.path.relpath(masterPath1, os.path.dirname(testDocPath))
298*e1fe3e4aSElliott Hughes    s1.name = "master.ufo1"
299*e1fe3e4aSElliott Hughes    s1.copyInfo = True
300*e1fe3e4aSElliott Hughes    s1.location = dict(weight=0)
301*e1fe3e4aSElliott Hughes    doc.addSource(s1)
302*e1fe3e4aSElliott Hughes    # add master 2
303*e1fe3e4aSElliott Hughes    s2 = SourceDescriptor()
304*e1fe3e4aSElliott Hughes    s2.filename = os.path.relpath(masterPath2, os.path.dirname(testDocPath))
305*e1fe3e4aSElliott Hughes    s2.name = "master.ufo2"
306*e1fe3e4aSElliott Hughes    s2.location = dict(weight=1000)
307*e1fe3e4aSElliott Hughes    doc.addSource(s2)
308*e1fe3e4aSElliott Hughes    # add instance 1
309*e1fe3e4aSElliott Hughes    i1 = InstanceDescriptor()
310*e1fe3e4aSElliott Hughes    i1.filename = os.path.relpath(instancePath1, os.path.dirname(testDocPath))
311*e1fe3e4aSElliott Hughes    i1.familyName = "Montserrat"
312*e1fe3e4aSElliott Hughes    i1.styleName = "SemiBold"
313*e1fe3e4aSElliott Hughes    i1.styleMapFamilyName = "Montserrat SemiBold"
314*e1fe3e4aSElliott Hughes    i1.styleMapStyleName = "Regular"
315*e1fe3e4aSElliott Hughes    i1.setFamilyName("Montserrat", "fr")
316*e1fe3e4aSElliott Hughes    i1.setFamilyName("モンセラート", "ja")
317*e1fe3e4aSElliott Hughes    i1.setStyleName("Demigras", "fr")
318*e1fe3e4aSElliott Hughes    i1.setStyleName("半ば", "ja")
319*e1fe3e4aSElliott Hughes    i1.setStyleMapStyleName("Standard", "de")
320*e1fe3e4aSElliott Hughes    i1.setStyleMapFamilyName("Montserrat Halbfett", "de")
321*e1fe3e4aSElliott Hughes    i1.setStyleMapFamilyName("モンセラート SemiBold", "ja")
322*e1fe3e4aSElliott Hughes    i1.name = "instance.ufo1"
323*e1fe3e4aSElliott Hughes    i1.location = dict(
324*e1fe3e4aSElliott Hughes        weight=500, spooky=666
325*e1fe3e4aSElliott Hughes    )  # this adds a dimension that is not defined.
326*e1fe3e4aSElliott Hughes    i1.postScriptFontName = "InstancePostscriptName"
327*e1fe3e4aSElliott Hughes    glyphData = dict(name="arrow", mute=True, unicodes=[0x123])
328*e1fe3e4aSElliott Hughes    i1.glyphs["arrow"] = glyphData
329*e1fe3e4aSElliott Hughes    doc.addInstance(i1)
330*e1fe3e4aSElliott Hughes    # now we have sources and instances, but no axes yet.
331*e1fe3e4aSElliott Hughes    doc.axes = []  # clear the axes
332*e1fe3e4aSElliott Hughes    # write some axes
333*e1fe3e4aSElliott Hughes    a1 = AxisDescriptor()
334*e1fe3e4aSElliott Hughes    a1.minimum = 0
335*e1fe3e4aSElliott Hughes    a1.maximum = 1000
336*e1fe3e4aSElliott Hughes    a1.default = 0
337*e1fe3e4aSElliott Hughes    a1.name = "weight"
338*e1fe3e4aSElliott Hughes    a1.tag = "wght"
339*e1fe3e4aSElliott Hughes    # note: just to test the element language, not an actual label name recommendations.
340*e1fe3e4aSElliott Hughes    a1.labelNames["fa-IR"] = "قطر"
341*e1fe3e4aSElliott Hughes    a1.labelNames["en"] = "Wéíght"
342*e1fe3e4aSElliott Hughes    doc.addAxis(a1)
343*e1fe3e4aSElliott Hughes    a2 = AxisDescriptor()
344*e1fe3e4aSElliott Hughes    a2.minimum = 0
345*e1fe3e4aSElliott Hughes    a2.maximum = 1000
346*e1fe3e4aSElliott Hughes    a2.default = 0
347*e1fe3e4aSElliott Hughes    a2.name = "width"
348*e1fe3e4aSElliott Hughes    a2.tag = "wdth"
349*e1fe3e4aSElliott Hughes    a2.map = [(0.0, 10.0), (401.0, 66.0), (1000.0, 990.0)]
350*e1fe3e4aSElliott Hughes    a2.labelNames["fr"] = "Poids"
351*e1fe3e4aSElliott Hughes    doc.addAxis(a2)
352*e1fe3e4aSElliott Hughes    # add an axis that is not part of any location to see if that works
353*e1fe3e4aSElliott Hughes    a3 = AxisDescriptor()
354*e1fe3e4aSElliott Hughes    a3.minimum = 333
355*e1fe3e4aSElliott Hughes    a3.maximum = 666
356*e1fe3e4aSElliott Hughes    a3.default = 444
357*e1fe3e4aSElliott Hughes    a3.name = "spooky"
358*e1fe3e4aSElliott Hughes    a3.tag = "spok"
359*e1fe3e4aSElliott Hughes    a3.map = [(0.0, 10.0), (401.0, 66.0), (1000.0, 990.0)]
360*e1fe3e4aSElliott Hughes    # doc.addAxis(a3)    # uncomment this line to test the effects of default axes values
361*e1fe3e4aSElliott Hughes    # write some rules
362*e1fe3e4aSElliott Hughes    r1 = RuleDescriptor()
363*e1fe3e4aSElliott Hughes    r1.name = "named.rule.1"
364*e1fe3e4aSElliott Hughes    r1.conditionSets.append(
365*e1fe3e4aSElliott Hughes        [
366*e1fe3e4aSElliott Hughes            dict(name="weight", minimum=200, maximum=500),
367*e1fe3e4aSElliott Hughes            dict(name="width", minimum=0, maximum=150),
368*e1fe3e4aSElliott Hughes        ]
369*e1fe3e4aSElliott Hughes    )
370*e1fe3e4aSElliott Hughes    r1.subs.append(("a", "a.alt"))
371*e1fe3e4aSElliott Hughes    doc.addRule(r1)
372*e1fe3e4aSElliott Hughes    # write the document
373*e1fe3e4aSElliott Hughes    doc.write(testDocPath)
374*e1fe3e4aSElliott Hughes    assert os.path.exists(testDocPath)
375*e1fe3e4aSElliott Hughes    # import it again
376*e1fe3e4aSElliott Hughes    new = DesignSpaceDocument()
377*e1fe3e4aSElliott Hughes    new.read(testDocPath)
378*e1fe3e4aSElliott Hughes    new.write(testDocPath2)
379*e1fe3e4aSElliott Hughes    with open(testDocPath, "r", encoding="utf-8") as f1:
380*e1fe3e4aSElliott Hughes        t1 = f1.read()
381*e1fe3e4aSElliott Hughes    with open(testDocPath2, "r", encoding="utf-8") as f2:
382*e1fe3e4aSElliott Hughes        t2 = f2.read()
383*e1fe3e4aSElliott Hughes    assert t1 == t2
384*e1fe3e4aSElliott Hughes
385*e1fe3e4aSElliott Hughes
386*e1fe3e4aSElliott Hughesdef test_handleNoAxes(tmpdir):
387*e1fe3e4aSElliott Hughes    tmpdir = str(tmpdir)
388*e1fe3e4aSElliott Hughes    # test what happens if the designspacedocument has no axes element.
389*e1fe3e4aSElliott Hughes    testDocPath = os.path.join(tmpdir, "testNoAxes_source.designspace")
390*e1fe3e4aSElliott Hughes    testDocPath2 = os.path.join(tmpdir, "testNoAxes_recontructed.designspace")
391*e1fe3e4aSElliott Hughes    masterPath1 = os.path.join(tmpdir, "masters", "masterTest1.ufo")
392*e1fe3e4aSElliott Hughes    masterPath2 = os.path.join(tmpdir, "masters", "masterTest2.ufo")
393*e1fe3e4aSElliott Hughes    instancePath1 = os.path.join(tmpdir, "instances", "instanceTest1.ufo")
394*e1fe3e4aSElliott Hughes    instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo")
395*e1fe3e4aSElliott Hughes
396*e1fe3e4aSElliott Hughes    # Case 1: No axes element in the document, but there are sources and instances
397*e1fe3e4aSElliott Hughes    doc = DesignSpaceDocument()
398*e1fe3e4aSElliott Hughes
399*e1fe3e4aSElliott Hughes    for name, value in [("One", 1), ("Two", 2), ("Three", 3)]:
400*e1fe3e4aSElliott Hughes        a = AxisDescriptor()
401*e1fe3e4aSElliott Hughes        a.minimum = 0
402*e1fe3e4aSElliott Hughes        a.maximum = 1000
403*e1fe3e4aSElliott Hughes        a.default = 0
404*e1fe3e4aSElliott Hughes        a.name = "axisName%s" % (name)
405*e1fe3e4aSElliott Hughes        a.tag = "ax_%d" % (value)
406*e1fe3e4aSElliott Hughes        doc.addAxis(a)
407*e1fe3e4aSElliott Hughes
408*e1fe3e4aSElliott Hughes    # add master 1
409*e1fe3e4aSElliott Hughes    s1 = SourceDescriptor()
410*e1fe3e4aSElliott Hughes    s1.filename = os.path.relpath(masterPath1, os.path.dirname(testDocPath))
411*e1fe3e4aSElliott Hughes    s1.name = "master.ufo1"
412*e1fe3e4aSElliott Hughes    s1.copyLib = True
413*e1fe3e4aSElliott Hughes    s1.copyInfo = True
414*e1fe3e4aSElliott Hughes    s1.copyFeatures = True
415*e1fe3e4aSElliott Hughes    s1.location = dict(axisNameOne=-1000, axisNameTwo=0, axisNameThree=1000)
416*e1fe3e4aSElliott Hughes    s1.familyName = "MasterFamilyName"
417*e1fe3e4aSElliott Hughes    s1.styleName = "MasterStyleNameOne"
418*e1fe3e4aSElliott Hughes    doc.addSource(s1)
419*e1fe3e4aSElliott Hughes
420*e1fe3e4aSElliott Hughes    # add master 2
421*e1fe3e4aSElliott Hughes    s2 = SourceDescriptor()
422*e1fe3e4aSElliott Hughes    s2.filename = os.path.relpath(masterPath2, os.path.dirname(testDocPath))
423*e1fe3e4aSElliott Hughes    s2.name = "master.ufo1"
424*e1fe3e4aSElliott Hughes    s2.copyLib = False
425*e1fe3e4aSElliott Hughes    s2.copyInfo = False
426*e1fe3e4aSElliott Hughes    s2.copyFeatures = False
427*e1fe3e4aSElliott Hughes    s2.location = dict(axisNameOne=1000, axisNameTwo=1000, axisNameThree=0)
428*e1fe3e4aSElliott Hughes    s2.familyName = "MasterFamilyName"
429*e1fe3e4aSElliott Hughes    s2.styleName = "MasterStyleNameTwo"
430*e1fe3e4aSElliott Hughes    doc.addSource(s2)
431*e1fe3e4aSElliott Hughes
432*e1fe3e4aSElliott Hughes    # add instance 1
433*e1fe3e4aSElliott Hughes    i1 = InstanceDescriptor()
434*e1fe3e4aSElliott Hughes    i1.filename = os.path.relpath(instancePath1, os.path.dirname(testDocPath))
435*e1fe3e4aSElliott Hughes    i1.familyName = "InstanceFamilyName"
436*e1fe3e4aSElliott Hughes    i1.styleName = "InstanceStyleName"
437*e1fe3e4aSElliott Hughes    i1.name = "instance.ufo1"
438*e1fe3e4aSElliott Hughes    i1.location = dict(axisNameOne=(-1000, 500), axisNameTwo=100)
439*e1fe3e4aSElliott Hughes    i1.postScriptFontName = "InstancePostscriptName"
440*e1fe3e4aSElliott Hughes    i1.styleMapFamilyName = "InstanceStyleMapFamilyName"
441*e1fe3e4aSElliott Hughes    i1.styleMapStyleName = "InstanceStyleMapStyleName"
442*e1fe3e4aSElliott Hughes    doc.addInstance(i1)
443*e1fe3e4aSElliott Hughes
444*e1fe3e4aSElliott Hughes    doc.write(testDocPath)
445*e1fe3e4aSElliott Hughes    verify = DesignSpaceDocument()
446*e1fe3e4aSElliott Hughes    verify.read(testDocPath)
447*e1fe3e4aSElliott Hughes    verify.write(testDocPath2)
448*e1fe3e4aSElliott Hughes
449*e1fe3e4aSElliott Hughes
450*e1fe3e4aSElliott Hughesdef test_pathNameResolve(tmpdir):
451*e1fe3e4aSElliott Hughes    tmpdir = str(tmpdir)
452*e1fe3e4aSElliott Hughes    # test how descriptor.path and descriptor.filename are resolved
453*e1fe3e4aSElliott Hughes    testDocPath1 = os.path.join(tmpdir, "testPathName_case1.designspace")
454*e1fe3e4aSElliott Hughes    testDocPath2 = os.path.join(tmpdir, "testPathName_case2.designspace")
455*e1fe3e4aSElliott Hughes    testDocPath3 = os.path.join(tmpdir, "testPathName_case3.designspace")
456*e1fe3e4aSElliott Hughes    testDocPath4 = os.path.join(tmpdir, "testPathName_case4.designspace")
457*e1fe3e4aSElliott Hughes    testDocPath5 = os.path.join(tmpdir, "testPathName_case5.designspace")
458*e1fe3e4aSElliott Hughes    testDocPath6 = os.path.join(tmpdir, "testPathName_case6.designspace")
459*e1fe3e4aSElliott Hughes    masterPath1 = os.path.join(tmpdir, "masters", "masterTest1.ufo")
460*e1fe3e4aSElliott Hughes    masterPath2 = os.path.join(tmpdir, "masters", "masterTest2.ufo")
461*e1fe3e4aSElliott Hughes    instancePath1 = os.path.join(tmpdir, "instances", "instanceTest1.ufo")
462*e1fe3e4aSElliott Hughes    instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo")
463*e1fe3e4aSElliott Hughes
464*e1fe3e4aSElliott Hughes    a1 = AxisDescriptor()
465*e1fe3e4aSElliott Hughes    a1.tag = "TAGA"
466*e1fe3e4aSElliott Hughes    a1.name = "axisName_a"
467*e1fe3e4aSElliott Hughes    a1.minimum = 0
468*e1fe3e4aSElliott Hughes    a1.maximum = 1000
469*e1fe3e4aSElliott Hughes    a1.default = 0
470*e1fe3e4aSElliott Hughes
471*e1fe3e4aSElliott Hughes    # Case 1: filename and path are both empty. Nothing to calculate, nothing to put in the file.
472*e1fe3e4aSElliott Hughes    doc = DesignSpaceDocument()
473*e1fe3e4aSElliott Hughes    doc.addAxis(a1)
474*e1fe3e4aSElliott Hughes    s = SourceDescriptor()
475*e1fe3e4aSElliott Hughes    s.filename = None
476*e1fe3e4aSElliott Hughes    s.path = None
477*e1fe3e4aSElliott Hughes    s.copyInfo = True
478*e1fe3e4aSElliott Hughes    s.location = dict(weight=0)
479*e1fe3e4aSElliott Hughes    s.familyName = "MasterFamilyName"
480*e1fe3e4aSElliott Hughes    s.styleName = "MasterStyleNameOne"
481*e1fe3e4aSElliott Hughes    doc.addSource(s)
482*e1fe3e4aSElliott Hughes    doc.write(testDocPath1)
483*e1fe3e4aSElliott Hughes    verify = DesignSpaceDocument()
484*e1fe3e4aSElliott Hughes    verify.read(testDocPath1)
485*e1fe3e4aSElliott Hughes    assert verify.sources[0].filename == None
486*e1fe3e4aSElliott Hughes    assert verify.sources[0].path == None
487*e1fe3e4aSElliott Hughes
488*e1fe3e4aSElliott Hughes    # Case 2: filename is empty, path points somewhere: calculate a new filename.
489*e1fe3e4aSElliott Hughes    doc = DesignSpaceDocument()
490*e1fe3e4aSElliott Hughes    doc.addAxis(a1)
491*e1fe3e4aSElliott Hughes    s = SourceDescriptor()
492*e1fe3e4aSElliott Hughes    s.filename = None
493*e1fe3e4aSElliott Hughes    s.path = masterPath1
494*e1fe3e4aSElliott Hughes    s.copyInfo = True
495*e1fe3e4aSElliott Hughes    s.location = dict(weight=0)
496*e1fe3e4aSElliott Hughes    s.familyName = "MasterFamilyName"
497*e1fe3e4aSElliott Hughes    s.styleName = "MasterStyleNameOne"
498*e1fe3e4aSElliott Hughes    doc.addSource(s)
499*e1fe3e4aSElliott Hughes    doc.write(testDocPath2)
500*e1fe3e4aSElliott Hughes    verify = DesignSpaceDocument()
501*e1fe3e4aSElliott Hughes    verify.read(testDocPath2)
502*e1fe3e4aSElliott Hughes    assert verify.sources[0].filename == "masters/masterTest1.ufo"
503*e1fe3e4aSElliott Hughes    assert verify.sources[0].path == posix(masterPath1)
504*e1fe3e4aSElliott Hughes
505*e1fe3e4aSElliott Hughes    # Case 3: the filename is set, the path is None.
506*e1fe3e4aSElliott Hughes    doc = DesignSpaceDocument()
507*e1fe3e4aSElliott Hughes    doc.addAxis(a1)
508*e1fe3e4aSElliott Hughes    s = SourceDescriptor()
509*e1fe3e4aSElliott Hughes    s.filename = "../somewhere/over/the/rainbow.ufo"
510*e1fe3e4aSElliott Hughes    s.path = None
511*e1fe3e4aSElliott Hughes    s.copyInfo = True
512*e1fe3e4aSElliott Hughes    s.location = dict(weight=0)
513*e1fe3e4aSElliott Hughes    s.familyName = "MasterFamilyName"
514*e1fe3e4aSElliott Hughes    s.styleName = "MasterStyleNameOne"
515*e1fe3e4aSElliott Hughes    doc.addSource(s)
516*e1fe3e4aSElliott Hughes    doc.write(testDocPath3)
517*e1fe3e4aSElliott Hughes    verify = DesignSpaceDocument()
518*e1fe3e4aSElliott Hughes    verify.read(testDocPath3)
519*e1fe3e4aSElliott Hughes    assert verify.sources[0].filename == "../somewhere/over/the/rainbow.ufo"
520*e1fe3e4aSElliott Hughes    # make the absolute path for filename so we can see if it matches the path
521*e1fe3e4aSElliott Hughes    p = os.path.abspath(
522*e1fe3e4aSElliott Hughes        os.path.join(os.path.dirname(testDocPath3), verify.sources[0].filename)
523*e1fe3e4aSElliott Hughes    )
524*e1fe3e4aSElliott Hughes    assert verify.sources[0].path == posix(p)
525*e1fe3e4aSElliott Hughes
526*e1fe3e4aSElliott Hughes    # Case 4: the filename points to one file, the path points to another. The path takes precedence.
527*e1fe3e4aSElliott Hughes    doc = DesignSpaceDocument()
528*e1fe3e4aSElliott Hughes    doc.addAxis(a1)
529*e1fe3e4aSElliott Hughes    s = SourceDescriptor()
530*e1fe3e4aSElliott Hughes    s.filename = "../somewhere/over/the/rainbow.ufo"
531*e1fe3e4aSElliott Hughes    s.path = masterPath1
532*e1fe3e4aSElliott Hughes    s.copyInfo = True
533*e1fe3e4aSElliott Hughes    s.location = dict(weight=0)
534*e1fe3e4aSElliott Hughes    s.familyName = "MasterFamilyName"
535*e1fe3e4aSElliott Hughes    s.styleName = "MasterStyleNameOne"
536*e1fe3e4aSElliott Hughes    doc.addSource(s)
537*e1fe3e4aSElliott Hughes    doc.write(testDocPath4)
538*e1fe3e4aSElliott Hughes    verify = DesignSpaceDocument()
539*e1fe3e4aSElliott Hughes    verify.read(testDocPath4)
540*e1fe3e4aSElliott Hughes    assert verify.sources[0].filename == "masters/masterTest1.ufo"
541*e1fe3e4aSElliott Hughes
542*e1fe3e4aSElliott Hughes    # Case 5: the filename is None, path has a value, update the filename
543*e1fe3e4aSElliott Hughes    doc = DesignSpaceDocument()
544*e1fe3e4aSElliott Hughes    doc.addAxis(a1)
545*e1fe3e4aSElliott Hughes    s = SourceDescriptor()
546*e1fe3e4aSElliott Hughes    s.filename = None
547*e1fe3e4aSElliott Hughes    s.path = masterPath1
548*e1fe3e4aSElliott Hughes    s.copyInfo = True
549*e1fe3e4aSElliott Hughes    s.location = dict(weight=0)
550*e1fe3e4aSElliott Hughes    s.familyName = "MasterFamilyName"
551*e1fe3e4aSElliott Hughes    s.styleName = "MasterStyleNameOne"
552*e1fe3e4aSElliott Hughes    doc.addSource(s)
553*e1fe3e4aSElliott Hughes    doc.write(testDocPath5)  # so that the document has a path
554*e1fe3e4aSElliott Hughes    doc.updateFilenameFromPath()
555*e1fe3e4aSElliott Hughes    assert doc.sources[0].filename == "masters/masterTest1.ufo"
556*e1fe3e4aSElliott Hughes
557*e1fe3e4aSElliott Hughes    # Case 6: the filename has a value, path has a value, update the filenames with force
558*e1fe3e4aSElliott Hughes    doc = DesignSpaceDocument()
559*e1fe3e4aSElliott Hughes    doc.addAxis(a1)
560*e1fe3e4aSElliott Hughes    s = SourceDescriptor()
561*e1fe3e4aSElliott Hughes    s.filename = "../somewhere/over/the/rainbow.ufo"
562*e1fe3e4aSElliott Hughes    s.path = masterPath1
563*e1fe3e4aSElliott Hughes    s.copyInfo = True
564*e1fe3e4aSElliott Hughes    s.location = dict(weight=0)
565*e1fe3e4aSElliott Hughes    s.familyName = "MasterFamilyName"
566*e1fe3e4aSElliott Hughes    s.styleName = "MasterStyleNameOne"
567*e1fe3e4aSElliott Hughes    doc.write(testDocPath5)  # so that the document has a path
568*e1fe3e4aSElliott Hughes    doc.addSource(s)
569*e1fe3e4aSElliott Hughes    assert doc.sources[0].filename == "../somewhere/over/the/rainbow.ufo"
570*e1fe3e4aSElliott Hughes    doc.updateFilenameFromPath(force=True)
571*e1fe3e4aSElliott Hughes    assert doc.sources[0].filename == "masters/masterTest1.ufo"
572*e1fe3e4aSElliott Hughes
573*e1fe3e4aSElliott Hughes
574*e1fe3e4aSElliott Hughesdef test_normalise1():
575*e1fe3e4aSElliott Hughes    # normalisation of anisotropic locations, clipping
576*e1fe3e4aSElliott Hughes    doc = DesignSpaceDocument()
577*e1fe3e4aSElliott Hughes    # write some axes
578*e1fe3e4aSElliott Hughes    a1 = AxisDescriptor()
579*e1fe3e4aSElliott Hughes    a1.minimum = -1000
580*e1fe3e4aSElliott Hughes    a1.maximum = 1000
581*e1fe3e4aSElliott Hughes    a1.default = 0
582*e1fe3e4aSElliott Hughes    a1.name = "axisName_a"
583*e1fe3e4aSElliott Hughes    a1.tag = "TAGA"
584*e1fe3e4aSElliott Hughes    doc.addAxis(a1)
585*e1fe3e4aSElliott Hughes    assert doc.normalizeLocation(dict(axisName_a=0)) == {"axisName_a": 0.0}
586*e1fe3e4aSElliott Hughes    assert doc.normalizeLocation(dict(axisName_a=1000)) == {"axisName_a": 1.0}
587*e1fe3e4aSElliott Hughes    # clipping beyond max values:
588*e1fe3e4aSElliott Hughes    assert doc.normalizeLocation(dict(axisName_a=1001)) == {"axisName_a": 1.0}
589*e1fe3e4aSElliott Hughes    assert doc.normalizeLocation(dict(axisName_a=500)) == {"axisName_a": 0.5}
590*e1fe3e4aSElliott Hughes    assert doc.normalizeLocation(dict(axisName_a=-1000)) == {"axisName_a": -1.0}
591*e1fe3e4aSElliott Hughes    assert doc.normalizeLocation(dict(axisName_a=-1001)) == {"axisName_a": -1.0}
592*e1fe3e4aSElliott Hughes    # anisotropic coordinates normalise to isotropic
593*e1fe3e4aSElliott Hughes    assert doc.normalizeLocation(dict(axisName_a=(1000, -1000))) == {"axisName_a": 1.0}
594*e1fe3e4aSElliott Hughes    doc.normalize()
595*e1fe3e4aSElliott Hughes    r = []
596*e1fe3e4aSElliott Hughes    for axis in doc.axes:
597*e1fe3e4aSElliott Hughes        r.append((axis.name, axis.minimum, axis.default, axis.maximum))
598*e1fe3e4aSElliott Hughes    r.sort()
599*e1fe3e4aSElliott Hughes    assert r == [("axisName_a", -1.0, 0.0, 1.0)]
600*e1fe3e4aSElliott Hughes
601*e1fe3e4aSElliott Hughes
602*e1fe3e4aSElliott Hughesdef test_normalise2():
603*e1fe3e4aSElliott Hughes    # normalisation with minimum > 0
604*e1fe3e4aSElliott Hughes    doc = DesignSpaceDocument()
605*e1fe3e4aSElliott Hughes    # write some axes
606*e1fe3e4aSElliott Hughes    a2 = AxisDescriptor()
607*e1fe3e4aSElliott Hughes    a2.minimum = 100
608*e1fe3e4aSElliott Hughes    a2.maximum = 1000
609*e1fe3e4aSElliott Hughes    a2.default = 100
610*e1fe3e4aSElliott Hughes    a2.name = "axisName_b"
611*e1fe3e4aSElliott Hughes    doc.addAxis(a2)
612*e1fe3e4aSElliott Hughes    assert doc.normalizeLocation(dict(axisName_b=0)) == {"axisName_b": 0.0}
613*e1fe3e4aSElliott Hughes    assert doc.normalizeLocation(dict(axisName_b=1000)) == {"axisName_b": 1.0}
614*e1fe3e4aSElliott Hughes    # clipping beyond max values:
615*e1fe3e4aSElliott Hughes    assert doc.normalizeLocation(dict(axisName_b=1001)) == {"axisName_b": 1.0}
616*e1fe3e4aSElliott Hughes    assert doc.normalizeLocation(dict(axisName_b=500)) == {
617*e1fe3e4aSElliott Hughes        "axisName_b": 0.4444444444444444
618*e1fe3e4aSElliott Hughes    }
619*e1fe3e4aSElliott Hughes    assert doc.normalizeLocation(dict(axisName_b=-1000)) == {"axisName_b": 0.0}
620*e1fe3e4aSElliott Hughes    assert doc.normalizeLocation(dict(axisName_b=-1001)) == {"axisName_b": 0.0}
621*e1fe3e4aSElliott Hughes    # anisotropic coordinates normalise to isotropic
622*e1fe3e4aSElliott Hughes    assert doc.normalizeLocation(dict(axisName_b=(1000, -1000))) == {"axisName_b": 1.0}
623*e1fe3e4aSElliott Hughes    assert doc.normalizeLocation(dict(axisName_b=1001)) == {"axisName_b": 1.0}
624*e1fe3e4aSElliott Hughes    doc.normalize()
625*e1fe3e4aSElliott Hughes    r = []
626*e1fe3e4aSElliott Hughes    for axis in doc.axes:
627*e1fe3e4aSElliott Hughes        r.append((axis.name, axis.minimum, axis.default, axis.maximum))
628*e1fe3e4aSElliott Hughes    r.sort()
629*e1fe3e4aSElliott Hughes    assert r == [("axisName_b", 0.0, 0.0, 1.0)]
630*e1fe3e4aSElliott Hughes
631*e1fe3e4aSElliott Hughes
632*e1fe3e4aSElliott Hughesdef test_normalise3():
633*e1fe3e4aSElliott Hughes    # normalisation of negative values, with default == maximum
634*e1fe3e4aSElliott Hughes    doc = DesignSpaceDocument()
635*e1fe3e4aSElliott Hughes    # write some axes
636*e1fe3e4aSElliott Hughes    a3 = AxisDescriptor()
637*e1fe3e4aSElliott Hughes    a3.minimum = -1000
638*e1fe3e4aSElliott Hughes    a3.maximum = 0
639*e1fe3e4aSElliott Hughes    a3.default = 0
640*e1fe3e4aSElliott Hughes    a3.name = "ccc"
641*e1fe3e4aSElliott Hughes    doc.addAxis(a3)
642*e1fe3e4aSElliott Hughes    assert doc.normalizeLocation(dict(ccc=0)) == {"ccc": 0.0}
643*e1fe3e4aSElliott Hughes    assert doc.normalizeLocation(dict(ccc=1)) == {"ccc": 0.0}
644*e1fe3e4aSElliott Hughes    assert doc.normalizeLocation(dict(ccc=-1000)) == {"ccc": -1.0}
645*e1fe3e4aSElliott Hughes    assert doc.normalizeLocation(dict(ccc=-1001)) == {"ccc": -1.0}
646*e1fe3e4aSElliott Hughes    doc.normalize()
647*e1fe3e4aSElliott Hughes    r = []
648*e1fe3e4aSElliott Hughes    for axis in doc.axes:
649*e1fe3e4aSElliott Hughes        r.append((axis.name, axis.minimum, axis.default, axis.maximum))
650*e1fe3e4aSElliott Hughes    r.sort()
651*e1fe3e4aSElliott Hughes    assert r == [("ccc", -1.0, 0.0, 0.0)]
652*e1fe3e4aSElliott Hughes
653*e1fe3e4aSElliott Hughes
654*e1fe3e4aSElliott Hughesdef test_normalise4():
655*e1fe3e4aSElliott Hughes    # normalisation with a map
656*e1fe3e4aSElliott Hughes    doc = DesignSpaceDocument()
657*e1fe3e4aSElliott Hughes    # write some axes
658*e1fe3e4aSElliott Hughes    a4 = AxisDescriptor()
659*e1fe3e4aSElliott Hughes    a4.minimum = 0
660*e1fe3e4aSElliott Hughes    a4.maximum = 1000
661*e1fe3e4aSElliott Hughes    a4.default = 0
662*e1fe3e4aSElliott Hughes    a4.name = "ddd"
663*e1fe3e4aSElliott Hughes    a4.map = [(0, 100), (300, 500), (600, 500), (1000, 900)]
664*e1fe3e4aSElliott Hughes    doc.addAxis(a4)
665*e1fe3e4aSElliott Hughes    doc.normalize()
666*e1fe3e4aSElliott Hughes    r = []
667*e1fe3e4aSElliott Hughes    for axis in doc.axes:
668*e1fe3e4aSElliott Hughes        r.append((axis.name, axis.map))
669*e1fe3e4aSElliott Hughes    r.sort()
670*e1fe3e4aSElliott Hughes    assert r == [("ddd", [(0, 0.0), (300, 0.5), (600, 0.5), (1000, 1.0)])]
671*e1fe3e4aSElliott Hughes
672*e1fe3e4aSElliott Hughes
673*e1fe3e4aSElliott Hughesdef test_axisMapping():
674*e1fe3e4aSElliott Hughes    # note: because designspance lib does not do any actual
675*e1fe3e4aSElliott Hughes    # processing of the mapping data, we can only check if there data is there.
676*e1fe3e4aSElliott Hughes    doc = DesignSpaceDocument()
677*e1fe3e4aSElliott Hughes    # write some axes
678*e1fe3e4aSElliott Hughes    a4 = AxisDescriptor()
679*e1fe3e4aSElliott Hughes    a4.minimum = 0
680*e1fe3e4aSElliott Hughes    a4.maximum = 1000
681*e1fe3e4aSElliott Hughes    a4.default = 0
682*e1fe3e4aSElliott Hughes    a4.name = "ddd"
683*e1fe3e4aSElliott Hughes    a4.map = [(0, 100), (300, 500), (600, 500), (1000, 900)]
684*e1fe3e4aSElliott Hughes    doc.addAxis(a4)
685*e1fe3e4aSElliott Hughes    doc.normalize()
686*e1fe3e4aSElliott Hughes    r = []
687*e1fe3e4aSElliott Hughes    for axis in doc.axes:
688*e1fe3e4aSElliott Hughes        r.append((axis.name, axis.map))
689*e1fe3e4aSElliott Hughes    r.sort()
690*e1fe3e4aSElliott Hughes    assert r == [("ddd", [(0, 0.0), (300, 0.5), (600, 0.5), (1000, 1.0)])]
691*e1fe3e4aSElliott Hughes
692*e1fe3e4aSElliott Hughes
693*e1fe3e4aSElliott Hughesdef test_axisMappingsRoundtrip(tmpdir):
694*e1fe3e4aSElliott Hughes    # tests of axisMappings in a document, roundtripping.
695*e1fe3e4aSElliott Hughes
696*e1fe3e4aSElliott Hughes    tmpdir = str(tmpdir)
697*e1fe3e4aSElliott Hughes    srcDocPath = (Path(__file__) / "../data/test_avar2.designspace").resolve()
698*e1fe3e4aSElliott Hughes    testDocPath = os.path.join(tmpdir, "test_avar2.designspace")
699*e1fe3e4aSElliott Hughes    shutil.copy(srcDocPath, testDocPath)
700*e1fe3e4aSElliott Hughes    testDocPath2 = os.path.join(tmpdir, "test_avar2_roundtrip.designspace")
701*e1fe3e4aSElliott Hughes    doc = DesignSpaceDocument()
702*e1fe3e4aSElliott Hughes    doc.read(testDocPath)
703*e1fe3e4aSElliott Hughes    assert doc.axisMappings
704*e1fe3e4aSElliott Hughes    assert len(doc.axisMappings) == 2
705*e1fe3e4aSElliott Hughes    assert doc.axisMappings[0].inputLocation == {"Justify": -100.0, "Width": 100.0}
706*e1fe3e4aSElliott Hughes
707*e1fe3e4aSElliott Hughes    # This is a bit of a hack, but it's the only way to make sure
708*e1fe3e4aSElliott Hughes    # that the save works on Windows if the tempdir and the data
709*e1fe3e4aSElliott Hughes    # dir are on different drives.
710*e1fe3e4aSElliott Hughes    for descriptor in doc.sources + doc.instances:
711*e1fe3e4aSElliott Hughes        descriptor.path = None
712*e1fe3e4aSElliott Hughes
713*e1fe3e4aSElliott Hughes    doc.write(testDocPath2)
714*e1fe3e4aSElliott Hughes    # verify these results
715*e1fe3e4aSElliott Hughes    doc2 = DesignSpaceDocument()
716*e1fe3e4aSElliott Hughes    doc2.read(testDocPath2)
717*e1fe3e4aSElliott Hughes    assert [mapping.inputLocation for mapping in doc.axisMappings] == [
718*e1fe3e4aSElliott Hughes        mapping.inputLocation for mapping in doc2.axisMappings
719*e1fe3e4aSElliott Hughes    ]
720*e1fe3e4aSElliott Hughes    assert [mapping.outputLocation for mapping in doc.axisMappings] == [
721*e1fe3e4aSElliott Hughes        mapping.outputLocation for mapping in doc2.axisMappings
722*e1fe3e4aSElliott Hughes    ]
723*e1fe3e4aSElliott Hughes    assert [mapping.description for mapping in doc.axisMappings] == [
724*e1fe3e4aSElliott Hughes        mapping.description for mapping in doc2.axisMappings
725*e1fe3e4aSElliott Hughes    ]
726*e1fe3e4aSElliott Hughes    assert [mapping.groupDescription for mapping in doc.axisMappings] == [
727*e1fe3e4aSElliott Hughes        mapping.groupDescription for mapping in doc2.axisMappings
728*e1fe3e4aSElliott Hughes    ]
729*e1fe3e4aSElliott Hughes
730*e1fe3e4aSElliott Hughes
731*e1fe3e4aSElliott Hughesdef test_rulesConditions(tmpdir):
732*e1fe3e4aSElliott Hughes    # tests of rules, conditionsets and conditions
733*e1fe3e4aSElliott Hughes    r1 = RuleDescriptor()
734*e1fe3e4aSElliott Hughes    r1.name = "named.rule.1"
735*e1fe3e4aSElliott Hughes    r1.conditionSets.append(
736*e1fe3e4aSElliott Hughes        [
737*e1fe3e4aSElliott Hughes            dict(name="axisName_a", minimum=0, maximum=1000),
738*e1fe3e4aSElliott Hughes            dict(name="axisName_b", minimum=0, maximum=3000),
739*e1fe3e4aSElliott Hughes        ]
740*e1fe3e4aSElliott Hughes    )
741*e1fe3e4aSElliott Hughes    r1.subs.append(("a", "a.alt"))
742*e1fe3e4aSElliott Hughes
743*e1fe3e4aSElliott Hughes    assert evaluateRule(r1, dict(axisName_a=500, axisName_b=0)) == True
744*e1fe3e4aSElliott Hughes    assert evaluateRule(r1, dict(axisName_a=0, axisName_b=0)) == True
745*e1fe3e4aSElliott Hughes    assert evaluateRule(r1, dict(axisName_a=1000, axisName_b=0)) == True
746*e1fe3e4aSElliott Hughes    assert evaluateRule(r1, dict(axisName_a=1000, axisName_b=-100)) == False
747*e1fe3e4aSElliott Hughes    assert evaluateRule(r1, dict(axisName_a=1000.0001, axisName_b=0)) == False
748*e1fe3e4aSElliott Hughes    assert evaluateRule(r1, dict(axisName_a=-0.0001, axisName_b=0)) == False
749*e1fe3e4aSElliott Hughes    assert evaluateRule(r1, dict(axisName_a=-100, axisName_b=0)) == False
750*e1fe3e4aSElliott Hughes    assert processRules([r1], dict(axisName_a=500, axisName_b=0), ["a", "b", "c"]) == [
751*e1fe3e4aSElliott Hughes        "a.alt",
752*e1fe3e4aSElliott Hughes        "b",
753*e1fe3e4aSElliott Hughes        "c",
754*e1fe3e4aSElliott Hughes    ]
755*e1fe3e4aSElliott Hughes    assert processRules(
756*e1fe3e4aSElliott Hughes        [r1], dict(axisName_a=500, axisName_b=0), ["a.alt", "b", "c"]
757*e1fe3e4aSElliott Hughes    ) == ["a.alt", "b", "c"]
758*e1fe3e4aSElliott Hughes    assert processRules([r1], dict(axisName_a=2000, axisName_b=0), ["a", "b", "c"]) == [
759*e1fe3e4aSElliott Hughes        "a",
760*e1fe3e4aSElliott Hughes        "b",
761*e1fe3e4aSElliott Hughes        "c",
762*e1fe3e4aSElliott Hughes    ]
763*e1fe3e4aSElliott Hughes
764*e1fe3e4aSElliott Hughes    # rule with only a maximum
765*e1fe3e4aSElliott Hughes    r2 = RuleDescriptor()
766*e1fe3e4aSElliott Hughes    r2.name = "named.rule.2"
767*e1fe3e4aSElliott Hughes    r2.conditionSets.append([dict(name="axisName_a", maximum=500)])
768*e1fe3e4aSElliott Hughes    r2.subs.append(("b", "b.alt"))
769*e1fe3e4aSElliott Hughes
770*e1fe3e4aSElliott Hughes    assert evaluateRule(r2, dict(axisName_a=0)) == True
771*e1fe3e4aSElliott Hughes    assert evaluateRule(r2, dict(axisName_a=-500)) == True
772*e1fe3e4aSElliott Hughes    assert evaluateRule(r2, dict(axisName_a=1000)) == False
773*e1fe3e4aSElliott Hughes
774*e1fe3e4aSElliott Hughes    # rule with only a minimum
775*e1fe3e4aSElliott Hughes    r3 = RuleDescriptor()
776*e1fe3e4aSElliott Hughes    r3.name = "named.rule.3"
777*e1fe3e4aSElliott Hughes    r3.conditionSets.append([dict(name="axisName_a", minimum=500)])
778*e1fe3e4aSElliott Hughes    r3.subs.append(("c", "c.alt"))
779*e1fe3e4aSElliott Hughes
780*e1fe3e4aSElliott Hughes    assert evaluateRule(r3, dict(axisName_a=0)) == False
781*e1fe3e4aSElliott Hughes    assert evaluateRule(r3, dict(axisName_a=1000)) == True
782*e1fe3e4aSElliott Hughes    assert evaluateRule(r3, dict(axisName_a=1000)) == True
783*e1fe3e4aSElliott Hughes
784*e1fe3e4aSElliott Hughes    # rule with only a minimum, maximum in separate conditions
785*e1fe3e4aSElliott Hughes    r4 = RuleDescriptor()
786*e1fe3e4aSElliott Hughes    r4.name = "named.rule.4"
787*e1fe3e4aSElliott Hughes    r4.conditionSets.append(
788*e1fe3e4aSElliott Hughes        [dict(name="axisName_a", minimum=500), dict(name="axisName_b", maximum=500)]
789*e1fe3e4aSElliott Hughes    )
790*e1fe3e4aSElliott Hughes    r4.subs.append(("c", "c.alt"))
791*e1fe3e4aSElliott Hughes
792*e1fe3e4aSElliott Hughes    assert evaluateRule(r4, dict(axisName_a=1000, axisName_b=0)) == True
793*e1fe3e4aSElliott Hughes    assert evaluateRule(r4, dict(axisName_a=0, axisName_b=0)) == False
794*e1fe3e4aSElliott Hughes    assert evaluateRule(r4, dict(axisName_a=1000, axisName_b=1000)) == False
795*e1fe3e4aSElliott Hughes
796*e1fe3e4aSElliott Hughes
797*e1fe3e4aSElliott Hughesdef test_rulesDocument(tmpdir):
798*e1fe3e4aSElliott Hughes    # tests of rules in a document, roundtripping.
799*e1fe3e4aSElliott Hughes    tmpdir = str(tmpdir)
800*e1fe3e4aSElliott Hughes    testDocPath = os.path.join(tmpdir, "testRules.designspace")
801*e1fe3e4aSElliott Hughes    testDocPath2 = os.path.join(tmpdir, "testRules_roundtrip.designspace")
802*e1fe3e4aSElliott Hughes    doc = DesignSpaceDocument()
803*e1fe3e4aSElliott Hughes    doc.rulesProcessingLast = True
804*e1fe3e4aSElliott Hughes    a1 = AxisDescriptor()
805*e1fe3e4aSElliott Hughes    a1.minimum = 0
806*e1fe3e4aSElliott Hughes    a1.maximum = 1000
807*e1fe3e4aSElliott Hughes    a1.default = 0
808*e1fe3e4aSElliott Hughes    a1.name = "axisName_a"
809*e1fe3e4aSElliott Hughes    a1.tag = "TAGA"
810*e1fe3e4aSElliott Hughes    b1 = AxisDescriptor()
811*e1fe3e4aSElliott Hughes    b1.minimum = 2000
812*e1fe3e4aSElliott Hughes    b1.maximum = 3000
813*e1fe3e4aSElliott Hughes    b1.default = 2000
814*e1fe3e4aSElliott Hughes    b1.name = "axisName_b"
815*e1fe3e4aSElliott Hughes    b1.tag = "TAGB"
816*e1fe3e4aSElliott Hughes    doc.addAxis(a1)
817*e1fe3e4aSElliott Hughes    doc.addAxis(b1)
818*e1fe3e4aSElliott Hughes    r1 = RuleDescriptor()
819*e1fe3e4aSElliott Hughes    r1.name = "named.rule.1"
820*e1fe3e4aSElliott Hughes    r1.conditionSets.append(
821*e1fe3e4aSElliott Hughes        [
822*e1fe3e4aSElliott Hughes            dict(name="axisName_a", minimum=0, maximum=1000),
823*e1fe3e4aSElliott Hughes            dict(name="axisName_b", minimum=0, maximum=3000),
824*e1fe3e4aSElliott Hughes        ]
825*e1fe3e4aSElliott Hughes    )
826*e1fe3e4aSElliott Hughes    r1.subs.append(("a", "a.alt"))
827*e1fe3e4aSElliott Hughes    # rule with minium and maximum
828*e1fe3e4aSElliott Hughes    doc.addRule(r1)
829*e1fe3e4aSElliott Hughes    assert len(doc.rules) == 1
830*e1fe3e4aSElliott Hughes    assert len(doc.rules[0].conditionSets) == 1
831*e1fe3e4aSElliott Hughes    assert len(doc.rules[0].conditionSets[0]) == 2
832*e1fe3e4aSElliott Hughes    assert _axesAsDict(doc.axes) == {
833*e1fe3e4aSElliott Hughes        "axisName_a": {
834*e1fe3e4aSElliott Hughes            "map": [],
835*e1fe3e4aSElliott Hughes            "name": "axisName_a",
836*e1fe3e4aSElliott Hughes            "default": 0,
837*e1fe3e4aSElliott Hughes            "minimum": 0,
838*e1fe3e4aSElliott Hughes            "maximum": 1000,
839*e1fe3e4aSElliott Hughes            "tag": "TAGA",
840*e1fe3e4aSElliott Hughes        },
841*e1fe3e4aSElliott Hughes        "axisName_b": {
842*e1fe3e4aSElliott Hughes            "map": [],
843*e1fe3e4aSElliott Hughes            "name": "axisName_b",
844*e1fe3e4aSElliott Hughes            "default": 2000,
845*e1fe3e4aSElliott Hughes            "minimum": 2000,
846*e1fe3e4aSElliott Hughes            "maximum": 3000,
847*e1fe3e4aSElliott Hughes            "tag": "TAGB",
848*e1fe3e4aSElliott Hughes        },
849*e1fe3e4aSElliott Hughes    }
850*e1fe3e4aSElliott Hughes    assert doc.rules[0].conditionSets == [
851*e1fe3e4aSElliott Hughes        [
852*e1fe3e4aSElliott Hughes            {"minimum": 0, "maximum": 1000, "name": "axisName_a"},
853*e1fe3e4aSElliott Hughes            {"minimum": 0, "maximum": 3000, "name": "axisName_b"},
854*e1fe3e4aSElliott Hughes        ]
855*e1fe3e4aSElliott Hughes    ]
856*e1fe3e4aSElliott Hughes    assert doc.rules[0].subs == [("a", "a.alt")]
857*e1fe3e4aSElliott Hughes    doc.normalize()
858*e1fe3e4aSElliott Hughes    assert doc.rules[0].name == "named.rule.1"
859*e1fe3e4aSElliott Hughes    assert doc.rules[0].conditionSets == [
860*e1fe3e4aSElliott Hughes        [
861*e1fe3e4aSElliott Hughes            {"minimum": 0.0, "maximum": 1.0, "name": "axisName_a"},
862*e1fe3e4aSElliott Hughes            {"minimum": 0.0, "maximum": 1.0, "name": "axisName_b"},
863*e1fe3e4aSElliott Hughes        ]
864*e1fe3e4aSElliott Hughes    ]
865*e1fe3e4aSElliott Hughes    # still one conditionset
866*e1fe3e4aSElliott Hughes    assert len(doc.rules[0].conditionSets) == 1
867*e1fe3e4aSElliott Hughes    doc.write(testDocPath)
868*e1fe3e4aSElliott Hughes    # add a stray conditionset
869*e1fe3e4aSElliott Hughes    _addUnwrappedCondition(testDocPath)
870*e1fe3e4aSElliott Hughes    doc2 = DesignSpaceDocument()
871*e1fe3e4aSElliott Hughes    doc2.read(testDocPath)
872*e1fe3e4aSElliott Hughes    assert doc2.rulesProcessingLast
873*e1fe3e4aSElliott Hughes    assert len(doc2.axes) == 2
874*e1fe3e4aSElliott Hughes    assert len(doc2.rules) == 1
875*e1fe3e4aSElliott Hughes    assert len(doc2.rules[0].conditionSets) == 2
876*e1fe3e4aSElliott Hughes    doc2.write(testDocPath2)
877*e1fe3e4aSElliott Hughes    # verify these results
878*e1fe3e4aSElliott Hughes    # make sure the stray condition is now neatly wrapped in a conditionset.
879*e1fe3e4aSElliott Hughes    doc3 = DesignSpaceDocument()
880*e1fe3e4aSElliott Hughes    doc3.read(testDocPath2)
881*e1fe3e4aSElliott Hughes    assert len(doc3.rules) == 1
882*e1fe3e4aSElliott Hughes    assert len(doc3.rules[0].conditionSets) == 2
883*e1fe3e4aSElliott Hughes
884*e1fe3e4aSElliott Hughes
885*e1fe3e4aSElliott Hughesdef _addUnwrappedCondition(path):
886*e1fe3e4aSElliott Hughes    # only for testing, so we can make an invalid designspace file
887*e1fe3e4aSElliott Hughes    # older designspace files may have conditions that are not wrapped in a conditionset
888*e1fe3e4aSElliott Hughes    # These can be read into a new conditionset.
889*e1fe3e4aSElliott Hughes    with open(path, "r", encoding="utf-8") as f:
890*e1fe3e4aSElliott Hughes        d = f.read()
891*e1fe3e4aSElliott Hughes    print(d)
892*e1fe3e4aSElliott Hughes    d = d.replace(
893*e1fe3e4aSElliott Hughes        '<rule name="named.rule.1">',
894*e1fe3e4aSElliott Hughes        '<rule name="named.rule.1">\n\t<condition maximum="22" minimum="33" name="axisName_a" />',
895*e1fe3e4aSElliott Hughes    )
896*e1fe3e4aSElliott Hughes    with open(path, "w", encoding="utf-8") as f:
897*e1fe3e4aSElliott Hughes        f.write(d)
898*e1fe3e4aSElliott Hughes
899*e1fe3e4aSElliott Hughes
900*e1fe3e4aSElliott Hughesdef test_documentLib(tmpdir):
901*e1fe3e4aSElliott Hughes    # roundtrip test of the document lib with some nested data
902*e1fe3e4aSElliott Hughes    tmpdir = str(tmpdir)
903*e1fe3e4aSElliott Hughes    testDocPath1 = os.path.join(tmpdir, "testDocumentLibTest.designspace")
904*e1fe3e4aSElliott Hughes    doc = DesignSpaceDocument()
905*e1fe3e4aSElliott Hughes    a1 = AxisDescriptor()
906*e1fe3e4aSElliott Hughes    a1.tag = "TAGA"
907*e1fe3e4aSElliott Hughes    a1.name = "axisName_a"
908*e1fe3e4aSElliott Hughes    a1.minimum = 0
909*e1fe3e4aSElliott Hughes    a1.maximum = 1000
910*e1fe3e4aSElliott Hughes    a1.default = 0
911*e1fe3e4aSElliott Hughes    doc.addAxis(a1)
912*e1fe3e4aSElliott Hughes    dummyData = dict(a=123, b="äbc", c=[1, 2, 3], d={"a": 123})
913*e1fe3e4aSElliott Hughes    dummyKey = "org.fontTools.designspaceLib"
914*e1fe3e4aSElliott Hughes    doc.lib = {dummyKey: dummyData}
915*e1fe3e4aSElliott Hughes    doc.write(testDocPath1)
916*e1fe3e4aSElliott Hughes    new = DesignSpaceDocument()
917*e1fe3e4aSElliott Hughes    new.read(testDocPath1)
918*e1fe3e4aSElliott Hughes    assert dummyKey in new.lib
919*e1fe3e4aSElliott Hughes    assert new.lib[dummyKey] == dummyData
920*e1fe3e4aSElliott Hughes
921*e1fe3e4aSElliott Hughes
922*e1fe3e4aSElliott Hughesdef test_updatePaths(tmpdir):
923*e1fe3e4aSElliott Hughes    doc = DesignSpaceDocument()
924*e1fe3e4aSElliott Hughes    doc.path = str(tmpdir / "foo" / "bar" / "MyDesignspace.designspace")
925*e1fe3e4aSElliott Hughes
926*e1fe3e4aSElliott Hughes    s1 = SourceDescriptor()
927*e1fe3e4aSElliott Hughes    doc.addSource(s1)
928*e1fe3e4aSElliott Hughes
929*e1fe3e4aSElliott Hughes    doc.updatePaths()
930*e1fe3e4aSElliott Hughes
931*e1fe3e4aSElliott Hughes    # expect no changes
932*e1fe3e4aSElliott Hughes    assert s1.path is None
933*e1fe3e4aSElliott Hughes    assert s1.filename is None
934*e1fe3e4aSElliott Hughes
935*e1fe3e4aSElliott Hughes    name1 = "../masters/Source1.ufo"
936*e1fe3e4aSElliott Hughes    path1 = posix(str(tmpdir / "foo" / "masters" / "Source1.ufo"))
937*e1fe3e4aSElliott Hughes
938*e1fe3e4aSElliott Hughes    s1.path = path1
939*e1fe3e4aSElliott Hughes    s1.filename = None
940*e1fe3e4aSElliott Hughes
941*e1fe3e4aSElliott Hughes    doc.updatePaths()
942*e1fe3e4aSElliott Hughes
943*e1fe3e4aSElliott Hughes    assert s1.path == path1
944*e1fe3e4aSElliott Hughes    assert s1.filename == name1  # empty filename updated
945*e1fe3e4aSElliott Hughes
946*e1fe3e4aSElliott Hughes    name2 = "../masters/Source2.ufo"
947*e1fe3e4aSElliott Hughes    s1.filename = name2
948*e1fe3e4aSElliott Hughes
949*e1fe3e4aSElliott Hughes    doc.updatePaths()
950*e1fe3e4aSElliott Hughes
951*e1fe3e4aSElliott Hughes    # conflicting filename discarded, path always gets precedence
952*e1fe3e4aSElliott Hughes    assert s1.path == path1
953*e1fe3e4aSElliott Hughes    assert s1.filename == "../masters/Source1.ufo"
954*e1fe3e4aSElliott Hughes
955*e1fe3e4aSElliott Hughes    s1.path = None
956*e1fe3e4aSElliott Hughes    s1.filename = name2
957*e1fe3e4aSElliott Hughes
958*e1fe3e4aSElliott Hughes    doc.updatePaths()
959*e1fe3e4aSElliott Hughes
960*e1fe3e4aSElliott Hughes    # expect no changes
961*e1fe3e4aSElliott Hughes    assert s1.path is None
962*e1fe3e4aSElliott Hughes    assert s1.filename == name2
963*e1fe3e4aSElliott Hughes
964*e1fe3e4aSElliott Hughes
965*e1fe3e4aSElliott Hughesdef test_read_with_path_object():
966*e1fe3e4aSElliott Hughes    source = (Path(__file__) / "../data/test_v4_original.designspace").resolve()
967*e1fe3e4aSElliott Hughes    assert source.exists()
968*e1fe3e4aSElliott Hughes    doc = DesignSpaceDocument()
969*e1fe3e4aSElliott Hughes    doc.read(source)
970*e1fe3e4aSElliott Hughes
971*e1fe3e4aSElliott Hughes
972*e1fe3e4aSElliott Hughesdef test_with_with_path_object(tmpdir):
973*e1fe3e4aSElliott Hughes    tmpdir = str(tmpdir)
974*e1fe3e4aSElliott Hughes    dest = Path(tmpdir) / "test_v4_original.designspace"
975*e1fe3e4aSElliott Hughes    doc = DesignSpaceDocument()
976*e1fe3e4aSElliott Hughes    doc.write(dest)
977*e1fe3e4aSElliott Hughes    assert dest.exists()
978*e1fe3e4aSElliott Hughes
979*e1fe3e4aSElliott Hughes
980*e1fe3e4aSElliott Hughesdef test_findDefault_axis_mapping():
981*e1fe3e4aSElliott Hughes    designspace_string = """\
982*e1fe3e4aSElliott Hughes<?xml version='1.0' encoding='UTF-8'?>
983*e1fe3e4aSElliott Hughes<designspace format="4.0">
984*e1fe3e4aSElliott Hughes  <axes>
985*e1fe3e4aSElliott Hughes    <axis tag="wght" name="Weight" minimum="100" maximum="800" default="400">
986*e1fe3e4aSElliott Hughes      <map input="100" output="20"/>
987*e1fe3e4aSElliott Hughes      <map input="300" output="40"/>
988*e1fe3e4aSElliott Hughes      <map input="400" output="80"/>
989*e1fe3e4aSElliott Hughes      <map input="700" output="126"/>
990*e1fe3e4aSElliott Hughes      <map input="800" output="170"/>
991*e1fe3e4aSElliott Hughes    </axis>
992*e1fe3e4aSElliott Hughes    <axis tag="ital" name="Italic" minimum="0" maximum="1" default="1"/>
993*e1fe3e4aSElliott Hughes  </axes>
994*e1fe3e4aSElliott Hughes  <sources>
995*e1fe3e4aSElliott Hughes    <source filename="Font-Light.ufo">
996*e1fe3e4aSElliott Hughes      <location>
997*e1fe3e4aSElliott Hughes        <dimension name="Weight" xvalue="20"/>
998*e1fe3e4aSElliott Hughes        <dimension name="Italic" xvalue="0"/>
999*e1fe3e4aSElliott Hughes      </location>
1000*e1fe3e4aSElliott Hughes    </source>
1001*e1fe3e4aSElliott Hughes    <source filename="Font-Regular.ufo">
1002*e1fe3e4aSElliott Hughes      <location>
1003*e1fe3e4aSElliott Hughes        <dimension name="Weight" xvalue="80"/>
1004*e1fe3e4aSElliott Hughes        <dimension name="Italic" xvalue="0"/>
1005*e1fe3e4aSElliott Hughes      </location>
1006*e1fe3e4aSElliott Hughes    </source>
1007*e1fe3e4aSElliott Hughes    <source filename="Font-Bold.ufo">
1008*e1fe3e4aSElliott Hughes      <location>
1009*e1fe3e4aSElliott Hughes        <dimension name="Weight" xvalue="170"/>
1010*e1fe3e4aSElliott Hughes        <dimension name="Italic" xvalue="0"/>
1011*e1fe3e4aSElliott Hughes      </location>
1012*e1fe3e4aSElliott Hughes    </source>
1013*e1fe3e4aSElliott Hughes    <source filename="Font-LightItalic.ufo">
1014*e1fe3e4aSElliott Hughes      <location>
1015*e1fe3e4aSElliott Hughes        <dimension name="Weight" xvalue="20"/>
1016*e1fe3e4aSElliott Hughes        <dimension name="Italic" xvalue="1"/>
1017*e1fe3e4aSElliott Hughes      </location>
1018*e1fe3e4aSElliott Hughes    </source>
1019*e1fe3e4aSElliott Hughes    <source filename="Font-Italic.ufo">
1020*e1fe3e4aSElliott Hughes      <location>
1021*e1fe3e4aSElliott Hughes        <dimension name="Weight" xvalue="80"/>
1022*e1fe3e4aSElliott Hughes        <dimension name="Italic" xvalue="1"/>
1023*e1fe3e4aSElliott Hughes      </location>
1024*e1fe3e4aSElliott Hughes    </source>
1025*e1fe3e4aSElliott Hughes    <source filename="Font-BoldItalic.ufo">
1026*e1fe3e4aSElliott Hughes      <location>
1027*e1fe3e4aSElliott Hughes        <dimension name="Weight" xvalue="170"/>
1028*e1fe3e4aSElliott Hughes        <dimension name="Italic" xvalue="1"/>
1029*e1fe3e4aSElliott Hughes      </location>
1030*e1fe3e4aSElliott Hughes    </source>
1031*e1fe3e4aSElliott Hughes  </sources>
1032*e1fe3e4aSElliott Hughes</designspace>
1033*e1fe3e4aSElliott Hughes    """
1034*e1fe3e4aSElliott Hughes    designspace = DesignSpaceDocument.fromstring(designspace_string)
1035*e1fe3e4aSElliott Hughes    assert designspace.findDefault().filename == "Font-Italic.ufo"
1036*e1fe3e4aSElliott Hughes
1037*e1fe3e4aSElliott Hughes    designspace.axes[1].default = 0
1038*e1fe3e4aSElliott Hughes
1039*e1fe3e4aSElliott Hughes    assert designspace.findDefault().filename == "Font-Regular.ufo"
1040*e1fe3e4aSElliott Hughes
1041*e1fe3e4aSElliott Hughes
1042*e1fe3e4aSElliott Hughesdef test_loadSourceFonts():
1043*e1fe3e4aSElliott Hughes    def opener(path):
1044*e1fe3e4aSElliott Hughes        font = ttLib.TTFont()
1045*e1fe3e4aSElliott Hughes        font.importXML(path)
1046*e1fe3e4aSElliott Hughes        return font
1047*e1fe3e4aSElliott Hughes
1048*e1fe3e4aSElliott Hughes    # this designspace file contains .TTX source paths
1049*e1fe3e4aSElliott Hughes    path = os.path.join(
1050*e1fe3e4aSElliott Hughes        os.path.dirname(os.path.dirname(__file__)),
1051*e1fe3e4aSElliott Hughes        "varLib",
1052*e1fe3e4aSElliott Hughes        "data",
1053*e1fe3e4aSElliott Hughes        "SparseMasters.designspace",
1054*e1fe3e4aSElliott Hughes    )
1055*e1fe3e4aSElliott Hughes    designspace = DesignSpaceDocument.fromfile(path)
1056*e1fe3e4aSElliott Hughes
1057*e1fe3e4aSElliott Hughes    # force two source descriptors to have the same path
1058*e1fe3e4aSElliott Hughes    designspace.sources[1].path = designspace.sources[0].path
1059*e1fe3e4aSElliott Hughes
1060*e1fe3e4aSElliott Hughes    fonts = designspace.loadSourceFonts(opener)
1061*e1fe3e4aSElliott Hughes
1062*e1fe3e4aSElliott Hughes    assert len(fonts) == 3
1063*e1fe3e4aSElliott Hughes    assert all(isinstance(font, ttLib.TTFont) for font in fonts)
1064*e1fe3e4aSElliott Hughes    assert fonts[0] is fonts[1]  # same path, identical font object
1065*e1fe3e4aSElliott Hughes
1066*e1fe3e4aSElliott Hughes    fonts2 = designspace.loadSourceFonts(opener)
1067*e1fe3e4aSElliott Hughes
1068*e1fe3e4aSElliott Hughes    for font1, font2 in zip(fonts, fonts2):
1069*e1fe3e4aSElliott Hughes        assert font1 is font2
1070*e1fe3e4aSElliott Hughes
1071*e1fe3e4aSElliott Hughes
1072*e1fe3e4aSElliott Hughesdef test_loadSourceFonts_no_required_path():
1073*e1fe3e4aSElliott Hughes    designspace = DesignSpaceDocument()
1074*e1fe3e4aSElliott Hughes    designspace.sources.append(SourceDescriptor())
1075*e1fe3e4aSElliott Hughes
1076*e1fe3e4aSElliott Hughes    with pytest.raises(DesignSpaceDocumentError, match="no 'path' attribute"):
1077*e1fe3e4aSElliott Hughes        designspace.loadSourceFonts(lambda p: p)
1078*e1fe3e4aSElliott Hughes
1079*e1fe3e4aSElliott Hughes
1080*e1fe3e4aSElliott Hughesdef test_addAxisDescriptor():
1081*e1fe3e4aSElliott Hughes    ds = DesignSpaceDocument()
1082*e1fe3e4aSElliott Hughes
1083*e1fe3e4aSElliott Hughes    axis = ds.addAxisDescriptor(
1084*e1fe3e4aSElliott Hughes        name="Weight", tag="wght", minimum=100, default=400, maximum=900
1085*e1fe3e4aSElliott Hughes    )
1086*e1fe3e4aSElliott Hughes
1087*e1fe3e4aSElliott Hughes    assert ds.axes[0] is axis
1088*e1fe3e4aSElliott Hughes    assert isinstance(axis, AxisDescriptor)
1089*e1fe3e4aSElliott Hughes    assert axis.name == "Weight"
1090*e1fe3e4aSElliott Hughes    assert axis.tag == "wght"
1091*e1fe3e4aSElliott Hughes    assert axis.minimum == 100
1092*e1fe3e4aSElliott Hughes    assert axis.default == 400
1093*e1fe3e4aSElliott Hughes    assert axis.maximum == 900
1094*e1fe3e4aSElliott Hughes
1095*e1fe3e4aSElliott Hughes
1096*e1fe3e4aSElliott Hughesdef test_addAxisDescriptor():
1097*e1fe3e4aSElliott Hughes    ds = DesignSpaceDocument()
1098*e1fe3e4aSElliott Hughes
1099*e1fe3e4aSElliott Hughes    mapping = ds.addAxisMappingDescriptor(
1100*e1fe3e4aSElliott Hughes        inputLocation={"weight": 900, "width": 150}, outputLocation={"weight": 870}
1101*e1fe3e4aSElliott Hughes    )
1102*e1fe3e4aSElliott Hughes
1103*e1fe3e4aSElliott Hughes    assert ds.axisMappings[0] is mapping
1104*e1fe3e4aSElliott Hughes    assert isinstance(mapping, AxisMappingDescriptor)
1105*e1fe3e4aSElliott Hughes    assert mapping.inputLocation == {"weight": 900, "width": 150}
1106*e1fe3e4aSElliott Hughes    assert mapping.outputLocation == {"weight": 870}
1107*e1fe3e4aSElliott Hughes
1108*e1fe3e4aSElliott Hughes
1109*e1fe3e4aSElliott Hughesdef test_addSourceDescriptor():
1110*e1fe3e4aSElliott Hughes    ds = DesignSpaceDocument()
1111*e1fe3e4aSElliott Hughes
1112*e1fe3e4aSElliott Hughes    source = ds.addSourceDescriptor(name="TestSource", location={"Weight": 400})
1113*e1fe3e4aSElliott Hughes
1114*e1fe3e4aSElliott Hughes    assert ds.sources[0] is source
1115*e1fe3e4aSElliott Hughes    assert isinstance(source, SourceDescriptor)
1116*e1fe3e4aSElliott Hughes    assert source.name == "TestSource"
1117*e1fe3e4aSElliott Hughes    assert source.location == {"Weight": 400}
1118*e1fe3e4aSElliott Hughes
1119*e1fe3e4aSElliott Hughes
1120*e1fe3e4aSElliott Hughesdef test_addInstanceDescriptor():
1121*e1fe3e4aSElliott Hughes    ds = DesignSpaceDocument()
1122*e1fe3e4aSElliott Hughes
1123*e1fe3e4aSElliott Hughes    instance = ds.addInstanceDescriptor(
1124*e1fe3e4aSElliott Hughes        name="TestInstance",
1125*e1fe3e4aSElliott Hughes        location={"Weight": 400},
1126*e1fe3e4aSElliott Hughes        styleName="Regular",
1127*e1fe3e4aSElliott Hughes        styleMapStyleName="regular",
1128*e1fe3e4aSElliott Hughes    )
1129*e1fe3e4aSElliott Hughes
1130*e1fe3e4aSElliott Hughes    assert ds.instances[0] is instance
1131*e1fe3e4aSElliott Hughes    assert isinstance(instance, InstanceDescriptor)
1132*e1fe3e4aSElliott Hughes    assert instance.name == "TestInstance"
1133*e1fe3e4aSElliott Hughes    assert instance.location == {"Weight": 400}
1134*e1fe3e4aSElliott Hughes    assert instance.styleName == "Regular"
1135*e1fe3e4aSElliott Hughes    assert instance.styleMapStyleName == "regular"
1136*e1fe3e4aSElliott Hughes
1137*e1fe3e4aSElliott Hughes
1138*e1fe3e4aSElliott Hughesdef test_addRuleDescriptor(tmp_path):
1139*e1fe3e4aSElliott Hughes    ds = DesignSpaceDocument()
1140*e1fe3e4aSElliott Hughes
1141*e1fe3e4aSElliott Hughes    rule = ds.addRuleDescriptor(
1142*e1fe3e4aSElliott Hughes        name="TestRule",
1143*e1fe3e4aSElliott Hughes        conditionSets=[
1144*e1fe3e4aSElliott Hughes            [
1145*e1fe3e4aSElliott Hughes                dict(name="Weight", minimum=100, maximum=200),
1146*e1fe3e4aSElliott Hughes                dict(name="Weight", minimum=700, maximum=900),
1147*e1fe3e4aSElliott Hughes            ]
1148*e1fe3e4aSElliott Hughes        ],
1149*e1fe3e4aSElliott Hughes        subs=[("a", "a.alt")],
1150*e1fe3e4aSElliott Hughes    )
1151*e1fe3e4aSElliott Hughes
1152*e1fe3e4aSElliott Hughes    assert ds.rules[0] is rule
1153*e1fe3e4aSElliott Hughes    assert isinstance(rule, RuleDescriptor)
1154*e1fe3e4aSElliott Hughes    assert rule.name == "TestRule"
1155*e1fe3e4aSElliott Hughes    assert rule.conditionSets == [
1156*e1fe3e4aSElliott Hughes        [
1157*e1fe3e4aSElliott Hughes            dict(name="Weight", minimum=100, maximum=200),
1158*e1fe3e4aSElliott Hughes            dict(name="Weight", minimum=700, maximum=900),
1159*e1fe3e4aSElliott Hughes        ]
1160*e1fe3e4aSElliott Hughes    ]
1161*e1fe3e4aSElliott Hughes    assert rule.subs == [("a", "a.alt")]
1162*e1fe3e4aSElliott Hughes
1163*e1fe3e4aSElliott Hughes    # Test it doesn't crash.
1164*e1fe3e4aSElliott Hughes    ds.write(tmp_path / "test.designspace")
1165*e1fe3e4aSElliott Hughes
1166*e1fe3e4aSElliott Hughes
1167*e1fe3e4aSElliott Hughesdef test_deepcopyExceptFonts():
1168*e1fe3e4aSElliott Hughes    ds = DesignSpaceDocument()
1169*e1fe3e4aSElliott Hughes    ds.addSourceDescriptor(font=object())
1170*e1fe3e4aSElliott Hughes    ds.addSourceDescriptor(font=object())
1171*e1fe3e4aSElliott Hughes
1172*e1fe3e4aSElliott Hughes    ds_copy = ds.deepcopyExceptFonts()
1173*e1fe3e4aSElliott Hughes
1174*e1fe3e4aSElliott Hughes    assert ds.tostring() == ds_copy.tostring()
1175*e1fe3e4aSElliott Hughes    assert ds.sources[0].font is ds_copy.sources[0].font
1176*e1fe3e4aSElliott Hughes    assert ds.sources[1].font is ds_copy.sources[1].font
1177*e1fe3e4aSElliott Hughes
1178*e1fe3e4aSElliott Hughes
1179*e1fe3e4aSElliott Hughesdef test_Range_post_init():
1180*e1fe3e4aSElliott Hughes    # test min and max are sorted and default is clamped to either min/max
1181*e1fe3e4aSElliott Hughes    r = Range(minimum=2, maximum=-1, default=-2)
1182*e1fe3e4aSElliott Hughes    assert r.minimum == -1
1183*e1fe3e4aSElliott Hughes    assert r.maximum == 2
1184*e1fe3e4aSElliott Hughes    assert r.default == -1
1185*e1fe3e4aSElliott Hughes
1186*e1fe3e4aSElliott Hughes
1187*e1fe3e4aSElliott Hughesdef test_get_axes(datadir: Path) -> None:
1188*e1fe3e4aSElliott Hughes    ds = DesignSpaceDocument.fromfile(datadir / "test_v5.designspace")
1189*e1fe3e4aSElliott Hughes
1190*e1fe3e4aSElliott Hughes    assert ds.getAxis("Width") is ds.getAxisByTag("wdth")
1191*e1fe3e4aSElliott Hughes    assert ds.getAxis("Italic") is ds.getAxisByTag("ital")
1192