def test_default_featureWriters_in_designspace_lib(tmpdir, ufo_module): """Test that the glyphsLib custom featureWriters settings (with mode="append") are exported to the designspace lib whenever a GSFont contains a manual 'kern' feature. And that they are not imported back to GSFont.userData if they are the same as the default value. """ font = classes.GSFont() font.masters.append(classes.GSFontMaster()) kern = classes.GSFeature(name="kern", code="pos a b 100;") font.features.append(kern) designspace = to_designspace(font, ufo_module=ufo_module) path = str(tmpdir / "test.designspace") designspace.write(path) for source in designspace.sources: source.font.save(str(tmpdir / source.filename)) designspace2 = DesignSpaceDocument.fromfile(path) assert UFO2FT_FEATURE_WRITERS_KEY in designspace2.lib assert designspace2.lib[ UFO2FT_FEATURE_WRITERS_KEY] == DEFAULT_FEATURE_WRITERS font2 = to_glyphs(designspace2, ufo_module=ufo_module) assert not len(font2.userData) assert len([f for f in font2.features if f.name == "kern"]) == 1
def test_custom_featureWriters_in_designpace_lib(tmpdir, ufo_module): """Test that we can roundtrip custom user-defined ufo2ft featureWriters settings that are stored in the designspace lib or GSFont.userData. """ font = classes.GSFont() font.masters.append(classes.GSFontMaster()) kern = classes.GSFeature(name="kern", code="pos a b 100;") font.features.append(kern) customFeatureWriters = list(DEFAULT_FEATURE_WRITERS) + [{ "class": "MyCustomWriter", "module": "myCustomWriter" }] font.userData[UFO2FT_FEATURE_WRITERS_KEY] = customFeatureWriters designspace = to_designspace(font, ufo_module=ufo_module) path = str(tmpdir / "test.designspace") designspace.write(path) for source in designspace.sources: source.font.save(str(tmpdir / source.filename)) designspace2 = DesignSpaceDocument.fromfile(path) assert UFO2FT_FEATURE_WRITERS_KEY in designspace2.lib assert designspace2.lib[UFO2FT_FEATURE_WRITERS_KEY] == customFeatureWriters font2 = to_glyphs(designspace2, ufo_module=ufo_module) assert len(font2.userData) == 1 assert font2.userData[UFO2FT_FEATURE_WRITERS_KEY] == customFeatureWriters
def test_font_user_data_to_ufo_lib(): # This happens only when not building a designspace # Since there is no designspace.lib to store the font userData, # the latter is duplicated in each output ufo font = classes.GSFont() font.masters.append(classes.GSFontMaster()) font.masters.append(classes.GSFontMaster()) font.userData['fontUserDataKey'] = 'fontUserDataValue' ufo1, ufo2 = to_ufos(font) assert ufo1.lib[GLYPHLIB_PREFIX + 'fontUserData'] == { 'fontUserDataKey': 'fontUserDataValue' } assert ufo2.lib[GLYPHLIB_PREFIX + 'fontUserData'] == { 'fontUserDataKey': 'fontUserDataValue' } font = to_glyphs([ufo1, ufo2]) assert font.userData['fontUserDataKey'] == 'fontUserDataValue'
def test_DisplayStrings_ufo_lib(): font = classes.GSFont() font.masters.append(classes.GSFontMaster()) font.masters.append(classes.GSFontMaster()) font.DisplayStrings = "" ufo1, ufo2 = to_ufos(font) assert FONT_CUSTOM_PARAM_PREFIX + "DisplayStrings" not in ufo1.lib assert FONT_CUSTOM_PARAM_PREFIX + "DisplayStrings" not in ufo2.lib font = to_glyphs([ufo1, ufo2]) assert font.DisplayStrings == "" # --- font.DisplayStrings = "a" ufo1, ufo2 = to_ufos(font) assert ufo1.lib[FONT_CUSTOM_PARAM_PREFIX + "DisplayStrings"] == "a" assert ufo2.lib[FONT_CUSTOM_PARAM_PREFIX + "DisplayStrings"] == "a" font = to_glyphs([ufo1, ufo2]) assert font.DisplayStrings == "a"
def test_layer_lib_in_font_user_data(ufo_module): font = classes.GSFont() font.masters.append(classes.GSFontMaster()) font.masters.append(classes.GSFontMaster()) glyph = classes.GSGlyph(name="a") font.glyphs.append(glyph) layer = classes.GSLayer() layer.layerId = font.masters[0].id layer.width = 0 glyph.layers.append(layer) layer = classes.GSLayer() layer.layerId = font.masters[1].id layer.width = 0 glyph.layers.append(layer) font.userData[GLYPHLIB_PREFIX + "layerLib.public.default"] = { "layerLibKey": "layerLibValue" } ufo1, ufo2 = to_ufos(font) assert "layerLibKey" in ufo1.layers["public.default"].lib assert ufo1.layers["public.default"].lib["layerLibKey"] == "layerLibValue" assert "layerLibKey" in ufo2.layers["public.default"].lib assert ufo2.layers["public.default"].lib["layerLibKey"] == "layerLibValue"
def test_node_user_data_into_glif_lib(): font = classes.GSFont() master = classes.GSFontMaster() master.id = "M1" font.masters.append(master) glyph = classes.GSGlyph("a") layer = classes.GSLayer() layer.layerId = "M1" layer.associatedMasterId = "M1" glyph.layers.append(layer) font.glyphs.append(glyph) path = classes.GSPath() layer.paths.append(path) node1 = classes.GSNode() node1.userData["nodeUserDataKey1"] = "nodeUserDataValue1" node1.name = "node1" node2 = classes.GSNode() node2.userData["nodeUserDataKey2"] = "nodeUserDataValue2" node2.name = "node2" path.nodes.append(classes.GSNode()) path.nodes.append(node1) path.nodes.append(classes.GSNode()) path.nodes.append(classes.GSNode()) path.nodes.append(node2) (ufo, ) = to_ufos(font, minimize_glyphs_diffs=True) assert ufo["a"].lib[GLYPHLIB_PREFIX + "nodeUserData.0.1"] == { "nodeUserDataKey1": "nodeUserDataValue1" } assert ufo["a"][0][2].name == "node1" assert ufo["a"].lib[GLYPHLIB_PREFIX + "nodeUserData.0.4"] == { "nodeUserDataKey2": "nodeUserDataValue2" } assert ufo["a"][0][0].name == "node2" font = to_glyphs([ufo]) path = font.glyphs["a"].layers["M1"].paths[0] assert path.nodes[1].userData["nodeUserDataKey1"] == "nodeUserDataValue1" assert path.nodes[1].name == "node1" assert path.nodes[4].userData["nodeUserDataKey2"] == "nodeUserDataValue2" assert path.nodes[4].name == "node2"
def test_glyph_user_data_into_ufo_lib(): font = classes.GSFont() font.masters.append(classes.GSFontMaster()) glyph = classes.GSGlyph('a') glyph.userData['glyphUserDataKey'] = 'glyphUserDataValue' font.glyphs.append(glyph) layer = classes.GSLayer() layer.layerId = font.masters[0].id glyph.layers.append(layer) ufo, = to_ufos(font) assert ufo.lib[GLYPHLIB_PREFIX + 'glyphUserData.a'] == { 'glyphUserDataKey': 'glyphUserDataValue' } font = to_glyphs([ufo]) assert font.glyphs['a'].userData[ 'glyphUserDataKey'] == 'glyphUserDataValue'
def test_glyph_user_data_into_ufo_lib(): font = classes.GSFont() font.masters.append(classes.GSFontMaster()) glyph = classes.GSGlyph("a") glyph.userData["glyphUserDataKey"] = "glyphUserDataValue" font.glyphs.append(glyph) layer = classes.GSLayer() layer.layerId = font.masters[0].id glyph.layers.append(layer) (ufo, ) = to_ufos(font) assert ufo.lib[GLYPHLIB_PREFIX + "glyphUserData.a"] == { "glyphUserDataKey": "glyphUserDataValue" } font = to_glyphs([ufo]) assert font.glyphs["a"].userData[ "glyphUserDataKey"] == "glyphUserDataValue"
def test_node_user_data_into_glif_lib(): font = classes.GSFont() master = classes.GSFontMaster() master.id = "M1" font.masters.append(master) glyph = classes.GSGlyph('a') layer = classes.GSLayer() layer.layerId = "M1" layer.associatedMasterId = "M1" glyph.layers.append(layer) font.glyphs.append(glyph) path = classes.GSPath() layer.paths.append(path) node1 = classes.GSNode() node1.userData['nodeUserDataKey1'] = 'nodeUserDataValue1' node2 = classes.GSNode() node2.userData['nodeUserDataKey2'] = 'nodeUserDataValue2' path.nodes.append(classes.GSNode()) path.nodes.append(node1) path.nodes.append(classes.GSNode()) path.nodes.append(classes.GSNode()) path.nodes.append(node2) ufo, = to_ufos(font, minimize_glyphs_diffs=True) assert ufo['a'].lib[GLYPHLIB_PREFIX + 'nodeUserData.0.1'] == { 'nodeUserDataKey1': 'nodeUserDataValue1' } assert ufo['a'].lib[GLYPHLIB_PREFIX + 'nodeUserData.0.4'] == { 'nodeUserDataKey2': 'nodeUserDataValue2' } font = to_glyphs([ufo]) path = font.glyphs['a'].layers['M1'].paths[0] assert path.nodes[1].userData['nodeUserDataKey1'] == 'nodeUserDataValue1' assert path.nodes[4].userData['nodeUserDataKey2'] == 'nodeUserDataValue2'
def test_empty_font(self): empty_font = classes.GSFont() empty_font.masters.append(classes.GSFontMaster()) self.assertUFORoundtrip(empty_font)
def test_write_font_attributes(self): """Test the writer on all GSFont attributes""" font = classes.GSFont() # List of properties from https://docu.glyphsapp.com/#gsfont # parent: not handled because it's internal and read-only # masters m1 = classes.GSFontMaster() m1.id = "M1" font.masters.insert(0, m1) m2 = classes.GSFontMaster() m2.id = "M2" font.masters.insert(1, m2) # instances i1 = classes.GSInstance() i1.name = "MuchBold" font.instances.append(i1) # glyphs g1 = classes.GSGlyph() g1.name = 'G1' font.glyphs.append(g1) # classes c1 = classes.GSClass() c1.name = "C1" font.classes.append(c1) # features f1 = classes.GSFeature() f1.name = "F1" font.features.append(f1) # featurePrefixes fp1 = classes.GSFeaturePrefix() fp1 = "FP1" font.featurePrefixes.append(fp1) # copyright font.copyright = "Copyright Bob" # designer font.designer = "Bob" # designerURL font.designerURL = "bob.me" # manufacturer font.manufacturer = "Manu" # manufacturerURL font.manufacturerURL = "manu.com" # versionMajor font.versionMajor = 2 # versionMinor font.versionMinor = 104 # date font.date = glyphs_datetime('2017-10-03 07:35:46 +0000') # familyName font.familyName = "Sans Rien" # upm font.upm = 2000 # note font.note = "Was bored, made this" # kerning font.kerning = OrderedDict([ ('M1', OrderedDict([ ('@MMK_L_G1', OrderedDict([ ('@MMK_R_G1', 0.1) ])) ])) ]) # userData font.userData = { 'a': 'test', 'b': [1, {'c': 2}], 'd': [1, "1"], } # grid -> gridLength font.grid = 35 # gridSubDivisions font.gridSubDivisions = 5 # keyboardIncrement font.keyboardIncrement = 1.2 # disablesNiceNames font.disablesNiceNames = True # customParameters font.customParameters['ascender'] = 300 # selection: not written # selectedLayers: not written # selectedFontMaster: not written # masterIndex: not written # currentText: not written # tabs: not written # currentTab: not written # filepath: not written # tool: not written # tools: not handled because it is a read-only list of GUI features # .appVersion (extra property that is not in the docs!) font.appVersion = "895" self.assertWrites(font, dedent("""\ { .appVersion = "895"; classes = ( { code = ""; name = C1; } ); copyright = "Copyright Bob"; customParameters = ( { name = note; value = "Was bored, made this"; }, { name = ascender; value = 300; } ); date = "2017-10-03 07:35:46 +0000"; designer = Bob; designerURL = bob.me; disablesNiceNames = 1; familyName = "Sans Rien"; featurePrefixes = ( FP1 ); features = ( { code = ""; name = F1; } ); fontMaster = ( { ascender = 800; capHeight = 700; id = M1; xHeight = 500; }, { ascender = 800; capHeight = 700; id = M2; xHeight = 500; } ); glyphs = ( { glyphname = G1; } ); gridLength = 35; gridSubDivision = 5; instances = ( { name = MuchBold; } ); kerning = { M1 = { "@MMK_L_G1" = { "@MMK_R_G1" = 0.1; }; }; }; keyboardIncrement = 1.2; manufacturer = Manu; manufacturerURL = manu.com; unitsPerEm = 2000; userData = { a = test; b = ( 1, { c = 2; } ); d = ( 1, "1" ); }; versionMajor = 2; versionMinor = 104; } """)) # Don't write the keyboardIncrement if it's 1 (default) font.keyboardIncrement = 1 written = test_helpers.write_to_lines(font) self.assertFalse(any("keyboardIncrement" in line for line in written))
def test_write_glyph(self): glyph = classes.GSGlyph() # https://docu.glyphsapp.com/#gsglyph # parent: not written # layers # Put the glyph in a font with at least one master for the magic in # `glyph.layers.append()` to work. font = classes.GSFont() master = classes.GSFontMaster() master.id = "MASTER-ID" font.masters.insert(0, master) font.glyphs.append(glyph) layer = classes.GSLayer() layer.layerId = "LAYER-ID" layer.name = "L1" glyph.layers.insert(0, layer) # name glyph.name = "Aacute" # unicode glyph.unicode = "00C1" # string: not written # id: not written # category glyph.category = "Letter" # subCategory glyph.subCategory = "Uppercase" # script glyph.script = "latin" # productionName glyph.productionName = "Aacute.prod" # glyphInfo: not written # leftKerningGroup glyph.leftKerningGroup = "A" # rightKerningGroup glyph.rightKerningGroup = "A" # leftKerningKey: not written # rightKerningKey: not written # leftMetricsKey glyph.leftMetricsKey = "A" # rightMetricsKey glyph.rightMetricsKey = "A" # widthMetricsKey glyph.widthMetricsKey = "A" # export glyph.export = False # color glyph.color = 11 # colorObject: not written # note glyph.note = "Stunning one-bedroom A with renovated acute accent" # selected: not written # mastersCompatible: not stored # userData glyph.userData['rememberToMakeCoffe'] = True # Check that empty collections are written glyph.userData['com.someoneelse.coolsoftware.customdata'] = [ OrderedDict([ ('zero', 0), ('emptyList', []), ('emptyDict', {}), ('emptyString', ""), ]), [], {}, "", "hey", 0, 1, ] # smartComponentAxes axis = classes.GSSmartComponentAxis() axis.name = "crotchDepth" glyph.smartComponentAxes.append(axis) # lastChange glyph.lastChange = glyphs_datetime('2017-10-03 07:35:46 +0000') self.assertWrites(glyph, dedent("""\ { color = 11; export = 0; glyphname = Aacute; lastChange = "2017-10-03 07:35:46 +0000"; layers = ( { associatedMasterId = "MASTER-ID"; layerId = "LAYER-ID"; name = L1; width = 0; } ); leftKerningGroup = A; leftMetricsKey = A; widthMetricsKey = A; note = "Stunning one-bedroom A with renovated acute accent"; rightKerningGroup = A; rightMetricsKey = A; unicode = 00C1; script = latin; category = Letter; subCategory = Uppercase; userData = { com.someoneelse.coolsoftware.customdata = ( { zero = 0; emptyList = ( ); emptyDict = { }; emptyString = ""; }, ( ), { }, "", hey, 0, 1 ); rememberToMakeCoffe = 1; }; partsSettings = ( { name = crotchDepth; bottomValue = 0; topValue = 0; } ); } """)) # Write the script even when it's an empty string # Same for category and subCategory glyph.script = "" glyph.category = "" glyph.subCategory = "" written = test_helpers.write_to_lines(glyph) self.assertIn('script = "";', written) self.assertIn('category = "";', written) self.assertIn('subCategory = "";', written)
def test_write_font_master_attributes(self): """Test the writer on all GSFontMaster attributes""" master = classes.GSFontMaster() # List of properties from https://docu.glyphsapp.com/#gsfontmaster # id master.id = "MASTER-ID" # name # Cannot set the `name` attribute directly # master.name = "Hairline Megawide" master.customParameters['Master Name'] = "Hairline Megawide" # weight master.weight = "Thin" # width master.width = "Wide" # weightValue master.weightValue = 0.01 # widthValue master.widthValue = 0.99 # customValue # customName master.customName = "cuteness" # A value of 0.0 is not written to the file. master.customValue = 0.001 master.customName1 = "color" master.customValue1 = 0.1 master.customName2 = "depth" master.customValue2 = 0.2 master.customName3 = "surealism" master.customValue3 = 0.3 # ascender master.ascender = 234.5 # capHeight master.capHeight = 200.6 # xHeight master.xHeight = 59.1 # descender master.descender = -89.2 # italicAngle master.italicAngle = 12.2 # verticalStems master.verticalStems = [1, 2, 3] # horizontalStems master.horizontalStems = [4, 5, 6] # alignmentZones zone = classes.GSAlignmentZone(0, -30) master.alignmentZones = [ zone ] # blueValues: not handled because it is read-only # otherBlues: not handled because it is read-only # guides guide = classes.GSGuideLine() guide.name = "middle" master.guides.append(guide) # userData master.userData['rememberToMakeTea'] = True # customParameters master.customParameters['underlinePosition'] = -135 self.assertWrites(master, dedent("""\ { alignmentZones = ( "{0, -30}" ); ascender = 234.5; capHeight = 200.6; custom = cuteness; customValue = 0.001; custom1 = color; customValue1 = 0.1; custom2 = depth; customValue2 = 0.2; custom3 = surealism; customValue3 = 0.3; customParameters = ( { name = "Master Name"; value = "Hairline Megawide"; }, { name = underlinePosition; value = -135; } ); descender = -89.2; guideLines = ( { name = middle; } ); horizontalStems = ( 4, 5, 6 ); id = "MASTER-ID"; italicAngle = 12.2; name = "Hairline Megawide"; userData = { rememberToMakeTea = 1; }; verticalStems = ( 1, 2, 3 ); weight = Thin; weightValue = 0.01; width = Wide; widthValue = 0.99; xHeight = 59.1; } """)) # Write the capHeight and xHeight even if they are "0" master.xHeight = 0 master.capHeight = 0 written = test_helpers.write_to_lines(master) self.assertIn("xHeight = 0;", written) self.assertIn("capHeight = 0;", written)