├── ShowDistanceAndAngleOfNodes.glyphsReporter └── Contents │ ├── MacOS │ └── plugin │ ├── Info.plist │ └── Resources │ └── plugin.py └── README.md /ShowDistanceAndAngleOfNodes.glyphsReporter/Contents/MacOS/plugin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark2Mark/Show-Distance-And-Angle-Of-Nodes/HEAD/ShowDistanceAndAngleOfNodes.glyphsReporter/Contents/MacOS/plugin -------------------------------------------------------------------------------- /ShowDistanceAndAngleOfNodes.glyphsReporter/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | plugin 9 | CFBundleIdentifier 10 | com.markfromberg.ShowDistanceAndAngle 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ShowDistanceAndAngle 15 | CFBundleVersion 16 | 9 17 | CFBundleShortVersionString 18 | 2.0.3 19 | UpdateFeedURL 20 | 21 | productPageURL 22 | 23 | productReleaseNotes 24 | Glyphs 3 compatibility 25 | NSHumanReadableCopyright 26 | Copyright, by Mark Frömberg (@mark2mark), 2015 27 | NSPrincipalClass 28 | ShowDistanceAndAngle 29 | PyMainFileNames 30 | 31 | plugin.py 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Buy Me a Coffee at ko-fi.com 3 |

4 | 5 | # Show Distance And Angle 6 | 7 | *This is a plugin for the [Glyphs font editor](http://glyphsapp.com/).* 8 | 9 | It shows the direct distance of two selected elements (`Nodes, Anchors, Components`) and their angle. The times of temporaryly created guidelines are passé. 10 | 11 | ### Install 12 | 13 | 1. Install via the Plugin Manager in Glyphs. 14 | 2. Restart Glyphs. 15 | 16 | ### How to use 17 | 18 | When ever you need it, toggle `Show * Distance and Angle` from the view menu. 19 | 20 | ### Examples 21 | 22 | ![Show Distance And Angle Demo](https://raw.githubusercontent.com/Mark2Mark/Show-Distance-And-Angle-Of-Nodes/40229b6cd64bca593a2990a9c40e2b355fdc3c42/Images/ShowDistanceAndAngle01.gif "Show Distance And Angle") 23 | 24 | ##### Pull Requests 25 | 26 | Feel free to comment or pull requests for any improvements. 27 | 28 | ##### License 29 | 30 | Copyright 2015 [Mark Frömberg](http://www.markfromberg.com/) *@Mark2Mark* 31 | 32 | Made possible with the GlyphsSDK by Georg Seifert (@schriftgestalt) and Rainer Erich Scheichelbauer (@mekkablue). 33 | 34 | Licensed under the Apache License, Version 2.0 (the "License"); 35 | you may not use this file except in compliance with the License. 36 | You may obtain a copy of the License at 37 | 38 | http://www.apache.org/licenses/LICENSE-2.0 39 | 40 | See the License file included in this repository for further details. 41 | -------------------------------------------------------------------------------- /ShowDistanceAndAngleOfNodes.glyphsReporter/Contents/Resources/plugin.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | from __future__ import division, print_function, unicode_literals 3 | 4 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 5 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 6 | # 7 | # --> let me know if you have ideas for improving 8 | # --> Mark Froemberg aka DeutschMark @ GitHub 9 | # --> www.markfromberg.com 10 | # 11 | # - Note: 12 | # + About Relative/Absolute/Shortest angle: https://forum.glyphsapp.com/t/show-distance-and-angle/8176/17 13 | # 14 | # - ToDo 15 | # - 16 | # 17 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 18 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 19 | 20 | from GlyphsApp import Glyphs 21 | from GlyphsApp.plugins import ReporterPlugin 22 | from Foundation import NSString, NSRect, NSPoint, NSLog 23 | from AppKit import NSColor, NSBezierPath 24 | import math 25 | import objc 26 | import traceback 27 | 28 | @objc.python_method 29 | def UnitVectorFromTo(B, A): 30 | A.x -= B.x 31 | A.y -= B.y 32 | Length = math.sqrt((A.x * A.x) + (A.y * A.y)) 33 | A.x /= Length 34 | A.y /= Length 35 | return A 36 | 37 | COLOR = 0, .6, 1, 0.75 38 | 39 | class ShowDistanceAndAngle(ReporterPlugin): 40 | 41 | @objc.python_method 42 | def settings(self): 43 | try: 44 | self.menuName = Glyphs.localize({ 45 | 'en': "Distance & Angle", 46 | 'de': 'Abstand & Winkel', 47 | 'fr': 'distance & angle', 48 | 'es': 'distancia & angulo', 49 | }) 50 | 51 | # print(self.menuName, "Version 1.0.5") 52 | self.thisMenuTitle = {"name": "%s:" % self.menuName, "action": None} 53 | self.vID = "com.markfromberg.ShowDistanceAndAngle" # vendorID 54 | 55 | self.angleAbsolute = True 56 | if not self.LoadPreferences(): 57 | print("Error: Could not load preferences. Will resort to defaults.") 58 | 59 | self.angleStyles = { 60 | True: u"= Relative Angle", 61 | False: u"= Shortest Angle", # Absolute = % 360 62 | } 63 | 64 | self.generalContextMenus = [ 65 | self.thisMenuTitle, 66 | {"name": u"%s" % self.angleStyles[bool(self.angleAbsolute)], "action": self.toggleAngleStyle_}, 67 | ] 68 | except: 69 | print(traceback.format_exc()) 70 | 71 | def toggleAngleStyle_(self, sender): 72 | try: 73 | self.angleAbsolute = not self.angleAbsolute 74 | self.generalContextMenus = [ 75 | self.thisMenuTitle, 76 | {"name": "%s" % self.angleStyles[bool(self.angleAbsolute)], "action": self.toggleAngleStyle_}, 77 | ] 78 | self.RefreshView() 79 | self.SavePreferences() 80 | except: 81 | print(traceback.format_exc()) 82 | 83 | @objc.python_method 84 | def SavePreferences(self): 85 | try: 86 | Glyphs.defaults["%s.angleStyle" % self.vID] = self.angleAbsolute # self.w.hTarget.get() 87 | except: 88 | print(traceback.format_exc()) 89 | 90 | @objc.python_method 91 | def LoadPreferences(self): 92 | try: 93 | Glyphs.registerDefault("%s.angleStyle" % self.vID, True) # Default 94 | try: 95 | self.angleAbsolute = Glyphs.boolDefaults["%s.angleStyle" % self.vID] 96 | except: 97 | return False 98 | 99 | return True 100 | except: 101 | print(traceback.format_exc()) 102 | 103 | @objc.python_method 104 | def foregroundInViewCoords(self, layer=None): 105 | if layer is None: 106 | layer = self.controller.activeLayer() 107 | if layer: 108 | try: 109 | self.drawNodeDistanceText(layer) 110 | except: 111 | print(traceback.format_exc()) 112 | 113 | @objc.python_method 114 | def background(self, layer): 115 | try: 116 | selection = layer.selection 117 | if len(selection) == 2: 118 | x1, y1 = selection[0].x, selection[0].y 119 | x2, y2 = selection[1].x, selection[1].y 120 | self.drawLine(x1, y1, x2, y2) 121 | except: 122 | print(traceback.format_exc()) 123 | 124 | @objc.python_method 125 | def drawCoveringBadge(self, x, y, width, height, radius): 126 | try: 127 | myPath = NSBezierPath.alloc().init() 128 | NSColor.colorWithCalibratedRed_green_blue_alpha_(*COLOR).set() 129 | myRect = NSRect((x, y), (width, height)) 130 | thisPath = NSBezierPath.bezierPathWithRoundedRect_cornerRadius_(myRect, radius) 131 | myPath.appendBezierPath_(thisPath) 132 | myPath.fill() 133 | except: 134 | print(traceback.format_exc()) 135 | 136 | @objc.python_method 137 | def drawLine(self, x1, y1, x2, y2, strokeWidth=1): 138 | try: 139 | myPath = NSBezierPath.bezierPath() 140 | myPath.moveToPoint_((x1, y1)) 141 | myPath.lineToPoint_((x2, y2)) 142 | myPath.setLineWidth_(strokeWidth / self.getScale()) 143 | NSColor.colorWithCalibratedRed_green_blue_alpha_(*COLOR).set() 144 | myPath.stroke() 145 | except: 146 | print(traceback.format_exc()) 147 | 148 | @objc.python_method 149 | def drawNodeDistanceText(self, layer): 150 | if layer is None: 151 | return 152 | try: 153 | try: 154 | selection = layer.selection 155 | except: 156 | selection = layer.selection() 157 | if len(selection) == 2: 158 | x1, y1 = selection[0].x, selection[0].y 159 | x2, y2 = selection[1].x, selection[1].y 160 | t = 0.5 # MIDLLE 161 | xAverage = x1 + (x2 - x1) * t 162 | yAverage = y1 + (y2 - y1) * t 163 | dist = math.hypot(x2 - x1, y2 - y1) 164 | 165 | # Angle 166 | #====== 167 | # print(x2 >= x1 or y2 >= y1) 168 | switch = (x1, y1) >= (x2, y2) 169 | 170 | if switch and not self.angleAbsolute: 171 | dx, dy = x1 - x2, y1 - y2 172 | #print("switch") 173 | else: 174 | dx, dy = x2 - x1, y2 - y1 175 | rads = math.atan2(dy, dx) 176 | degs = math.degrees(rads) 177 | 178 | if self.angleAbsolute: 179 | degs = degs % 180 # Not using 360 here. same angles will have the same number, no matter the path direction of this segment 180 | else: 181 | degs = abs(degs) % 90 182 | 183 | scale = self.getScale() 184 | string = NSString.stringWithString_(u"%s\n%s°" % (round(dist, 1), round(degs, 1))) 185 | attributes = NSString.drawTextAttributes_(NSColor.whiteColor()) 186 | textSize = string.sizeWithAttributes_(attributes) 187 | 188 | # Badge 189 | #====== 190 | badgeWidth = textSize.width + 8 191 | badgeHeight = textSize.height + 4 192 | badgeRadius = 5 193 | 194 | unitVector = UnitVectorFromTo(NSPoint(x1, y1), NSPoint(x2, y2)) 195 | 196 | badgeOffsetX = -unitVector.y * (badgeWidth / 2 + 4) 197 | badgeOffsetY = unitVector.x * (badgeHeight / 2 + 4) 198 | 199 | cpX, cpY = math.floor(xAverage), math.floor(yAverage) 200 | 201 | glyphEditView = self.controller.graphicView() 202 | try: 203 | selection = glyphEditView.selectedLayerRange() 204 | except: 205 | selection = glyphEditView.textStorage().selectedRange() 206 | origin = glyphEditView.cachedPositionAtIndex_(selection.location) 207 | cpX = cpX * scale + origin[0] 208 | cpY = cpY * scale + origin[1] 209 | 210 | self.drawCoveringBadge(cpX - badgeWidth / 2 - badgeOffsetX, cpY - badgeHeight / 2 - badgeOffsetY, badgeWidth, badgeHeight, badgeRadius) 211 | self.drawText(string, (cpX - badgeOffsetX, cpY - badgeOffsetY)) 212 | except: 213 | print(traceback.format_exc()) 214 | 215 | @objc.python_method 216 | def drawText(self, text, textPosition, fontColor=NSColor.whiteColor()): 217 | try: 218 | string = NSString.stringWithString_(text) 219 | string.drawAtPoint_color_alignment_handleSize_(textPosition, fontColor, 4, -1) 220 | except: 221 | print(traceback.format_exc()) 222 | 223 | def needsExtraMainOutlineDrawingForInactiveLayer_(self, layer): 224 | return True 225 | 226 | @objc.python_method 227 | def RefreshView(self): 228 | try: 229 | currentTabView = Glyphs.font.currentTab 230 | if currentTabView: 231 | currentTabView.graphicView().setNeedsDisplay_(True) 232 | except: 233 | pass 234 | 235 | @objc.python_method 236 | def logToConsole(self, message): 237 | myLog = "Show %s plugin:\n%s" % (self.title(), message) 238 | NSLog(myLog) 239 | 240 | @objc.python_method 241 | def __file__(self): 242 | """Please leave this method unchanged""" 243 | return __file__ 244 | --------------------------------------------------------------------------------