Displaying distances between selected points in the glyph editor ↩

This example shows how to display distances between selected points in the glyph editor using Subscriber and Merz.
from math import hypot
from mojo.subscriber import Subscriber, registerGlyphEditorSubscriber
from mojo.roboFont import RPoint
RED = 1, 0, 0, 1
WHITE = 1, 1, 1, 1
class PointDistanceController(Subscriber):
debug = True
def build(self):
glyphEditor = self.getGlyphEditor()
self.container = glyphEditor.extensionContainer(
identifier="com.roboFont.PointDistanceController.foreground",
location="foreground",
clear=True
)
self.distancesLayer = self.container.appendBaseSublayer()
def glyphEditorDidSetGlyph(self, info):
self.drawMeasurements(info)
def glyphDidChangeSelection(self, info):
self.drawMeasurements(info)
def drawMeasurements(self, info):
# get the glyph from the notification
glyph = info["glyph"]
# get the selection
selection = glyph.selectedPoints + glyph.selectedAnchors
# check if the selection is more than 1 and less than 6
if 2 <= len(selection) <= 5:
self.distancesLayer.setVisible(True)
self.distancesLayer.clearSublayers()
done = []
# if 2 points selected, add one to get horizontal/vertical measurements
if len(selection) == 2:
pos = selection[0].x, selection[1].y
# only add the point if it doesn’t match with an existing one
if not any([pos == p.position for p in selection]):
helperPoint = RPoint()
helperPoint.position = pos
selection = set(selection + (helperPoint,))
# loop over all the points in the selection
for p in selection:
# loope in a the loop again over all the points in the selection
for p2 in selection:
# check if the point is not the same
if p == p2:
continue
# check if we already handled the point
if set([p, p2]) in done:
continue
# add a line to the distances layer
self.distancesLayer.appendLineSublayer(
startPoint=(p.x, p.y),
endPoint=(p2.x, p2.y),
strokeWidth=1,
strokeColor=RED
)
# calculate the center point
cx = p.x + (p2.x - p.x) * .5
cy = p.y + (p2.y - p.y) * .5
# calculate the distance
dist = hypot(p2.x - p.x, p2.y - p.y)
# store connections to avoid duplicates
done.append(set([p, p2]))
# create the label layer
self.distancesLayer.appendTextLineSublayer(
position=(cx, cy),
backgroundColor=RED,
text=f"{dist:.0f}" if dist % 1 == 0 else f"{dist:.2f}",
font="system",
weight="bold",
pointSize=12,
padding=(4, 1),
cornerRadius=4,
fillColor=WHITE,
horizontalAlignment='center',
verticalAlignment='center',
)
else:
self.distancesLayer.setVisible(False)
if __name__ == '__main__':
registerGlyphEditorSubscriber(PointDistanceController)
If you want to be able to toggle the visualization on and off, you can get some help from a WindowController. Use the callback of a vanilla checkbox to register or unregister the subscriber object.
#!/usr/bin/env python3
from math import hypot
from mojo.subscriber import Subscriber, WindowController
from mojo.subscriber import registerGlyphEditorSubscriber, unregisterGlyphEditorSubscriber
from mojo.roboFont import OpenWindow
from vanilla import FloatingWindow, CheckBox
RED = 1, 0, 0, 1
WHITE = 1, 1, 1, 1
class PointDistanceControllerPalette(WindowController):
debug = True
def build(self):
self.w = FloatingWindow((200, 40), "PointDistanceController")
self.w.checkBox = CheckBox((10, 10, -10, 20), "Display Distances",
callback=self.checkBoxCallback, value=True)
self.w.open()
def started(self):
PointDistanceController.controller = self
registerGlyphEditorSubscriber(PointDistanceController)
def destroy(self):
unregisterGlyphEditorSubscriber(PointDistanceController)
PointDistanceController.controller = None
def checkBoxCallback(self, sender):
if sender.get():
PointDistanceController.controller = self
registerGlyphEditorSubscriber(PointDistanceController)
else:
PointDistanceController.controller = None
unregisterGlyphEditorSubscriber(PointDistanceController)
class PointDistanceController(Subscriber):
debug = True
def build(self):
glyphEditor = self.getGlyphEditor()
self.container = glyphEditor.extensionContainer(
identifier="com.roboFont.PointDistanceController.foreground",
location="foreground",
clear=True
)
self.distancesLayer = self.container.appendBaseSublayer()
def destroy(self):
self.container.clearSublayers()
def glyphDidChangeSelection(self, info):
# get the glyph from the notification
glyph = info["glyph"]
# get the selection
selection = glyph.selection
# check if the selection is more than 1 and less than 6
if 2 <= len(selection) <= 5:
self.distancesLayer.setVisible(True)
self.distancesLayer.clearSublayers()
done = []
# loop over all the points in the selection
for p in selection:
# loope in a the loop again over all the points in the selection
for p2 in selection:
# check if the point is not the same
if p == p2:
continue
# check if we already handled the point
if set([p, p2]) in done:
continue
# add a line to the distances layer
self.distancesLayer.appendLineSublayer(
startPoint=(p.x, p.y),
endPoint=(p2.x, p2.y),
strokeWidth=1,
strokeColor=RED
)
# calculate the center point
cx = p.x + (p2.x - p.x) * .5
cy = p.y + (p2.y - p.y) * .5
# calculate the distance
dist = hypot(p2.x - p.x, p2.y - p.y)
# store connections to avoid duplicates
done.append(set([p, p2]))
# create the label layer
self.distancesLayer.appendTextLineSublayer(
position=(cx, cy),
backgroundColor=RED,
text=f"{dist:.0f}" if dist % 1 == 0 else f"{dist:.2f}",
font="system",
weight="bold",
pointSize=12,
padding=(4, 1),
cornerRadius=4,
fillColor=WHITE,
horizontalAlignment='center',
verticalAlignment='center',
)
else:
self.distancesLayer.setVisible(False)
if __name__ == '__main__':
OpenWindow(PointDistanceControllerPalette)