Last active
April 8, 2026 19:31
-
-
Save adbac/2d2150d5e90a1008c1b8ecb28febfc60 to your computer and use it in GitHub Desktop.
A new version of ProductionType's magneticMetrics script, updated for RoboFont 4 (using the Subscriber and merz modules)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| v.2.3 | |
| Observer to make sidebearings magnetic to outline modifications. | |
| Its aim to be used as a startup script so it can be activated and deactivated whenever you need. | |
| Version history: | |
| ProductionType: | |
| - v1.002: first private release | |
| - v1.003: fix magnet multiple drawing | |
| Adrien Bachelart: | |
| - v2.000: Updated for RF4: use Subscriber instead of observers and merz instead of mojo.drawingTools | |
| - v2.001: maintenance and performance | |
| - Define the magnet factory outside of the Subscriber | |
| - Stop updating the internal data when the tool isn't active | |
| - Check that font isn't `None` when using it | |
| - v2.1: fixed long-standing performance issue with infinite subscriber callback loop that was slowing down the whole glyph editor. | |
| - v2.2: | |
| - Add support for italic magnets positioning (with slant offset) | |
| - Centralize lib key logic | |
| - v2.3: | |
| - Used angled glyph margins for when the italic angle is set | |
| """ | |
| import math | |
| from typing import Literal | |
| import merz | |
| from fontTools.misc import transform | |
| from merz.tools.drawingTools import NSImageDrawingTools | |
| from mojo.events import extractNSEvent | |
| from mojo.subscriber import ( | |
| Subscriber, | |
| disableSubscriberEvents, | |
| registerGlyphEditorSubscriber, | |
| ) | |
| PRESSED_KEY = "M" | |
| # Register merz magnet factory | |
| def magnetSymbolFactory(scale=1): | |
| bot = NSImageDrawingTools((50 * scale, 50 * scale)) | |
| bot.scale(scale) | |
| bot.fill(0, 0, 0, 0.75) | |
| pen = bot.BezierPath() | |
| pen.moveTo((25, 15)) | |
| pen.curveTo((20.0, 15.0), (17, 16), (17, 20)) | |
| pen.lineTo((17, 45)) | |
| pen.curveTo((17.0, 48.0), (15, 50), (9, 50)) | |
| pen.curveTo((2, 50), (0, 48.0), (0, 45)) | |
| pen.lineTo((0, 22)) | |
| pen.curveTo((0, 6), (9, 0), (25, 0)) | |
| pen.curveTo((41.0, 0.0), (50, 6), (50, 22)) | |
| pen.lineTo((50, 45)) | |
| pen.curveTo((50, 48), (48, 50), (41, 50)) | |
| pen.curveTo((35, 50), (33, 48), (33, 45)) | |
| pen.lineTo((33, 20)) | |
| pen.curveTo((33, 16), (30, 15), (25, 15)) | |
| pen.closePath() | |
| bot.drawPath(pen) | |
| bot.fill(1) | |
| pen = bot.BezierPath() | |
| pen.moveTo((25, 3)) | |
| pen.curveTo((11.0, 3), (3, 8), (3, 22)) | |
| pen.lineTo((3, 34)) | |
| pen.lineTo((14, 34)) | |
| pen.lineTo((14, 20)) | |
| pen.curveTo((14.0, 14.0), (18, 12), (25, 12)) | |
| pen.curveTo((32.0, 12.0), (36.0, 14.0), (36, 20)) | |
| pen.lineTo((36, 34)) | |
| pen.lineTo((47, 34)) | |
| pen.lineTo((47, 22)) | |
| pen.curveTo((47, 8), (39, 3), (25, 3)) | |
| pen.closePath() | |
| bot.drawPath(pen) | |
| image = bot.getImage() | |
| return image | |
| LIB_KEY = lambda s=None: ( | |
| f"com.adrienbc.MagneticMargins{('.' + s) if s is not None else ''}" | |
| ) | |
| MAGNET_SYMBOL_KEY = LIB_KEY("magnet") | |
| merz.SymbolImageVendor.registerImageFactory(MAGNET_SYMBOL_KEY, magnetSymbolFactory) | |
| # Register Subscriber | |
| class MagneticMetricsSubscriber(Subscriber): | |
| def build(self): | |
| self.glyphEditor = self.getGlyphEditor() | |
| self.glyph = self.glyphEditor.getGlyph().asFontParts() | |
| self.font = self.glyph.font | |
| self.leftMargin, self.rightMargin = 0, 0 | |
| self.updateMarginsDataFromGlyph() | |
| self.status = 0 | |
| self.magnetScale = 0.3 | |
| self.magnetsLayer = self.glyphEditor.extensionContainer(LIB_KEY()) | |
| self.magnetsLayer.setVisible(False) | |
| self.leftMagnet = self.magnetsLayer.appendSymbolSublayer( | |
| imageSettings=dict(name=MAGNET_SYMBOL_KEY, scale=self.magnetScale) | |
| ) | |
| self.rightMagnet = self.magnetsLayer.appendSymbolSublayer( | |
| imageSettings=dict(name=MAGNET_SYMBOL_KEY, scale=self.magnetScale) | |
| ) | |
| self.updateMagnetsPosition() | |
| def glyphEditorDidKeyDown(self, info): | |
| event = extractNSEvent(info["NSEvent"]) | |
| keyDown = event["keyDown"] | |
| if keyDown == PRESSED_KEY: | |
| if self.status == 0: | |
| self.status = 1 | |
| self.updateGlyphReference() | |
| self.updateMarginsDataFromGlyph() | |
| self.updateMagnetsPosition() | |
| self.magnetsLayer.setVisible(True) | |
| else: | |
| self.status = 0 | |
| self.magnetsLayer.setVisible(False) | |
| def glyphEditorDidSetGlyph(self, info): | |
| if self.status == 1: | |
| self.updateGlyphReference() | |
| self.updateMarginsDataFromGlyph() | |
| self.updateMagnetsPosition() | |
| glyphEditorGlyphDidChangeContoursDelay = 0.01 | |
| def glyphEditorGlyphDidChangeContours(self, info): | |
| if self.status == 1: | |
| self.updateGlyphFromMarginsData() | |
| self.updateMagnetsPosition() | |
| def updateGlyphReference(self): | |
| self.glyph = self.glyphEditor.getGlyph().asFontParts() | |
| self.font = self.glyph.font | |
| def updateMarginsDataFromGlyph(self): | |
| self.leftMargin = self.glyph.angledLeftMargin | |
| self.rightMargin = self.glyph.angledRightMargin | |
| def updateGlyphFromMarginsData(self): | |
| with disableSubscriberEvents(): | |
| self.glyph.angledLeftMargin = self.leftMargin | |
| self.glyph.angledRightMargin = self.rightMargin | |
| def getMagnetPosition(self, side: Literal["left", "right"]): | |
| # Y position | |
| if self.font is not None and self.font.info.capHeight is not None: | |
| yPos = self.font.info.capHeight / 2 | |
| else: | |
| yPos = 0 | |
| # Simplest case = no italic | |
| if self.font is not None and self.font.info.italicAngle in {None, 0}: | |
| if side == "left": | |
| return (0, yPos) | |
| else: | |
| return (self.glyph.width, yPos) | |
| offset = self.font.lib.get("com.typemytype.robofont.italicSlantOffset", 0) | |
| # Calculate slanted position | |
| x = y = math.radians(-(self.font.info.italicAngle or 0)) | |
| matrix = transform.Identity.skew(x=x, y=y) | |
| t = transform.Transform() | |
| oX, oY = (0, 0) | |
| t = t.translate(oX, oY) | |
| t = t.transform(matrix) | |
| t = t.translate(-oX, -oY) | |
| trans = tuple(t) | |
| ot = transform.Transform(*trans) | |
| n = ot.transformPoint( | |
| ((0 if side == "left" else self.glyph.width) + offset, yPos) | |
| ) | |
| return (n[0], yPos) | |
| def updateMagnetsPosition(self): | |
| self.leftMagnet.setPosition(self.getMagnetPosition("left")) | |
| self.rightMagnet.setPosition(self.getMagnetPosition("right")) | |
| registerGlyphEditorSubscriber(MagneticMetricsSubscriber) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment