def test_warn_diff_between_designspace_and_ufos(caplog): ufo = defcon.Font() ufo.info.familyName = 'UFO Family Name' ufo.info.styleName = 'UFO Style Name' # ufo.info.styleMapFamilyName = 'UFO Stylemap Family Name' # ufo.info.styleMapStyleName = 'bold' doc = DesignSpaceDocument() source = doc.newSourceDescriptor() source.font = ufo source.familyName = 'DS Family Name' source.styleName = 'DS Style Name' doc.addSource(source) font = to_glyphs(doc, minimize_ufo_diffs=True) assert any(record.levelname == 'WARNING' for record in caplog.records) assert 'The familyName is different between the UFO and the designspace source' in caplog.text assert 'The styleName is different between the UFO and the designspace source' in caplog.text doc = to_designspace(font) source = doc.sources[0] # The UFO info will prevail assert ufo.info.familyName == 'UFO Family Name' assert ufo.info.styleName == 'UFO Style Name' assert source.font.info.familyName == 'UFO Family Name' assert source.font.info.styleName == 'UFO Style Name'
def buildVF(font, opts): for instance in font.instances: print(f" MASTER {instance.name}") build(instance, opts) if instance.name == "Regular": regular = instance ds = DesignSpaceDocument() for axisDef in font.customParameters["Axes"]: axis = ds.newAxisDescriptor() axis.tag = axisDef["Tag"] axis.name = axisDef["Name"] axis.maximum = max(i.axes[axis.tag] for i in font.instances) axis.minimum = min(i.axes[axis.tag] for i in font.instances) axis.default = regular.axes[axis.tag] ds.addAxis(axis) for instance in font.instances: source = ds.newSourceDescriptor() source.font = instance.font source.familyName = instance.familyName source.styleName = instance.name source.name = instance.fullName source.location = {a.name: instance.axes[a.tag] for a in ds.axes} ds.addSource(source) print(f" MERGE {font.familyName}") otf, _, _ = merge(ds) return otf
def test_designspace_source_locations(tmpdir): """Check that opening UFOs from their source descriptor works with both the filename and the path attributes. """ designspace_path = os.path.join(str(tmpdir), 'test.designspace') light_ufo_path = os.path.join(str(tmpdir), 'light.ufo') bold_ufo_path = os.path.join(str(tmpdir), 'bold.ufo') designspace = DesignSpaceDocument() light_source = designspace.newSourceDescriptor() light_source.filename = 'light.ufo' designspace.addSource(light_source) bold_source = designspace.newSourceDescriptor() bold_source.path = bold_ufo_path designspace.addSource(bold_source) designspace.write(designspace_path) light = defcon.Font() light.info.ascender = 30 light.save(light_ufo_path) bold = defcon.Font() bold.info.ascender = 40 bold.save(bold_ufo_path) designspace = DesignSpaceDocument() designspace.read(designspace_path) font = to_glyphs(designspace) assert len(font.masters) == 2 assert font.masters[0].ascender == 30 assert font.masters[1].ascender == 40
def test_warn_diff_between_designspace_and_ufos(caplog, ufo_module): ufo = ufo_module.Font() ufo.info.familyName = "UFO Family Name" ufo.info.styleName = "UFO Style Name" # ufo.info.styleMapFamilyName = 'UFO Stylemap Family Name' # ufo.info.styleMapStyleName = 'bold' doc = DesignSpaceDocument() source = doc.newSourceDescriptor() source.font = ufo source.familyName = "DS Family Name" source.styleName = "DS Style Name" doc.addSource(source) font = to_glyphs(doc, minimize_ufo_diffs=True) assert any(record.levelname == "WARNING" for record in caplog.records) assert ( "The familyName is different between the UFO and the designspace source" in caplog.text) assert ( "The styleName is different between the UFO and the designspace source" in caplog.text) doc = to_designspace(font, ufo_module=ufo_module) source = doc.sources[0] # The UFO info will prevail assert ufo.info.familyName == "UFO Family Name" assert ufo.info.styleName == "UFO Style Name" assert source.font.info.familyName == "UFO Family Name" assert source.font.info.styleName == "UFO Style Name"
def test_unicodes(tmpdir): tmpdir = str(tmpdir) testDocPath = os.path.join(tmpdir, "testUnicodes.designspace") testDocPath2 = os.path.join(tmpdir, "testUnicodes_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.name = "instance.ufo1" i1.location = dict(weight=500) glyphData = dict(name="arrow", mute=True, unicodes=[100, 200, 300]) 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" doc.addAxis(a1) # write the document doc.write(testDocPath) assert os.path.exists(testDocPath) # import it again new = DesignSpaceDocument() new.read(testDocPath) new.write(testDocPath2) # compare the file contents 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 # check the unicode values read from the document assert new.instances[0].glyphs['arrow']['unicodes'] == [100, 200, 300]
def test_unicodes(tmpdir): tmpdir = str(tmpdir) testDocPath = os.path.join(tmpdir, "testUnicodes.designspace") testDocPath2 = os.path.join(tmpdir, "testUnicodes_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.name = "instance.ufo1" i1.location = dict(weight=500) glyphData = dict(name="arrow", mute=True, unicodes=[100, 200, 300]) 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" doc.addAxis(a1) # write the document doc.write(testDocPath) assert os.path.exists(testDocPath) # import it again new = DesignSpaceDocument() new.read(testDocPath) new.write(testDocPath2) # compare the file contents with open(testDocPath, 'r', encoding='utf-8') as f1: t1 = f1.read() with open(testDocPath2, 'r', encoding='utf-8') as f2: t2 = f2.read() assert t1 == t2 # check the unicode values read from the document assert new.instances[0].glyphs['arrow']['unicodes'] == [100,200,300]
def test_load_masters_layerName_without_required_font(): ds = DesignSpaceDocument() s = SourceDescriptor() s.font = None s.layerName = "Medium" ds.addSource(s) with pytest.raises( AttributeError, match="specified a layer name but lacks the required TTFont object", ): load_masters(ds)
def test_adjustAxisDefaultToNeutral(tmpdir): tmpdir = str(tmpdir) testDocPath = os.path.join(tmpdir, "testAdjustAxisDefaultToNeutral.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.copyFeatures = True s1.location = dict(weight=55, width=1000) doc.addSource(s1) # write some axes a1 = AxisDescriptor() a1.minimum = 0 a1.maximum = 1000 a1.default = 0 # the wrong value a1.name = "weight" a1.tag = "wght" doc.addAxis(a1) a2 = AxisDescriptor() a2.minimum = -10 a2.maximum = 10 a2.default = 0 # the wrong value a2.name = "width" a2.tag = "wdth" doc.addAxis(a2) # write the document doc.write(testDocPath) assert os.path.exists(testDocPath) # import it again new = DesignSpaceDocument() new.read(testDocPath) new.check() loc = new.default.location for axisObj in new.axes: n = axisObj.name assert axisObj.default == loc.get(n)
def test_designspace_source_locations(tmpdir, ufo_module): """Check that opening UFOs from their source descriptor works with both the filename and the path attributes. """ designspace_path = os.path.join(str(tmpdir), "test.designspace") light_ufo_path = os.path.join(str(tmpdir), "light.ufo") bold_ufo_path = os.path.join(str(tmpdir), "bold.ufo") designspace = DesignSpaceDocument() wght = AxisDescriptor() wght.minimum = 100 wght.maximum = 700 wght.default = 100 wght.name = "Weight" wght.tag = "wght" designspace.addAxis(wght) light_source = designspace.newSourceDescriptor() light_source.filename = "light.ufo" light_source.location = {"Weight": 100} designspace.addSource(light_source) bold_source = designspace.newSourceDescriptor() bold_source.path = bold_ufo_path bold_source.location = {"Weight": 700} designspace.addSource(bold_source) designspace.write(designspace_path) light = ufo_module.Font() light.info.ascender = 30 light.save(light_ufo_path) bold = ufo_module.Font() bold.info.ascender = 40 bold.save(bold_ufo_path) designspace = DesignSpaceDocument() designspace.read(designspace_path) font = to_glyphs(designspace, ufo_module=ufo_module) assert len(font.masters) == 2 assert font.masters[0].ascender == 30 assert font.masters[1].ascender == 40
def test_updatePaths(tmpdir): doc = DesignSpaceDocument() doc.path = str(tmpdir / "foo" / "bar" / "MyDesignspace.designspace") s1 = SourceDescriptor() doc.addSource(s1) doc.updatePaths() # expect no changes assert s1.path is None assert s1.filename is None name1 = "../masters/Source1.ufo" path1 = posix(str(tmpdir / "foo" / "masters" / "Source1.ufo")) s1.path = path1 s1.filename = None doc.updatePaths() assert s1.path == path1 assert s1.filename == name1 # empty filename updated name2 = "../masters/Source2.ufo" s1.filename = name2 doc.updatePaths() # conflicting filename discarded, path always gets precedence assert s1.path == path1 assert s1.filename == "../masters/Source1.ufo" s1.path = None s1.filename = name2 doc.updatePaths() # expect no changes assert s1.path is None assert s1.filename == name2
def buildVF(opts): font = GSFont(opts.glyphs) glyphOrder = buildAltGlyphs(font) prepare(font) for instance in font.instances: print(f" MASTER {instance.name}") build(instance, opts, glyphOrder) if instance.name == "Regular": regular = instance ds = DesignSpaceDocument() for i, axisDef in enumerate(font.axes): axis = ds.newAxisDescriptor() axis.tag = axisDef.axisTag axis.name = axisDef.name axis.maximum = max(x.axes[i] for x in font.instances) axis.minimum = min(x.axes[i] for x in font.instances) axis.default = regular.axes[i] ds.addAxis(axis) for instance in font.instances: source = ds.newSourceDescriptor() source.font = instance.font source.familyName = instance.familyName source.styleName = instance.name source.name = instance.fullName source.location = { a.name: instance.axes[i] for i, a in enumerate(ds.axes) } ds.addSource(source) print(f" MERGE {font.familyName}") otf, _, _ = merge(ds) subroutinize(otf) if not opts.debug: otf["post"].formatType = 3.0 return otf
def test_ufo_filename_is_kept_the_same(tmpdir, ufo_module): """Check that the filenames of existing UFOs are correctly written to the designspace document when doing UFOs -> Glyphs -> designspace. This only works when the option "minimize_ufo_diffs" is given, because keeping track of this information adds stuff to the Glyphs file. """ light_ufo_path = os.path.join(str(tmpdir), "light.ufo") bold_ufo_path = os.path.join(str(tmpdir), "subdir/bold.ufo") light = ufo_module.Font() light.info.ascender = 30 light.save(light_ufo_path) bold = ufo_module.Font() bold.info.ascender = 40 bold.save(bold_ufo_path) # First check: when going from UFOs -> Glyphs -> designspace font = to_glyphs([light, bold], minimize_ufo_diffs=True) designspace = to_designspace(font, ufo_module=ufo_module) assert designspace.sources[0].path == light_ufo_path assert designspace.sources[1].path == bold_ufo_path # Second check: going from designspace -> Glyphs -> designspace designspace_path = os.path.join(str(tmpdir), "test.designspace") designspace = DesignSpaceDocument() light_source = designspace.newSourceDescriptor() light_source.filename = "light.ufo" designspace.addSource(light_source) bold_source = designspace.newSourceDescriptor() bold_source.path = bold_ufo_path designspace.addSource(bold_source) designspace.write(designspace_path) font = to_glyphs([light, bold], minimize_ufo_diffs=True) assert designspace.sources[0].filename == "light.ufo" assert designspace.sources[1].filename == "subdir/bold.ufo"
def buildDesignSpace(sources, instances, axes): doc = DesignSpaceDocument() # build source descriptors from source list for source in sources: s = SourceDescriptor() s.path = source["path"] s.name = source["name"] s.copyInfo = source["copyInfo"] s.location = source["location"] s.familyName = source["familyName"] s.styleName = source["styleName"] doc.addSource(s) # build instance descriptors from instance list for instance in instances: i = InstanceDescriptor() i.location = instance["location"] i.familyName = instance["familyName"] i.styleName = instance["styleName"] i.path = instance["path"] i.postScriptFontName = instance["postScriptFontName"] i.styleMapFamilyName = instance["styleMapFamilyName"] i.styleMapStyleName = instance["styleMapStyleName"] doc.addInstance(i) # build axis descriptors from axis list for axis in axes: a = AxisDescriptor() a.minimum = axis["minimum"] a.maximum = axis["maximum"] a.default = axis["default"] a.name = axis["name"] a.tag = axis["tag"] for languageCode, labelName in axis["labelNames"].items(): a.labelNames[languageCode] = labelName a.map = axis["map"] doc.addAxis(a) return doc
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") a1 = AxisDescriptor() a1.tag = "TAGA" a1.name = "axisName_a" a1.minimum = 0 a1.maximum = 1000 a1.default = 0 # Case 1: filename and path are both empty. Nothing to calculate, nothing to put in the file. doc = DesignSpaceDocument() doc.addAxis(a1) 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() doc.addAxis(a1) 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() doc.addAxis(a1) 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() doc.addAxis(a1) 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() doc.addAxis(a1) 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() doc.addAxis(a1) 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"
def test_check(invalid_designspace, tmpdir): tmpdir = str(tmpdir) # check if the checks are checking testDocPath = os.path.join(tmpdir, invalid_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") # no default selected doc = DesignSpaceDocument() # add master 1 s1 = SourceDescriptor() s1.path = masterPath1 s1.name = "master.ufo1" s1.location = dict(snap=0, pop=10) s1.familyName = "MasterFamilyName" s1.styleName = "MasterStyleNameOne" doc.addSource(s1) # add master 2 s2 = SourceDescriptor() s2.path = masterPath2 s2.name = "master.ufo2" s2.location = dict(snap=1000, pop=20) s2.familyName = "MasterFamilyName" s2.styleName = "MasterStyleNameTwo" doc.addSource(s2) doc.checkAxes() doc.getAxisOrder() == ['snap', 'pop'] assert doc.default == None doc.checkDefault() assert doc.default.name == 'master.ufo1' # default selected doc = DesignSpaceDocument() # add master 1 s1 = SourceDescriptor() s1.path = masterPath1 s1.name = "master.ufo1" s1.location = dict(snap=0, pop=10) s1.familyName = "MasterFamilyName" s1.styleName = "MasterStyleNameOne" doc.addSource(s1) # add master 2 s2 = SourceDescriptor() s2.path = masterPath2 s2.name = "master.ufo2" s2.copyInfo = True s2.location = dict(snap=1000, pop=20) s2.familyName = "MasterFamilyName" s2.styleName = "MasterStyleNameTwo" doc.addSource(s2) doc.checkAxes() assert doc.getAxisOrder() == ['snap', 'pop'] assert doc.default == None doc.checkDefault() assert doc.default.name == 'master.ufo2' # generate a doc without axes, save and read again doc = DesignSpaceDocument() # add master 1 s1 = SourceDescriptor() s1.path = masterPath1 s1.name = "master.ufo1" s1.location = dict(snap=0, pop=10) s1.familyName = "MasterFamilyName" s1.styleName = "MasterStyleNameOne" doc.addSource(s1) # add master 2 s2 = SourceDescriptor() s2.path = masterPath2 s2.name = "master.ufo2" s2.location = dict(snap=1000, pop=20) s2.familyName = "MasterFamilyName" s2.styleName = "MasterStyleNameTwo" doc.addSource(s2) doc.checkAxes() doc.write(testDocPath) __removeAxesFromDesignSpace(testDocPath) new = DesignSpaceDocument() new.read(testDocPath) assert len(new.axes) == 2 new.checkAxes() assert len(new.axes) == 2 assert print([a.name for a in new.axes]) == ['snap', 'pop'] new.write(testDocPath)
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
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.conditionSets.append([ dict(name='weight', minimum=200, maximum=500), dict(name='width', minimum=0, maximum=150) ]) 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) with open(testDocPath, 'r', encoding='utf-8') as f1: t1 = f1.read() with open(testDocPath2, 'r', encoding='utf-8') as f2: t2 = f2.read() assert t1 == t2
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) verify = DesignSpaceDocument() verify.read(testDocPath) verify.write(testDocPath2)
#--------- # masters #--------- s0 = SourceDescriptor() s0.path = "PaperOn.ufo" s0.name = "master.PaperFont.PaperOn.0" s0.familyName = familyName s0.styleName = "Light" s0.location = dict(weight=0, width=0) s0.copyLib = True s0.copyInfo = True s0.copyGroups = True s0.copyFeatures = True doc.addSource(s0) s1 = SourceDescriptor() s1.path = "PaperOff.ufo" s0.name = "master.PaperFont.PaperOff.0" s1.familyName = familyName s1.styleName = "Bold" s1.location = dict(weight=1000, width=0) doc.addSource(s1) #-------- # saving #-------- path = os.path.join(root, "PaperFont.designspace") doc.write(path)
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 = 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 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
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)
def _extractSubSpace( doc: DesignSpaceDocument, userRegion: Region, *, keepVFs: bool, makeNames: bool, expandLocations: bool, makeInstanceFilename: MakeInstanceFilenameCallable, ) -> DesignSpaceDocument: subDoc = DesignSpaceDocument() # Don't include STAT info # FIXME: (Jany) let's think about it. Not include = OK because the point of # the splitting is to build VFs and we'll use the STAT data of the full # document to generate the STAT of the VFs, so "no need" to have STAT data # in sub-docs. Counterpoint: what if someone wants to split this DS for # other purposes? Maybe for that it would be useful to also subset the STAT # data? # subDoc.elidedFallbackName = doc.elidedFallbackName def maybeExpandDesignLocation(object): if expandLocations: return object.getFullDesignLocation(doc) else: return object.designLocation for axis in doc.axes: range = userRegion[axis.name] if isinstance(range, Range) and hasattr(axis, "minimum"): # Mypy doesn't support narrowing union types via hasattr() # TODO(Python 3.10): use TypeGuard # https://mypy.readthedocs.io/en/stable/type_narrowing.html axis = cast(AxisDescriptor, axis) subDoc.addAxis( AxisDescriptor( # Same info tag=axis.tag, name=axis.name, labelNames=axis.labelNames, hidden=axis.hidden, # Subset range minimum=max(range.minimum, axis.minimum), default=range.default or axis.default, maximum=min(range.maximum, axis.maximum), map=[ (user, design) for user, design in axis.map if range.minimum <= user <= range.maximum ], # Don't include STAT info axisOrdering=None, axisLabels=None, ) ) # Don't include STAT info # subDoc.locationLabels = doc.locationLabels # Rules: subset them based on conditions designRegion = userRegionToDesignRegion(doc, userRegion) subDoc.rules = _subsetRulesBasedOnConditions(doc.rules, designRegion) subDoc.rulesProcessingLast = doc.rulesProcessingLast # Sources: keep only the ones that fall within the kept axis ranges for source in doc.sources: if not locationInRegion(doc.map_backward(source.designLocation), userRegion): continue subDoc.addSource( SourceDescriptor( filename=source.filename, path=source.path, font=source.font, name=source.name, designLocation=_filterLocation( userRegion, maybeExpandDesignLocation(source) ), layerName=source.layerName, familyName=source.familyName, styleName=source.styleName, muteKerning=source.muteKerning, muteInfo=source.muteInfo, mutedGlyphNames=source.mutedGlyphNames, ) ) # Copy family name translations from the old default source to the new default vfDefault = subDoc.findDefault() oldDefault = doc.findDefault() if vfDefault is not None and oldDefault is not None: vfDefault.localisedFamilyName = oldDefault.localisedFamilyName # Variable fonts: keep only the ones that fall within the kept axis ranges if keepVFs: # Note: call getVariableFont() to make the implicit VFs explicit for vf in doc.getVariableFonts(): vfUserRegion = getVFUserRegion(doc, vf) if regionInRegion(vfUserRegion, userRegion): subDoc.addVariableFont( VariableFontDescriptor( name=vf.name, filename=vf.filename, axisSubsets=[ axisSubset for axisSubset in vf.axisSubsets if isinstance(userRegion[axisSubset.name], Range) ], lib=vf.lib, ) ) # Instances: same as Sources + compute missing names for instance in doc.instances: if not locationInRegion(instance.getFullUserLocation(doc), userRegion): continue if makeNames: statNames = getStatNames(doc, instance.getFullUserLocation(doc)) familyName = instance.familyName or statNames.familyNames.get("en") styleName = instance.styleName or statNames.styleNames.get("en") subDoc.addInstance( InstanceDescriptor( filename=instance.filename or makeInstanceFilename(doc, instance, statNames), path=instance.path, font=instance.font, name=instance.name or f"{familyName} {styleName}", userLocation={} if expandLocations else instance.userLocation, designLocation=_filterLocation( userRegion, maybeExpandDesignLocation(instance) ), familyName=familyName, styleName=styleName, postScriptFontName=instance.postScriptFontName or statNames.postScriptFontName, styleMapFamilyName=instance.styleMapFamilyName or statNames.styleMapFamilyNames.get("en"), styleMapStyleName=instance.styleMapStyleName or statNames.styleMapStyleName, localisedFamilyName=instance.localisedFamilyName or statNames.familyNames, localisedStyleName=instance.localisedStyleName or statNames.styleNames, localisedStyleMapFamilyName=instance.localisedStyleMapFamilyName or statNames.styleMapFamilyNames, localisedStyleMapStyleName=instance.localisedStyleMapStyleName or {}, lib=instance.lib, ) ) else: subDoc.addInstance( InstanceDescriptor( filename=instance.filename, path=instance.path, font=instance.font, name=instance.name, userLocation={} if expandLocations else instance.userLocation, designLocation=_filterLocation( userRegion, maybeExpandDesignLocation(instance) ), familyName=instance.familyName, styleName=instance.styleName, postScriptFontName=instance.postScriptFontName, styleMapFamilyName=instance.styleMapFamilyName, styleMapStyleName=instance.styleMapStyleName, localisedFamilyName=instance.localisedFamilyName, localisedStyleName=instance.localisedStyleName, localisedStyleMapFamilyName=instance.localisedStyleMapFamilyName, localisedStyleMapStyleName=instance.localisedStyleMapStyleName, lib=instance.lib, ) ) subDoc.lib = doc.lib return subDoc
def 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)) 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 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) # 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"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.check() assert new.default.location == {'width': 20.0, 'weight': 0.0} # >>> 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
#--------- # masters #--------- s0 = SourceDescriptor() s0.path = "MutatorSansLightCondensed.ufo" s0.name = "master.MutatorSansTest.LightCondensed.0" s0.familyName = familyName s0.styleName = "LightCondensed" s0.location = dict(weight=0, width=0) s0.copyLib = True s0.copyInfo = True s0.copyGroups = True s0.copyFeatures = True doc.addSource(s0) s1 = SourceDescriptor() s1.path = "MutatorSansBoldCondensed.ufo" s1.name = "master.MutatorSansTest.BoldCondensed.1" s1.familyName = familyName s1.styleName = "BoldCondensed" s1.location = dict(weight=1000, width=0) doc.addSource(s1) s2 = SourceDescriptor() s2.path = "MutatorSansLightWide.ufo" s2.name = "master.MutatorSansTest.LightWide.2" s2.familyName = familyName s2.styleName = "LightWide" s2.location = dict(weight=0, width=1000)
else: blackUfo = newUfo font = OpenFont(newUfo) tweakSpacing(font, adjustments['max']['offset'], adjustments['max']['percentage']) font.save() if adjustments['max']['scaleFactor'] != 1: factor = adjustments['max']['scaleFactor'] print('Scaling %s by %s' % (font.path, factor)) scaleFont(font.path, font.path, factor) source.location = {'Weight': wght['max'], 'Optical size': opsz['min']} source.styleName = "BlackMin" doc.addSource(source) # removeAreas(font) doc.write(path) doc.write(minPath) minDoc = DesignSpaceDocument() minDoc.read(minPath) # resetting instances minDoc.instances = [] # Calculate location weightMin = wght.get('regular') - (wght.get('regular') - wght.get('min')) * weightCropIndex weightMax = wght.get('regular') + (wght.get('max') -