├── .gitignore ├── Atom ├── package.json └── snippets │ └── Atom.cson ├── LICENSE ├── README.md └── TextMate └── Python for Glyphs.tmbundle ├── Snippets ├── Glyphs gui button help.tmSnippet ├── Glyphs gui button popup.tmSnippet ├── Glyphs gui button square.tmSnippet ├── Glyphs gui checkbox.tmSnippet ├── Glyphs gui combo box.tmSnippet ├── Glyphs gui pref load.tmSnippet ├── Glyphs gui pref register & load.tmSnippet ├── Glyphs gui pref register.tmSnippet ├── Glyphs gui pref save.tmSnippet ├── Glyphs gui progress bar.tmSnippet ├── Glyphs gui radiobuttons.tmSnippet ├── Glyphs gui text box.tmSnippet ├── Glyphs gui text edit.tmSnippet ├── Glyphs gui tooltip.tmSnippet ├── Glyphs gui.tmSnippet ├── Glyphs plugin conditions for drawing.tmSnippet ├── Glyphs plugin def.tmSnippet ├── Glyphs plugin keyboard shortcut.tmSnippet ├── Glyphs plugin log.tmSnippet ├── Glyphs plugin productPageURL.tmSnippet ├── Glyphs plugin updateFeedURL.tmSnippet ├── Glyphs py UI update.tmSnippet ├── Glyphs py angle.tmSnippet ├── Glyphs py circle.tmSnippet ├── Glyphs py clear log.tmSnippet ├── Glyphs py create ot class.tmSnippet ├── Glyphs py create ot feature.tmSnippet ├── Glyphs py distance.tmSnippet ├── Glyphs py italic.tmSnippet ├── Glyphs py layer offset.tmSnippet ├── Glyphs py layer round.tmSnippet ├── Glyphs py macro window.tmSnippet ├── Glyphs py measurement.tmSnippet ├── Glyphs py menutitle.tmSnippet ├── Glyphs py msg.tmSnippet ├── Glyphs py notification.tmSnippet ├── Glyphs py path intersect.tmSnippet ├── Glyphs py path subtract.tmSnippet ├── Glyphs py report.tmSnippet ├── Glyphs py tab with layers.tmSnippet ├── Glyphs py tab with text.tmSnippet ├── Glyphs py transform.tmSnippet ├── Glyphs py.tmSnippet ├── Glyphs2:3 if:then.tmSnippet ├── PyObjC Decorators.tmSnippet ├── py NSColor RGB.tmSnippet ├── py NSEvent Modifier Keys.tmSnippet ├── py abc.tmSnippet ├── py bezier.tmSnippet ├── py bezierQ.tmSnippet ├── py center of NSRect.tmSnippet ├── py clipboard.tmSnippet ├── py future imports.tmSnippet ├── py intersect two lines.tmSnippet ├── py random.tmSnippet ├── py sort.tmSnippet ├── py terminal.tmSnippet ├── py timer.tmSnippet ├── py traceback.tmSnippet ├── py write file.tmSnippet └── vanilla help.tmSnippet └── info.plist /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | TextMate/.DS_Store 3 | 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /Atom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "python-for-glyphs", 3 | "version": "0.0.0", 4 | "description": "These are text editor snippets intended to facilitate Python development for Glyphs.app. Have fun.", 5 | "repository": "https://github.com/mekkablue/python-for-glyphs", 6 | "license": "Apache License, Version 2.0", 7 | "engines": { 8 | "atom": ">0.50.0" 9 | }, 10 | "dependencies": { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Atom/snippets/Atom.cson: -------------------------------------------------------------------------------- 1 | '*': 2 | 'Glyphs gui checkbox': 3 | 'prefix': 'checkbox' 4 | 'body': 'self.w.${1:myCheckBox} = vanilla.CheckBox( (15, ${2:170}, -15, 20), "${3:Activate option}", value=${4:False}, callback=self.SavePreferences, sizeStyle=\'small\' )\n$0' 5 | 'Glyphs gui pref load': 6 | 'prefix': 'prefload' 7 | 'body': 'self.w.${2:itemClassName}.set( Glyphs.defaults["com.mekkablue.${1:mainClassName}.$2"] )$0' 8 | 'Glyphs gui pref save': 9 | 'prefix': 'prefsave' 10 | 'body': 'Glyphs.defaults["com.mekkablue.${1:mainClassName}.${2:itemClassName}"] = self.w.$2.get()$0' 11 | 'Glyphs gui radiobuttons': 12 | 'prefix': 'radio' 13 | 'body': 'listOfOptions = [ ${1:"Option 1", "Option 2", "Option 3"} ]\nself.w.${2:radioButtons} = vanilla.RadioGroup( (15, ${3:100}, -15, 20*len(listOfOptions) ), listOfOptions, callback=self.SavePreferences, sizeStyle = \'small\' )\nself.w.$2.set( 0 )\n$0' 14 | 'Glyphs gui': 15 | 'prefix': 'gsgui' 16 | 'body': '#MenuTitle: ${1:$TM_FILENAME}\n# -*- coding: utf-8 -*-\n__doc__="""\n${2:Create effect for selected glyphs.}\n"""\n\nimport vanilla\nimport GlyphsApp\n\nclass ${3:${1/ //g}}( object ):\n\tdef __init__( self ):\n\t\t# Window \'self.w\':\n\t\twindowWidth = 350\n\t\twindowHeight = 260\n\t\twindowWidthResize = 100 # user can resize width by this value\n\t\twindowHeightResize = 0 # user can resize height by this value\n\t\tself.w = vanilla.FloatingWindow(\n\t\t\t( windowWidth, windowHeight ), # default window size\n\t\t\t"$1", # window title\n\t\t\tminSize = ( windowWidth, windowHeight ), # minimum size (for resizing)\n\t\t\tmaxSize = ( windowWidth + windowWidthResize, windowHeight + windowHeightResize ), # maximum size (for resizing)\n\t\t\tautosaveName = "com.${4:mekkablue}.$3.mainwindow" # stores last window position and size\n\t\t)\n\t\t\n\t\t# UI elements:\n\t\tself.w.text_1 = vanilla.TextBox( (15-1, 12+2, 75, 14), "Insert", sizeStyle=\'small\' )\n\t\tself.w.popup_1 = vanilla.PopUpButton( (15+40, 12, 50, 17), [str(x) for x in range( 3, 12 )], callback=self.SavePreferences, sizeStyle=\'small\' )\n\t\tself.w.edit_1 = vanilla.EditText( (15+40+50+10, 12, -15, 15+3), "insert text here", sizeStyle = \'small\')\n\t\t\n\t\t# Run Button:\n\t\tself.w.runButton = vanilla.Button((-80-15, -20-15, -15, -15), "Run", sizeStyle=\'regular\', callback=self.$5 )\n\t\tself.w.setDefaultButton( self.w.runButton )\n\t\t\n\t\t# Load Settings:\n\t\tif not self.LoadPreferences():\n\t\t\tprint "Note: \'$1\' could not load preferences. Will resort to defaults"\n\t\t\n\t\t# Open window and focus on it:\n\t\tself.w.open()\n\t\tself.w.makeKey()\n\t\t\n\tdef SavePreferences( self, sender ):\n\t\ttry:\n\t\t\tGlyphs.defaults["com.$4.$3.popup_1"] = self.w.popup_1.get()\n\t\texcept:\n\t\t\treturn False\n\t\t\t\n\t\treturn True\n\n\tdef LoadPreferences( self ):\n\t\ttry:\n\t\t\tNSUserDefaults.standardUserDefaults().registerDefaults_(\n\t\t\t\t{\n\t\t\t\t\t"com.$4.$3.popup_1": "0"\n\t\t\t\t}\n\t\t\t)\n\t\t\tself.w.popup_1.set( Glyphs.defaults["com.$4.$3.popup_1"] )\n\t\texcept:\n\t\t\treturn False\n\t\t\t\n\t\treturn True\n\n\tdef ${5:$3Main}( self, sender ):\n\t\ttry:\n\t\t\t${7:thisFont = Glyphs.font # frontmost font\n\t\t\tlistOfSelectedLayers = thisFont.selectedLayers # active layers of currently selected glyphs\n\t\t\tfor thisLayer in listOfSelectedLayers: # loop through layers\n\t\t\t\tthisGlyph = thisLayer.parent\n\t\t\t\tprint thisGlyph.name, thisLayer.name\n\t\t\t\t# output all node coordinates:\n\t\t\t\tfor thisPath in thisLayer.paths:\n\t\t\t\t\tfor thisNode in thisLayer.nodes:\n\t\t\t\t\t\tprint "-- %.1f %.1f" % ( thisNode.x, thisNode.y )\n\t\t\t}\n\t\t\t$0\n\t\t\t\n\t\t\tif not self.SavePreferences( self ):\n\t\t\t\tprint "Note: \'$1\' could not write preferences."\n\t\t\t\n\t\t\t${6:self.w.close() # delete if you want window to stay open}\n\t\texcept Exception, e:\n\t\t\t# brings macro window to front and reports error:\n\t\t\tGlyphs.showMacroWindow()\n\t\t\tprint "$1 Error: %s" % e\n\n$3()' 17 | 'Glyphs plugin def': 18 | 'prefix': 'plugindef' 19 | 'body': 'def ${1:fname}( ${2:`if [ "$TM_CURRENT_LINE" != "" ]\n\t\t\t\t# poor man\'s way ... check if there is an indent or not\n\t\t\t\t# (cuz we would have lost the class scope by this point)\n\t\t\t\tthen\n\t\t\t\t\techo "self"\n\t\t\t\tfi`} ):\n\t${3/.+/"""/}${3:docstring for $1}${3/.+/"""\\n/}${3/.+/\\t\\t/}try:\n\t\t${0:pass}\n\texcept Exception as e:\n\t\tself.logToConsole( "$1: %s" % str(e) )\n\t\t' 20 | 'Glyphs plugin log': 21 | 'prefix': 'log' 22 | 'body': 'self.logToConsole( "${1:method name}: %s" % str(e) )$0' 23 | 'Glyphs py angle': 24 | 'prefix': 'angle' 25 | 'body': '${1:import math}\n\ndef angle( firstPoint, secondPoint ):\n\txDiff = firstPoint.x - secondPoint.x\n\tyDiff = firstPoint.y - secondPoint.y\n\ttangens = yDiff / xDiff\n\tangle = math.atan( tangens ) * 180.0 / math.pi\n\treturn angle\n$0' 26 | 'Glyphs py clear log': 27 | 'prefix': 'clear' 28 | 'body': 'Glyphs.clearLog() # clears macro window log$0' 29 | 'Glyphs py create ot class': 30 | 'prefix': 'otclass' 31 | 'body': '${1:import GlyphsApp\nFont = Glyphs.font\n}\ndef create_otclass( classname = "@default", \n classglyphs = [ x.parent.name for x in Font.selectedLayers ], \n targetfont = Font ):\n\t"""\n\tCreates an OpenType class in the font.\n\tDefault: class @default with currently selected glyphs in the current font.\n\tReturns a status message in form of a string.\n\t"""\n\t\n\t# strip \'@\' from beginning:\n\tif classname[0] == "@":\n\t\tclassname = classname[1:]\n\t\n\tclassCode = " ".join( classglyphs )\n\t\n\tif classname in [ c.name for c in targetfont.classes ]:\n\t\ttargetfont.classes[classname].code = classCode\n\t\treturn "Updated existing OT class \'%s\'." % classname\n\telse:\n\t\tnewClass = GSClass()\n\t\tnewClass.name = classname\n\t\tnewClass.code = classCode\n\t\ttargetfont.classes.append( newClass )\n\t\treturn "Created new OT class: \'%s\'" % classname\n\n$0' 32 | 'Glyphs py create ot feature': 33 | 'prefix': 'otfeature' 34 | 'body': '${1:import GlyphsApp\nFont = Glyphs.font\n}\ndef updated_code( oldcode, beginsig, endsig, newcode ):\n\t"""Replaces text in oldcode with newcode, but only between beginsig and endsig."""\n\tbegin_offset = oldcode.find( beginsig )\n\tend_offset = oldcode.find( endsig ) + len( endsig )\n\tnewcode = oldcode[:begin_offset] + beginsig + newcode + "\\n" + endsig + oldcode[end_offset:]\n\treturn newcode\n\ndef create_otfeature( featurename = "calt", \n featurecode = "# empty feature code", \n targetfont = Font,\n codesig = "DEFAULT-CODE-SIGNATURE" ):\n\t"""\n\tCreates or updates an OpenType feature in the font.\n\tReturns a status message in form of a string.\n\t"""\n\t\n\tbeginSig = "# BEGIN " + codesig + "\\n"\n\tendSig = "# END " + codesig + "\\n"\n\t\n\tif featurename in [ f.name for f in targetfont.features ]:\n\t\t# feature already exists:\n\t\ttargetfeature = targetfont.features[ featurename ]\n\t\t\n\t\tif beginSig in targetfeature.code:\n\t\t\t# replace old code with new code:\n\t\t\ttargetfeature.code = updated_code( targetfeature.code, beginSig, endSig, featurecode )\n\t\telse:\n\t\t\t# append new code:\n\t\t\ttargetfeature.code += "\\n" + beginSig + featurecode + "\\n" + endSig\n\t\t\t\n\t\treturn "Updated existing OT feature \'%s\'." % featurename\n\telse:\n\t\t# create feature with new code:\n\t\tnewFeature = GSFeature()\n\t\tnewFeature.name = featurename\n\t\tnewFeature.code = beginSig + featurecode + "\\n" + endSig\n\t\ttargetfont.features.append( newFeature )\n\t\treturn "Created new OT feature \'%s\'" % featurename\n\n$0' 35 | 'Glyphs py distance': 36 | 'prefix': 'distance' 37 | 'body': '${1:import math}\n\ndef distance( node1, node2 ):\n\treturn math.hypot( node1.x - node2.x, node1.y - node2.y )\n$0' 38 | 'Glyphs py doc': 39 | 'prefix': 'doc' 40 | 'body': 'thisDoc = Glyphs.currentDocument' 41 | 'Glyphs py macro window': 42 | 'prefix': 'macro' 43 | 'body': '# brings macro window to front and clears its log:\nGlyphs.clearLog()\nGlyphs.showMacroWindow()\n${1:print "${2:Output}:"}$0' 44 | 'Glyphs py measurement': 45 | 'prefix': 'measurement' 46 | 'body': '${1:from Foundation import *}\n\ndef intersectionsBetweenPoints( thisLayer, startPoint, endPoint ):\n\t"""\n\tReturns list of intersection NSPoints from startPoint to endPoint.\n\tthisLayer ... a glyph layer\n\tstartPoint, endPoint ... NSPoints\n\t"""\n\t\n\t# prepare layer copy for measurement:\n\tcleanLayer = thisLayer.copyDecomposedLayer()\n\tcleanLayer.removeOverlap()\n\t\n\t# initiate the measurement tool and measure:\n\tmeasurementTool = NSClassFromString("GlyphsToolMeasurement").alloc().init()\n\treturnList = [ NSPoint( p.pointValue().x, p.pointValue().y ) for p in listOfIntersections ]\n\treturn returnList\n$0' 47 | 'Glyphs py menutitle': 48 | 'prefix': 'title' 49 | 'body': '#MenuTitle: ${1:$TM_FILENAME}\n# -*- coding: utf-8 -*-\n__doc__="""\n${2:$1}.\n"""\n$0' 50 | 'Glyphs py select': 51 | 'prefix': 'select' 52 | 'body': '# selects items on thisLayer:\nitemsToBeSelected = NSMutableArray.arrayWithObject_( ${1:myObjects} )\n${2:thisLayer}.setSelection_( itemsToBeSelected )\n$0' 53 | 'Glyphs py tab': 54 | 'prefix': 'tab' 55 | 'body': '# opens new Edit tab:\n${3:from PyObjCTools.AppHelper import callAfter}\ncallAfter( ${1:Glyphs.currentDocument}.windowController().addTabWithString_, ${2:"abc"} )$0\n' 56 | 'Glyphs py': 57 | 'prefix': 'gspy' 58 | 'body': '#MenuTitle: ${1:$TM_FILENAME}\n# -*- coding: utf-8 -*-\n__doc__="""\n${2:Create effect for selected glyphs.}\n"""\n\nimport GlyphsApp\n${3:import random\nrandom.seed()}\n\nthisFont = Glyphs.font # frontmost font\nthisFontMaster = thisFont.selectedFontMaster # active master\nlistOfSelectedLayers = thisFont.selectedLayers # active layers of selected glyphs\n${4:selection = listOfSelectedLayers[0].selection() # node selection in edit mode}\n${5:thisDoc = Glyphs.currentDocument}\n\ndef process( thisLayer ):\n\tfor thisPath in thisLayer.paths:\n\t\tfor thisNode in thisPath.nodes:\n\t\t\tprint "-- node %.1f %.1f (type %i)" % ( thisNode.x, thisNode.y, thisNode.type )\n\t\t\t$0\n\nthisFont.disableUpdateInterface() # suppresses UI updates in Font View\n\nfor thisLayer in listOfSelectedLayers:\n\tthisGlyph = thisLayer.parent\n\tprint "Processing", thisGlyph.name\n\tthisGlyph.beginUndo() # begin undo grouping\n\tprocess( thisLayer )\n\tthisGlyph.endUndo() # end undo grouping\n\nthisFont.enableUpdateInterface() # re-enables UI updates in Font View\n' 59 | 'py bezier': 60 | 'prefix': 'bezier' 61 | 'body': 'def bezier( x1,y1, x2,y2, x3,y3, x4,y4, t ):\n\t"""\n\tReturns coordinates for t (=0.0...1.0) on curve segment.\n\tx1,y1 and x4,y4: coordinates of on-curve nodes\n\tx2,y2 and x3,y3: coordinates of BCPs\n\t"""\n\tx = x1*(1-t)**3 + x2*3*t*(1-t)**2 + x3*3*t**2*(1-t) + x4*t**3\n\ty = y1*(1-t)**3 + y2*3*t*(1-t)**2 + y3*3*t**2*(1-t) + y4*t**3\n\n\treturn x, y\n' 62 | 'py clipboard': 63 | 'prefix': 'clipboard' 64 | 'body': '${1:from AppKit import *}\n\ndef setClipboard( myText ):\n\t"""\n\tSets the contents of the clipboard to myText.\n\tReturns True if successful, False if unsuccessful.\n\t"""\n\ttry:\n\t\tmyClipboard = NSPasteboard.generalPasteboard()\n\t\tmyClipboard.declareTypes_owner_( [NSStringPboardType], None )\n\t\tmyClipboard.setString_forType_( myText, NSStringPboardType )\n\t\treturn True\n\texcept Exception as e:\n\t\treturn False\n\n${2:if not setClipboard(${3:"clipboard text"}):\n\tprint "Warning: could not set clipboard to %s" % ( $3 )}\n$0' 65 | 'py intersect': 66 | 'prefix': 'intersect' 67 | 'body': 'def intersect( x1, y1, x2, y2, x3, y3, x4, y4 ):\n\t"""Calculates the intersection of line P1-P2 with P3-P4."""\n\tslope12 = ( float(y2) - float(y1) ) / ( float(x2) - float(x1) )\n\tslope34 = ( float(y4) - float(y3) ) / ( float(x4) - float(x3) )\n\t\n\tx = ( slope12 * x1 - y1 - slope34 * x3 + y3 ) / ( slope12 - slope34 )\n\ty = slope12 * ( x - x1 ) + y1\n\t\n\treturn x, y\n\t\n' 68 | 'py random': 69 | 'prefix': 'random' 70 | 'body': 'import random\nrandom.seed()\n${1:randomNumber = random.randint( -100, 100 )\n}$0' 71 | 'py rotate coordinates': 72 | 'prefix': 'rotate' 73 | 'body': '${1:import math\n}\ndef rotate( x, y, angle=180.0, x_orig=0.0, y_orig=0.0):\n\t"""Rotates x/y around x_orig/y_orig by angle and returns result as [x,y]."""\n\t\n\tnew_angle = ( angle / 180.0 ) * math.pi\n\tnew_x = ( x - x_orig ) * math.cos( new_angle ) - ( y - y_orig ) * math.sin( new_angle ) + x_orig\n\tnew_y = ( x - x_orig ) * math.sin( new_angle ) + ( y - y_orig ) * math.cos( new_angle ) + y_orig\n\t\n\treturn [ new_x, new_y ]\n$0' 74 | 'py skew coordinates': 75 | 'prefix': 'skew' 76 | 'body': '${1:import math}\n\ndef italicSkew( x, y, angle=10.0, height=0.0 ):\n\t"""Skews x/y along the x axis and returns skewed x value. If angle is the italic angle, then height should be half x-height."""\n\t\n\tnew_angle = ( angle / 180.0 ) * math.pi\n\tnew_x = x + ( y - height ) * math.tan( new_angle )\n\t\n\treturn new_x\n\t\n\t# [1 tan(angle) 0 1 0 0]\n\t# x\' = x*a + y*b + e\n\t# y\' = x*c + y*d + f\n$0' 77 | 'py sort': 78 | 'prefix': 'sort' 79 | 'body': 'sorted( ${1:[ ${2:[l.parent.name, l.bounds.origin.y]} for l in selectedLayers]}, key = lambda thisListItem: ${3:thisListItem[1]} )$0' 80 | 'py terminal': 81 | 'prefix': 'terminal' 82 | 'body': '${1:import os}\nterminalCommand = \'${2:uptime}\'\nterminalCommandResult = os.system( terminalCommand )\n${3:print terminalCommandResult}$0\n' 83 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python development for Glyphs.app 2 | 3 | These are text editor snippets intended to facilitate Python (both 2 and 3) development for Glyphs.app. Have fun. 4 | 5 | **TextMate:** Since I use TextMate myself, the TextMate snippets can safely be assumed to be up to date most of the time. 6 | 7 | **Sublime Text:** Newer versions of Sublime Text can read TextMate bundles, so you are up to date automatically. See installation instructions below. 8 | 9 | **Atom:** Since I don’t use Atom, I depend on other people’s pull requests. Hence, the Atom snippets are usually out of date. If you want up-to-date snippets for Atom, feel free to contribute to this repository on a regular basis. 10 | 11 | ### Snippets 12 | 13 | * `gspy⇥` Basic structure of a Glyphs script that iterates through selected layers. 14 | * `title⇥` MenuTitle (name of the script as it appears in the *Script* menu) and docstring (text for the tooltip). 15 | * `clear⇥` Clears the Macro Window log. 16 | * `macro⇥` Clears the Macro Window log and brings the Macro Window to the front. 17 | * `angle⇥` Function returning the angle between two points. 18 | * `center⇥` Convenience function returning the center (as NSPoint) of an NSRect. 19 | * `circle⇥` Function returning a GSPath containing a circle. 20 | * `otclass⇥` Function that creates or (if it already exists) updates an OpenType class. 21 | * `otfeature⇥` Function that creates or (if it already exists) updates an OpenType feature. 22 | * `italic⇥` Function that returns a point position that respects an italic angle. 23 | * `offset⇥` Function that offsets a GSLayer. 24 | * `round⇥` Function that rounds the corners of a GSLayer. 25 | * `measurement⇥` Function that returns intersection points (like the Measurement tool does) of the decomposed and overlap-removed layer. 26 | * `msg⇥` Function for displaying a simple (non-Vanilla) message dialog. 27 | * `select⇥` Adds item on a layer to the user selection. 28 | * `tab⇥` Opens a new tab with supplied text or collection of layers. 29 | * `transform⇥` Function for creating a NSAffineTransform object, necessary for shifting, scaling, skewing, rotating. 30 | * `bezier⇥` Function for returning the x,y for a given t on a cubic Bézier curve segment. 31 | * `clipboard⇥` Function for setting the clipboard. 32 | * `random⇥` Import the *random* library, seed, and create a random integer. 33 | * `sort⇥` Sort a list of objects by a certain attribute of those objects (e.g. layers by their max y). 34 | * `terminal⇥` Execute a Terminal (bash) command. 35 | * `trace⇥` Import the *traceback* library and print a traceback. Useful for *except* clauses. 36 | * `subtract⇥` Subtract GSPath objects. 37 | * `intersect⇥` Intersect GSPath objects. 38 | * `timer⇥` Method for taking and reporting elapsed time. 39 | * `notification⇥` Display a floating notification in top right corner of screen. 40 | * `write⇥` Method *saveFileInLocation()* for writing UTF-8 string into a new file. 41 | * `keys⇥` NSEvent algorithm for determining which modifier keys are pressed by the user. 42 | * `g23⇥` Differentiating code for Glyphs 2 and 3. 43 | * `fut⇥` The *from future* imports for Glyphs 2/3 and Python 2/3 compatibility. 44 | * `update⇥` Send an interface update notification to the Notification Center. Only use this to force a UI update if Glyphs does not redraw as it should. (Glyphs 2 only.) 45 | 46 | #### Vanilla-specific snippets 47 | 48 | * `gsgui⇥` Basic structure of a Glyphs script with a dialog (GUI). Uses Vanilla. 49 | * `pref⇥` Register, load, register & load, or save preferences for Vanilla GUI items. 50 | * `button⇥` Vanilla popup and square button. 51 | * `text⇥` Vanilla text box or text edit field. 52 | * `checkbox⇥` Vanilla checkbox. 53 | * `radio⇥` Vanilla radiobuttons. 54 | * `progress⇥` Vanilla progress bar. 55 | * `tooltip⇥` Tooltip for a Vanilla object. 56 | * `vanilla⇥` Show help for Vanilla classes. 57 | 58 | #### Plugin SDK-specific snippets 59 | 60 | * `dec⇥` Decorators. 61 | * `rgb⇥` NSColor object with RGB definition. 62 | * `plugindef⇥` Function definition structure better suited for the [Plugin SDK](https://github.com/schriftgestalt/GlyphsSDK/). 63 | * `log⇥` Log a message to the console. 64 | * `url⇥` Prefill the *productPageURL* or *updateFeedURL* entry of a plug-in’s *Info.plist* with a GitHub URL. 65 | * `key⇥` Define a keyboard shortcut for a plug-in, including the modifier keys. 66 | * `cond⇥` Inserts the `conditionsAreMetForDrawing()` method for [Reporter plug-ins](https://github.com/schriftgestalt/GlyphsSDK/tree/master/Python%20Templates/Reporter). It will return `False` if the user is in text mode or switched to the Hand tool (H). 67 | 68 | ### Installation 69 | 70 | * TextMate bundle: double click to install 71 | * Sublime Text: Open *Sublime Text > Preferences > Browse Packages…* and move the `Python for Glyphs.tmbundle` into the folder that appears 72 | * Atom: move to `~/.atom/packages/python-to-glyphs` 73 | 74 | ### License 75 | 76 | Copyright 2014 Rainer Erich Scheichelbauer (@mekkablue). 77 | Some code by Georg Seifert (@schriftgestalt). Atom adaptation by Kenneth Ormandy (@kennethormandy). 78 | 79 | Licensed under the Apache License, Version 2.0 (the "License"); 80 | you may not use this file except in compliance with the License. 81 | You may obtain a copy of the License at 82 | 83 | http://www.apache.org/licenses/LICENSE-2.0 84 | 85 | See the License file included in this repository for further details. 86 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs gui button help.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | self.w.${1:helpButton} = vanilla.HelpButton((${2:inset+${3:100}}, linePos, 21, 21), callback=self.${4:openURL}) 7 | ${5:linePos += lineHeight 8 | }$0 9 | 10 | name 11 | Glyphs gui button help 12 | tabTrigger 13 | button 14 | uuid 15 | BBCDD180-BDAE-4D73-B812-8F4DC9CA6DD2 16 | 17 | 18 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs gui button popup.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | self.w.${1:popupButton} = vanilla.PopUpButton((${2:inset+${3:100}}, linePos, ${4:-inset}, 17), ${5:("Option 1", "Option 2")}, sizeStyle="small", callback=self.SavePreferences) 7 | ${6:linePos += lineHeight 8 | }$0 9 | 10 | name 11 | Glyphs gui button popup 12 | tabTrigger 13 | button 14 | uuid 15 | D046CB98-4894-4F6F-8359-3FCADC7696D4 16 | 17 | 18 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs gui button square.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | self.w.${1:updateButton} = vanilla.SquareButton((${2:-inset-20}, linePos, ${3:-inset}, 18), "${4:↺}", sizeStyle="small", callback=self.${5:update}) 7 | ${6:linePos += lineHeight 8 | }$0 9 | name 10 | Glyphs gui button square 11 | tabTrigger 12 | button 13 | uuid 14 | C2963F4D-7E80-4D76-9F3E-F91C236E20E5 15 | 16 | 17 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs gui checkbox.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | self.w.${1:myCheckBox} = vanilla.CheckBox((${2:inset}, ${3:linePos-1}, ${4:-$2}, 20), "${5:Activate option}", value=${6|False,True|}, callback=self.SavePreferences, sizeStyle="small") 7 | ${7:linePos += lineHeight 8 | }$0 9 | name 10 | Glyphs gui checkbox 11 | tabTrigger 12 | checkbox 13 | uuid 14 | 843CAD52-41F4-4351-B332-477882EAE808 15 | 16 | 17 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs gui combo box.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | self.w.${1:comboBox} = vanilla.ComboBox((${2:inset}, linePos-1, ${3:-$2}, 19), ${4:[g.name for g in Glyphs.font.glyphs]}, sizeStyle="small", callback=self.SavePreferences) 7 | ${5:linePos += lineHeight 8 | }$0 9 | name 10 | Glyphs gui combo box 11 | tabTrigger 12 | combo 13 | uuid 14 | FDC049BA-634A-4878-B29D-6B7507E371D1 15 | 16 | 17 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs gui pref load.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | self.w.${1:itemName}.set(self.pref("$1"))$0 7 | name 8 | Glyphs gui pref load 9 | tabTrigger 10 | pref 11 | uuid 12 | 0767D626-5D92-41C1-8607-598A5AEA4177 13 | 14 | 15 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs gui pref register & load.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | Glyphs.registerDefault(self.domain("${1:itemName}"), ${2:0}) 7 | ${3:self.w.$1.set(self.pref("$1"))}$0 8 | name 9 | Glyphs gui pref register & load 10 | tabTrigger 11 | pref 12 | uuid 13 | 21A48C34-CCAF-4905-85C3-1324F8340F0D 14 | 15 | 16 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs gui pref register.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | Glyphs.registerDefault(self.domain("${1:itemName}"), ${2:0})$0 7 | 8 | name 9 | Glyphs gui pref register 10 | tabTrigger 11 | pref 12 | uuid 13 | F9B4762D-B48F-4325-B57B-DA8B2790AB70 14 | 15 | 16 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs gui pref save.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | Glyphs.defaults[self.domain("${1:itemName}")] = self.w.$1.get()$0 7 | 8 | name 9 | Glyphs gui pref save 10 | tabTrigger 11 | pref 12 | uuid 13 | 4E5AE413-E026-44AE-B5AC-6169EF6056BC 14 | 15 | 16 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs gui progress bar.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | self.w.progress = vanilla.ProgressBar((inset, linePos, -inset, 16)) 7 | self.w.progress.set(0) # set progress indicator to zero 8 | ${1:linePos+=lineHeight}$0 9 | 10 | name 11 | Glyphs gui progress bar 12 | tabTrigger 13 | progress 14 | uuid 15 | EE4778B2-A0E7-4A0D-93DD-B133AC2BCA0D 16 | 17 | 18 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs gui radiobuttons.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | radioOptions = (${1:"Option 1", "Option 2", "Option 3"}) 7 | self.w.${2:radioButtons} = vanilla.RadioGroup((inset${3:*2}, linePos, -inset, lineHeight*len(radioOptions)), radioOptions, callback=self.SavePreferences, sizeStyle="small") 8 | self.w.$2.set( 0 ) 9 | ${4:linePos += lineHeight*len(radioOptions)}$0 10 | 11 | name 12 | Glyphs gui radiobuttons 13 | tabTrigger 14 | radio 15 | uuid 16 | 79C8FE42-5BB9-4529-AEA7-6C5A9BDA92AC 17 | 18 | 19 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs gui text box.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | self.w.${1:descriptionText} = vanilla.TextBox((${2:inset}, linePos+2, ${3:-$2}, 14), "${4:Apply function to the font}", sizeStyle="small", selectable=True) 7 | ${5:linePos += lineHeight 8 | }$0 9 | name 10 | Glyphs gui text box 11 | tabTrigger 12 | text 13 | uuid 14 | 5BA43232-D3E7-42D6-8744-2A87A8193192 15 | 16 | 17 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs gui text edit.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | self.w.${1:textEntry} = vanilla.EditText((${2:inset${3:+80}}, linePos, ${4:-inset}, 19), "${5:insert text here}", callback=self.SavePreferences, sizeStyle="small") 7 | ${6:linePos += lineHeight 8 | }$0 9 | 10 | name 11 | Glyphs gui text edit 12 | tabTrigger 13 | text 14 | uuid 15 | C1706817-3A24-43DB-9B9F-B2A54CD9DAF6 16 | 17 | 18 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs gui tooltip.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | self.w.${1:element}.getNS${2|TextField,Button,ComboBox,PopUpButton,ProgressIndicator,Slider,Window,Box,ColorWell,DatePicker,Drawer,ImageView,LevelIndicator,Matrix,OutlineView,ScrollView,SearchField,SecureTextField,SegmentedButton,SplitView,TableView,TabView,View,TextView,VisualEffectView,WindowController|}().setToolTip_(${3:"Tooltip text."})$0 7 | name 8 | Glyphs gui tooltip 9 | tabTrigger 10 | tooltip 11 | uuid 12 | EE0FDDF3-0AB2-477D-B901-AE26BA3816F8 13 | 14 | 15 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs gui.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | #MenuTitle: ${1:$TM_FILENAME} 7 | # -*- coding: utf-8 -*- 8 | from __future__ import division, print_function, unicode_literals 9 | __doc__=""" 10 | ${2:Create effect for selected glyphs.} 11 | """ 12 | 13 | import vanilla, sys 14 | 15 | class ${3:${1/ //g}}(object): 16 | prefID = "com.${4:mekkablue}.$3" 17 | prefDict = { 18 | # "prefName": defaultValue, 19 | "popup_1": 0, 20 | } 21 | 22 | def __init__( self ): 23 | # Window 'self.w': 24 | windowWidth = 350 25 | windowHeight = 260 26 | windowWidthResize = 100 # user can resize width by this value 27 | windowHeightResize = 0 # user can resize height by this value 28 | self.w = vanilla.FloatingWindow( 29 | (windowWidth, windowHeight), # default window size 30 | "$1", # window title 31 | minSize = (windowWidth, windowHeight), # minimum size (for resizing) 32 | maxSize = (windowWidth + windowWidthResize, windowHeight + windowHeightResize), # maximum size (for resizing) 33 | autosaveName = self.domain("mainwindow") # stores last window position and size 34 | ) 35 | 36 | # UI elements: 37 | linePos, inset, lineHeight = 12, 15, 22 38 | self.w.descriptionText = vanilla.TextBox((inset, linePos+2, -inset, 14), "Apply function to the font", sizeStyle="small", selectable=True) 39 | linePos += lineHeight 40 | 41 | # Run Button: 42 | self.w.runButton = vanilla.Button((-80-inset, -20-inset, -inset, -inset), "Run", sizeStyle="regular", callback=self.$5) 43 | self.w.setDefaultButton(self.w.runButton) 44 | 45 | # Load Settings: 46 | if not self.LoadPreferences(): 47 | print("⚠️ ‘$1’ could not load preferences. Will resort to defaults.") 48 | 49 | # Open window and focus on it: 50 | self.w.open() 51 | self.w.makeKey() 52 | 53 | def domain(self, prefName): 54 | prefName = prefName.strip().strip(".") 55 | return self.prefID + "." + prefName.strip() 56 | 57 | def pref(self, prefName): 58 | prefDomain = self.domain(prefName) 59 | return Glyphs.defaults[prefDomain] 60 | 61 | def SavePreferences(self, sender=None): 62 | try: 63 | # write current settings into prefs: 64 | for prefName in self.prefDict.keys(): 65 | Glyphs.defaults[self.domain(prefName)] = getattr(self.w, prefName).get() 66 | return True 67 | except: 68 | import traceback 69 | print(traceback.format_exc()) 70 | return False 71 | 72 | def LoadPreferences(self): 73 | try: 74 | for prefName in self.prefDict.keys(): 75 | # register defaults: 76 | Glyphs.registerDefault(self.domain(prefName), self.prefDict[prefName]) 77 | # load previously written prefs: 78 | getattr(self.w, prefName).set(self.pref(prefName)) 79 | return True 80 | except: 81 | import traceback 82 | print(traceback.format_exc()) 83 | return False 84 | 85 | def ${5:$3Main}(self, sender=None): 86 | try: 87 | # clear macro window log: 88 | Glyphs.clearLog() 89 | 90 | # update settings to the latest user input: 91 | if not self.SavePreferences(): 92 | print("⚠️ ‘$1’ could not write preferences.") 93 | 94 | # read prefs: 95 | for prefName in self.prefDict.keys(): 96 | try: 97 | setattr(sys.modules[__name__], prefName, self.pref(prefName)) 98 | except: 99 | fallbackValue = self.prefDict[prefName] 100 | print(f"⚠️ Could not set pref ‘{prefName}’, resorting to default value: ‘{fallbackValue}’.") 101 | setattr(sys.modules[__name__], prefName, fallbackValue) 102 | 103 | thisFont = Glyphs.font # frontmost font 104 | if thisFont is None: 105 | Message(title="No Font Open", message="The script requires a font. Open a font and run the script again.", OKButton=None) 106 | else: 107 | filePath = thisFont.filepath 108 | if filePath: 109 | reportName = f"{filePath.lastPathComponent()}\n📄 {filePath}" 110 | else: 111 | reportName = f"{thisFont.familyName}\n⚠️ The font file has not been saved yet." 112 | print(f"$1 Report for {reportName}") 113 | print() 114 | 115 | ${7:listOfSelectedLayers = thisFont.selectedLayers 116 | for thisLayer in listOfSelectedLayers: 117 | thisGlyph = thisLayer.parent 118 | print(thisGlyph.name, thisLayer.name) 119 | for thisPath in thisLayer.paths: 120 | for thisNode in thisLayer.nodes: 121 | print("\t", thisNode.x, thisNode.y) 122 | } 123 | $0 124 | 125 | ${6: self.w.close() # delete if you want window to stay open 126 | } 127 | print("\nDone.") 128 | 129 | except Exception as e: 130 | # brings macro window to front and reports error: 131 | Glyphs.showMacroWindow() 132 | print(f"$1 Error: {e}") 133 | import traceback 134 | print(traceback.format_exc()) 135 | 136 | $3() 137 | name 138 | Glyphs gui 139 | tabTrigger 140 | gsgui 141 | uuid 142 | EB1EA211-2FA0-4C31-BBB4-818161C0977A 143 | 144 | 145 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs plugin conditions for drawing.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | @objc.python_method 7 | def conditionsAreMetForDrawing(self): 8 | """ 9 | Don't activate if text or pan (hand) tool are active. 10 | """ 11 | currentController = self.controller.view().window().windowController() 12 | if currentController: 13 | tool = currentController.toolDrawDelegate() 14 | textToolIsActive = tool.isKindOfClass_(NSClassFromString("GlyphsToolText")) 15 | handToolIsActive = tool.isKindOfClass_(NSClassFromString("GlyphsToolHand")) 16 | if not textToolIsActive and not handToolIsActive: 17 | return True 18 | return False 19 | $0 20 | name 21 | Glyphs plugin conditions for drawing 22 | tabTrigger 23 | cond 24 | uuid 25 | 188106AA-A839-4B79-95EF-362A80276641 26 | 27 | 28 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs plugin def.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | @objc.python_method 7 | def ${1:fname}(${2:`if [ "$TM_CURRENT_LINE" != "" ] 8 | # poor man's way ... check if there is an indent or not 9 | # (cuz we would have lost the class scope by this point) 10 | then 11 | echo "self" 12 | fi`}): 13 | ${3/.+/"""/}${3:docstring for $1}${3/.+/"""\n/}${3/.+/\t\t/}try: 14 | ${0:pass} 15 | except Exception as e: 16 | self.logToConsole(f"$1: {str(e)}") 17 | 18 | name 19 | Glyphs plugin def 20 | tabTrigger 21 | plugindef 22 | uuid 23 | DBB9E029-8E61-4E7F-9FDD-ECEE1097358B 24 | 25 | 26 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs plugin keyboard shortcut.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | self.keyboardShortcut = "$1" 7 | self.keyboardShortcutModifier = ${2:NSShiftKeyMask | }${3:NSControlKeyMask | }${4:NSAlternateKeyMask | }${5:NSCommandKeyMask}$0 8 | 9 | name 10 | Glyphs plugin keyboard shortcut 11 | tabTrigger 12 | key 13 | uuid 14 | FB9F3769-FC13-4E14-9BCA-50DCF4944E4A 15 | 16 | 17 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs plugin log.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | self.logToConsole(f"${1:method name}: {str(e)}")$0 7 | name 8 | Glyphs plugin log 9 | tabTrigger 10 | log 11 | uuid 12 | 2EC85DB8-6F40-43A9-BDF6-CD9C37C054CF 13 | 14 | 15 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs plugin productPageURL.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | https://github.com/${1:github username}/${2:plugin name}$0 7 | name 8 | Glyphs plugin productPageURL 9 | tabTrigger 10 | url 11 | uuid 12 | 5E4C10DA-0947-4453-91A1-A0FC5CA250EC 13 | 14 | 15 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs plugin updateFeedURL.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | https://raw.githubusercontent.com/${1:github username}/${2:plugin name}/master/$2.${3|glyphsReporter,glyphsFilter,glyphsTool,glyphsFileFormat,glyphsPlugin,glyphsPalette|}/Contents/Info.plist$0 7 | name 8 | Glyphs plugin updateFeedURL 9 | tabTrigger 10 | url 11 | uuid 12 | 2A905466-09E0-429D-BD01-268964BFABCD 13 | 14 | 15 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs py UI update.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | ${1:from AppKit import NSNotificationCenter} 7 | if ${2:Glyphs.versionNumber>=3 and }${3:Glyphs.font} and $3.currentTab: 8 | NSNotificationCenter.defaultCenter().postNotificationName_object_("GSUpdateInterface", $3.currentTab) 9 | $0 10 | name 11 | Glyphs py UI update 12 | tabTrigger 13 | update 14 | uuid 15 | 9E641F44-77F1-44E9-9BA4-B3805ED59F60 16 | 17 | 18 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs py angle.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | ${1:import math} 7 | 8 | def angle(firstPoint, secondPoint): 9 | """ 10 | Returns the angle (in degrees) of the straight line between firstPoint and secondPoint, 11 | 0 degrees being the second point to the right of first point. 12 | firstPoint, secondPoint: must be NSPoint or GSNode 13 | """ 14 | xDiff = secondPoint.x - firstPoint.x 15 | yDiff = secondPoint.y - firstPoint.y 16 | return math.degrees(math.atan2(yDiff, xDiff)) 17 | $0 18 | name 19 | Glyphs py angle 20 | tabTrigger 21 | angle 22 | uuid 23 | 041A438A-830E-4E28-96E4-CAC9BC3A0816 24 | 25 | 26 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs py circle.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | from Foundation import NSPoint 7 | 8 | def circlePath(center=NSPoint(0,0), radius=50, bcp=4.0*(2.0**0.5-1.0)/3.0): 9 | circle = GSPath() 10 | x = center.x 11 | y = center.y 12 | handle = radius*bcp 13 | points=( 14 | ((x+handle, y-radius), (x+radius, y-handle), (x+radius, y)), 15 | ((x+radius, y+handle), (x+handle, y+radius), (x, y+radius)), 16 | ((x-handle, y+radius), (x-radius, y+handle), (x-radius, y)), 17 | ((x-radius, y-handle), (x-handle, y-radius), (x, y-radius)) 18 | ) 19 | # Add the segments: 20 | for triplet in points: 21 | # Add the two BCPs of the segment: 22 | bcp1 = NSPoint(triplet[0][0], triplet[0][1]) 23 | bcp2 = NSPoint(triplet[1][0], triplet[1][1]) 24 | for bcpPosition in (bcp1,bcp2): 25 | newBCP = GSNode() 26 | newBCP.type = OFFCURVE 27 | newBCP.position = bcpPosition 28 | circle.nodes.append(newBCP) 29 | 30 | # Add the On-Curve of the segment: 31 | newCurvepoint = GSNode() 32 | newCurvepoint.type = CURVE 33 | newCurvepoint.smooth = True 34 | newCurvepoint.position = NSPoint(triplet[2][0], triplet[2][1]) 35 | circle.nodes.append(newCurvepoint) 36 | 37 | circle.closed = True 38 | return circle 39 | $0 40 | name 41 | Glyphs py circle 42 | tabTrigger 43 | circle 44 | uuid 45 | C9BB77B6-FB1C-48FE-A5C4-522F07D761FC 46 | 47 | 48 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs py clear log.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | Glyphs.clearLog() # clears macro window log$0 7 | name 8 | Glyphs py clear log 9 | tabTrigger 10 | clear 11 | uuid 12 | 3DC14F55-B90A-4E03-B97C-96BCD61857CC 13 | 14 | 15 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs py create ot class.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | def createOTClass(className = "@default", 7 | classGlyphs = [l.parent.name for l in Glyphs.font.selectedLayers], 8 | targetFont = Glyphs.font): 9 | """ 10 | Creates an OpenType class called className in targetFont 11 | containing classGlyphs, or updates it if it already exists. 12 | className: name of the OT class, with or without leading at sign, 13 | classGlyphs: list of glyph names, 14 | targetFont: the GSFont that receives the OT class. 15 | """ 16 | 17 | # strip '@' from beginning: 18 | if className[0] == "@": 19 | className = className[1:] 20 | 21 | classCode = " ".join(classGlyphs) 22 | 23 | if className in [c.name for c in targetFont.classes]: 24 | targetFont.classes[className].code = classCode 25 | return "Updated existing OT class ‘{className}’." 26 | else: 27 | newClass = GSClass() 28 | newClass.name = className 29 | newClass.code = classCode 30 | targetFont.classes.append(newClass) 31 | return "Created new OT class: ‘{className}’" 32 | 33 | $0 34 | name 35 | Glyphs py create ot class 36 | tabTrigger 37 | otclass 38 | uuid 39 | E4198030-3DF0-4783-A946-AA926BC6C3C3 40 | 41 | 42 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs py create ot feature.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | def updatedCode( oldCode, beginSig, endSig, newCode ): 7 | """Replaces text in oldCode with newCode, but only between beginSig and endSig.""" 8 | beginOffset = oldCode.find( beginSig ) 9 | endOffset = oldCode.find( endSig ) + len( endSig ) 10 | newCode = oldCode[:beginOffset] + beginSig + newCode + "\n" + endSig + oldCode[endOffset:] 11 | return newCode 12 | 13 | def createOTFeature(featureName = "calt", 14 | featureCode = "# empty feature code", 15 | targetFont = Glyphs.font, 16 | codeSig = "CUSTOM-CONTEXTUAL-ALTERNATES"): 17 | """ 18 | Creates or updates an OpenType feature in the font. 19 | Returns a status message in form of a string. 20 | featureName: name of the feature (str), 21 | featureCode: the AFDKO feature code (str), 22 | targetFont: the GSFont object receiving the feature, 23 | codeSig: the code signature (str) used as delimiters. 24 | """ 25 | 26 | beginSig = "# BEGIN " + codeSig + "\n" 27 | endSig = "# END " + codeSig + "\n" 28 | 29 | if featureName in [ f.name for f in targetFont.features ]: 30 | # feature already exists: 31 | targetFeature = targetFont.features[ featureName ] 32 | 33 | if beginSig in targetFeature.code: 34 | # replace old code with new code: 35 | targetFeature.code = updatedCode( targetFeature.code, beginSig, endSig, featureCode ) 36 | else: 37 | # append new code: 38 | targetFeature.code += "\n" + beginSig + featureCode + "\n" + endSig 39 | 40 | return "Updated existing OT feature ‘{featureName}’." 41 | else: 42 | # create feature with new code: 43 | newFeature = GSFeature() 44 | newFeature.name = featureName 45 | newFeature.code = beginSig + featureCode + "\n" + endSig 46 | targetFont.features.append( newFeature ) 47 | return "Created new OT feature ‘{featureName}’" 48 | 49 | $0 50 | name 51 | Glyphs py create ot feature 52 | tabTrigger 53 | otfeature 54 | uuid 55 | FDD8912B-3BDA-405D-9878-E007520B3A85 56 | 57 | 58 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs py distance.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | ${1:import math} 7 | 8 | def distance(node1, node2): 9 | return math.hypot(node1.x - node2.x, node1.y - node2.y) 10 | $0 11 | name 12 | Glyphs py distance 13 | tabTrigger 14 | distance 15 | uuid 16 | 459BCD0B-E20C-4EF8-9768-40F945E24C39 17 | 18 | 19 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs py italic.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | ${1:import math} 7 | ${2:from Foundation import NSPoint} 8 | 9 | def italicize(thisPoint, italicAngle=0.0, pivotalY=0.0): 10 | """ 11 | Returns the italicized position of an NSPoint 'thisPoint' 12 | for a given angle 'italicAngle' and the pivotal height 'pivotalY', 13 | around which the italic slanting is executed, usually half x-height. 14 | Usage: myPoint = italicize(myPoint,10,xHeight*0.5) 15 | """ 16 | x = thisPoint.x 17 | yOffset = thisPoint.y - pivotalY # calculate vertical offset 18 | italicAngle = math.radians(italicAngle) # convert to radians 19 | tangens = math.tan(italicAngle) # math.tan needs radians 20 | horizontalDeviance = tangens * yOffset # vertical distance from pivotal point 21 | x += horizontalDeviance # x of point that is yOffset from pivotal point 22 | return NSPoint(x, thisPoint.y) 23 | $0 24 | name 25 | Glyphs py italic 26 | tabTrigger 27 | italic 28 | uuid 29 | 9BAF48FA-2469-4EED-9DA6-A2D40D0A5000 30 | 31 | 32 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs py layer offset.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | ${1:from Foundation import NSClassFromString} 7 | 8 | def offsetLayer(thisLayer, offset, makeStroke=False, position=0.5, autoStroke=False): 9 | offsetFilter = NSClassFromString("GlyphsFilterOffsetCurve") 10 | try: 11 | # GLYPHS 3: 12 | offsetFilter.offsetLayer_offsetX_offsetY_makeStroke_autoStroke_position_metrics_error_shadow_capStyleStart_capStyleEnd_keepCompatibleOutlines_( 13 | thisLayer, 14 | offset, offset, # horizontal and vertical offset 15 | makeStroke, # if True, creates a stroke 16 | autoStroke, # if True, distorts resulting shape to vertical metrics 17 | position, # stroke distribution to the left and right, 0.5 = middle 18 | None, None, None, 0, 0, False) 19 | except: 20 | # GLYPHS 2: 21 | offsetFilter.offsetLayer_offsetX_offsetY_makeStroke_autoStroke_position_error_shadow_( 22 | thisLayer, 23 | offset, offset, # horizontal and vertical offset 24 | makeStroke, # if True, creates a stroke 25 | autoStroke, # if True, distorts resulting shape to vertical metrics 26 | position, # stroke distribution to the left and right, 0.5 = middle 27 | None, None) 28 | $0 29 | name 30 | Glyphs py layer offset 31 | tabTrigger 32 | offset 33 | uuid 34 | A8D22845-230E-493A-AE0C-6D0B4CF10BAD 35 | 36 | 37 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs py layer round.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | ${1:from Foundation import NSClassFromString} 7 | 8 | def roundLayer(thisLayer, radius, checkSelection=False, visualCorrection=True, respectGrid=False): 9 | roundFilter = NSClassFromString("GlyphsFilterRoundCorner") 10 | roundFilter.roundLayer_radius_checkSelection_visualCorrect_grid_( 11 | thisLayer, # GSLayer 12 | radius, # float 13 | checkSelection, # bool: if True, only rounds user-selected points 14 | visualCorrection, # bool: visual correction of non-perpendicular angles 15 | respectGrid # bool: whether it the resulting path should stick to the grid 16 | ) 17 | 18 | def roundLayerForCompatibility(layer, radius, checkSelection=False, visualCorrect=True, keepCompatible=True, roundToGrid=False): 19 | roundFilter = NSClassFromString("GlyphsFilterRoundCorner") 20 | roundFilter.roundLayer_radius_checkSelection_visualCorrect_forceCompatibilty_grid_( 21 | layer, # GSLayer 22 | radius, # float 23 | checkSelection, # BOOL 24 | visualCorrect, # BOOL 25 | keepCompatible, # BOOL 26 | roundToGrid, # BOOL 27 | ) 28 | $0 29 | 30 | name 31 | Glyphs py layer round 32 | tabTrigger 33 | round 34 | uuid 35 | CCE23F72-F832-47BD-BBBF-5363201EC30A 36 | 37 | 38 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs py macro window.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | # brings macro window to front and clears its log: 7 | Glyphs.clearLog() 8 | Glyphs.showMacroWindow() 9 | ${1:print "${2:Output}:"}$0 10 | name 11 | Glyphs py macro window 12 | tabTrigger 13 | macro 14 | uuid 15 | E771FC7C-50E6-4CAA-A906-33A680C1F8E3 16 | 17 | 18 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs py measurement.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | def intersectionsBetweenPoints(thisLayer, startPoint, endPoint): 7 | """ 8 | Returns list of intersection NSPoints from startPoint to endPoint. 9 | thisLayer ... a glyph layer 10 | startPoint, endPoint ... NSPoints 11 | """ 12 | 13 | # prepare layer copy for measurement: 14 | cleanLayer = thisLayer.copyDecomposedLayer() 15 | cleanLayer.removeOverlap() 16 | 17 | # measure and return tuple: 18 | listOfIntersections = cleanLayer.intersectionsBetweenPoints(startPoint, endPoint) 19 | return listOfIntersections 20 | $0 21 | name 22 | Glyphs py measurement 23 | tabTrigger 24 | measurement 25 | uuid 26 | 104B4B66-F24F-4436-A302-444A7B72F1A0 27 | 28 | 29 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs py menutitle.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | #MenuTitle: ${1:$TM_FILENAME} 7 | # -*- coding: utf-8 -*- 8 | from __future__ import division, print_function, unicode_literals 9 | __doc__=""" 10 | ${2:$1}. 11 | """ 12 | $0 13 | name 14 | Glyphs py menutitle 15 | tabTrigger 16 | title 17 | uuid 18 | 3CCCB797-92CC-4A32-B2C1-53BCB7B595F3 19 | 20 | 21 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs py msg.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | Message( 7 | title="${1:Script Error}", 8 | message="${2:An error occurred during the execution of the script.}", 9 | OKButton=${3|None,"Apply"|}, 10 | )$0 11 | name 12 | Glyphs py msg 13 | tabTrigger 14 | msg 15 | uuid 16 | 4982A23A-493D-4FFA-8036-8CFBC6CB2548 17 | 18 | 19 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs py notification.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | # Floating notification: 7 | Glyphs.showNotification( 8 | "$1{thisFont.familyName$1}", 9 | ${3:"${2:Script is finished.}",} 10 | )$0 11 | 12 | name 13 | Glyphs py notification 14 | tabTrigger 15 | notification 16 | uuid 17 | CBFD7759-0A45-4AA7-BDCA-C75CB056B9CA 18 | 19 | 20 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs py path intersect.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | paths = ${1:[Layer.paths[2]]} # list of original paths 7 | intersectPaths = ${2:[Layer.paths[0], Layer.paths[1]]} # list of paths to be intersected with original paths 8 | pathOperator = GSPathOperator.alloc().init() # needs to be initialized only once 9 | pathOperator.intersectPaths_from_error_(intersectPaths, paths, None) 10 | ${3:thisLayer.paths} = paths # result of boolean operation is in paths 11 | $0 12 | name 13 | Glyphs py path intersect 14 | tabTrigger 15 | intersect 16 | uuid 17 | FF4C5BEA-2BCC-48FE-82CB-3BCA5663EEC8 18 | 19 | 20 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs py path subtract.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | paths = ${1:[Layer.paths[2]]} # list of original paths 7 | subtractPaths = ${2:[Layer.paths[0], Layer.paths[1]]} # list of paths to be subtracted from original paths 8 | pathOperator = GSPathOperator.alloc().init() # needs to be initialized only once 9 | pathOperator.subtractPaths_from_error_debug_(subtractPaths, paths, None, False) 10 | ${3:thisLayer.paths} = paths # result of boolean operation is in paths 11 | $0 12 | name 13 | Glyphs py path subtract 14 | tabTrigger 15 | subtract 16 | uuid 17 | B2070AD7-1990-4927-9C70-F9E1C5DDADB6 18 | 19 | 20 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs py report.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | #MenuTitle: ${1:$TM_FILENAME} 7 | # -*- coding: utf-8 -*- 8 | from __future__ import division, print_function, unicode_literals 9 | __doc__=""" 10 | Opens a new Edit tab containing all glyphs ${2:exceeding the ascender height.} 11 | """ 12 | 13 | def isLayerAffected(thisLayer): 14 | ascender = thisLayer.master.ascender 15 | for thisPath in thisLayer.paths: 16 | for thisNode in thisPath.nodes: 17 | if thisNode.y > ascender: 18 | return True$0 19 | return False 20 | 21 | Glyphs.clearLog() # clears log of Macro window 22 | thisFont = Glyphs.font # frontmost font 23 | affectedLayers = [] 24 | for thisGlyph in thisFont.glyphs: # loop through all glyphs 25 | for thisLayer in thisGlyph.layers: # loop through all layers 26 | print("Analysing {thisGlyph.name}") # report status in Macro window 27 | # collect affected layers: 28 | if isLayerAffected(thisLayer): 29 | affectedLayers.append(thisLayer) 30 | 31 | # open a new tab with the affected layers: 32 | if affectedLayers: 33 | newTab = thisFont.newTab() 34 | newTab.layers = affectedLayers 35 | # otherwise send a message: 36 | else: 37 | Message( 38 | title = "Nothing Found", 39 | message = "Could not find any glyphs $2", 40 | OKButton = None 41 | ) 42 | 43 | 44 | name 45 | Glyphs py report 46 | tabTrigger 47 | gspy 48 | uuid 49 | 519D1078-9594-4037-876B-6813C2010F98 50 | 51 | 52 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs py tab with layers.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | # opens new Edit tab: 7 | newTab = ${1:thisFont}.newTab() 8 | newTab.layers = ${2:$1.selectedLayers}$0 9 | 10 | name 11 | Glyphs py tab with layers 12 | tabTrigger 13 | tab 14 | uuid 15 | E44AAE2E-F6AE-4E8B-8891-8488DC5CC5E5 16 | 17 | 18 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs py tab with text.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | # opens new Edit tab: 7 | ${1:thisFont}.newTab(${2:"abc"})$0 8 | 9 | name 10 | Glyphs py tab with text 11 | tabTrigger 12 | tab 13 | uuid 14 | 6362E8C7-4931-4011-8782-B91FC63DFB53 15 | 16 | 17 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs py transform.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | ${1:import math 7 | }${2:from AppKit import NSAffineTransform, NSAffineTransformStruct 8 | } 9 | def transform(shiftX=0.0, shiftY=0.0, rotate=0.0, skew=0.0, scale=1.0): 10 | """ 11 | Returns an NSAffineTransform object for transforming layers. 12 | Apply an NSAffineTransform t object like this: 13 | Layer.transform_checkForSelection_doComponents_(t,False,True) 14 | Access its transformation matrix like this: 15 | tMatrix = t.transformStruct() # returns the 6-float tuple 16 | Apply the matrix tuple like this: 17 | Layer.applyTransform(tMatrix) 18 | Component.applyTransform(tMatrix) 19 | Path.applyTransform(tMatrix) 20 | Chain multiple NSAffineTransform objects t1, t2 like this: 21 | t1.appendTransform_(t2) 22 | """ 23 | myTransform = NSAffineTransform.transform() 24 | if rotate: 25 | myTransform.rotateByDegrees_(rotate) 26 | if scale != 1.0: 27 | myTransform.scaleBy_(scale) 28 | if not (shiftX == 0.0 and shiftY == 0.0): 29 | myTransform.translateXBy_yBy_(shiftX,shiftY) 30 | if skew: 31 | myTransform.shearBy_(math.tan(math.radians(skew))) 32 | return myTransform 33 | $0 34 | name 35 | Glyphs py transform 36 | tabTrigger 37 | transform 38 | uuid 39 | B5109A06-D1AA-4F24-BC46-27EFDE50B31E 40 | 41 | 42 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs py.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | #MenuTitle: ${1:$TM_FILENAME} 7 | # -*- coding: utf-8 -*- 8 | from __future__ import division, print_function, unicode_literals 9 | __doc__=""" 10 | ${2:Create effect for selected glyphs.} 11 | """ 12 | ${3: 13 | import random 14 | random.seed() 15 | } 16 | def process(thisLayer): 17 | for thisPath in thisLayer.paths: 18 | if thisPath.closed: 19 | print("- closed path:") 20 | else: 21 | print("- open path:") 22 | for thisNode in thisPath.nodes: 23 | print(f"-- node {thisNode.x:.1f} {thisNode.y:.1f} (type {thisNode.type})") 24 | for thisComponent in thisLayer.components: 25 | print(f"- component {thisComponent.componentName} at {thisComponent.position.x:.1f} {thisComponent.position.y:.1f}") 26 | for thisAnchor in thisLayer.anchors: 27 | print(f"- anchor {thisAnchor.name} at {thisAnchor.x:.1f} {thisAnchor.y:.1f}") 28 | $0 29 | 30 | thisFont = Glyphs.font # frontmost font 31 | ${4:thisFontMaster = thisFont.selectedFontMaster # active master 32 | }selectedLayers = thisFont.selectedLayers # active layers of selected glyphs 33 | ${5:selection = selectedLayers[0].selection # node selection in edit mode 34 | }Glyphs.clearLog() # clears log in Macro window 35 | 36 | ${6:thisFont.disableUpdateInterface() # suppresses UI updates in Font View 37 | }try: 38 | for thisLayer in selectedLayers: 39 | thisGlyph = thisLayer.parent 40 | print(f"Processing {thisGlyph.name}") 41 | ${7: thisGlyph.beginUndo() # begin undo grouping 42 | } process(thisLayer) 43 | ${8: thisGlyph.endUndo() # end undo grouping 44 | }except Exception as e: 45 | Glyphs.showMacroWindow() 46 | print("\n⚠️ Error in script: $1\n") 47 | import traceback 48 | print(traceback.format_exc()) 49 | print() 50 | raise e 51 | ${9:finally: 52 | thisFont.enableUpdateInterface() # re-enables UI updates in Font View} 53 | 54 | name 55 | Glyphs py 56 | tabTrigger 57 | gspy 58 | uuid 59 | E578EB41-51AC-4101-8DF3-034F6E9CE6B4 60 | 61 | 62 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/Glyphs2:3 if:then.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | if Glyphs.versionNumber >= 3: 7 | # GLYPHS 3 8 | ${1:$TM_SELECTED_TEXT} 9 | else: 10 | # GLYPHS 2 11 | ${2:$TM_SELECTED_TEXT} 12 | $0 13 | keyEquivalent 14 | ^~@G 15 | name 16 | Glyphs2/3 if/then 17 | tabTrigger 18 | g23 19 | uuid 20 | 638A98AB-F9D5-494A-81A6-62509B3E8A7B 21 | 22 | 23 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/PyObjC Decorators.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | ${1|@objc.python_method,@IBAction,@IBOutlet|}$0 7 | name 8 | PyObjC Decorators 9 | tabTrigger 10 | dec 11 | uuid 12 | AF2326C0-21FD-4747-8D59-3DB45C6EFFB1 13 | 14 | 15 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/py NSColor RGB.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | ${1:from AppKit import NSColor} 7 | ${2:thisColor} = NSColor.colorWithRed_green_blue_alpha_(${3:1.0}, ${4:0.1}, ${5:0.3}, ${6:0.3})$0 8 | name 9 | py NSColor RGB 10 | tabTrigger 11 | rgb 12 | uuid 13 | 8A8ADA16-81EC-4086-B142-E0EE21D61B77 14 | 15 | 16 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/py NSEvent Modifier Keys.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | ${2:from AppKit import ${1:NSEvent}} 7 | 8 | keysPressed = NSEvent.modifierFlags() 9 | ${3:optionKey = 524288 10 | }${5:commandKey = 1048576 11 | }${7:ctrlKey = 262144 12 | }${9:fnKey = 8388608 13 | }${11:shiftKey = 131072 14 | }${4:optionKeyPressed = keysPressed & optionKey == optionKey 15 | }${6:commandKeyPressed = keysPressed & commandKey == commandKey 16 | }${8:ctrlKeyPressed = keysPressed & ctrlKey == ctrlKey 17 | }${10:fnKeyPressed = keysPressed & fnKey == fnKey 18 | }${12:shiftKeyPressed = keysPressed & shiftKey == shiftKey 19 | }$0 20 | name 21 | py NSEvent Modifier Keys 22 | tabTrigger 23 | keys 24 | uuid 25 | 056A298C-4F4C-4632-B496-BA32852DD41F 26 | 27 | 28 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/py abc.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | from string import ascii_uppercase, ascii_lowercase 7 | alphabet = ascii_uppercase+ascii_lowercase 8 | $0 9 | name 10 | py abc 11 | tabTrigger 12 | abc 13 | uuid 14 | AB4509EC-47B1-4301-963C-54AFCB3E19BC 15 | 16 | 17 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/py bezier.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | def bezier(x1,y1, x2,y2, x3,y3, x4,y4, t): 7 | """ 8 | Returns coordinates for t (=0.0...1.0) on curve segment. 9 | x1,y1 and x4,y4: coordinates of on-curve nodes 10 | x2,y2 and x3,y3: coordinates of BCPs 11 | """ 12 | x = x1*(1-t)**3 + x2*3*t*(1-t)**2 + x3*3*t**2*(1-t) + x4*t**3 13 | y = y1*(1-t)**3 + y2*3*t*(1-t)**2 + y3*3*t**2*(1-t) + y4*t**3 14 | 15 | return x, y 16 | 17 | name 18 | py bezier 19 | tabTrigger 20 | bezier 21 | uuid 22 | A92363FC-E425-419A-8F0B-452C9603E8BC 23 | 24 | 25 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/py bezierQ.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | def bezierQ(x1,y1, x2,y2, x3,y3, t): 7 | """ 8 | Returns coordinates for t (=0.0...1.0) on curve segment. 9 | x1,y1 and x3,y3: coordinates of (implied) on-curve nodes 10 | x2,y2: coordinates of off-curve 11 | """ 12 | x = x1*(1-t)**2 + x2*2*t*(1-t) + x3*t**2 13 | y = y1*(1-t)**2 + y2*2*t*(1-t) + y3*t**2 14 | 15 | return x, y 16 | 17 | 18 | name 19 | py bezierQ 20 | tabTrigger 21 | bezier 22 | uuid 23 | 51956992-5760-496A-8919-2812C33CE539 24 | 25 | 26 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/py center of NSRect.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | ${1:from Foundation import NSPoint, NSRect 7 | }def centerOfRect(rect): 8 | """ 9 | Returns the center of NSRect rect as an NSPoint. 10 | """ 11 | center = NSPoint(rect.origin.x + rect.size.width/2, rect.origin.y + rect.size.height/2) 12 | return center 13 | $0 14 | name 15 | py center of NSRect 16 | tabTrigger 17 | center 18 | uuid 19 | FDE9D8DD-19CD-450C-A3CE-9837FA2A419B 20 | 21 | 22 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/py clipboard.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | ${1:from AppKit import NSPasteboard, NSStringPboardType 7 | } 8 | def setClipboard(myText): 9 | """ 10 | Sets the contents of the clipboard to myText. 11 | Returns True if successful, False if unsuccessful. 12 | """ 13 | try: 14 | myClipboard = NSPasteboard.generalPasteboard() 15 | myClipboard.declareTypes_owner_([NSStringPboardType], None) 16 | myClipboard.setString_forType_(myText, NSStringPboardType) 17 | return True 18 | except Exception as e: 19 | return False 20 | 21 | ${2:if not setClipboard(${3:"clipboard text"}): 22 | print("Warning: could not set clipboard to {$3}") 23 | $0 24 | name 25 | py clipboard 26 | tabTrigger 27 | clipboard 28 | uuid 29 | 79CAECE7-25C2-4F7A-A2DD-72152006D94F 30 | 31 | 32 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/py future imports.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | from __future__ import division, print_function, unicode_literals 7 | $0 8 | name 9 | py future imports 10 | tabTrigger 11 | fut 12 | uuid 13 | 4D66C425-84C1-42D7-A734-7151EF8422AF 14 | 15 | 16 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/py intersect two lines.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | ${1:from Foundation import NSPoint 7 | } 8 | def intersect(pointA, pointB, pointC, pointD): 9 | """ 10 | Returns an NSPoint of the intersection AB with CD, 11 | or None if there is no intersection. 12 | pointA, pointB: NSPoints or GSNodes representing the first line AB, 13 | pointC, pointD: NSPoints or GSNodes representing the second line CD. 14 | """ 15 | xA, yA = pointA.x, pointA.y 16 | xB, yB = pointB.x, pointB.y 17 | xC, yC = pointC.x, pointC.y 18 | xD, yD = pointD.x, pointD.y 19 | 20 | try: 21 | slopeAB = (float(yB) - float(yA)) / (float(xB) - float(xA)) 22 | except: 23 | slopeAB = None # division by zero if vertical 24 | 25 | try: 26 | slopeCD = (float(yD) - float(yC)) / (float(xD) - float(xC)) 27 | except: 28 | slopeCD = None # division by zero if vertical 29 | 30 | if slopeAB == slopeCD: # parallel, no intersection 31 | return None 32 | elif slopeAB is None: # first line is vertical 33 | x = xA 34 | y = slopeCD * (x - xC) + yC 35 | elif slopeCD is None: # second line is vertical 36 | x = xC 37 | y = slopeAB * (x - xA) + yA 38 | else: # both lines have different angles other than vertical 39 | x = (slopeAB * xA - yA - slopeCD * xC + yC) / (slopeAB - slopeCD) 40 | y = slopeAB * (x - xA) + yA 41 | 42 | return NSPoint(x, y) 43 | $0 44 | name 45 | py intersect two lines 46 | tabTrigger 47 | intersect 48 | uuid 49 | 73E8D678-EE48-4BA2-B7CC-BDB76113E635 50 | 51 | 52 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/py random.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | import random 7 | random.seed() 8 | ${1:randomNumber = random.randint(-100, 100) 9 | }$0 10 | name 11 | py random 12 | tabTrigger 13 | random 14 | uuid 15 | E2A706E9-ADC6-4265-A39C-E80784BC11BB 16 | 17 | 18 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/py sort.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | sorted(${1:[ ${2:[l.parent.name, l.bounds.origin.y]} for l in selectedLayers]}, key=lambda thisListItem: ${3:thisListItem[1]})$0 7 | name 8 | py sort 9 | tabTrigger 10 | sort 11 | uuid 12 | 5225E965-C129-4C77-A7FE-C667F7D45566 13 | 14 | 15 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/py terminal.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | ${1:import subprocess} 7 | command = '${2:uptime}' 8 | commandArgs = [${3/.+/"/}${3:~/Library/Application Support/Glyphs/Scripts}${3/.+/"/}] # arguments (may contain spaces) as lists of strings 9 | commandExecution = subprocess.Popen(command.split(" ")+commandArgs, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 10 | output, error = commandExecution.communicate() 11 | ${4:print("RETURNCODE (0=OK): {commandExecution.returncode}")} 12 | if output: 13 | ${5:print("\nOUTPUT:\n{output}")} 14 | if error: 15 | ${6:print("\nERROR:\n{error}")} 16 | $0 17 | 18 | name 19 | py terminal 20 | tabTrigger 21 | terminal 22 | uuid 23 | 35EC5588-93F3-4657-AA2D-B5F7D7F667FB 24 | 25 | 26 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/py timer.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | from timeit import default_timer as timer 7 | 8 | def reportTimeInNaturalLanguage( seconds ): 9 | if seconds > 60.0: 10 | timereport = "{seconds//60}:{seconds%60:02i} minutes" 11 | elif seconds < 1.0: 12 | timereport = "{seconds:.2f} seconds" 13 | elif seconds < 20.0: 14 | timereport = "{seconds:.1f} seconds" 15 | else: 16 | timereport = "{seconds} seconds" 17 | return timereport 18 | 19 | ${2:# start taking time: 20 | start = timer() 21 | 22 | # take time: 23 | end = timer() 24 | timereport = reportTimeInNaturalLanguage(end - start) 25 | ${1:print("Time elapsed: {timereport}.")} 26 | }$0 27 | name 28 | py timer 29 | tabTrigger 30 | timer 31 | uuid 32 | 3BD03C70-490F-478A-99C7-1B2E8945D09E 33 | 34 | 35 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/py traceback.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | ${1:import traceback 7 | }print(traceback.format_exc())$0 8 | name 9 | py traceback 10 | tabTrigger 11 | trace 12 | uuid 13 | 2C753F76-767A-45FD-90D0-42C68659B23D 14 | 15 | 16 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/py write file.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | ${1:import codecs 7 | }def saveFileInLocation(content="", fileName="test.txt", filePath="~/Desktop"): 8 | saveFileLocation = f"{filePath}/{fileName}" 9 | saveFileLocation = saveFileLocation.replace( "//", "/" ) 10 | with codecs.open(saveFileLocation, "w", "utf-8-sig") as thisFile: 11 | print(f"Exporting to: {thisFile.name}") 12 | thisFile.write(content) 13 | thisFile.close() 14 | return True 15 | 16 | name 17 | py write file 18 | tabTrigger 19 | write 20 | uuid 21 | F8A9B749-480B-480A-A181-5755C93F34E6 22 | 23 | 24 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/Snippets/vanilla help.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | import vanilla 7 | help(vanilla.${1|ActionButton,Box,Button,CheckBox,CheckBoxListCell,ColorWell,ComboBox,DatePicker,Drawer,EditText,FloatingWindow,GradientButton,Group,HUDFloatingWindow,HelpButton,HorizontalLine,ImageButton,ImageListCell,ImageView,LevelIndicator,LevelIndicatorListCell,List,ObjectBrowser,PathControl,PopUpButton,PopUpButtonListCell,Popover,ProgressBar,ProgressSpinner,RadioGroup,ScrollView,SearchBox,SecureEditText,SegmentedButton,SegmentedButtonListCell,Sheet,Slider,SliderListCell,SplitView,SplitView2,SquareButton,Tabs,TextBox,TextEditor,VanillaBaseControl,VanillaBaseObject,VanillaError,VerticalLine,Window|}) 8 | $0 9 | name 10 | vanilla help 11 | tabTrigger 12 | vanilla 13 | uuid 14 | 240EFB4E-E02A-45FD-AA0F-BDB148FBD2BE 15 | 16 | 17 | -------------------------------------------------------------------------------- /TextMate/Python for Glyphs.tmbundle/info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | contactEmailRot13 6 | erf ng tylcufncc qbg pbz 7 | contactName 8 | Rainer Erich Scheichelbauer 9 | description 10 | Python snippets for Glyphs.app coding. 11 | http://glyphsapp.com/ 12 | https://github.com/mekkablue/Python-for-Glyphs 13 | 14 | name 15 | Python for Glyphs 16 | uuid 17 | 9A5B7518-7352-4AE4-8CD9-9F16EF3B825D 18 | 19 | 20 | --------------------------------------------------------------------------------