├── .gitignore ├── readme_imgs ├── screen-backupClone.png ├── screen-comparesets.png ├── screen-redcircles.png ├── screen-createotclass.png ├── screen-importAnchors.png ├── screen-slantAnchors.png ├── screen-smooth2sharp.png ├── screen-addGuidelineName.png ├── screen-newTabModifiedGlpyhs.png ├── screen-newTabWithNotMonoGlyphs.png ├── screen-nodesNearAlignmentZones.png └── screen-newTabMoreThanXComponents.png ├── closeAllTabs.py ├── deleteNotes.py ├── AnchorDictionary.py ├── ReportGlyphAnchors.py ├── reportGlyphComponents.py ├── addNamesToStylisticSets.py ├── getComponentAnchors.py ├── copyAnchorsFromBase.py ├── addGuidelineName.py ├── centerAnchors.py ├── newTabWithNotMonoGlyphs.py ├── insertImagesToBackground.py ├── markZeroHandles.py ├── newTabMoreThanXComponents.py ├── ReportAnchorsOffMetrics.py ├── newTabModifiedGlyphs.py ├── slantAnchors.py ├── createAccentedVersions.py ├── CompareSets.py ├── nodesNearAlignmentZones.py ├── automaticOTclasses.py ├── importAnchors.py ├── smooth2sharp.py ├── createOTClass.py ├── anchorBackupAndClone.py ├── getFromNextFont.py ├── README.md └── AddAnchorstoGlpyhs.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | readme_imgs/.DS_Store 3 | 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /readme_imgs/screen-backupClone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guidoferreyra/Glyphs-Scripts/HEAD/readme_imgs/screen-backupClone.png -------------------------------------------------------------------------------- /readme_imgs/screen-comparesets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guidoferreyra/Glyphs-Scripts/HEAD/readme_imgs/screen-comparesets.png -------------------------------------------------------------------------------- /readme_imgs/screen-redcircles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guidoferreyra/Glyphs-Scripts/HEAD/readme_imgs/screen-redcircles.png -------------------------------------------------------------------------------- /readme_imgs/screen-createotclass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guidoferreyra/Glyphs-Scripts/HEAD/readme_imgs/screen-createotclass.png -------------------------------------------------------------------------------- /readme_imgs/screen-importAnchors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guidoferreyra/Glyphs-Scripts/HEAD/readme_imgs/screen-importAnchors.png -------------------------------------------------------------------------------- /readme_imgs/screen-slantAnchors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guidoferreyra/Glyphs-Scripts/HEAD/readme_imgs/screen-slantAnchors.png -------------------------------------------------------------------------------- /readme_imgs/screen-smooth2sharp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guidoferreyra/Glyphs-Scripts/HEAD/readme_imgs/screen-smooth2sharp.png -------------------------------------------------------------------------------- /readme_imgs/screen-addGuidelineName.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guidoferreyra/Glyphs-Scripts/HEAD/readme_imgs/screen-addGuidelineName.png -------------------------------------------------------------------------------- /readme_imgs/screen-newTabModifiedGlpyhs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guidoferreyra/Glyphs-Scripts/HEAD/readme_imgs/screen-newTabModifiedGlpyhs.png -------------------------------------------------------------------------------- /readme_imgs/screen-newTabWithNotMonoGlyphs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guidoferreyra/Glyphs-Scripts/HEAD/readme_imgs/screen-newTabWithNotMonoGlyphs.png -------------------------------------------------------------------------------- /readme_imgs/screen-nodesNearAlignmentZones.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guidoferreyra/Glyphs-Scripts/HEAD/readme_imgs/screen-nodesNearAlignmentZones.png -------------------------------------------------------------------------------- /readme_imgs/screen-newTabMoreThanXComponents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guidoferreyra/Glyphs-Scripts/HEAD/readme_imgs/screen-newTabMoreThanXComponents.png -------------------------------------------------------------------------------- /closeAllTabs.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: Close All Tabs 2 | # -*- coding: utf-8 -*- 3 | import GlyphsApp 4 | 5 | for i in range(len(Glyphs.font.tabs)): 6 | del Glyphs.font.tabs[0] -------------------------------------------------------------------------------- /deleteNotes.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: deleteNotes 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | This Script delete notes in selected glyphs 5 | """ 6 | 7 | Font = Glyphs.font 8 | selectedLayers = Font.selectedLayers 9 | 10 | for layer in selectedLayers: 11 | layer.setAnnotations_(None) 12 | -------------------------------------------------------------------------------- /AnchorDictionary.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: AnchorDictionaryMaker 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | Creates a python dictionary with the name of the anchors used on the selected glyphs. 5 | Outputs to the console 6 | """ 7 | output = 'anchorDict = {\n' 8 | for thisLayer in Glyphs.font.selectedLayers: 9 | output += '\t"'+thisLayer.parent.name+'"'+':(' 10 | for i in thisLayer.anchors: 11 | output += '"'+i.name+'"'+',' 12 | output += '),\n' 13 | output += '}' 14 | 15 | Glyphs.showMacroWindow() 16 | print (output) -------------------------------------------------------------------------------- /ReportGlyphAnchors.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: Report Glyph Anchors 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | Print in the console the anchors used on selected Glyphs. 5 | """ 6 | Glyphs.clearLog() 7 | Glyphs.showMacroWindow() 8 | currentLayer = Glyphs.font.selectedLayers[0] 9 | print (font.familyName, currentLayer.name) 10 | 11 | for thisLayer in Glyphs.font.selectedLayers: 12 | glyphAnchors = [] 13 | for i in thisLayer.anchors: 14 | glyphAnchors.append(i.name) 15 | 16 | print (thisLayer.parent.name +": %s" % ", ".join(glyphAnchors)) 17 | -------------------------------------------------------------------------------- /reportGlyphComponents.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: Report Glyph Components 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | Prints in the console the components used on selecteds glyphs. 5 | """ 6 | Glyphs.clearLog() 7 | Glyphs.showMacroWindow() 8 | currentLayer = Glyphs.font.selectedLayers[0] 9 | print (font.familyName, currentLayer.name) 10 | 11 | for thisLayer in Glyphs.font.selectedLayers: 12 | glyphComponents = [] 13 | for i in thisLayer.components: 14 | glyphComponents.append(i.name) 15 | 16 | print (thisLayer.parent.name +": %s" % ", ".join(glyphComponents)) 17 | -------------------------------------------------------------------------------- /addNamesToStylisticSets.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: Add descriptive names to stylistic sets 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | Based on a given list adds descriptive names to stylistic sets 5 | """ 6 | 7 | featureName = [ 8 | ("ss01","Long descender g"), 9 | ("ss02","Cursive k"), 10 | ("ss03","Big terminal r"), 11 | ("ss04","Curved y "), 12 | ("ss05","Inverted y tail"), 13 | ("ss06","Baseline aligned J"), 14 | ("ss07","Cursive K"), 15 | ("ss08","Straight leg K "), 16 | ("ss09","Vertical stress zero"), 17 | ("ss10","Hairline zero"), 18 | ("ss11","Openface arms E, F, L, T, Z, z"), 19 | ] 20 | 21 | 22 | font = Glyphs.font 23 | 24 | for feature in featureName: 25 | try: 26 | font.features[feature[0]].notes = "Name: "+feature[1] 27 | print (feature[0]+" Name: "+feature[1]) 28 | except: 29 | pass -------------------------------------------------------------------------------- /getComponentAnchors.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: Get component anchors 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | In a glyph made of components, gets component acnhors and add it to the current layer, useful for ligatures made of components. 5 | """ 6 | 7 | font = Glyphs.font 8 | i = 1 9 | for thisLayer in font.selectedLayers: 10 | thisLayerID = thisLayer.associatedMasterId 11 | for thisComponent in thisLayer.components: 12 | offsetX = thisComponent.position.x 13 | offsetY = thisComponent.position.y 14 | referenceGlyph = font.glyphs[thisComponent.componentName] 15 | 16 | for referenceAnchor in referenceGlyph.layers[thisLayerID].anchors: 17 | 18 | newAnchor = GSAnchor.alloc().init() 19 | newAnchor.name = referenceAnchor.name + "_" + str(i) 20 | thisLayer.addAnchor_( newAnchor ) 21 | newPosition = NSPoint( referenceAnchor.x + offsetX, referenceAnchor.y + offsetY) 22 | newAnchor.setPosition_( newPosition ) 23 | i+= 1 24 | 25 | -------------------------------------------------------------------------------- /copyAnchorsFromBase.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: Copy Anchors from base glyph 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | On a suffixed glyph run the script to copy the anchors from the default version. (i.e. run on /a.ss01 and get /a anchors). 5 | """ 6 | from Foundation import NSPoint 7 | 8 | font = Glyphs.font 9 | 10 | selectedLayers = font.selectedLayers 11 | 12 | def copyAnchor(thisLayer, anchorName, anchorX, anchorY): 13 | newAnchor = GSAnchor.alloc().init() 14 | newAnchor.name = anchorName 15 | thisLayer.addAnchor_( newAnchor ) 16 | newPosition = NSPoint( anchorX, anchorY) 17 | newAnchor.setPosition_( newPosition ) 18 | 19 | 20 | for thisLayer in selectedLayers: 21 | glyphName = thisLayer.parent.name 22 | 23 | id = thisLayer.layerId 24 | 25 | baseName = glyphName[0:glyphName.find('.')] 26 | 27 | for anchor in font.glyphs[baseName].layers[id].anchors: 28 | anchorName = anchor.name 29 | anchorX = anchor.position.x 30 | anchorY = anchor.position.y 31 | 32 | copyAnchor(thisLayer, anchorName, anchorX, anchorY) -------------------------------------------------------------------------------- /addGuidelineName.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: Add name to Guideline 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | (UI) Add name to guideline. 5 | """ 6 | 7 | from GlyphsApp import * 8 | from vanilla import * 9 | 10 | 11 | class addGuidelineName(object): 12 | 13 | def __init__(self): 14 | self.w = FloatingWindow((250, 70), "Add Name to Guideline") 15 | self.w.label = TextBox((10, 10, -120, 20), "Guideline Name:") 16 | self.w.textInName = EditText((-130, 10, -10, 20), "", sizeStyle='small') 17 | self.w.createButton = Button((10, -30, -10, 20), "Add Name", callback=self.buttonCallback) 18 | self.w.open() 19 | 20 | def buttonCallback(self, sender): 21 | guidesToName = [] 22 | for selection in Layer.selection: 23 | if isinstance(selection, GSGuideLine): 24 | guidesToName.append(selection) 25 | 26 | if len(guidesToName) > 0: 27 | for guide in guidesToName: 28 | guide.name = self.w.textInName.get() 29 | Glyphs.redraw() 30 | else: 31 | Message("Please select at least one guideline", "Add name to guideline") 32 | 33 | addGuidelineName() 34 | -------------------------------------------------------------------------------- /centerAnchors.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: CenterAnchors 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | This script center all anchors of all layers 5 | """ 6 | import GlyphsApp 7 | from Foundation import NSPoint 8 | import math 9 | 10 | font = Glyphs.font 11 | allSelectedGlyphs = [l.parent for l in font.selectedLayers] 12 | 13 | def angle(angle, height, yPos): 14 | offset = math.tan(math.radians(angle)) * height / 2 15 | shift = math.tan(math.radians(angle)) * yPos - offset 16 | return shift 17 | 18 | 19 | for thisGlyph in allSelectedGlyphs: 20 | 21 | for thisLayer in thisGlyph.layers: 22 | #Set variables 23 | masterID = thisLayer.associatedMasterId 24 | thisMasterXheight = font.masters[masterID].xHeight 25 | thisMasterAngle = thisLayer.italicAngle 26 | width = thisLayer.width 27 | 28 | for thisAnchor in thisLayer.anchors: 29 | posY = thisLayer.anchors[thisAnchor.name].position.y 30 | centerOfLayer = width/2 + angle(thisMasterAngle, thisMasterXheight, posY) 31 | 32 | thisLayer.anchors[thisAnchor.name].position = NSPoint(centerOfLayer, posY) -------------------------------------------------------------------------------- /newTabWithNotMonoGlyphs.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: New Tab with not monospaced glyphs 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | (UI) Opens a newtab with those glyphs that doesn’t match with the indicated glyph width. Useful for checking monospaced fonts or tabular glyphs. 5 | """ 6 | 7 | from GlyphsApp import * 8 | from vanilla import * 9 | 10 | font = Glyphs.font 11 | 12 | class notMono(object): 13 | 14 | def __init__(self): 15 | 16 | 17 | monoW = 600 18 | self.w = FloatingWindow((160, 70), "Check Width") 19 | self.w.textBox = EditText((10, 10, 60, 20), monoW) 20 | self.w.EditText = TextBox((80, 10, 90, 20), "upm") 21 | self.w.button = Button((10, 40, -10, 20), "Check & Open", 22 | callback=self.buttonCallback) 23 | self.w.open() 24 | 25 | def buttonCallback(self, sender): 26 | selectedLayers = font.selectedLayers 27 | customWidth = self.w.textBox.get() 28 | notMonoList = [] 29 | for layer in selectedLayers: 30 | if layer.width != float(customWidth): 31 | print (layer.parent.name, layer.name, layer.width) 32 | notMonoList.append(layer.parent.name) 33 | 34 | tabString = "/%s" % "/".join(notMonoList) 35 | 36 | font.newTab( tabString ) 37 | 38 | notMono() 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /insertImagesToBackground.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: Insert images in background 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | In the dialog box select images to insert as background. Images must be named with the corresponding glyphname (ie. a.jpg) in case the glyph is not in the font the script will create it. 5 | """ 6 | from os import listdir 7 | from os.path import splitext 8 | from os.path import split 9 | 10 | font = Glyphs.font 11 | 12 | font.disableUpdateInterface() 13 | 14 | 15 | paths = GetOpenFile( 16 | message = "Select image:", 17 | allowsMultipleSelection = True, 18 | filetypes = ["jpeg", "png", "tif", "gif", "pdf"] 19 | ) 20 | 21 | for imageFilePath in paths: 22 | fileName = split(imageFilePath) 23 | glyphName = splitext(fileName[1])[0] 24 | 25 | if glyphName in font.glyphs: 26 | thisGlyph = font.glyphs[glyphName] 27 | for layer in thisGlyph.layers: 28 | layer.backgroundImage = GSBackgroundImage(imageFilePath) 29 | thisGlyph.updateGlyphInfo() 30 | else: 31 | font.glyphs.append(GSGlyph(glyphName)) 32 | thisGlyph = font.glyphs[glyphName] 33 | for layer in thisGlyph.layers: 34 | layer.backgroundImage = GSBackgroundImage(imageFilePath) 35 | thisGlyph.updateGlyphInfo() 36 | 37 | print ("Added image to", glyphName, "background. Be sure to have View> Show image activated.") 38 | 39 | font.enableUpdateInterface() 40 | -------------------------------------------------------------------------------- /markZeroHandles.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: Mark Zero Handles 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | This script searches for control points that are overlapping nodes and marks them with an annotation circle, setting also glyph's color to red. 5 | How to use: select the glyphs and run the script. It works for one master at a time. 6 | A much better tool for this is RedArrows by Jens Kutilek but it only works on Glpyhs 2. 7 | """ 8 | 9 | Font = Glyphs.font 10 | selectedLayers = Font.selectedLayers 11 | 12 | 13 | def insertArrow(layer, x, y, width=30): 14 | arrow = GSAnnotation.alloc().initWithElementDict_({ "position":"{"+str(x)+", "+str(y)+"}", "type":"Circle", "width":width }) 15 | layer.addAnnotation_(arrow) 16 | 17 | for layer in selectedLayers: 18 | paths = layer.paths 19 | layer.setAnnotations_(None) 20 | layer.setColorIndex_(9223372036854775807) 21 | 22 | for thisPath in paths: 23 | nodos = thisPath.nodes 24 | bcps = [thisNodo for thisNodo in nodos if thisNodo.type == GSOFFCURVE] 25 | bps = [thisNodo for thisNodo in nodos if thisNodo.type != GSOFFCURVE] 26 | 27 | 28 | 29 | for thisNodo in bps: 30 | for offcurvenode in bcps: 31 | posx = thisNodo.position.x == offcurvenode.position.x 32 | posy = thisNodo.position.y == offcurvenode.position.y 33 | 34 | if posx and posy: 35 | layer.setColorIndex_(9223372036854775807) 36 | insertArrow(layer, thisNodo.position.x, thisNodo.position.y) 37 | layer.setColorIndex_(1) 38 | -------------------------------------------------------------------------------- /newTabMoreThanXComponents.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: New tab with more than x components 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | (UI) Opens in a new tab the glyphs with more than the indicated ammout of components 5 | """ 6 | 7 | import GlyphsApp 8 | from vanilla import * 9 | 10 | 11 | thisFont = Glyphs.font 12 | selectedLayers = thisFont.selectedLayers 13 | 14 | 15 | class newTabXComponents(object): 16 | 17 | def __init__(self): 18 | 19 | 20 | compAmount = 1 21 | self.w = FloatingWindow((250, 90), "Amount of components") 22 | self.w.EditText = TextBox((10, 10, 250, 20), "New tab with glyphs with") 23 | self.w.EditText2 = TextBox((10, 35, 90, 20), "more than") 24 | self.w.textBox = EditText((78, 35, 30, 20), compAmount) 25 | self.w.EditText3 = TextBox((110, 35, 90, 20), "components") 26 | self.w.button = Button((10, 60, -10, 20), "Check & Open", 27 | callback=self.buttonCallback) 28 | self.w.open() 29 | 30 | 31 | 32 | def buttonCallback(self, sender): 33 | 34 | lista = [] 35 | 36 | amoutOfComponents = self.w.textBox.get() 37 | for thisLayer in selectedLayers: 38 | if len( thisLayer.components ) > int(amoutOfComponents): 39 | thisGlyph = thisLayer.parent 40 | lista.append(thisGlyph.name) 41 | 42 | if len(lista) > 0: 43 | tabString = "/%s" % "/".join(lista) 44 | thisFont.newTab( tabString ) 45 | else: 46 | Glyphs.clearLog() 47 | Glyphs.showMacroWindow() 48 | print ("No glyphs with more than", amoutOfComponents, "components") 49 | newTabXComponents() 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /ReportAnchorsOffMetrics.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: Report anchors off metrics 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | Print in the console a list with the gliphs and anchors off the metrics. 5 | This script is a modification of a mekkablue's script. 6 | """ 7 | Glyphs.clearLog() 8 | Glyphs.showMacroWindow() 9 | font = Glyphs.font 10 | selectedLayers = font.selectedLayers 11 | 12 | def checkAnchors (thisLayer): 13 | 14 | try: 15 | # Glyphs 3 16 | metricsPosition = [] 17 | for metric in thisLayer.metrics(): 18 | metricsPosition.append(metric.position()) 19 | except: 20 | # Glyphs 2 21 | thisMaster = thisLayer.associatedFontMaster() 22 | metricsPosition = [0.0, thisMaster.xHeight, thisMaster.descender, thisMaster.ascender, thisMaster.capHeight] 23 | try: 24 | thisMasterSmallheight = float(thisMaster.customParameters['smallCapHeight']) 25 | metricsPosition.append(thisMasterSmallheight) 26 | except: 27 | pass 28 | 29 | anchorList = [] 30 | for thisAnchor in thisLayer.anchors: 31 | posy = thisAnchor.position.y 32 | if posy not in metricsPosition: 33 | anchorList.append(thisAnchor.name) 34 | if len(anchorList) != 0: 35 | print (thisLayer.parent.name + ":" + "%s" % ", ".join(anchorList)) 36 | return True 37 | 38 | 39 | 40 | listOfGlyphs = [] 41 | for thisLayer in selectedLayers: 42 | 43 | if checkAnchors (thisLayer) is True: 44 | listOfGlyphs.append(thisLayer.parent.name) 45 | 46 | if listOfGlyphs: 47 | print ("\nGlyphs with off anchors in this master:\n/%s" % "/".join(listOfGlyphs)) 48 | else: 49 | print ("\nAll anchors on metric lines.") 50 | 51 | -------------------------------------------------------------------------------- /newTabModifiedGlyphs.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: New tab with modified glyphs 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | (UI) Opens in a new tab modified glyphs after or before certain date. 5 | """ 6 | 7 | from GlyphsApp import * 8 | from vanilla import * 9 | 10 | import time 11 | import datetime 12 | 13 | glyphList = [] 14 | thisFont = Glyphs.font 15 | 16 | 17 | class openGlyphs(object): 18 | 19 | def __init__(self): 20 | 21 | self.w = Window((240, 115), "New tab with modified glyphs") 22 | self.w.radioGroup = RadioGroup((10, 10, 140, 40), ["After", "Before"], isVertical=False) 23 | self.w.fechaPick = DatePicker((10, 50, -10, 22), timeDisplay=None) 24 | self.w.goButton = Button((10, -30, -10, 20), "Open in a new tab", callback=self.buttonCallback) 25 | self.w.radioGroup.set(0) 26 | self.w.open() 27 | 28 | def buttonCallback(self, sender): 29 | Glyphs.showMacroWindow() 30 | Glyphs.clearLog() 31 | pickedDate = self.w.fechaPick.get() 32 | split1 = str(pickedDate).split(' ')[0] 33 | split2 = split1.split('-') 34 | year = int(split2[0]) 35 | month = int(split2[1]) 36 | day = int(split2[2]) 37 | date = datetime.date(year,month,day) 38 | unixtime = time.mktime(date.timetuple()) 39 | 40 | optionChoice = self.w.radioGroup.get() 41 | ## #sortedglyphList = sorted(glyphList, key=lambda x: x[1]) 42 | for glyph in Font.glyphs: 43 | if optionChoice == 0: 44 | if glyph.lastChange > unixtime: 45 | glyphList.append(glyph.name) 46 | else: 47 | if glyph.lastChange < unixtime: 48 | glyphList.append(glyph.name) 49 | 50 | 51 | tabString = "/%s" % "/".join(glyphList) 52 | thisFont.newTab( tabString ) 53 | for glyph in glyphList: 54 | thisGlyph = thisFont.glyphs[glyph] 55 | lastChange = time.strftime("%d/%m/%y", time.localtime(thisGlyph.lastChange)) 56 | print (thisGlyph.name+": "+lastChange) 57 | 58 | openGlyphs() 59 | -------------------------------------------------------------------------------- /slantAnchors.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: Slant Anchors 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | (UI) Moves anchors according to master italic angle. Useful when importing anchors from roman versions, or after changing the font angle. 5 | """ 6 | 7 | import GlyphsApp 8 | from Foundation import NSPoint 9 | from vanilla import * 10 | import math 11 | 12 | font = Glyphs.font 13 | selectedLayers = font.selectedLayers 14 | 15 | 16 | class slantAnchor(object): 17 | 18 | def __init__(self): 19 | 20 | self.w = Window((160, 100), "Slant anchors") 21 | self.w.textBox = EditText((10, 10, 60, 20), "") 22 | self.w.EditText = TextBox((80, 10, 90, 20), u"\u00B0") 23 | self.w.checkBox = CheckBox((10, 40, -10, 20), "Use italic angle", 24 | callback=self.checkBoxCallback, value=False) 25 | self.w.button = Button((10, 70, -10, 20), "Move", 26 | callback=self.buttonCallback) 27 | self.w.open() 28 | 29 | def checkBoxCallback(self, sender): 30 | print "check box state change!", sender.get() 31 | 32 | 33 | def buttonCallback(self, sender): 34 | 35 | def angle(angle, height, yPos): 36 | offset = math.tan(math.radians(angle)) * height / 2 37 | shift = math.tan(math.radians(angle)) * yPos - offset 38 | return shift 39 | 40 | 41 | for thisLayer in selectedLayers: 42 | #Set variables 43 | masterID = thisLayer.associatedMasterId 44 | print masterID 45 | thisMasterXheight = font.masters[masterID].xHeight 46 | thisMasterAngle = thisLayer.glyphMetrics()[5] 47 | 48 | for thisAnchor in thisLayer.anchors: 49 | posY = thisAnchor.position.y 50 | 51 | if self.w.checkBox.get() == True: 52 | newPosition = thisAnchor.position.x + angle(thisMasterAngle, thisMasterXheight, posY) 53 | thisAnchor.position = NSPoint(newPosition, posY) 54 | else: 55 | customAngle = self.w.textBox.get() 56 | 57 | newPosition = thisAnchor.position.x + angle(float(customAngle), thisMasterXheight, posY) 58 | thisAnchor.position = NSPoint(newPosition, posY) 59 | 60 | slantAnchor() 61 | 62 | -------------------------------------------------------------------------------- /createAccentedVersions.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: Create accented versions of glyph 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | Based on a dictionary adds to the font the accented versions of the selected glyphs. 5 | """ 6 | 7 | font = Glyphs.font 8 | 9 | layers = font.selectedLayers 10 | 11 | listOfGlyphs = [] 12 | 13 | Glyphs.clearLog() 14 | Glyphs.showMacroWindow() 15 | 16 | 17 | diacriticsDict = { 18 | "a":("acute", "breve", "circumflex", "dieresis", "grave", "macron", "ogonek", "ring", "tilde"), 19 | "c":("acute", "caron", "cedilla", "dotaccent"), 20 | "d":("caron","croat"), 21 | "e":("acute", "caron", "circumflex", "dieresis", "dotaccent", "grave", "macron", "ogonek"), 22 | "g":("breve", "commaaccent", "dotaccent"), 23 | "h":("bar",), 24 | "i":("acute", "circumflex", "dieresis", "dotaccent", "grave", "macron", "ogonek",), 25 | "k":("commaaccent",), 26 | "l":("acute", "caron", "commaaccent","slash",), 27 | "n":("acute","caron","commaaccent","tilde"), 28 | "o":("acute", "circumflex", "dieresis", "grave", "hungarumlaut", "macron", "slash", "tilde"), 29 | "r":("acute", "caron", "commaaccent"), 30 | "s":("acute", "caron", "cedilla", "commaaccent"), 31 | "t":("bar", "caron", "cedilla", "commaaccent"), 32 | "u":("acute", "circumflex", "dieresis", "grave", "hungarumlaut", "macron", "ogonek", "ring"), 33 | "w":("acute", "circumflex", "dieresis", "grave"), 34 | "y":("acute", "circumflex", "dieresis", "grave"), 35 | "z":("acute", "caron", "dotaccent") 36 | } 37 | 38 | 39 | 40 | def addGlyphs(currentGlyph): 41 | if "." in currentGlyph: 42 | baseGlyph = currentGlyph.split(".")[0] 43 | for diacritic in diacriticsDict[baseGlyph]: 44 | if "." in currentGlyph: 45 | newGlyphName = currentGlyph.replace('.', diacritic+'.') 46 | print newGlyphName 47 | #font.glyphs.append(GSGlyph(newGlyphName)) 48 | #for master in font.masters: 49 | # newLayer = font.glyphs[newGlyphName].layers[master.id] 50 | # newLayer.makeComponents() 51 | else: 52 | newGlyphName = currentGlyph+diacritic 53 | print newGlyphName 54 | #font.glyphs.append(GSGlyph(newGlyphName)) 55 | #for master in font.masters: 56 | # newLayer = font.glyphs[newGlyphName].layers[master.id] 57 | # newLayer.makeComponents() 58 | 59 | 60 | for layer in layers: 61 | glyphName = layer.parent.name 62 | listOfGlyphs.append(glyphName) 63 | addGlyphs(glyphName) -------------------------------------------------------------------------------- /CompareSets.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: CompareSets 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | (UI) Shows diffrences between glyph sets and allows to add glyphs to fonts. 5 | """ 6 | 7 | from GlyphsApp import * 8 | from vanilla import * 9 | 10 | font1 = Glyphs.fonts[0] 11 | font2 = Glyphs.fonts[1] 12 | styleName1 = font1.instances[0].name 13 | styleName2 = font2.instances[0].name 14 | 15 | 16 | class ListDemo(object): 17 | 18 | lista1 = [] 19 | lista2 = [] 20 | 21 | def __init__(self): 22 | 23 | self.createLists() 24 | DiffLists = (self.lista1, self.lista2) 25 | 26 | #User interface elements 27 | self.w = Window((220, 400),"Compare Sets", minSize=(220,400), maxSize=(330,1000)) 28 | self.w.textBox = TextBox((10, 10, -10, 55), "Missing on \n" + font1.familyName + " " + styleName1 + "\nvs. " + font2.familyName + " " + styleName2) 29 | self.w.myList = List((10, 75, -10, -40), self.lista2) 30 | self.w.button = Button((10, -30, -10, 20), "Add to font", 31 | callback=self.button1Callback) 32 | 33 | self.w.open() 34 | 35 | #Button Callbacks 36 | def button1Callback(self, sender): 37 | seleccion = self.w.myList.getSelection() 38 | trash = [] 39 | # Iterate over the selection, add glyph to font and append to trash list. 40 | for i in seleccion: 41 | name = self.w.myList.__getitem__(i) 42 | font1.glyphs.append(GSGlyph(name)) 43 | trash.append(name) 44 | #Add automatic components to the created glyph. 45 | for master in font1.masters: 46 | layer = font1.glyphs[name].layers[master.id] 47 | layer.makeComponents() 48 | #Iterates over the trash list and remove the element from the UI list. 49 | for ii in trash: 50 | self.w.myList.remove(ii) 51 | 52 | 53 | def createLists(self): 54 | # Create lists with the name of the glyphs of each font 55 | for a in font1.glyphs: 56 | self.lista1.append(a.name) #A, B, C, D, etc. 57 | for a in font2.glyphs: 58 | self.lista2.append(a.name) 59 | #Create sets of the lists for later comparision. 60 | font1Set = set(self.lista1) 61 | font2Set = set(self.lista2) 62 | #Create new sets with the differences. 63 | OneDiff = font1Set.difference(font2Set) 64 | TwoDiff = font2Set.difference(font1Set) 65 | #Transorm the sets in list again to send it to the UI 66 | self.lista1 = list(OneDiff) 67 | self.lista2 = list(TwoDiff) 68 | 69 | # Glyphs.clearLog() 70 | # Glyphs.showMacroWindow() 71 | ListDemo() -------------------------------------------------------------------------------- /nodesNearAlignmentZones.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: Nodes near Alignment zones 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | (UI) The script searches for nodes near the alignment zones using an slider as a threshold in units. 5 | """ 6 | from vanilla import * 7 | 8 | class nearAlignment(object): 9 | 10 | def __init__(self): 11 | 12 | self.w = Window((180, 70), "Check Nodes") 13 | self.w.slider = Slider((10, 10, -10, 23), 14 | tickMarkCount=10, 15 | value=0, minValue=0, maxValue=40, 16 | callback=self.sliderCallback) 17 | self.w.textBox = TextBox((10, 40, -10, 17)) 18 | self.updateInfoText() 19 | self.w.open() 20 | 21 | def updateInfoText(self, umbral=0): 22 | infoText = "Threshold: %d u" % int(umbral) 23 | self.w.textBox.set(infoText) 24 | 25 | def sliderCallback(self, sender): 26 | umbral = int(self.w.slider.get()) 27 | self.updateInfoText(umbral) 28 | 29 | def insertArrow(thisLayer, posX, posY, width=30): 30 | arrow = GSAnnotation() 31 | arrow.position = (posX, posY) 32 | arrow.type = CIRCLE 33 | arrow.width = width 34 | thisLayer.annotations.append(arrow) 35 | Glyphs.boolDefaults["showAnnotations"] = True 36 | 37 | def zoneList(master): 38 | zoneList = [] 39 | for z in master.alignmentZones: 40 | zoneOrigin = int(z.position) 41 | zoneEnd = zoneOrigin + int(z.size) 42 | if zoneOrigin < zoneEnd: 43 | zoneList.append((zoneOrigin, zoneEnd)) 44 | else: 45 | zoneList.append((zoneEnd, zoneOrigin)) 46 | return zoneList 47 | 48 | def nextToZone(thisLayer, masterZones, pos, umbral): 49 | for thisZone in masterZones: 50 | zoneOrigin = thisZone[0] 51 | zoneEnd = thisZone[1] 52 | 53 | if pos >= zoneOrigin - umbral and pos < zoneOrigin: 54 | # just below the zone 55 | return True 56 | if pos > zoneEnd and pos <= zoneEnd + umbral: 57 | # just above the zone 58 | return True 59 | 60 | font = Glyphs.font 61 | selectedLayers = font.selectedLayers 62 | 63 | for thisLayer in selectedLayers: 64 | thisLayer.setAnnotations_(None) 65 | masterId = thisLayer.associatedMasterId 66 | master = font.masters[masterId] 67 | 68 | masterZones = zoneList(master) 69 | 70 | for thisPath in thisLayer.paths: 71 | for thisNode in thisPath.nodes: 72 | if thisNode.type != GSOFFCURVE: 73 | posY = thisNode.y 74 | posX = thisNode.x 75 | 76 | if nextToZone(thisLayer, masterZones, posY, umbral) is True: 77 | insertArrow (thisLayer, posX, posY) 78 | print ("A") 79 | 80 | nearAlignment() -------------------------------------------------------------------------------- /automaticOTclasses.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: Automatic OT Classes 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | Create and update Opentype classes based on information (name of class and text in glpyh name) stored in the custom parameter "otFeatureClass". 5 | Useful for update continuously growing classes during the development. Also can create OT class based on another class finding and replacing a value. 6 | Examples of custom parameters: 7 | 8 | Property: otClass 9 | Value: new_class_name;text_in_glyphname 10 | ss01;alt1 11 | 12 | Property: otClassCopyEdit 13 | Value: new_class_name;base_class_name;find_text;replace_text; 14 | ss02;ss01;alt1;alt2; 15 | 16 | """ 17 | 18 | from GlyphsApp import * 19 | 20 | font = Glyphs.font 21 | 22 | Glyphs.clearLog() 23 | Glyphs.showMacroWindow() 24 | 25 | def updateClass(className, text): 26 | listOfClasses = Glyphs.font.classes 27 | lisOfClassesNames = [ c.name for c in Glyphs.font.classes ] 28 | 29 | textInName = text 30 | glyphsForClass = [] 31 | 32 | for glyph in font.glyphs: 33 | if glyph.name.endswith(textInName): 34 | glyphsForClass.append(glyph.name) 35 | 36 | classCode = ' '.join(glyphsForClass) 37 | if len(glyphsForClass) != 0: 38 | if className in lisOfClassesNames: 39 | print ("Updated", className, "with:\n", classCode, "\n") 40 | listOfClasses[ className ].code = classCode 41 | else: 42 | print ("New", className, "with:\n", classCode, "\n") 43 | font.classes.append(GSClass(className, classCode)) 44 | else: 45 | print ("No glyph names with the text: " + text + "\n") 46 | 47 | 48 | 49 | def copyAndEdit (newClass, oldClass, oldText, newText): 50 | listOfClasses = Glyphs.font.classes 51 | lisOfClassesNames = [ c.name for c in Glyphs.font.classes ] 52 | 53 | 54 | oldClassCode = listOfClasses[ oldClass ].code 55 | 56 | newClassCode = oldClassCode.replace(oldText.replace('"', ''), newText.replace('"', '')) 57 | 58 | if newClass in lisOfClassesNames: 59 | print ("Updated", newClass, "with:\n", newClassCode, "\n") 60 | listOfClasses[ newClass ].code = newClassCode 61 | else: 62 | print ("New", newClass, "with:\n", newClassCode, "\n") 63 | font.classes.append(GSClass(newClass, newClassCode)) 64 | 65 | 66 | 67 | 68 | 69 | 70 | for parameter in font.customParameters: 71 | if parameter.name == "otClass": 72 | value = parameter.value.split(";") 73 | name, text = value[0], value[1] 74 | updateClass (name, text) 75 | elif parameter.name == "otClassCopyEdit": 76 | value = parameter.value.split(";") 77 | newClass = value[0] 78 | oldClass = value[1] 79 | oldText = value[2] 80 | newText = value[3] 81 | 82 | # print newClass, oldClass, oldText, newText 83 | copyAndEdit (newClass, oldClass, oldText, newText) 84 | 85 | 86 | -------------------------------------------------------------------------------- /importAnchors.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: Import Anchors 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | (UI) Import anchors from antother font. 5 | """ 6 | 7 | from GlyphsApp import * 8 | from vanilla import * 9 | from Foundation import NSPoint 10 | 11 | 12 | font = Glyphs.font 13 | 14 | 15 | class AnchorTeleporter(object): 16 | 17 | def __init__(self): 18 | infoText = "" 19 | self.w = FloatingWindow((240, 190), "Import Anchors") 20 | self.w.textBox = TextBox((10, 10, -10, 17), "Import Anchors from") 21 | self.w.popUpFont = PopUpButton((10, 35, -10, 20), 22 | self.createFontList(), 23 | callback=self.popUpButtonCallback) 24 | self.w.textBox2 = TextBox((10, 65, -10, 17), "Layer:") 25 | self.w.popUpButton = PopUpButton((10, 85, -10, 20),[""]) 26 | self.w.restoreButton = Button((10, 115, -10, 20), "Copy Anchors", callback=self.cloneCallback) 27 | self.w.line2 = HorizontalLine((10, 145, -10, 1)) 28 | self.w.box = Box((10, 155, -10, 24)) 29 | self.w.box.infoBox = TextBox((0, 0, -10, -0), infoText, alignment='center', sizeStyle='small') 30 | self.w.popUpButton.enable( False ) 31 | self.w.restoreButton.enable( False ) 32 | self.w.open() 33 | 34 | def popUpButtonCallback(self, sender): 35 | selectedFont = Glyphs.fonts[sender.get()] 36 | selectedLayers = selectedFont.selectedLayers 37 | for thisLayer in selectedLayers: 38 | layerList = [] 39 | glyph = thisLayer.parent 40 | for layer in glyph.layers: 41 | layerList.append(layer.name) 42 | self.w.popUpButton.setItems( layerList ) 43 | self.w.popUpButton.enable( True ) 44 | self.w.restoreButton.enable( True ) 45 | 46 | def createFontList(self): 47 | fonts = Glyphs.fonts 48 | fontList = [] 49 | for thisFont in fonts: 50 | fontList.append(thisFont.familyName) 51 | return fontList 52 | 53 | def reloadButtonCallback( self, sender ): 54 | layerList = self.createLayerList() 55 | self.w.popUpButton.setItems( layerList ) 56 | self.w.box.infoBox.set("Anchor list reloaded") 57 | 58 | def cloneCallback(self, sender): 59 | selectedLayers = Glyphs.font.selectedLayers 60 | self.w.box.infoBox.set("Anchors Copied!") 61 | 62 | def copyToFront( thisLayer, anchorName, x, y): 63 | newAnchor = GSAnchor.alloc().init() 64 | newAnchor.name = anchorName 65 | thisLayer.addAnchor_( newAnchor ) 66 | newPosition = NSPoint( x, y) 67 | newAnchor.setPosition_( newPosition ) 68 | 69 | for thisLayer in selectedLayers: 70 | glyph = thisLayer.parent 71 | layerIndex = self.w.popUpButton.get() 72 | popchoose = self.w.popUpButton.getItems() 73 | 74 | 75 | try: 76 | 77 | for i in Glyphs.fonts[1].glyphs[glyph.name].layers: 78 | 79 | if i.name == list(popchoose) [layerIndex]: 80 | backupLayer = i 81 | break 82 | 83 | allAnchors = backupLayer.anchors 84 | 85 | for thisAnchor in allAnchors: 86 | anchorName = thisAnchor.name 87 | x = thisAnchor.position.x 88 | y = thisAnchor.position.y 89 | 90 | copyToFront (thisLayer, anchorName, x, y) 91 | except: 92 | pass 93 | 94 | AnchorTeleporter() -------------------------------------------------------------------------------- /smooth2sharp.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: Smooth2Sharp 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | The script searches smooth nodes with disaligned handles and changes them to sharp. 5 | """ 6 | 7 | import math 8 | 9 | Font = Glyphs.font 10 | selectedLayers = Font.selectedLayers 11 | 12 | for layer in selectedLayers: 13 | paths = layer.paths 14 | 15 | for thisPath in paths: 16 | numOfNodes = len(thisPath.nodes) 17 | 18 | for thisNodeIndex in range(numOfNodes): 19 | thisNode = thisPath.nodes[ thisNodeIndex ] 20 | prevNode = thisPath.nodes[ (thisNodeIndex - 1) % numOfNodes ] 21 | nextNode = thisPath.nodes[ (thisNodeIndex + 1) % numOfNodes ] 22 | 23 | posx = thisNode.position.x 24 | posy = thisNode.position.y 25 | prevposx = prevNode.position.x 26 | prevposy = prevNode.position.y 27 | 28 | nextposx = nextNode.position.x 29 | nextposy = nextNode.position.y 30 | 31 | #1er. Filtro. Fuera OFFCURVES Y SHARPS 32 | if thisNode.type == GSCURVE and thisNode.connection == GSSMOOTH: 33 | 34 | #2do. Filtro. Fuera ortogonales 35 | if not (( posx - prevposx ) + ( posx - nextposx ) == 0.0 or ( posy - prevposy ) + ( posy - nextposy ) == 0.0): 36 | 37 | #triangulo 1 38 | ladoX1 = prevposx - posx 39 | ladoY1 = prevposy - posy 40 | 41 | #triangulo 2 42 | ladoX2 = nextposx - posx 43 | ladoY2 = nextposy - posy 44 | 45 | #3er. Filtro. Fuera nodos con 1 tirador sobre Y o X 46 | if ladoY1 == 0.0 or ladoY2 == 0.0 or ladoX1 == 0.0 or ladoX2 == 0.0: 47 | thisNode.connection = GSSHARP 48 | 49 | else: 50 | #Tangente Angulo Triangulo1 51 | angle1 = ladoY1 / ladoX1 52 | 53 | #Tangente Angulo Triangulo2 54 | angle2 = ladoY2 / ladoX2 55 | 56 | #Redondeo Angulo1 57 | ceil1=(math.ceil(angle1*100)/100) 58 | 59 | #Redondeo Angulo2 60 | ceil2=(math.ceil(angle2*100)/100) 61 | 62 | #Resta de los redondeos 63 | resta = ceil1 - ceil2 64 | 65 | #Limite permitido 66 | if resta > 0.1 or resta < -0.1: 67 | thisNode.connection = GSSHARP -------------------------------------------------------------------------------- /createOTClass.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: Create OT Class 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | (UI) Create an Opentype Class with glyphs containing an specified text. 5 | This script uses code from Mekkablue's "Make OT class from selected glyphs". 6 | """ 7 | 8 | from GlyphsApp import * 9 | from vanilla import * 10 | 11 | 12 | font = Glyphs.font 13 | 14 | class createOtClass(object): 15 | 16 | def __init__(self): 17 | 18 | self.w = Window((240, 145), "Create Ot Class") 19 | self.w.label = TextBox((10, 10, -120, 20), "text in name:") 20 | self.w.textInName = EditText((-130, 10, -10, 20), "", sizeStyle='small') 21 | self.w.label2 = TextBox((10, 40, -120, 20), "Class name:") 22 | self.w.class_name = EditText((-130, 40, -10, 20), "", sizeStyle='small', callback=self.buttonCheck) 23 | self.w.box = Box((10, 70, -10, 36)) 24 | self.w.box.infoBox = TextBox((0, 0, -0, -0), "", alignment='left', sizeStyle='small') 25 | self.w.createButton = Button((10, 115, -10, 20), "Create Class", callback=self.buttonCallback) 26 | 27 | self.w.open() 28 | self.buttonCheck( self.w.class_name ) 29 | 30 | 31 | def buttonCheck(self, sender): 32 | className = sender.get() 33 | lisOfClassesNames = [ c.name for c in Glyphs.font.classes ] 34 | 35 | #print existingClasses 36 | 37 | if className in lisOfClassesNames: 38 | self.w.createButton.enable( True ) 39 | self.w.box.infoBox.set( "Class name already exists. It will overwrite" ) 40 | elif len( className ) == 0 : 41 | self.w.createButton.enable( False ) 42 | self.w.box.infoBox.set( "Class name empty." ) 43 | elif self.checkstring( className ): 44 | self.w.createButton.enable( True ) 45 | self.w.box.infoBox.set( "Class name appears to be ok." ) 46 | elif className[0] in "0123456789": 47 | self.w.createButton.enable( False ) 48 | self.w.box.infoBox.set( "Class name must not start with a figure." ) 49 | else: 50 | self.w.createButton.enable( False ) 51 | self.w.box.infoBox.set( "Illegal characters. Only use A-Z, a-z, figures, period, underscore." ) 52 | 53 | def checkstring(self, teststring, ok=True): 54 | allowedchars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890._" 55 | 56 | if len( teststring ) > 1 : 57 | return self.checkstring( teststring[:-1], ok ) and ( teststring[-1] in allowedchars ) 58 | else: 59 | # first char must not be a figure 60 | return ( teststring[-1] in allowedchars and teststring[-1] not in "1234567890" ) 61 | 62 | 63 | def buttonCallback(self, sender): 64 | listOfClasses = Glyphs.font.classes 65 | lisOfClassesNames = [ c.name for c in Glyphs.font.classes ] 66 | className = str( self.w.class_name.get() ) 67 | textInName = str( self.w.textInName.get() ) 68 | gliphstForClass = [] 69 | 70 | print (textInName) 71 | for glyph in font.glyphs: 72 | if textInName in glyph.name: 73 | gliphstForClass.append(glyph.name) 74 | 75 | classCode = ' '.join(gliphstForClass) 76 | 77 | if className in lisOfClassesNames: 78 | print ("Updating class:", className, "with this glyphs:", classCode) 79 | listOfClasses[ className ].code = classCode 80 | else: 81 | font.classes.append(GSClass(className, classCode)) 82 | print ("Created class:", className, "with this glyphs:", classCode) 83 | 84 | 85 | 86 | 87 | createOtClass() 88 | 89 | -------------------------------------------------------------------------------- /anchorBackupAndClone.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: Anchor Backup and Clone 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | (UI) Backup anchors and copy layer to layer. 5 | """ 6 | 7 | from GlyphsApp import * 8 | from vanilla import * 9 | from Foundation import NSPoint 10 | 11 | 12 | font = Glyphs.font 13 | 14 | 15 | class AnchorTeleporter(object): 16 | 17 | def __init__(self): 18 | 19 | self.createLayerList() 20 | infoText = "" 21 | self.w = Window((240, 180), "Anchor Backup and Clone") 22 | self.w.button = Button((10, 10, -10, 20), "Backup Anchors", 23 | callback=self.buttonCallback) 24 | self.w.line = HorizontalLine((10, 40, -10, 1)) 25 | self.w.textBox = TextBox((10, 50, -10, 17), "Clone Anchors from") 26 | self.w.popUpButton = PopUpButton((10, 75, -50, 20), 27 | self.createLayerList()) 28 | self.w.reloadButton = Button((-40, 75, -10, 20), u"↺", 29 | callback=self.reloadButtonCallback) 30 | self.w.restoreButton = Button((10, 105, -10, 20), "Clone Anchors", callback=self.cloneCallback) 31 | self.w.line2 = HorizontalLine((10, 135, -10, 1)) 32 | self.w.box = Box((10, 145, -10, 24)) 33 | self.w.box.infoBox = TextBox((0, 0, -0, -0), infoText, alignment='center', sizeStyle='small') 34 | 35 | self.w.open() 36 | 37 | def buttonCallback(self, sender): 38 | selectedLayers = Glyphs.font.selectedLayers 39 | self.w.box.infoBox.set("Anchors Backuped") 40 | def createLayer (thisLayer): 41 | glyph = thisLayer.parent 42 | newLayer = GSLayer() 43 | #print font.masters[masterId] 44 | thisMaster = font.masters[thisLayer.associatedMasterId] 45 | newLayer.associatedMasterId = thisMaster.id 46 | 47 | font.glyphs[glyph.name].layers.append(newLayer) 48 | id = newLayer.layerId 49 | backupLayer = font.glyphs[thisLayer.parent.name].layers[id] 50 | relatedMaster = font.masters[backupLayer.associatedMasterId].name 51 | newLayer.name = 'AnchorBackup ' + str(relatedMaster) 52 | 53 | return backupLayer 54 | 55 | #Copy anchors to backup layer 56 | def copyToBackup(backupLayer, anchorName, x, y): 57 | newAnchor = GSAnchor.alloc().init() 58 | newAnchor.name = anchorName 59 | backupLayer.addAnchor_( newAnchor ) 60 | newPosition = NSPoint( x, y) 61 | newAnchor.setPosition_( newPosition ) 62 | 63 | for thisLayer in selectedLayers: 64 | backupLayer = createLayer (thisLayer) 65 | allAnchors = thisLayer.anchors 66 | for thisAnchor in allAnchors: 67 | anchorName = thisAnchor.name 68 | x = thisAnchor.position.x 69 | y = thisAnchor.position.y 70 | 71 | copyToBackup (backupLayer, anchorName, x, y) 72 | # Reload List 73 | layerList = self.createLayerList() 74 | self.w.popUpButton.setItems( layerList ) 75 | 76 | def createLayerList(self): 77 | selectedLayers = Glyphs.font.selectedLayers 78 | 79 | for thisLayer in selectedLayers: 80 | layerList = [] 81 | glyph = thisLayer.parent 82 | for layer in glyph.layers: 83 | layerList.append(layer.name) 84 | return layerList 85 | 86 | def reloadButtonCallback( self, sender ): 87 | layerList = self.createLayerList() 88 | self.w.popUpButton.setItems( layerList ) 89 | self.w.box.infoBox.set("Anchor list reloaded") 90 | 91 | def cloneCallback(self, sender): 92 | selectedLayers = Glyphs.font.selectedLayers 93 | self.w.box.infoBox.set("Anchors cloned!") 94 | 95 | def copyToFront( thisLayer, anchorName, x, y): 96 | newAnchor = GSAnchor.alloc().init() 97 | newAnchor.name = anchorName 98 | thisLayer.addAnchor_( newAnchor ) 99 | newPosition = NSPoint( x, y) 100 | newAnchor.setPosition_( newPosition ) 101 | 102 | for thisLayer in selectedLayers: 103 | glyph = thisLayer.parent 104 | layerIndex = self.w.popUpButton.get() 105 | popchoose = self.w.popUpButton.getItems() 106 | 107 | for i in font.glyphs[glyph.name].layers: 108 | if i.name == popchoose [layerIndex]: 109 | backupLayer = i 110 | 111 | allAnchors = backupLayer.anchors 112 | 113 | for thisAnchor in allAnchors: 114 | anchorName = thisAnchor.name 115 | x = thisAnchor.position.x 116 | y = thisAnchor.position.y 117 | 118 | copyToFront (thisLayer, anchorName, x, y) 119 | 120 | AnchorTeleporter() -------------------------------------------------------------------------------- /getFromNextFont.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: Get from next font 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | (UI) Import anchors from antother font. 5 | """ 6 | 7 | from GlyphsApp import * 8 | from vanilla import * 9 | from Foundation import NSPoint 10 | import copy 11 | 12 | font = Glyphs.font 13 | 14 | class AnchorTeleporter(object): 15 | 16 | def __init__(self): 17 | infoText = "" 18 | self.w = FloatingWindow((300, 320), "Get from next font") 19 | self.w.textBox = TextBox((10, 10, -10, 17), "Get from") 20 | self.w.popUpFont = PopUpButton((10, 35, -10, 20), 21 | self.createFontList(), 22 | callback=self.popUpFontCallback) 23 | self.w.textBox2 = TextBox((10, 65, -10, 17), "Layer:") 24 | self.w.popUpLayers = PopUpButton((10, 85, -10, 20),[""]) 25 | 26 | 27 | 28 | self.w.checkBoxPaths = CheckBox((10, 120, -10, 20), "import paths", value=False) 29 | self.w.checkBoxComponents = CheckBox((10, 145, -10, 20), "import components", value=False) 30 | self.w.checkBoxSidebearings = CheckBox((10, 170, -10, 20), "import Sidebearings", value=False) 31 | self.w.checkBoxAnchors = CheckBox((10, 195, -10, 20), "import Anchors", value=False) 32 | 33 | self.w.importButton = Button((10, -70, -10, 20), "Import", callback=self.cloneCallback) 34 | self.w.line2 = HorizontalLine((10, -40, -10, 1)) 35 | self.w.box = Box((10, -30, -10, 24)) 36 | self.w.box.infoBox = TextBox((0, 0, -10, -0), infoText, alignment='center', sizeStyle='small') 37 | 38 | self.w.popUpLayers.enable( False ) 39 | self.w.importButton.enable( False ) 40 | self.w.open() 41 | 42 | def popUpFontCallback(self, sender): 43 | thisLayer = Glyphs.font.selectedLayers[0] 44 | selectedFont = Glyphs.fonts[sender.get()] 45 | glyphName = thisLayer.parent.name 46 | layerList = [] 47 | 48 | for layer in selectedFont.glyphs[glyphName].layers: 49 | layerList.append(layer.name) 50 | 51 | self.w.popUpLayers.setItems( layerList ) 52 | self.w.popUpLayers.enable( True ) 53 | self.w.importButton.enable( True ) 54 | 55 | def createFontList(self): 56 | fonts = Glyphs.fonts 57 | fontList = [] 58 | for thisFont in fonts: 59 | fontList.append(thisFont.filepath.split("/")[-1]) 60 | return fontList 61 | 62 | def cloneCallback(self, sender): 63 | selectedLayers = Glyphs.font.selectedLayers 64 | 65 | varImportPaths = self.w.checkBoxPaths.get() 66 | varImportComponents = self.w.checkBoxComponents.get() 67 | varImportSidebearings = self.w.checkBoxSidebearings.get() 68 | varImportAnchors = self.w.checkBoxAnchors.get() 69 | 70 | self.w.box.infoBox.set("Anchors Copied!") 71 | 72 | 73 | def importAnchors ( thisLayer, anchorName, x, y): 74 | thisLayer.anchors[anchorName] = GSAnchor() 75 | thisLayer.anchors[anchorName].position = NSPoint(x,y) 76 | # thisLayer.anchors.append( newAnchor ) 77 | 78 | 79 | def importPaths ( thisLayer, nextPath): 80 | thisLayer.paths.append( nextPath ) 81 | 82 | def importComponents ( thisLayer, nextComponent): 83 | thisLayer.components.append( nextComponent ) 84 | 85 | def importSidebearings ( thisLayer, nextPath): 86 | thisLayer.paths.append( nextPath ) 87 | 88 | for thisLayer in selectedLayers: 89 | glyph = thisLayer.parent 90 | layerIndex = self.w.popUpLayers.get() 91 | popchoose = self.w.popUpLayers.getItems() 92 | 93 | nextFontIndex = self.w.popUpFont.get() 94 | 95 | 96 | 97 | try: 98 | 99 | for i in Glyphs.fonts[nextFontIndex].glyphs[glyph.name].layers: 100 | 101 | if i.name == list(popchoose) [layerIndex]: 102 | nextFontLayer = i 103 | 104 | if varImportAnchors == True: 105 | nextFontAnchors = nextFontLayer.anchors 106 | 107 | for nextAnchor in nextFontAnchors: 108 | thisAnchor = nextAnchor.copy() 109 | thisLayer.anchors[ str(thisAnchor.name) ] = thisAnchor 110 | #anchorName = nextAnchor.name 111 | # x = nextAnchor.position.x 112 | # y = nextAnchor.position.y 113 | 114 | # importAnchors (thisLayer, anchorName, x, y) 115 | 116 | if varImportPaths == True: 117 | nextFontPaths = nextFontLayer.paths 118 | for nextPath in nextFontPaths: 119 | importPaths (thisLayer, nextPath) 120 | 121 | if varImportComponents == True: 122 | nextFontComponents = nextFontLayer.components 123 | for nextComponent in nextFontComponents: 124 | importComponents (thisLayer, nextComponent) 125 | 126 | if varImportSidebearings == True: 127 | if nextFontLayer.leftMetricsKey != None: 128 | thisLayer.leftMetricsKey = nextFontLayer.leftMetricsKey 129 | else: 130 | thisLayer.LSB = nextFontLayer.LSB 131 | 132 | if nextFontLayer.rightMetricsKey != None: 133 | thisLayer.rightMetricsKey = nextFontLayer.rightMetricsKey 134 | else: 135 | thisLayer.RSB = nextFontLayer.RSB 136 | 137 | self.w.box.infoBox.set("Data imported!") 138 | break 139 | 140 | 141 | except Exception, e: 142 | print e 143 | 144 | 145 | 146 | AnchorTeleporter() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Glyphs-Scripts 2 | ============== 3 | Python scripts for the font editor Glyphs App. 4 | Please feel free to report bugs and send suggestions. 5 | 6 | 7 | #### addAnchorstoGlpyhs.py 8 | Using prebuilt dictionary the script adds anchors to all the master of the selected glyphs. 9 | Feel free to edit the dictionary to match your scheme of anchors. 10 | For those glyphs that are not in the dictionary the script do nothing. 11 | The position of the anchors are estimated, inside the script there is another dictionary with the position of the anchors. 12 | 13 | —Thanks to Mark Frömberg (mirque.de) for the angle function. 14 | 15 | --- 16 | 17 | #### addGuidelineName.py 18 | (UI) Allows to add name to guideline (¿hidden feature in Glyphs?) 19 | 20 | ![](readme_imgs/screen-addGuidelineName.png) 21 | 22 | --- 23 | 24 | #### addNamesToStylisticSets.py 25 | Based on a given list adds descriptive names to stylistic sets. (You have to edit the list inside the script) 26 | 27 | --- 28 | 29 | #### anchorBackupAndClone.py 30 | (UI) Backup anchors to a secondary layer and allows to copy them to another layer. 31 | 32 | ![](readme_imgs/screen-backupClone.png) 33 | 34 | 35 | --- 36 | 37 | #### anchorDictionary.py 38 | Use this complementarty script to create a dictionary based on a previous font. 39 | Outputs to the console. 40 | 41 | --- 42 | 43 | #### automaticOTclasses.py 44 | 45 | Create and update Opentype classes based on information (name of class and text in glpyh name) stored in the custom parameter "otFeatureClass". 46 | Useful for update continuously growing classes during the development. Also, can create OT class based on another class finding and replacing a value. 47 | 48 | Examples of custom parameters: 49 | 50 | Property: `otClass` 51 | Value: `new_class_name;text_in_glyphname;` 52 | Example: Using `ss01;alt1;` as value will create a class named "ss01" containing all glyphs with the text "alt1" in its name. 53 | 54 | Property: `otClassCopyEdit` 55 | Value: `new_class_name;base_class_name;find_text;replace_text;` 56 | Example: Using `ss02;ss01;alt1;alt2;` will findcreate a class named "ss02" based on "ss01" class replacing the text "alt1" for "alt2". 57 | 58 | --- 59 | 60 | #### centerAnchors.py 61 | Center all anchors of all layers of selected glyphs. 62 | 63 | --- 64 | 65 | #### closeAllTabs.py 66 | Close all open tabs in the document. 67 | 68 | --- 69 | 70 | #### compareSets.py 71 | (UI) Compares glyph sets between two open fonts and allows to add missing glyph to the other font. 72 | 73 | ![](readme_imgs/screen-comparesets.png) 74 | 75 | --- 76 | 77 | #### copyAnchorsFromBase.py 78 | On a suffixed glyph run the script to copy the anchors from the default version. (i.e. run on /a.ss01 and get /a anchors). 79 | 80 | --- 81 | 82 | #### createAccentedVersions.py 83 | 84 | Based on a dictionary stored inside the script outputs to the console the accented versions of the selected glyph. Useful for create accented versions of suffixed glyphs (i.e. run on /a.ss01 and get aacute.ss01, abreve.ss01...). Edit the dictionary to match your set. 85 | 86 | --- 87 | 88 | #### createOTClass.py 89 | (UI) Create an Opentype Class with glyphs containing an specified text. 90 | —This script uses code from Mekkablue's "Make OT class from selected glyphs". 91 | 92 | ![](readme_imgs/screen-createotclass.png) 93 | 94 | --- 95 | 96 | #### deleteNotes.py 97 | Delete annotations in selected glyphs 98 | 99 | --- 100 | 101 | #### getComponentAnchors.py 102 | In a glyph made of components, gets component acnhors and add it to the current layer, useful for ligatures. 103 | 104 | --- 105 | 106 | #### importAnchors.py 107 | (UI) Import anchors from antother font or another layer of the same font. 108 | 109 | ![](readme_imgs/screen-importAnchors.png) 110 | 111 | --- 112 | 113 | #### insertImagesToBackground.py 114 | In the dialog box select images to insert as background. Images must be named with the corresponding glyphname (ie. a.jpg) in case the glyph is not in the font the script will create it. 115 | 116 | --- 117 | 118 | #### markZeroHandles.py 119 | Searches for handles that are overlapping nodes and marks them with an annotation circle, setting also the layer label color to orange. 120 | 121 | --- 122 | 123 | #### newTabModifiedGlyphs.py 124 | (UI) Opens in a new tab modified glyphs after or before certain date. 125 | 126 | ![](readme_imgs/screen-newTabModifiedGlpyhs.png) 127 | 128 | --- 129 | 130 | #### newTabMoreThanXComponents.py 131 | (UI) Based on selected glyphs, opens in a new tab the glyphs with more than the indicated ammout of components. 132 | 133 | ![](readme_imgs/screen-newTabMoreThanXComponents.png) 134 | 135 | 136 | --- 137 | 138 | #### newTabWithNotMonoGlyphs.py 139 | (UI) Opens a newtab with those glyphs that doesn’t match with the indicated glyph width. Useful for checking monospaced fonts or tabular glyphs. 140 | 141 | ![](readme_imgs/screen-newTabWithNotMonoGlyphs.png) 142 | 143 | --- 144 | 145 | #### nodesNearAlignmentZones.py 146 | 147 | (UI) Based on a upm “threshold” slider, adds circle annotation to nodes that are close to alignment zones. Useful for misplaced nodes that affects hinting. 148 | 149 | ![](readme_imgs/screen-nodesNearAlignmentZones.png) 150 | 151 | --- 152 | 153 | #### reportAnchorsnOffMetrics.py 154 | 155 | Prints to the console a list with the anchors off the metrics of the selected glyphs. 156 | _This script is a modification of a mekkablue's script._ 157 | 158 | --- 159 | 160 | #### reportGlyphAnchors.py 161 | 162 | Prints to the console the anchors used on selected Glyphs. 163 | 164 | --- 165 | 166 | #### slantAnchors.py 167 | 168 | (UI) Moves anchors according to master italic angle. Useful when importing anchors from roman versions, or after changing the font angle. 169 | 170 | ![](readme_imgs/screen-slantAnchors.png) 171 | 172 | --- 173 | 174 | #### smooth2Sharp.py 175 | 176 | The script searches smooth nodes with disaligned handles and changes them to sharp. Maybe useful for auto traced illustrations. 177 | 178 | -------------------------------------------------------------------------------- /AddAnchorstoGlpyhs.py: -------------------------------------------------------------------------------- 1 | #MenuTitle: Add anchors to selected glyphs 2 | # -*- coding: utf-8 -*- 3 | __doc__=""" 4 | Using a dictionary the script adds anchors to all the master of the selected glyphs. 5 | For those glyphs that are not in the dictionary the script do nothing. 6 | Feel free to edit the dictionary to your needs. 7 | There is also another dictionary with the position of the anchors. 8 | """ 9 | #THIS SCRIPT is heavly based on Ohbendy's Place anchors in all masters 10 | #https://github.com/ohbendy/Python-scripts-for-Glyphs 11 | #Thanks to Mark Frömberg (mirque.de) for the angle function. 12 | 13 | import GlyphsApp 14 | from Foundation import NSPoint 15 | import math 16 | 17 | #Dictionary of anchors for Lowercase 18 | lowerDict = { 19 | "a":("bottom", "top", "grave", "acute", "ogonek",), 20 | "c":("bottom", "cedilla", "top", "acute",), 21 | "d":("bottom", "topRight", "top",), 22 | "e":("bottom", "ogonek", "top", "grave", "acute",), 23 | "g":("acute", "bottom", "top",), 24 | "h":("bottom", "top",), 25 | "i":("bottom", "ogonek",), 26 | "idotless":("acute", "grave", "top",), 27 | "jdotless":("top",), 28 | "k":("bottom",), 29 | "l":("bottom", "center", "topRight", "acute",), 30 | "n":("acute", "bottom", "top", "topLeft",), 31 | "o":("bottom", "ogonek", "grave", "top", "acute",), 32 | "r":("acute", "bottom", "top",), 33 | "s":("bottom", "cedilla", "top", "acute",), 34 | "t":("cedilla", "topRight", "bottom",), 35 | "u":("bottom", "acuteHigh", "graveHigh", "grave", "ogonek", "topHigh", "top", "acute",), 36 | "w":("top", "grave", "acute",), 37 | "y":("top", "grave", "acute",), 38 | "z":("acute", "bottom", "top",), 39 | "ae":("acute",), 40 | "s_t":( "bottomRight", "bottomLeft", "cedilla", "topRight",), 41 | "c_t":( "cedilla", "bottom", "topRight",), 42 | "acute":("_acuteHigh", "_acute",), 43 | "apostrophemod":("_topLeft",), 44 | "breve":("_top",), 45 | "breveinvertedcomb":("_top",), 46 | "caron":("_topHigh", "_top",), 47 | "caron.alt":("_topRight",), 48 | "cedilla":("_cedilla",), 49 | "circumflex":("_top",), 50 | "commaaccentcomb":("_bottom", "_bottomLeft", "_bottomRight",), 51 | "dblgravecomb":("_top",), 52 | "dieresis":("_top",), 53 | "dotaccent":("_top",), 54 | "dotbelowcomb":("_bottom",), 55 | "grave":("_graveHigh", "_grave",), 56 | "hungarumlaut":("_top",), 57 | "kreska":("_top",), 58 | "macron":("_topHigh", "_top",), 59 | "ogonek":("_ogonek",), 60 | "ring":("_top",), 61 | "tilde":("_top",), 62 | } 63 | #Dictionary of anchors for Uppercase 64 | upperDict = { 65 | "A":("bottom", "ogonek", "top", "grave", "acute",), 66 | "C":("bottom", "cedilla", "top", "acute",), 67 | "D":("top", "bottom",), 68 | "E":("bottom", "ogonek", "top", "grave", "acute",), 69 | "G":("acute", "bottom", "top",), 70 | "H":("top", "bottom",), 71 | "I":("bottom", "ogonek", "top", "grave", "acute",), 72 | "J":("top",), 73 | "K":("bottom",), 74 | "L":("bottom", "center", "topRight", "acute",), 75 | "N":("acute", "bottom", "top",), 76 | "O":("bottom", "ogonek", "top", "grave", "acute",), 77 | "R":("acute", "bottom", "top",), 78 | "S":("bottom", "cedilla", "top", "acute",), 79 | "T":("cedilla", "bottom", "top",), 80 | "U":("bottom", "acuteHigh", "graveHigh", "grave", "ogonek", "topHigh", "top", "acute",), 81 | "W":("acute", "grave", "top",), 82 | "Y":("acute", "grave", "top",), 83 | "Z":("acute", "bottom", "top",), 84 | "AE":("acute",), 85 | "acute.case":("_acuteHigh", "_acute",), 86 | "breve.case":("_top",), 87 | "breveinvertedcomb.case":("_top",), 88 | "caron.case":("_topHigh", "_top",), 89 | "cedilla.case":("_cedilla",), 90 | "circumflex.case":("_top",), 91 | "commaaccentcomb.case":("_bottom",), 92 | "dblgravecomb.case":("_top",), 93 | "dieresis.case":("_top",), 94 | "dotaccent.case":("_top",), 95 | "dotbelowcomb.case":("_bottom",), 96 | "grave.case":("_graveHigh", "_grave",), 97 | "hungarumlaut.case":("_top",), 98 | "kreska.case":("_top",), 99 | "macron.case":("_topHigh", "_top",), 100 | "ogonek.case":("_ogonek",), 101 | "ring.case":("_top",), 102 | "tilde.case":("_top",), 103 | } 104 | 105 | #Dictionary of anchors for SmallCaps 106 | smallDict = { 107 | "a.sc":("bottom", "ogonek", "top", "grave", "acute",), 108 | "c.sc":("bottom", "cedilla", "top", "acute",), 109 | "d.sc":("top", "bottom",), 110 | "e.sc":("bottom", "ogonek", "top", "grave", "acute",), 111 | "g.sc":("acute", "bottom", "top",), 112 | "h.sc":("top", "bottom",), 113 | "i.sc":("bottom", "ogonek", "top", "grave", "acute",), 114 | "j.sc":("top",), 115 | "k.sc":("bottom",), 116 | "l.sc":("bottom", "center", "topRight", "acute",), 117 | "n.sc":("acute", "bottom", "top", "topLeft",), 118 | "o.sc":("bottom", "ogonek", "top", "grave", "acute",), 119 | "r.sc":("acute", "bottom", "top",), 120 | "s.sc":("bottom", "cedilla", "top", "acute",), 121 | "t.sc":("cedilla", "bottom", "top",), 122 | "u.sc":("bottom", "acuteHigh", "graveHigh", "grave", "ogonek", "topHigh", "top", "acute",), 123 | "w.sc":("acute", "grave", "top",), 124 | "y.sc":("acute", "grave", "top",), 125 | "z.sc":("acute", "bottom", "top",), 126 | "ae.sc":("acute",), 127 | "acute.sc":("_acuteHigh", "_acute",), 128 | "apostrophemod.sc":("_topLeft",), 129 | "breve.sc":("_top",), 130 | "breveinvertedcomb.sc":("_top",), 131 | "caron.sc":("_topHigh", "_top",), 132 | "cedilla.sc":("_cedilla",), 133 | "circumflex.sc":("_top",), 134 | "commaaccentcomb.sc":("_bottom", "_bottomLeft", "_bottomRight",), 135 | "dblgravecomb.sc":("_top",), 136 | "dieresis.sc":("_top",), 137 | "dotaccent.sc":("_top",), 138 | "dotbelowcomb.sc":("_bottom",), 139 | "grave.sc":("_graveHigh", "_grave",), 140 | "hungarumlaut.sc":("_top",), 141 | "kreska.sc":("_top",), 142 | "macron.sc":("_topHigh", "_top",), 143 | "ogonek.sc":("_ogonek",), 144 | "ring.sc":("_top",), 145 | "tilde.sc":("_top",), 146 | } 147 | 148 | 149 | 150 | def selectDict(glyphName, gDict, gPosDict, gLayer): 151 | for anchorName in gDict[glyphName]: 152 | if anchorName in gPosDict: 153 | posXY = gPosDict[anchorName] 154 | else: 155 | posXY = (100, 200) 156 | pos = NSPoint(posXY[0], posXY[1]) 157 | addAnchorToLayer(gLayer, anchorName, pos) 158 | 159 | def addAnchorToLayer( thisLayer, anchorName, thisPosition): 160 | if thisLayer: 161 | newAnchor = GSAnchor.alloc().init() 162 | newAnchor.name = anchorName 163 | thisLayer.addAnchor_( newAnchor ) 164 | newPosition = NSPoint( thisPosition[0], thisPosition[1]) 165 | newAnchor.setPosition_( newPosition ) 166 | 167 | 168 | font = Glyphs.font 169 | layer = font.selectedLayers[0] 170 | glyph = layer.parent 171 | master = font.masters[0] 172 | allSelectedGlyphs = [l.parent for l in font.selectedLayers] 173 | lowerGlyphNames = lowerDict.keys() 174 | upperGlyphNames = upperDict.keys() 175 | smallGlyphNames = smallDict.keys() 176 | 177 | 178 | def angle(angle, height, yPos): 179 | ''' 180 | Italic Angle 181 | ''' 182 | # Thank to Mark Frömberg for this 183 | offset = math.tan(math.radians(angle)) * height / 2 184 | shift = math.tan(math.radians(angle)) * yPos - offset 185 | 186 | return shift 187 | 188 | 189 | # iterate through all layers of all selected glyphs: 190 | for thisGlyph in allSelectedGlyphs: 191 | for thisLayer in thisGlyph.layers: 192 | thisLayer.setAnchors_( None ) 193 | # determine the master to which the layer belongs: 194 | masterID = thisLayer.associatedMasterId 195 | thisMasterXheight = font.masters[masterID].xHeight 196 | thisMasterUpperheight = font.masters[masterID].capHeight 197 | if font.masters[masterID].customParameters['smallCapHeight']: 198 | thisMasterSmallheight = float(font.masters[masterID].customParameters['smallCapHeight']) 199 | else: 200 | thisMasterSmallheight = thisMasterXheight 201 | thisMasterAngle = thisLayer.glyphMetrics()[5] 202 | 203 | 204 | 205 | #Dictionaries whith the anchors positions. 206 | lowerPosDict = { 207 | "acute":(thisLayer.width/2 - 20+ angle(thisMasterAngle, thisMasterXheight, thisMasterXheight), thisMasterXheight), 208 | "bottom":(thisLayer.width/2 + 20+ angle(thisMasterAngle, thisMasterXheight, 0), 0), 209 | "center":(thisLayer.width/2 + 20+ angle(thisMasterAngle, thisMasterXheight, thisMasterXheight/2), thisMasterXheight/2), 210 | "grave":(thisLayer.width/2 + 20+ angle(thisMasterAngle, thisMasterXheight, thisMasterXheight), thisMasterXheight), 211 | "ogonek":(thisLayer.bounds.size.width, 0), 212 | "top":(thisLayer.width/2 + angle(thisMasterAngle, thisMasterXheight, thisMasterXheight), thisMasterXheight), 213 | "topHigh":(thisLayer.width/2 + angle(thisMasterAngle, thisMasterXheight, thisMasterXheight+100), thisMasterXheight+100), 214 | "acuteHigh":(thisLayer.width/2 - 20+ angle(thisMasterAngle, thisMasterXheight, thisMasterXheight+100), thisMasterXheight+100), 215 | "graveHigh":(thisLayer.width/2 + 20+ angle(thisMasterAngle, thisMasterXheight, thisMasterXheight+100), thisMasterXheight+100), 216 | "topRight":(thisLayer.bounds.size.width, thisMasterXheight), 217 | "_acute":(thisLayer.width/2+ angle(thisMasterAngle, thisMasterXheight, thisMasterXheight), thisMasterXheight), 218 | "_bottom":(thisLayer.width/2+ angle(thisMasterAngle, thisMasterXheight, 0), 0), 219 | "_center":(thisLayer.width/2+ angle(thisMasterAngle, thisMasterXheight, thisMasterXheight), thisMasterXheight), 220 | "_grave":(thisLayer.width/2+ angle(thisMasterAngle, thisMasterXheight, thisMasterXheight), thisMasterXheight), 221 | "_ogonek":(thisLayer.bounds.size.width, 0), 222 | "_top":(thisLayer.width/2 + angle(thisMasterAngle, thisMasterXheight, thisMasterXheight), thisMasterXheight), 223 | "_topHigh":(thisLayer.width/2 + angle(thisMasterAngle, thisMasterXheight, thisMasterXheight), thisMasterXheight), 224 | "_acuteHigh":(thisLayer.width/2 + angle(thisMasterAngle, thisMasterXheight, thisMasterXheight), thisMasterXheight), 225 | "_graveHigh":(thisLayer.width/2 + angle(thisMasterAngle, thisMasterXheight, thisMasterXheight), thisMasterXheight), 226 | "_topRight":(thisLayer.width/2 + angle(thisMasterAngle, thisMasterXheight, thisMasterXheight), thisMasterXheight), 227 | } 228 | 229 | upperPosDict = { 230 | "acute":(thisLayer.width/2 - 20+ angle(thisMasterAngle, thisMasterUpperheight, thisMasterUpperheight), thisMasterUpperheight), 231 | "bottom":(thisLayer.width/2 + 20+ angle(thisMasterAngle, thisMasterUpperheight, 0), 0), 232 | "center":(thisLayer.width/2 + 20+ angle(thisMasterAngle, thisMasterUpperheight, thisMasterUpperheight/2), thisMasterUpperheight/2), 233 | "grave":(thisLayer.width/2 + 20+ angle(thisMasterAngle, thisMasterUpperheight, thisMasterUpperheight), thisMasterUpperheight), 234 | "ogonek":(thisLayer.bounds.size.width, 0), 235 | "top":(thisLayer.width/2 + angle(thisMasterAngle, thisMasterUpperheight, thisMasterUpperheight), thisMasterUpperheight), 236 | "topHigh":(thisLayer.width/2 + angle(thisMasterAngle, thisMasterUpperheight, thisMasterUpperheight+100), thisMasterUpperheight+100), 237 | "acuteHigh":(thisLayer.width/2 - 20+ angle(thisMasterAngle, thisMasterUpperheight, thisMasterUpperheight+100), thisMasterUpperheight+100), 238 | "graveHigh":(thisLayer.width/2 + 20+ angle(thisMasterAngle, thisMasterUpperheight, thisMasterUpperheight+100), thisMasterUpperheight+100), 239 | "topRight":(thisLayer.bounds.size.width, thisMasterUpperheight), 240 | "_acute":(thisLayer.width/2+ angle(thisMasterAngle, thisMasterUpperheight, thisMasterUpperheight), thisMasterUpperheight), 241 | "_bottom":(thisLayer.width/2+ angle(thisMasterAngle, thisMasterUpperheight, 0), 0), 242 | "_center":(thisLayer.width/2+ angle(thisMasterAngle, thisMasterUpperheight, thisMasterUpperheight), thisMasterUpperheight), 243 | "_grave":(thisLayer.width/2+ angle(thisMasterAngle, thisMasterUpperheight, thisMasterUpperheight), thisMasterUpperheight), 244 | "_ogonek":(thisLayer.bounds.size.width, 0), 245 | "_top":(thisLayer.width/2 + angle(thisMasterAngle, thisMasterUpperheight, thisMasterUpperheight), thisMasterUpperheight), 246 | "_topHigh":(thisLayer.width/2 + angle(thisMasterAngle, thisMasterUpperheight, thisMasterUpperheight), thisMasterUpperheight), 247 | "_acuteHigh":(thisLayer.width/2 + angle(thisMasterAngle, thisMasterUpperheight, thisMasterUpperheight), thisMasterUpperheight), 248 | "_graveHigh":(thisLayer.width/2 + angle(thisMasterAngle, thisMasterUpperheight, thisMasterUpperheight), thisMasterUpperheight), 249 | "_topRight":(thisLayer.width/2 + angle(thisMasterAngle, thisMasterUpperheight, thisMasterUpperheight), thisMasterUpperheight), 250 | } 251 | smallPosDict = { 252 | "acute":(thisLayer.width/2 - 20+ angle(thisMasterAngle, thisMasterSmallheight, thisMasterSmallheight), thisMasterSmallheight), 253 | "bottom":(thisLayer.width/2 + 20+ angle(thisMasterAngle, thisMasterSmallheight, 0), 0), 254 | "center":(thisLayer.width/2 + 20+ angle(thisMasterAngle, thisMasterSmallheight, thisMasterSmallheight/2), thisMasterSmallheight/2), 255 | "grave":(thisLayer.width/2 + 20+ angle(thisMasterAngle, thisMasterSmallheight, thisMasterSmallheight), thisMasterSmallheight), 256 | "ogonek":(thisLayer.bounds.size.width, 0), 257 | "top":(thisLayer.width/2 + angle(thisMasterAngle, thisMasterSmallheight, thisMasterSmallheight), thisMasterSmallheight), 258 | "topHigh":(thisLayer.width/2 + angle(thisMasterAngle, thisMasterSmallheight, thisMasterSmallheight+100), thisMasterSmallheight+100), 259 | "acuteHigh":(thisLayer.width/2 - 20+ angle(thisMasterAngle, thisMasterSmallheight, thisMasterSmallheight+100), thisMasterSmallheight+100), 260 | "graveHigh":(thisLayer.width/2 + 20+ angle(thisMasterAngle, thisMasterSmallheight, thisMasterSmallheight+100), thisMasterSmallheight+100), 261 | "topRight":(thisLayer.bounds.size.width, thisMasterSmallheight), 262 | "_acute":(thisLayer.width/2+ angle(thisMasterAngle, thisMasterSmallheight, thisMasterSmallheight), thisMasterSmallheight), 263 | "_bottom":(thisLayer.width/2+ angle(thisMasterAngle, thisMasterSmallheight, 0), 0), 264 | "_center":(thisLayer.width/2+ angle(thisMasterAngle, thisMasterSmallheight, thisMasterSmallheight), thisMasterSmallheight), 265 | "_grave":(thisLayer.width/2+ angle(thisMasterAngle, thisMasterSmallheight, thisMasterSmallheight), thisMasterSmallheight), 266 | "_ogonek":(thisLayer.bounds.size.width, 0), 267 | "_top":(thisLayer.width/2 + angle(thisMasterAngle, thisMasterSmallheight, thisMasterSmallheight), thisMasterSmallheight), 268 | "_topHigh":(thisLayer.width/2 + angle(thisMasterAngle, thisMasterSmallheight, thisMasterSmallheight), thisMasterSmallheight), 269 | "_acuteHigh":(thisLayer.width/2 + angle(thisMasterAngle, thisMasterSmallheight, thisMasterSmallheight), thisMasterSmallheight), 270 | "_graveHigh":(thisLayer.width/2 + angle(thisMasterAngle, thisMasterSmallheight, thisMasterSmallheight), thisMasterSmallheight), 271 | "_topRight":(thisLayer.width/2 + angle(thisMasterAngle, thisMasterSmallheight, thisMasterSmallheight), thisMasterSmallheight), 272 | } 273 | 274 | # determine the master to which the layer belongs: 275 | thisGlyphName = thisGlyph.name 276 | # if it is listed in anchorDict, add the anchor at the given position: 277 | if thisGlyphName in lowerGlyphNames: 278 | selectDict(thisGlyphName, lowerDict, lowerPosDict, thisLayer) 279 | 280 | elif thisGlyphName in upperGlyphNames: 281 | selectDict(thisGlyphName, upperDict, upperPosDict, thisLayer) 282 | 283 | elif thisGlyphName in smallGlyphNames: 284 | selectDict(thisGlyphName, smallDict, smallPosDict, thisLayer) 285 | 286 | else: 287 | print ("the /" + thisGlyphName + " glyph is not in the dictionary") 288 | --------------------------------------------------------------------------------