예제 #1
0
def test_normalise():
    doc = DesignSpaceDocument()
    # write some axes
    a1 = AxisDescriptor()
    a1.minimum = -1000
    a1.maximum = 1000
    a1.default = 0
    a1.name = "aaa"
    a1.tag = "aaaa"
    doc.addAxis(a1)

    assert doc.normalizeLocation(dict(aaa=0)) == {'aaa': 0.0}
    assert doc.normalizeLocation(dict(aaa=1000)) == {'aaa': 1.0}

    # clipping beyond max values:
    assert doc.normalizeLocation(dict(aaa=1001)) == {'aaa': 1.0}
    assert doc.normalizeLocation(dict(aaa=500)) == {'aaa': 0.5}
    assert doc.normalizeLocation(dict(aaa=-1000)) == {'aaa': -1.0}
    assert doc.normalizeLocation(dict(aaa=-1001)) == {'aaa': -1.0}
    # anisotropic coordinates normalise to isotropic
    assert doc.normalizeLocation(dict(aaa=(1000, -1000))) == {'aaa': 1.0}
    doc.normalize()
    r = []
    for axis in doc.axes:
        r.append((axis.name, axis.minimum, axis.default, axis.maximum))
    r.sort()
    assert r == [('aaa', -1.0, 0.0, 1.0)]

    doc = DesignSpaceDocument()
    # write some axes
    a2 = AxisDescriptor()
    a2.minimum = 100
    a2.maximum = 1000
    a2.default = 100
    a2.name = "bbb"
    doc.addAxis(a2)
    assert doc.normalizeLocation(dict(bbb=0)) == {'bbb': 0.0}
    assert doc.normalizeLocation(dict(bbb=1000)) == {'bbb': 1.0}
    # clipping beyond max values:
    assert doc.normalizeLocation(dict(bbb=1001)) == {'bbb': 1.0}
    assert doc.normalizeLocation(dict(bbb=500)) == {'bbb': 0.4444444444444444}
    assert doc.normalizeLocation(dict(bbb=-1000)) == {'bbb': 0.0}
    assert doc.normalizeLocation(dict(bbb=-1001)) == {'bbb': 0.0}
    # anisotropic coordinates normalise to isotropic
    assert doc.normalizeLocation(dict(bbb=(1000, -1000))) == {'bbb': 1.0}
    assert doc.normalizeLocation(dict(bbb=1001)) == {'bbb': 1.0}
    doc.normalize()
    r = []
    for axis in doc.axes:
        r.append((axis.name, axis.minimum, axis.default, axis.maximum))
    r.sort()
    assert r == [('bbb', 0.0, 0.0, 1.0)]

    doc = DesignSpaceDocument()
    # write some axes
    a3 = AxisDescriptor()
    a3.minimum = -1000
    a3.maximum = 0
    a3.default = 0
    a3.name = "ccc"
    doc.addAxis(a3)
    assert doc.normalizeLocation(dict(ccc=0)) == {'ccc': 0.0}
    assert doc.normalizeLocation(dict(ccc=1)) == {'ccc': 0.0}
    assert doc.normalizeLocation(dict(ccc=-1000)) == {'ccc': -1.0}
    assert doc.normalizeLocation(dict(ccc=-1001)) == {'ccc': -1.0}

    doc.normalize()
    r = []
    for axis in doc.axes:
        r.append((axis.name, axis.minimum, axis.default, axis.maximum))
    r.sort()
    assert r == [('ccc', -1.0, 0.0, 0.0)]

    doc = DesignSpaceDocument()
    # write some axes
    a3 = AxisDescriptor()
    a3.minimum = 2000
    a3.maximum = 3000
    a3.default = 2000
    a3.name = "ccc"
    doc.addAxis(a3)
    assert doc.normalizeLocation(dict(ccc=0)) == {'ccc': 0.0}
    assert doc.normalizeLocation(dict(ccc=1)) == {'ccc': 0.0}
    assert doc.normalizeLocation(dict(ccc=-1000)) == {'ccc': 0.0}
    assert doc.normalizeLocation(dict(ccc=-1001)) == {'ccc': 0.0}

    doc.normalize()
    r = []
    for axis in doc.axes:
        r.append((axis.name, axis.minimum, axis.default, axis.maximum))
    r.sort()
    assert r == [('ccc', 0.0, 0.0, 1.0)]

    doc = DesignSpaceDocument()
    # write some axes
    a4 = AxisDescriptor()
    a4.minimum = 0
    a4.maximum = 1000
    a4.default = 0
    a4.name = "ddd"
    a4.map = [(0, 100), (300, 500), (600, 500), (1000, 900)]
    doc.addAxis(a4)
    doc.normalize()
    r = []
    for axis in doc.axes:
        r.append((axis.name, axis.map))
    r.sort()
    assert r == [('ddd', [(0, 0.1), (300, 0.5), (600, 0.5), (1000, 0.9)])]
예제 #2
0
def test_rules(tmpdir):
    tmpdir = str(tmpdir)
    testDocPath = os.path.join(tmpdir, "testRules.designspace")
    testDocPath2 = os.path.join(tmpdir, "testRules_roundtrip.designspace")
    doc = DesignSpaceDocument()
    # write some axes
    a1 = AxisDescriptor()
    a1.tag = "taga"
    a1.name = "aaaa"
    a1.minimum = 0
    a1.maximum = 1000
    a1.default = 0
    doc.addAxis(a1)
    a2 = AxisDescriptor()
    a2.tag = "tagb"
    a2.name = "bbbb"
    a2.minimum = 0
    a2.maximum = 3000
    a2.default = 0
    doc.addAxis(a2)

    r1 = RuleDescriptor()
    r1.name = "named.rule.1"
    r1.conditions.append(dict(name='aaaa', minimum=0, maximum=1000))
    r1.conditions.append(dict(name='bbbb', minimum=0, maximum=3000))
    r1.subs.append(("a", "a.alt"))

    # rule with minium and maximum
    doc.addRule(r1)
    assert len(doc.rules) == 1
    assert len(doc.rules[0].conditions) == 2
    assert evaluateRule(r1, dict(aaaa=500, bbbb=0)) == True
    assert evaluateRule(r1, dict(aaaa=0, bbbb=0)) == True
    assert evaluateRule(r1, dict(aaaa=1000, bbbb=0)) == True
    assert evaluateRule(r1, dict(aaaa=1000, bbbb=-100)) == False
    assert evaluateRule(r1, dict(aaaa=1000.0001, bbbb=0)) == False
    assert evaluateRule(r1, dict(aaaa=-0.0001, bbbb=0)) == False
    assert evaluateRule(r1, dict(aaaa=-100, bbbb=0)) == False
    assert processRules([r1], dict(aaaa=500),
                        ["a", "b", "c"]) == ['a.alt', 'b', 'c']
    assert processRules([r1], dict(aaaa=500),
                        ["a.alt", "b", "c"]) == ['a.alt', 'b', 'c']
    assert processRules([r1], dict(aaaa=2000),
                        ["a", "b", "c"]) == ['a', 'b', 'c']

    # rule with only a maximum
    r2 = RuleDescriptor()
    r2.name = "named.rule.2"
    r2.conditions.append(dict(name='aaaa', maximum=500))
    r2.subs.append(("b", "b.alt"))

    assert evaluateRule(r2, dict(aaaa=0)) == True
    assert evaluateRule(r2, dict(aaaa=-500)) == True
    assert evaluateRule(r2, dict(aaaa=1000)) == False

    # rule with only a minimum
    r3 = RuleDescriptor()
    r3.name = "named.rule.3"
    r3.conditions.append(dict(name='aaaa', minimum=500))
    r3.subs.append(("c", "c.alt"))

    assert evaluateRule(r3, dict(aaaa=0)) == False
    assert evaluateRule(r3, dict(aaaa=1000)) == True
    assert evaluateRule(r3, dict(bbbb=1000)) == True

    # rule with only a minimum, maximum in separate conditions
    r4 = RuleDescriptor()
    r4.name = "named.rule.4"
    r4.conditions.append(dict(name='aaaa', minimum=500))
    r4.conditions.append(dict(name='bbbb', maximum=500))
    r4.subs.append(("c", "c.alt"))

    assert evaluateRule(r4, dict()) == True  # is this what we expect though?
    assert evaluateRule(r4, dict(aaaa=1000, bbbb=0)) == True
    assert evaluateRule(r4, dict(aaaa=0, bbbb=0)) == False
    assert evaluateRule(r4, dict(aaaa=1000, bbbb=1000)) == False

    a1 = AxisDescriptor()
    a1.minimum = 0
    a1.maximum = 1000
    a1.default = 0
    a1.name = "aaaa"
    a1.tag = "aaaa"
    b1 = AxisDescriptor()
    b1.minimum = 2000
    b1.maximum = 3000
    b1.default = 2000
    b1.name = "bbbb"
    b1.tag = "bbbb"
    doc.addAxis(a1)
    doc.addAxis(b1)
    assert doc._prepAxesForBender() == {
        'aaaa': {
            'map': [],
            'name': 'aaaa',
            'default': 0,
            'minimum': 0,
            'maximum': 1000,
            'tag': 'aaaa'
        },
        'bbbb': {
            'map': [],
            'name': 'bbbb',
            'default': 2000,
            'minimum': 2000,
            'maximum': 3000,
            'tag': 'bbbb'
        }
    }

    assert doc.rules[0].conditions == [{
        'minimum': 0,
        'maximum': 1000,
        'name': 'aaaa'
    }, {
        'minimum': 0,
        'maximum': 3000,
        'name': 'bbbb'
    }]

    assert doc.rules[0].subs == [('a', 'a.alt')]

    doc.normalize()
    assert doc.rules[0].name == 'named.rule.1'
    assert doc.rules[0].conditions == [{
        'minimum': 0.0,
        'maximum': 1.0,
        'name': 'aaaa'
    }, {
        'minimum': 0.0,
        'maximum': 1.0,
        'name': 'bbbb'
    }]

    doc.write(testDocPath)
    new = DesignSpaceDocument()

    new.read(testDocPath)
    assert len(new.axes) == 4
    assert len(new.rules) == 1
    new.write(testDocPath2)
예제 #3
0
def test_handleNoAxes(tmpdir):
    tmpdir = str(tmpdir)
    # test what happens if the designspacedocument has no axes element.
    testDocPath = os.path.join(tmpdir, "testNoAxes_source.designspace")
    testDocPath2 = os.path.join(tmpdir, "testNoAxes_recontructed.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")
    instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo")

    # Case 1: No axes element in the document, but there are sources and instances
    doc = DesignSpaceDocument()

    for name, value in [('One', 1), ('Two', 2), ('Three', 3)]:
        a = AxisDescriptor()
        a.minimum = 0
        a.maximum = 1000
        a.default = 0
        a.name = "axisName%s" % (name)
        a.tag = "ax_%d" % (value)
        doc.addAxis(a)

    # add master 1
    s1 = SourceDescriptor()
    s1.filename = os.path.relpath(masterPath1, os.path.dirname(testDocPath))
    s1.name = "master.ufo1"
    s1.copyLib = True
    s1.copyInfo = True
    s1.copyFeatures = True
    s1.location = dict(axisNameOne=-1000, axisNameTwo=0, axisNameThree=1000)
    s1.familyName = "MasterFamilyName"
    s1.styleName = "MasterStyleNameOne"
    doc.addSource(s1)

    # add master 2
    s2 = SourceDescriptor()
    s2.filename = os.path.relpath(masterPath2, os.path.dirname(testDocPath))
    s2.name = "master.ufo1"
    s2.copyLib = False
    s2.copyInfo = False
    s2.copyFeatures = False
    s2.location = dict(axisNameOne=1000, axisNameTwo=1000, axisNameThree=0)
    s2.familyName = "MasterFamilyName"
    s2.styleName = "MasterStyleNameTwo"
    doc.addSource(s2)

    # add instance 1
    i1 = InstanceDescriptor()
    i1.filename = os.path.relpath(instancePath1, os.path.dirname(testDocPath))
    i1.familyName = "InstanceFamilyName"
    i1.styleName = "InstanceStyleName"
    i1.name = "instance.ufo1"
    i1.location = dict(axisNameOne=(-1000, 500), axisNameTwo=100)
    i1.postScriptFontName = "InstancePostscriptName"
    i1.styleMapFamilyName = "InstanceStyleMapFamilyName"
    i1.styleMapStyleName = "InstanceStyleMapStyleName"
    doc.addInstance(i1)

    doc.write(testDocPath)
    __removeAxesFromDesignSpace(testDocPath)
    verify = DesignSpaceDocument()
    verify.read(testDocPath)
    verify.write(testDocPath2)
예제 #4
0
def test_pathNameResolve(tmpdir):
    tmpdir = str(tmpdir)
    # test how descriptor.path and descriptor.filename are resolved
    testDocPath1 = os.path.join(tmpdir, "testPathName_case1.designspace")
    testDocPath2 = os.path.join(tmpdir, "testPathName_case2.designspace")
    testDocPath3 = os.path.join(tmpdir, "testPathName_case3.designspace")
    testDocPath4 = os.path.join(tmpdir, "testPathName_case4.designspace")
    testDocPath5 = os.path.join(tmpdir, "testPathName_case5.designspace")
    testDocPath6 = os.path.join(tmpdir, "testPathName_case6.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")
    instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo")

    # Case 1: filename and path are both empty. Nothing to calculate, nothing to put in the file.
    doc = DesignSpaceDocument()
    s = SourceDescriptor()
    s.filename = None
    s.path = None
    s.copyInfo = True
    s.location = dict(weight=0)
    s.familyName = "MasterFamilyName"
    s.styleName = "MasterStyleNameOne"
    doc.addSource(s)
    doc.write(testDocPath1)
    verify = DesignSpaceDocument()
    verify.read(testDocPath1)
    assert verify.sources[0].filename == None
    assert verify.sources[0].path == None

    # Case 2: filename is empty, path points somewhere: calculate a new filename.
    doc = DesignSpaceDocument()
    s = SourceDescriptor()
    s.filename = None
    s.path = masterPath1
    s.copyInfo = True
    s.location = dict(weight=0)
    s.familyName = "MasterFamilyName"
    s.styleName = "MasterStyleNameOne"
    doc.addSource(s)
    doc.write(testDocPath2)
    verify = DesignSpaceDocument()
    verify.read(testDocPath2)
    assert verify.sources[0].filename == "masters/masterTest1.ufo"
    assert verify.sources[0].path == posix(masterPath1)

    # Case 3: the filename is set, the path is None.
    doc = DesignSpaceDocument()
    s = SourceDescriptor()
    s.filename = "../somewhere/over/the/rainbow.ufo"
    s.path = None
    s.copyInfo = True
    s.location = dict(weight=0)
    s.familyName = "MasterFamilyName"
    s.styleName = "MasterStyleNameOne"
    doc.addSource(s)
    doc.write(testDocPath3)
    verify = DesignSpaceDocument()
    verify.read(testDocPath3)
    assert verify.sources[0].filename == "../somewhere/over/the/rainbow.ufo"
    # make the absolute path for filename so we can see if it matches the path
    p = os.path.abspath(
        os.path.join(os.path.dirname(testDocPath3),
                     verify.sources[0].filename))
    assert verify.sources[0].path == posix(p)

    # Case 4: the filename points to one file, the path points to another. The path takes precedence.
    doc = DesignSpaceDocument()
    s = SourceDescriptor()
    s.filename = "../somewhere/over/the/rainbow.ufo"
    s.path = masterPath1
    s.copyInfo = True
    s.location = dict(weight=0)
    s.familyName = "MasterFamilyName"
    s.styleName = "MasterStyleNameOne"
    doc.addSource(s)
    doc.write(testDocPath4)
    verify = DesignSpaceDocument()
    verify.read(testDocPath4)
    assert verify.sources[0].filename == "masters/masterTest1.ufo"

    # Case 5: the filename is None, path has a value, update the filename
    doc = DesignSpaceDocument()
    s = SourceDescriptor()
    s.filename = None
    s.path = masterPath1
    s.copyInfo = True
    s.location = dict(weight=0)
    s.familyName = "MasterFamilyName"
    s.styleName = "MasterStyleNameOne"
    doc.addSource(s)
    doc.write(testDocPath5)  # so that the document has a path
    doc.updateFilenameFromPath()
    assert doc.sources[0].filename == "masters/masterTest1.ufo"

    # Case 6: the filename has a value, path has a value, update the filenames with force
    doc = DesignSpaceDocument()
    s = SourceDescriptor()
    s.filename = "../somewhere/over/the/rainbow.ufo"
    s.path = masterPath1
    s.copyInfo = True
    s.location = dict(weight=0)
    s.familyName = "MasterFamilyName"
    s.styleName = "MasterStyleNameOne"
    doc.write(testDocPath5)  # so that the document has a path
    doc.addSource(s)
    assert doc.sources[0].filename == "../somewhere/over/the/rainbow.ufo"
    doc.updateFilenameFromPath(force=True)
    assert doc.sources[0].filename == "masters/masterTest1.ufo"
예제 #5
0
def test_fill_document(tmpdir):
    tmpdir = str(tmpdir)
    testDocPath = os.path.join(tmpdir, "test.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")
    instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo")
    doc = DesignSpaceDocument()
    # add master 1
    s1 = SourceDescriptor()
    s1.filename = os.path.relpath(masterPath1, os.path.dirname(testDocPath))
    assert s1.font is None
    s1.name = "master.ufo1"
    s1.copyLib = True
    s1.copyInfo = True
    s1.copyFeatures = True
    s1.location = dict(weight=0)
    s1.familyName = "MasterFamilyName"
    s1.styleName = "MasterStyleNameOne"
    s1.mutedGlyphNames.append("A")
    s1.mutedGlyphNames.append("Z")
    doc.addSource(s1)
    # add master 2
    s2 = SourceDescriptor()
    s2.filename = os.path.relpath(masterPath2, os.path.dirname(testDocPath))
    s2.name = "master.ufo2"
    s2.copyLib = False
    s2.copyInfo = False
    s2.copyFeatures = False
    s2.muteKerning = True
    s2.location = dict(weight=1000)
    s2.familyName = "MasterFamilyName"
    s2.styleName = "MasterStyleNameTwo"
    doc.addSource(s2)
    # add instance 1
    i1 = InstanceDescriptor()
    i1.filename = os.path.relpath(instancePath1, os.path.dirname(testDocPath))
    i1.familyName = "InstanceFamilyName"
    i1.styleName = "InstanceStyleName"
    i1.name = "instance.ufo1"
    i1.location = dict(
        weight=500, spooky=666)  # this adds a dimension that is not defined.
    i1.postScriptFontName = "InstancePostscriptName"
    i1.styleMapFamilyName = "InstanceStyleMapFamilyName"
    i1.styleMapStyleName = "InstanceStyleMapStyleName"
    glyphData = dict(name="arrow", mute=True, unicodes=[0x123, 0x124, 0x125])
    i1.glyphs['arrow'] = glyphData
    i1.lib['com.coolDesignspaceApp.specimenText'] = "Hamburgerwhatever"
    doc.addInstance(i1)
    # add instance 2
    i2 = InstanceDescriptor()
    i2.filename = os.path.relpath(instancePath2, os.path.dirname(testDocPath))
    i2.familyName = "InstanceFamilyName"
    i2.styleName = "InstanceStyleName"
    i2.name = "instance.ufo2"
    # anisotropic location
    i2.location = dict(weight=500, width=(400, 300))
    i2.postScriptFontName = "InstancePostscriptName"
    i2.styleMapFamilyName = "InstanceStyleMapFamilyName"
    i2.styleMapStyleName = "InstanceStyleMapStyleName"
    glyphMasters = [
        dict(font="master.ufo1",
             glyphName="BB",
             location=dict(width=20, weight=20)),
        dict(font="master.ufo2",
             glyphName="CC",
             location=dict(width=900, weight=900))
    ]
    glyphData = dict(name="arrow", unicodes=[101, 201, 301])
    glyphData['masters'] = glyphMasters
    glyphData['note'] = "A note about this glyph"
    glyphData['instanceLocation'] = dict(width=100, weight=120)
    i2.glyphs['arrow'] = glyphData
    i2.glyphs['arrow2'] = dict(mute=False)
    doc.addInstance(i2)

    doc.filename = "suggestedFileName.designspace"
    doc.lib['com.coolDesignspaceApp.previewSize'] = 30

    # now we have sources and instances, but no axes yet.
    doc.check()

    # Here, since the axes are not defined in the document, but instead are
    # infered from the locations of the instances, we cannot guarantee the
    # order in which they will be created by the `check()` method.
    assert set(doc.getAxisOrder()) == set(['spooky', 'weight', 'width'])
    doc.axes = []  # clear the axes

    # write some axes
    a1 = AxisDescriptor()
    a1.minimum = 0
    a1.maximum = 1000
    a1.default = 0
    a1.name = "weight"
    a1.tag = "wght"
    # note: just to test the element language, not an actual label name recommendations.
    a1.labelNames[u'fa-IR'] = u"قطر"
    a1.labelNames[u'en'] = u"Wéíght"
    doc.addAxis(a1)
    a2 = AxisDescriptor()
    a2.minimum = 0
    a2.maximum = 1000
    a2.default = 20
    a2.name = "width"
    a2.tag = "wdth"
    a2.map = [(0.0, 10.0), (401.0, 66.0), (1000.0, 990.0)]
    a2.hidden = True
    a2.labelNames[u'fr'] = u"Chasse"
    doc.addAxis(a2)
    # add an axis that is not part of any location to see if that works
    a3 = AxisDescriptor()
    a3.minimum = 333
    a3.maximum = 666
    a3.default = 444
    a3.name = "spooky"
    a3.tag = "spok"
    a3.map = [(0.0, 10.0), (401.0, 66.0), (1000.0, 990.0)]
    #doc.addAxis(a3)    # uncomment this line to test the effects of default axes values
    # write some rules
    r1 = RuleDescriptor()
    r1.name = "named.rule.1"
    r1.conditions.append(dict(name='aaaa', minimum=0, maximum=1))
    r1.conditions.append(dict(name='bbbb', minimum=2, maximum=3))
    r1.subs.append(("a", "a.alt"))
    doc.addRule(r1)
    # write the document
    doc.write(testDocPath)
    assert os.path.exists(testDocPath)
    assert_equals_test_file(testDocPath, 'data/test.designspace')
    # import it again
    new = DesignSpaceDocument()
    new.read(testDocPath)

    new.check()
    assert new.default.location == {'width': 20.0, 'weight': 0.0}
    assert new.filename == 'test.designspace'
    assert new.lib == doc.lib
    assert new.instances[0].lib == doc.instances[0].lib

    # >>> for a, b in zip(doc.instances, new.instances):
    # ...     a.compare(b)
    # >>> for a, b in zip(doc.sources, new.sources):
    # ...     a.compare(b)
    # >>> for a, b in zip(doc.axes, new.axes):
    # ...     a.compare(b)
    # >>> [n.mutedGlyphNames for n in new.sources]
    # [['A', 'Z'], []]
    # >>> doc.getFonts()
    # []

    # test roundtrip for the axis attributes and data
    axes = {}
    for axis in doc.axes:
        if axis.tag not in axes:
            axes[axis.tag] = []
        axes[axis.tag].append(axis.serialize())
    for axis in new.axes:
        if axis.tag[0] == "_":
            continue
        if axis.tag not in axes:
            axes[axis.tag] = []
        axes[axis.tag].append(axis.serialize())
    for v in axes.values():
        a, b = v
        assert a == b
예제 #6
0
def test_localisedNames(tmpdir):
    tmpdir = str(tmpdir)
    testDocPath = os.path.join(tmpdir, "testLocalisedNames.designspace")
    testDocPath2 = os.path.join(tmpdir,
                                "testLocalisedNames_roundtrip.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")
    instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo")
    doc = DesignSpaceDocument()
    # add master 1
    s1 = SourceDescriptor()
    s1.filename = os.path.relpath(masterPath1, os.path.dirname(testDocPath))
    s1.name = "master.ufo1"
    s1.copyInfo = True
    s1.location = dict(weight=0)
    doc.addSource(s1)
    # add master 2
    s2 = SourceDescriptor()
    s2.filename = os.path.relpath(masterPath2, os.path.dirname(testDocPath))
    s2.name = "master.ufo2"
    s2.location = dict(weight=1000)
    doc.addSource(s2)
    # add instance 1
    i1 = InstanceDescriptor()
    i1.filename = os.path.relpath(instancePath1, os.path.dirname(testDocPath))
    i1.familyName = "Montserrat"
    i1.styleName = "SemiBold"
    i1.styleMapFamilyName = "Montserrat SemiBold"
    i1.styleMapStyleName = "Regular"
    i1.setFamilyName("Montserrat", "fr")
    i1.setFamilyName(u"モンセラート", "ja")
    i1.setStyleName("Demigras", "fr")
    i1.setStyleName(u"半ば", "ja")
    i1.setStyleMapStyleName(u"Standard", "de")
    i1.setStyleMapFamilyName("Montserrat Halbfett", "de")
    i1.setStyleMapFamilyName(u"モンセラート SemiBold", "ja")
    i1.name = "instance.ufo1"
    i1.location = dict(
        weight=500, spooky=666)  # this adds a dimension that is not defined.
    i1.postScriptFontName = "InstancePostscriptName"
    glyphData = dict(name="arrow", mute=True, unicodes=[0x123])
    i1.glyphs['arrow'] = glyphData
    doc.addInstance(i1)
    # now we have sources and instances, but no axes yet.
    doc.axes = []  # clear the axes
    # write some axes
    a1 = AxisDescriptor()
    a1.minimum = 0
    a1.maximum = 1000
    a1.default = 0
    a1.name = "weight"
    a1.tag = "wght"
    # note: just to test the element language, not an actual label name recommendations.
    a1.labelNames[u'fa-IR'] = u"قطر"
    a1.labelNames[u'en'] = u"Wéíght"
    doc.addAxis(a1)
    a2 = AxisDescriptor()
    a2.minimum = 0
    a2.maximum = 1000
    a2.default = 0
    a2.name = "width"
    a2.tag = "wdth"
    a2.map = [(0.0, 10.0), (401.0, 66.0), (1000.0, 990.0)]
    a2.labelNames[u'fr'] = u"Poids"
    doc.addAxis(a2)
    # add an axis that is not part of any location to see if that works
    a3 = AxisDescriptor()
    a3.minimum = 333
    a3.maximum = 666
    a3.default = 444
    a3.name = "spooky"
    a3.tag = "spok"
    a3.map = [(0.0, 10.0), (401.0, 66.0), (1000.0, 990.0)]
    #doc.addAxis(a3)    # uncomment this line to test the effects of default axes values
    # write some rules
    r1 = RuleDescriptor()
    r1.name = "named.rule.1"
    r1.conditions.append(dict(name='aaaa', minimum=0, maximum=1))
    r1.conditions.append(dict(name='bbbb', minimum=2, maximum=3))
    r1.subs.append(("a", "a.alt"))
    doc.addRule(r1)
    # write the document
    doc.write(testDocPath)
    assert os.path.exists(testDocPath)
    # import it again
    new = DesignSpaceDocument()
    new.read(testDocPath)
    new.write(testDocPath2)
    f1 = open(testDocPath, 'r', encoding='utf-8')
    t1 = f1.read()
    f1.close()
    f2 = open(testDocPath2, 'r', encoding='utf-8')
    t2 = f2.read()
    f2.close()
    assert t1 == t2
예제 #7
0
def test_read_with_path_object():
    import pathlib
    source = (pathlib.Path(__file__) / "../data/test.designspace").resolve()
    assert source.exists()
    doc = DesignSpaceDocument()
    doc.read(source)
예제 #8
0
def test_loadSourceFonts_no_required_path():
    designspace = DesignSpaceDocument()
    designspace.sources.append(SourceDescriptor())

    with pytest.raises(DesignSpaceDocumentError, match="no 'path' attribute"):
        designspace.loadSourceFonts(lambda p: p)
예제 #9
0
def buildDesignSpace(masterFont=None,
                     destPath=None,
                     glyphNames=[],
                     compositionType="rotate",
                     outlineAmount=None,
                     zOffset=None,
                     shadowLengthFactor=1,
                     doForceSmooth=False,
                     doMakeSubSources=False,
                     familyName=None,
                     alwaysConnect=False,
                     cap="RoundSimple",
                     connection="Round",
                     layerName=None,
                     styleName=None):

    # Open the master UFO
    if type(masterFont) == str:
        masterFont = OpenFont(masterFont, showInterface=False)

    # Get the master file name, if it's saved
    basePath = None
    masterFileName = "Font"
    if masterFont.path:
        basePath, masterFileName = os.path.split(masterFont.path)

    # Try to make a dest path, if there isn't one
    if destPath == None:
        if basePath:
            destPath = os.path.join(basePath, "Rotated")

    # Make new folders for the destPath
    if not os.path.exists(destPath):
        os.makedirs(destPath)

    # Use all glyphs, if no names are called for
    if glyphNames == []:
        glyphNames = list(masterFont.keys())
        glyphNames.sort()

    # Default names
    if not familyName:
        familyName = masterFont.info.familyName
    if not styleName:
        styleName = "Regular"
    """ Collect glyph data """
    # Organize the point data out of the glyph lib
    # and check to see which glyphs need to be present in a SubSource
    glyphPointData = {}
    needSubHROT = [
    ]  # Glyphs that need to be included in a SubSource when HROT is default
    needSubVROT = []
    for gName in glyphNames:
        if gName in masterFont:
            g = masterFont[gName]
            if layerName:
                # Copy point data to the layer
                if ZPOSITIONLIBKEY in g.lib.keys():
                    libdata = copy.deepcopy(g.lib[ZPOSITIONLIBKEY])
                else:
                    libdata = {}
                g = g.getLayer(layerName)
                g.lib[ZPOSITIONLIBKEY] = libdata
            pointData = readGlyphPointData(g)
            glyphPointData[gName] = pointData
            # Test for self-overlapping contours
            if doMakeSubSources == True:
                overlapResult = checkCurveOverlap(g)
                if "x" in overlapResult:
                    needSubHROT.append(gName)
                elif "y" in overlapResult:
                    needSubVROT.append(gName)
    """ Organize Source combinations """

    # Collect source info, based on the layout type
    # Organize file names, designspace locations, glyph lists, etc.
    sourceCombinations = []
    for locHROT, tagHROT in [(-45, "HROTn"), (0, "HROTd"), (45, "HROTx")]:
        for locVROT, tagVROT in [(-45, "VROTn"), (0, "VROTd"), (45, "VROTx")]:
            if "shadow" in compositionType:
                for locSLEN, tagSLEN in [(0, "SLENn"), (100, "SLENx")]:
                    for locSANG, tagSANG in [(-45, "SANGn"), (45, "SANGx")]:
                        # Rotate and Shadow
                        fileName = "Source-%s_%s_%s_%s.ufo" % (
                            tagHROT, tagVROT, tagSLEN, tagSANG)
                        loc = dict(HROT=locHROT,
                                   VROT=locVROT,
                                   SLEN=locSLEN,
                                   SANG=locSANG)
                        sourceInfo = dict(glyphNames=glyphNames,
                                          loc=loc,
                                          fileName=fileName,
                                          nudgeLoc=[0, 0])
                        sourceCombinations.append(sourceInfo)
            elif "depth" in compositionType:
                for locDPTH, tagDPTH in [(0, "DPTHn"), (100, "DPTHx")]:
                    # Rotate and depth
                    fileName = "Source-%s_%s_%s.ufo" % (tagHROT, tagVROT,
                                                        tagDPTH)
                    loc = dict(HROT=locHROT, VROT=locVROT, DPTH=locDPTH)
                    sourceInfo = dict(glyphNames=glyphNames,
                                      loc=loc,
                                      fileName=fileName,
                                      nudgeLoc=[0, 0])
                    sourceCombinations.append(sourceInfo)
            else:
                # Rotate only
                fileName = "Source-%s_%s.ufo" % (tagHROT, tagVROT)
                loc = dict(HROT=locHROT, VROT=locVROT)
                sourceInfo = dict(glyphNames=glyphNames,
                                  loc=loc,
                                  fileName=fileName,
                                  nudgeLoc=[0, 0])
                sourceCombinations.append(sourceInfo)

    # Process the sourceCombinations and make SubSources if necessary
    #print("needSubHROT", needSubHROT)
    #print("needSubVROT", needSubVROT)
    # @@@ Temporarily force all glyphs to be in all submasters
    needSubHROT = glyphNames
    needSubVROT = glyphNames
    if doMakeSubSources:
        doSubHROT = len(needSubHROT)
        doSubVROT = len(needSubVROT)
    else:
        doSubHROT = False
        doSubVROT = False
    # Loop through once to add new HROT SubSources
    newSourceCombos = []
    for sourceInfo in sourceCombinations:
        if sourceInfo["loc"]["HROT"] == 0:
            if doSubHROT:
                subSourceInfo = copy.deepcopy(sourceInfo)
                subSourceInfo["nudgeLoc"][
                    0] = 0  # Don't nudge, move the location instead
                subSourceInfo["loc"]["HROT"] += SLIGHTLYOFFAXIS
                subSourceInfo["glyphNames"] = needSubHROT
                subSourceInfo[
                    "fileName"] = "Sub" + subSourceInfo["fileName"].replace(
                        "HROTd", "HROTdd")
                newSourceCombos.append(subSourceInfo)
                # Nudge the default source
                sourceInfo["nudgeLoc"][0] -= SLIGHTLYOFFAXIS
    sourceCombinations += newSourceCombos
    # Looping back through to add VROT SubSources and to catch all of the new HROT SubSources
    newSourceCombos = []
    for sourceInfo in sourceCombinations:
        if sourceInfo["loc"]["VROT"] == 0:
            if doSubVROT:
                subSourceInfo = copy.deepcopy(sourceInfo)
                subSourceInfo["nudgeLoc"][
                    1] = 0  # Don't nudge, move the location instead
                subSourceInfo["loc"]["VROT"] -= SLIGHTLYOFFAXIS
                # Append the glyph list if this was the HROT=SLIGHTLYOFFAXIS
                if not subSourceInfo["loc"]["HROT"] == SLIGHTLYOFFAXIS:
                    subSourceInfo["glyphNames"] = []
                subSourceInfo["glyphNames"] += needSubVROT
                subSourceInfo["fileName"] = subSourceInfo["fileName"].replace(
                    "VROTd", "VROTdd")
                if not "Sub" in subSourceInfo["fileName"]:
                    subSourceInfo[
                        "fileName"] = "Sub" + subSourceInfo["fileName"]
                newSourceCombos.append(subSourceInfo)
                # Nudge the default source
                sourceInfo["nudgeLoc"][1] += SLIGHTLYOFFAXIS
    sourceCombinations += newSourceCombos
    """ Make the source UFOs """

    for sourceInfo in sourceCombinations:
        sourceUfoPath = os.path.join(destPath, sourceInfo["fileName"])
        if not os.path.exists(sourceUfoPath):
            sourceFont = NewFont(showInterface=False)
            sourceFont.save(sourceUfoPath)
            sourceFont.info.familyName = familyName
            sourceFont.info.styleName = styleName
            sourceFont.save()
            sourceFont.close()
    """ Process Glyphs into Source UFOs """

    # Process each UFO source, one at a time
    for sourceInfo in sourceCombinations:

        # Combine the nudgeLoc and loc, use this value when rotating
        rotateLoc = copy.deepcopy(sourceInfo["loc"])
        rotateLoc["HROT"] += sourceInfo["nudgeLoc"][0]
        rotateLoc["VROT"] += sourceInfo["nudgeLoc"][1]

        sourceUfoPath = os.path.join(destPath, sourceInfo["fileName"])
        sourceFont = OpenFont(sourceUfoPath, showInterface=False)

        for gName in sourceInfo["glyphNames"]:
            if gName in glyphPointData:
                pointData = copy.deepcopy(glyphPointData[gName])
            else:
                pointData = {}

            # Get the glyph started
            g = masterFont[gName]
            if layerName:
                g = g.getLayer(layerName)
            # Remove the glyph if it already existed and make a new one
            if gName in sourceFont:
                for layer in sourceFont.layers:
                    if gName in layer:
                        layer.removeGlyph(gName)
            sourceFont.newGlyph(gName)
            gDest = sourceFont[gName]
            gDest.appendGlyph(g)
            gDest.width = g.width
            gDest.unicode = g.unicode

            # Add anchors to the pointData so that they shift correctly
            # Use anchor + str(idx) as the ident
            for aIdx, anc in enumerate(gDest.anchors):
                ident = "anchor%s" % aIdx
                pos = list(anc.position)
                pointData[ident] = dict(x=pos[0], y=pos[1], z=0)

            # Decompose components in glyphs that were not bulit with Glyph Builder
            #   If the glyph has components, and if it's not marked with the "Glyph Builder Gray",
            #   it will need to be decomposed at this stage (but leave overlaps of course)
            #   Otherwise, glyphs that are gray will have their components reapplied after rotating with Glyph Builder.
            isComponent = False
            if not g.markColor == COMPOSITEGRAY:
                if len(gDest.components):
                    for c in gDest.components:
                        baseName = c.baseGlyph
                        masterBaseGlyph = masterFont[baseName]
                        # Decomposing from the master font because the base glyph might not be in the source font yet
                        for mc in masterBaseGlyph.contours:
                            gDest.appendContour(mc, offset=c.offset)
                        gDest.removeComponent(c)
                        # ...and copy over point data, taking into account the component offset
                        if baseName in glyphPointData:
                            basePointData = copy.deepcopy(
                                glyphPointData[baseName])
                            for ident in basePointData:
                                pointData[ident] = dict(
                                    x=basePointData[ident]["x"] + c.offset[0],
                                    y=basePointData[ident]["y"] + c.offset[1],
                                    z=basePointData[ident]["z"])
                        isComponent = True

            # Flatten the depth
            if "DPTH" in sourceInfo["loc"].keys():
                for ident in pointData:
                    pointData[ident]["z"] *= (sourceInfo["loc"]["DPTH"] * 0.01
                                              )  # Scale by the depth value

            # Shift the "z" value by an offset
            if not zOffset == None:
                for ident in pointData:
                    if not "anchor" in ident:  # ...but don't shift the anchors, the components are already shifted
                        pointData[ident]["z"] += zOffset

            # Extend the shadow
            if "SANG" in sourceInfo["loc"].keys():
                if sourceInfo["loc"]["SANG"] == -45:
                    shadowDirection = "left"
                else:
                    shadowDirection = "right"
                finalShadowLengthFactor = (sourceInfo["loc"]["SLEN"] *
                                           0.01) * shadowLengthFactor
                pointData = flattenShadow(gDest, pointData, shadowDirection,
                                          finalShadowLengthFactor)

            # Rotate the glyph
            # Merge the location and the nudgeLoc, if there is one
            marginChange, pointData = rotateGlyphPointData(
                gDest, rotateLoc, pointData)

            # Move the contour points into the correct position
            for c in gDest.contours:
                for pt in c.points:
                    ident = getIdent(pt)
                    if ident in pointData:
                        pt.x = pointData[ident]["x"]
                        pt.y = pointData[ident]["y"]

            # Move the anchors into the correct position
            for aIdx, anc in enumerate(gDest.anchors):
                ident = "anchor%s" % aIdx
                if ident in pointData:
                    anc.position = (int(round(pointData[ident]["x"])),
                                    int(round(pointData[ident]["y"])))

            # Shift the glyph to take in the sidebearings
            gDest.moveBy((-marginChange[0], 0))
            gDest.width -= marginChange[0] * 2

            if doForceSmooth and not isComponent:
                # If a bPoint was a smooth curve point in the original glyph,
                # force the related bPoint in the rotated glyph to be smooth
                for cIdx, c in enumerate(gDest.contours):
                    for bptIdx, thisBPt in enumerate(c.bPoints):
                        sourceBPt = g.contours[cIdx].bPoints[bptIdx]
                        if sourceBPt.type == "curve":
                            forceSmooth(thisBPt)

            # Round the point coordinates before outlining
            gDest.round()
            gDest.changed()

            # Outline the glyph
            if outlineAmount:
                outlineGlyph(sourceFont,
                             gDest,
                             outlineAmount,
                             alwaysConnect=alwaysConnect,
                             cap=cap,
                             connection=connection)

                # Round the point coordinates again, now that it's outlined
                gDest.round()
                gDest.changed()

            # Update
            #gDest.changed()

        # Resort the font
        sourceFont.glyphOrder = masterFont.glyphOrder

        # Copy the kerning
        sourceFont.groups.update(copy.deepcopy(masterFont.groups))
        sourceFont.kerning.update(copy.deepcopy(masterFont.kerning))

        # Copy the features
        sourceFont.features.text = masterFont.features.text

        # Done, save
        sourceFont.changed()
        sourceFont.save()
    """ New DesignSpaceDocument """

    designSpace = DesignSpaceDocument()
    designSpaceDocFilename = os.path.splitext(
        masterFileName)[0] + ".designspace"
    designSpaceDocPath = os.path.join(destPath, designSpaceDocFilename)
    """ Axis Descriptors """

    for tag in sourceCombinations[0]["loc"].keys():
        a = AxisDescriptor()
        a.minimum = AXISINFO[tag]["minimum"]
        a.maximum = AXISINFO[tag]["maximum"]
        a.default = AXISINFO[tag]["default"]
        a.name = AXISINFO[tag]["name"]
        a.tag = tag
        a.labelNames[u'en'] = AXISINFO[tag]["name"]
        designSpace.addAxis(a)
    """ Source Descriptors """

    for sourceInfo in sourceCombinations:
        sourceUfoPath = os.path.join(destPath, sourceInfo["fileName"])
        # Make a source description
        s = SourceDescriptor()
        s.path = sourceUfoPath
        s.name = os.path.splitext(sourceInfo["fileName"])[0]
        #s.font = defcon.Font(s.name)
        s.copyLib = True
        s.copyInfo = True
        s.copyInfoures = True
        s.familyName = masterFont.info.familyName
        s.styleName = s.name
        # Convert the loc from tags to names
        loc = {}
        for tag, value in sourceInfo["loc"].items():
            axisName = AXISINFO[tag]["name"]
            loc[axisName] = value
        s.location = loc
        designSpace.addSource(s)

    designSpace.write(designSpaceDocPath)
예제 #10
0
def test_fill_document(tmpdir):
    tmpdir = str(tmpdir)
    testDocPath = os.path.join(tmpdir, "test.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")
    instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo")
    doc = DesignSpaceDocument()

    # write some axes
    a1 = AxisDescriptor()
    a1.minimum = 0
    a1.maximum = 1000
    a1.default = 0
    a1.name = "weight"
    a1.tag = "wght"
    # note: just to test the element language, not an actual label name recommendations.
    a1.labelNames[u'fa-IR'] = u"قطر"
    a1.labelNames[u'en'] = u"Wéíght"
    doc.addAxis(a1)
    a2 = AxisDescriptor()
    a2.minimum = 0
    a2.maximum = 1000
    a2.default = 15
    a2.name = "width"
    a2.tag = "wdth"
    a2.map = [(0.0, 10.0), (15.0, 20.0), (401.0, 66.0), (1000.0, 990.0)]
    a2.hidden = True
    a2.labelNames[u'fr'] = u"Chasse"
    doc.addAxis(a2)

    # add master 1
    s1 = SourceDescriptor()
    s1.filename = os.path.relpath(masterPath1, os.path.dirname(testDocPath))
    assert s1.font is None
    s1.name = "master.ufo1"
    s1.copyLib = True
    s1.copyInfo = True
    s1.copyFeatures = True
    s1.location = dict(weight=0)
    s1.familyName = "MasterFamilyName"
    s1.styleName = "MasterStyleNameOne"
    s1.mutedGlyphNames.append("A")
    s1.mutedGlyphNames.append("Z")
    doc.addSource(s1)
    # add master 2
    s2 = SourceDescriptor()
    s2.filename = os.path.relpath(masterPath2, os.path.dirname(testDocPath))
    s2.name = "master.ufo2"
    s2.copyLib = False
    s2.copyInfo = False
    s2.copyFeatures = False
    s2.muteKerning = True
    s2.location = dict(weight=1000)
    s2.familyName = "MasterFamilyName"
    s2.styleName = "MasterStyleNameTwo"
    doc.addSource(s2)
    # add master 3 from a different layer
    s3 = SourceDescriptor()
    s3.filename = os.path.relpath(masterPath2, os.path.dirname(testDocPath))
    s3.name = "master.ufo2"
    s3.copyLib = False
    s3.copyInfo = False
    s3.copyFeatures = False
    s3.muteKerning = False
    s3.layerName = "supports"
    s3.location = dict(weight=1000)
    s3.familyName = "MasterFamilyName"
    s3.styleName = "Supports"
    doc.addSource(s3)
    # add instance 1
    i1 = InstanceDescriptor()
    i1.filename = os.path.relpath(instancePath1, os.path.dirname(testDocPath))
    i1.familyName = "InstanceFamilyName"
    i1.styleName = "InstanceStyleName"
    i1.name = "instance.ufo1"
    i1.location = dict(weight=500, spooky=666)  # this adds a dimension that is not defined.
    i1.postScriptFontName = "InstancePostscriptName"
    i1.styleMapFamilyName = "InstanceStyleMapFamilyName"
    i1.styleMapStyleName = "InstanceStyleMapStyleName"
    glyphData = dict(name="arrow", mute=True, unicodes=[0x123, 0x124, 0x125])
    i1.glyphs['arrow'] = glyphData
    i1.lib['com.coolDesignspaceApp.binaryData'] = plistlib.Data(b'<binary gunk>')
    i1.lib['com.coolDesignspaceApp.specimenText'] = "Hamburgerwhatever"
    doc.addInstance(i1)
    # add instance 2
    i2 = InstanceDescriptor()
    i2.filename = os.path.relpath(instancePath2, os.path.dirname(testDocPath))
    i2.familyName = "InstanceFamilyName"
    i2.styleName = "InstanceStyleName"
    i2.name = "instance.ufo2"
    # anisotropic location
    i2.location = dict(weight=500, width=(400,300))
    i2.postScriptFontName = "InstancePostscriptName"
    i2.styleMapFamilyName = "InstanceStyleMapFamilyName"
    i2.styleMapStyleName = "InstanceStyleMapStyleName"
    glyphMasters = [dict(font="master.ufo1", glyphName="BB", location=dict(width=20,weight=20)), dict(font="master.ufo2", glyphName="CC", location=dict(width=900,weight=900))]
    glyphData = dict(name="arrow", unicodes=[101, 201, 301])
    glyphData['masters'] = glyphMasters
    glyphData['note'] = "A note about this glyph"
    glyphData['instanceLocation'] = dict(width=100, weight=120)
    i2.glyphs['arrow'] = glyphData
    i2.glyphs['arrow2'] = dict(mute=False)
    doc.addInstance(i2)

    doc.filename = "suggestedFileName.designspace"
    doc.lib['com.coolDesignspaceApp.previewSize'] = 30

    # write some rules
    r1 = RuleDescriptor()
    r1.name = "named.rule.1"
    r1.conditionSets.append([
        dict(name='axisName_a', minimum=0, maximum=1),
        dict(name='axisName_b', minimum=2, maximum=3)
    ])
    r1.subs.append(("a", "a.alt"))
    doc.addRule(r1)
    # write the document
    doc.write(testDocPath)
    assert os.path.exists(testDocPath)
    assert_equals_test_file(testDocPath, 'data/test.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.lib == doc.lib
    assert new.instances[0].lib == doc.instances[0].lib

    # test roundtrip for the axis attributes and data
    axes = {}
    for axis in doc.axes:
        if axis.tag not in axes:
            axes[axis.tag] = []
        axes[axis.tag].append(axis.serialize())
    for axis in new.axes:
        if axis.tag[0] == "_":
            continue
        if axis.tag not in axes:
            axes[axis.tag] = []
        axes[axis.tag].append(axis.serialize())
    for v in axes.values():
        a, b = v
        assert a == b
예제 #11
0
from mojo.UI import AskString
from fontTools.designspaceLib import DesignSpaceDocument
"""
Remove glyphs from designspace rules
2019_11_28 Benedikt Bramboeck, Alphabet

heavily based on https://github.com/arrowtype/varfont-prep/blob/master/remove-list-of-glyphs.py
"""

glyphsToDelete = AskString(
    'Input glyphnames to remove, then select designspace').replace(
        "'", "").replace(",", "").split(" ")

instruction = f"select designspace to remove {glyphsToDelete} from"

docPath = getFile(instruction,
                  allowsMultipleSelection=False,
                  fileTypes=["designspace"])

doc = DesignSpaceDocument()
doc.read(*docPath)

for gName in glyphsToDelete:
    for rule in doc.rules:
        newSubs = []
        for sub in rule.subs:
            if gName not in sub:
                newSubs.append(sub)
        rule.subs = newSubs
doc.write(*docPath)
예제 #12
0
def test_rulesDocument(tmpdir):
    # tests of rules in a document, roundtripping.
    tmpdir = str(tmpdir)
    testDocPath = os.path.join(tmpdir, "testRules.designspace")
    testDocPath2 = os.path.join(tmpdir, "testRules_roundtrip.designspace")
    doc = DesignSpaceDocument()
    a1 = AxisDescriptor()
    a1.minimum = 0
    a1.maximum = 1000
    a1.default = 0
    a1.name = "axisName_a"
    a1.tag = "TAGA"
    b1 = AxisDescriptor()
    b1.minimum = 2000
    b1.maximum = 3000
    b1.default = 2000
    b1.name = "axisName_b"
    b1.tag = "TAGB"
    doc.addAxis(a1)
    doc.addAxis(b1)
    r1 = RuleDescriptor()
    r1.name = "named.rule.1"
    r1.conditionSets.append([
        dict(name='axisName_a', minimum=0, maximum=1000),
        dict(name='axisName_b', minimum=0, maximum=3000)
    ])
    r1.subs.append(("a", "a.alt"))
    # rule with minium and maximum
    doc.addRule(r1)
    assert len(doc.rules) == 1
    assert len(doc.rules[0].conditionSets) == 1
    assert len(doc.rules[0].conditionSets[0]) == 2
    assert _axesAsDict(doc.axes) == {
        'axisName_a': {
            'map': [],
            'name': 'axisName_a',
            'default': 0,
            'minimum': 0,
            'maximum': 1000,
            'tag': 'TAGA'
        },
        'axisName_b': {
            'map': [],
            'name': 'axisName_b',
            'default': 2000,
            'minimum': 2000,
            'maximum': 3000,
            'tag': 'TAGB'
        }
    }
    assert doc.rules[0].conditionSets == [[{
        'minimum': 0,
        'maximum': 1000,
        'name': 'axisName_a'
    }, {
        'minimum': 0,
        'maximum': 3000,
        'name': 'axisName_b'
    }]]
    assert doc.rules[0].subs == [('a', 'a.alt')]
    doc.normalize()
    assert doc.rules[0].name == 'named.rule.1'
    assert doc.rules[0].conditionSets == [[{
        'minimum': 0.0,
        'maximum': 1.0,
        'name': 'axisName_a'
    }, {
        'minimum': 0.0,
        'maximum': 1.0,
        'name': 'axisName_b'
    }]]
    # still one conditionset
    assert len(doc.rules[0].conditionSets) == 1
    doc.write(testDocPath)
    # add a stray conditionset
    _addUnwrappedCondition(testDocPath)
    doc2 = DesignSpaceDocument()
    doc2.read(testDocPath)
    assert len(doc2.axes) == 2
    assert len(doc2.rules) == 1
    assert len(doc2.rules[0].conditionSets) == 2
    doc2.write(testDocPath2)
    # verify these results
    # make sure the stray condition is now neatly wrapped in a conditionset.
    doc3 = DesignSpaceDocument()
    doc3.read(testDocPath2)
    assert len(doc3.rules) == 1
    assert len(doc3.rules[0].conditionSets) == 2
예제 #13
0
    def test_superpolator_testdoc1():
        # read superpolator_testdoc1.sp3
        # and test all the values
        testDoc = DesignSpaceDocument()
        testPath = "../../Tests/spReader_testdocs/superpolator_testdoc1.sp3"
        reader = SuperpolatorReader(testPath, testDoc)
        reader.read()

        # check the axes
        names = [a.name for a in reader.documentObject.axes]
        names.sort()
        assert names == ['grade', 'space', 'weight', 'width']
        tags = [a.tag for a in reader.documentObject.axes]
        tags.sort()
        assert tags == ['SPCE', 'grad', 'wdth', 'wght']

        # check the data items
        assert superpolatorDataLibKey in reader.documentObject.lib
        items = list(reader.documentObject.lib[superpolatorDataLibKey].items())
        items.sort()
        assert items == [('expandRules', False),
                         ('horizontalPreviewAxis', 'width'),
                         ('includeLegacyRules', False),
                         ('instancefolder', 'instances'),
                         ('keepWorkFiles', True), ('lineInverted', True),
                         ('lineStacked', 'lined'), ('lineViewFilled', True),
                         ('outputFormatUFO', 3.0), ('previewtext', 'VA'),
                         ('roundGeometry', False),
                         ('verticalPreviewAxis', 'weight')]

        # check the sources
        for sd in reader.documentObject.sources:
            assert sd.familyName == "MutatorMathTest_SourceFamilyName"
            if sd.styleName == "Default":
                assert sd.location == {
                    'width': 0.0,
                    'weight': 0.0,
                    'space': 0.0,
                    'grade': -0.5
                }
                assert sd.copyLib == True
                assert sd.copyGroups == True
                assert sd.copyInfo == True
                assert sd.copyFeatures == True
            elif sd.styleName == "TheOther":
                assert sd.location == {
                    'width': 0.0,
                    'weight': 1000.0,
                    'space': 0.0,
                    'grade': -0.5
                }
                assert sd.copyLib == False
                assert sd.copyGroups == False
                assert sd.copyInfo == False
                assert sd.copyFeatures == False

        # check the instances
        for nd in reader.documentObject.instances:
            assert nd.familyName == "MutatorMathTest_InstanceFamilyName"
            if nd.styleName == "AWeightThatILike":
                assert nd.location == {
                    'width': 133.152174,
                    'weight': 723.981097,
                    'space': 0.0,
                    'grade': -0.5
                }
                assert nd.filename == "instances/MutatorMathTest_InstanceFamilyName-AWeightThatILike.ufo"
                assert nd.styleMapFamilyName == None
                assert nd.styleMapStyleName == None
            if nd.styleName == "wdth759.79_SPCE0.00_wght260.72":
                # note the converted anisotropic location in the width axis.
                assert nd.location == {
                    'grade': -0.5,
                    'width': 500.0,
                    'weight': 260.7217,
                    'space': 0.0
                }
                assert nd.filename == "instances/MutatorMathTest_InstanceFamilyName-wdth759.79_SPCE0.00_wght260.72.ufo"
                assert nd.styleMapFamilyName == "StyleMappedFamily"
                assert nd.styleMapStyleName == "bold"

        # check the rules
        for rd in reader.documentObject.rules:
            assert rd.name == "width: < 500.0"
            assert len(rd.conditionSets) == 1
            assert rd.subs == [('I', 'I.narrow')]
            for conditionSet in rd.conditionSets:
                for cd in conditionSet:
                    if cd['name'] == "width":
                        assert cd == {
                            'minimum': None,
                            'maximum': 500.0,
                            'name': 'width'
                        }
                    if cd['name'] == "grade":
                        assert cd == {
                            'minimum': 0.0,
                            'maximum': 500.0,
                            'name': 'grade'
                        }

        testDoc.write(
            testPath.replace(".sp3", "_output_roundtripped.designspace"))
 def designSpace(self):
     designspace = DesignSpaceDocument()
     designspace.read(self.designspace_path)
     return designspace
예제 #15
0
 def test_method(self, filename=filename):
     doc = DesignSpaceDocument(writerClass=InMemoryDocWriter)
     doc.read(filename)
     self.assertDesignspaceRoundtrip(doc)