def test_with_with_path_object(tmpdir): import pathlib tmpdir = str(tmpdir) dest = pathlib.Path(tmpdir) / "test.designspace" doc = DesignSpaceDocument() doc.write(dest) assert dest.exists()
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 sp3_to_designspace(sp3path, designspacePath=None): if designspacePath is None: designspacePath = sp3path.replace(".sp3", ".designspace") doc = DesignSpaceDocument() reader = SuperpolatorReader(sp3path, doc) reader.read() doc.write(designspacePath)
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
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 main(): root = os.path.join(os.getcwd(), "build") if os.path.exists(root): shutil.rmtree(root) # Copy files from src/masters, as we need to edit them ignore = shutil.ignore_patterns( ".git", ".git*", "*designspaces*", "recursive-mono*.designspace", "recursive-sans*.designspace", "*varfontprep*", ) shutil.copytree("../src/masters", os.path.join(root, "src"), ignore=ignore) src = os.path.join(root, "src", "recursive-MONO_XPRN_wght_slnt_ital.designspace") static_root = os.path.join(root, "static") var_root = os.path.join(root, "var") doc = DesignSpaceDocument() doc.read(src) # Fix default value for weight axis in design space file #for axis in doc.axes: # if axis.name == "Weight": # axis.default = 300 # Still in memory, but need to save for other operations doc.write(src) # Make STAT table #makeSTAT(src, doc) # Sort glyph order # Variable font build #print("Building Variable fonts") # Fix default va # fontmake -m $DS -o variable --output-path $outputDir/$fontName--$date.ttf # Static font build print("Building Static fonts") buildFolders(doc, static_root) buildFontMenuDB(doc, static_root) buildInstances(src, static_root)
def test_designspace_lib_equivalent_to_font_user_data(tmpdir): designspace = DesignSpaceDocument() designspace.lib['designspaceLibKey1'] = 'designspaceLibValue1' # Save to disk and reload the designspace to test the write/read of lib path = os.path.join(str(tmpdir), 'test.designspace') designspace.write(path) designspace = DesignSpaceDocument() designspace.read(path) font = to_glyphs(designspace) assert font.userData['designspaceLibKey1'] == 'designspaceLibValue1' designspace = to_designspace(font) assert designspace.lib['designspaceLibKey1'] == 'designspaceLibValue1'
def test_incompleteRule(tmpdir): tmpdir = str(tmpdir) testDocPath1 = os.path.join(tmpdir, "testIncompleteRule.designspace") doc = DesignSpaceDocument() r1 = RuleDescriptor() r1.name = "incomplete.rule.1" r1.conditionSets.append([ dict(name='axisName_a', minimum=100), dict(name='axisName_b', maximum=200) ]) r1.subs.append(("c", "c.alt")) doc.addRule(r1) doc.write(testDocPath1) __removeConditionMinimumMaximumDesignSpace(testDocPath1) new = DesignSpaceDocument() with pytest.raises(DesignSpaceDocumentError) as excinfo: new.read(testDocPath1) assert "No minimum or maximum defined in rule" in str(excinfo.value)
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_documentLib(tmpdir): # roundtrip test of the document lib with some nested data tmpdir = str(tmpdir) testDocPath1 = os.path.join(tmpdir, "testDocumentLibTest.designspace") doc = DesignSpaceDocument() a1 = AxisDescriptor() a1.tag = "TAGA" a1.name = "axisName_a" a1.minimum = 0 a1.maximum = 1000 a1.default = 0 doc.addAxis(a1) dummyData = dict(a=123, b=u"äbc", c=[1,2,3], d={'a':123}) dummyKey = "org.fontTools.designspaceLib" doc.lib = {dummyKey: dummyData} doc.write(testDocPath1) new = DesignSpaceDocument() new.read(testDocPath1) assert dummyKey in new.lib assert new.lib[dummyKey] == dummyData
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 main(): parser = argparse.ArgumentParser(description="Build Reem Kufi fonts.") parser.add_argument("file", metavar="FILE", help="input font to process") parser.add_argument("--out-file", metavar="FILE", help="output font to write", required=True) args = parser.parse_args() with TemporaryDirectory() as tempdir: ufos, designspace = build_masters(args.file, tempdir, tempdir) doc = DesignSpaceDocument() doc.read(designspace) doc.instances = [i for i in doc.instances if i.styleName == "Regular"] assert len(doc.instances) == 1 instance = doc.instances[0] instance.location = dict(Weight=108) instance.path = args.out_file doc.write(designspace) build(designspace, outputUFOFormatVersion=3)
def test_addRuleDescriptor(tmp_path): ds = DesignSpaceDocument() rule = ds.addRuleDescriptor( name="TestRule", conditionSets=[[ dict(name="Weight", minimum=100, maximum=200), dict(name="Weight", minimum=700, maximum=900), ]], subs=[("a", "a.alt")], ) assert ds.rules[0] is rule assert isinstance(rule, RuleDescriptor) assert rule.name == "TestRule" assert rule.conditionSets == [[ dict(name="Weight", minimum=100, maximum=200), dict(name="Weight", minimum=700, maximum=900), ]] assert rule.subs == [("a", "a.alt")] # Test it doesn't crash. ds.write(tmp_path / "test.designspace")
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_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)
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_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)
#--------- # 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)
# Read designspace file, write ps and style map names import csv from fontTools.designspaceLib import DesignSpaceDocument names = {} with open('instance_names.csv', newline='') as csvfile: reader = csv.DictReader(csvfile) for row in reader: names[(row["Family Name"], row["Style Name"])] = (row["postscript"], row["familymap"], row["stylemap"]) doc = DesignSpaceDocument() doc.read("../../src/masters/recursive-prop_xprn_weight_slnt_ital.designspace") for i in doc.instances: k = (i.familyName, i.styleName) ps, styleFamily, styleStyle = names[k] i.postScriptFontName = ps i.styleMapFamilyName = styleFamily i.styleMapStyleName = styleStyle doc.write("recursive-prop_xprn_weight_slnt_ital.designspace")
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
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)
doc = DesignSpaceDocument() doc_path = '/path/to/designspace_file.designspace' doc.read(doc_path) for axis in doc.axes: if len(axis.map) is not 0: # convert old map to a flat list old_map = [item for t in axis.map for item in t] new_map = [] # get extremes of old map old_min = min(old_map) old_max = max(old_map) # set new extremes according to min/max of axis new_min = axis.minimum new_max = axis.maximum #calculate new map values for value in old_map: new_value = (((value - old_min) * (new_max - new_min)) / (old_max - old_min)) + new_min new_map.append(round(new_value)) #pack flat list back into tuple pair list new_map = list(zip(*[iter(new_map)] * 2)) axis.map = new_map doc.write(doc_path)
""" doc_paths = [ "../sources/current_masters/Roman/Fraunces.designspace", "../sources/current_masters/Italic/FrauncesItalic.designspace" ] names = {} with open("../mastering/data/instance_names.csv") as csvfile: reader = csv.DictReader(csvfile) for row in reader: names[(row["Var Instance Family Name"], row["Var Instance Style Name"])] = (row["postscript"], row["Static Family Name"], row["Static Style Name"]) for path in doc_paths: base, extension = os.path.splitext(path) doc = DesignSpaceDocument() doc.read(path) for i in doc.instances: k = (i.familyName, i.styleName) ps, fm, sm = names[k] i.postScriptFontName = ps i.familyName = fm i.styleName = sm static_path = base + "_static" + extension doc.write(static_path)
ds_path = "/Users/marcfoley/Type/upstream_families/literata/sources/vf-italic.designspace" doc = DesignSpaceDocument() doc.read(ds_path) #doc.axes #doc.sources #doc.instances # #for instance in doc.instances: # re_pt = r"[0-9]{1,3}pt" # pt = re.search(re_pt, instance.styleName).group(0) # instance.familyName = f"{instance.familyName} {pt}" # instance.styleName = re.sub(re_pt+"\W", "", instance.styleName) # instance.filename = f"{instance.familyName}-{instance.styleName}".replace(" ", "") # for idx, instance in enumerate(doc.instances): re_pt = r"[0-9]{1,3}pt" instance.name = re.sub(re_pt + "\W", "", instance.name).lstrip().rstrip().replace(" ", " ") instance.styleName = re.sub(re_pt + "\W", "", instance.styleName).lstrip().rstrip().replace( " ", " ") instance.familyName = re.sub( re_pt + "\W", "", instance.familyName).lstrip().rstrip().replace(" ", " ") instance.path = f"instance_ufo/{instance.familyName}-{instance.styleName}.ufo".replace( " ", "") doc.write(ds_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 = 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
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)
# Instance names come from this spreadsheet: # https://docs.google.com/spreadsheets/d/1vo9R9Xjt_FmNEE7VHzuvkhQ1KHRR9s-0drIZcmhW7ew # Exported as CSV import csv from fontTools.designspaceLib import DesignSpaceDocument names = {} with open('instance_names.csv', newline='') as csvfile: reader = csv.DictReader(csvfile) for row in reader: names[(row["Var Instance Family Name"], row["Var Instance Style Name"])] = (row["Var postscript"], row["Var familymap"], row["stylemap"]) doc = DesignSpaceDocument() doc.read("../../src/masters/recursive-MONO_CASL_wght_slnt_ital--full_gsub.designspace") for i in doc.instances: k = (i.familyName, i.styleName) ps, fm, sm = names[k] i.postScriptFontName = ps i.styleMapFamilyName = fm i.styleMapStyleName = sm # Because re-writing the designspace removes all the comments, we save # a new one out to the data folder, then copy/paste into the working # designSpace file. Yes, this is dumb, but is the better solution to # save all the comments and formatting (for better git diffs) in the # working designSpace file. doc.write("../../src/masters/recursive-MONO_CASL_wght_slnt_ital--full_gsub_ps_names.designspace")
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') - wght.get('regular')) * weightCropIndex instance = InstanceDescriptor()
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)
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