├── 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 |
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 | 
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 |
--------------------------------------------------------------------------------