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)
Last edited on 01/09/2021