Skip to content

Instantly share code, notes, and snippets.

@connordavenport
Forked from okay-type/anchorHelper.py
Last active April 19, 2021 14:25
Show Gist options
  • Save connordavenport/ba7c5716c6210f3c1fe7dfd1a723ba6c to your computer and use it in GitHub Desktop.
Save connordavenport/ba7c5716c6210f3c1fe7dfd1a723ba6c to your computer and use it in GitHub Desktop.
the rough start of a rf4 anchor tool
from vanilla import *
import merz
import mojo.subscriber as subs
from mojo.subscriber import Subscriber, registerGlyphEditorSubscriber
from mojo.subscriber import registerSubscriberEvent, getRegisteredSubscriberEvents
from mojo.extensions import registerExtensionDefaults, setExtensionDefault, getExtensionDefault, removeExtensionDefault
from mojo.drawingTools import *
from mojo.UI import SetCurrentGlyphByName
from math import radians, tan, pi
from AppKit import NSColor
from fontTools.pens.basePen import BasePen
fillColor = (0.6326, 0, 0.9689, 0.3771)
'''
recent updates:
now a dropdown menu in the glyph edit window
stores checked components as a preference
expand/collapse state persists when switching glyphs
close button turns off the view (still need to figure out how to remove the tool)
draws glyph guidelines (left, middle, right)
to do:B
figure out how to turn the damn thing off !!!
draw accents that have components (apparently this requires more work than bot.drawGlyph)
draw in preview mode
make anchor list scrollable for small windows
ui to toggle guidelines
it's annoying that the focus stays on the controller window after checking a box, what's a better solution to keep the focus on the glyph edit window? p.s. floating windows are bad
when anchor selected:
only show components for the selected anchor
show glyph bounds lines
show accent bounds lines
left center right top middle bottom
for accent and glyph
italic-angle adjusted
if a derived glyph (e.g.: small-cap)
show color status
correct, within-fuzz, wrong-x, wrong-y, missing, extra
show suggested location
show button to fix location
x, y, both
when anchor not selected:
if a derived glyph
show color status
correct, within-fuzz, wrong-x, wrong-y, missing, extra
'''
class DecompPen(BasePen):
def __init__(self, glyphSet, outPen):
super(DecompPen, self).__init__(glyphSet)
self._moveTo = outPen.moveTo
self._lineTo = outPen.lineTo
self._curveToOne = outPen.curveTo
self._closePath = outPen.closePath
self._endPath = outPen.endPath
class anchorHelper(Subscriber):
debug = False
def build(self):
# initial data
self.toggleOnOff = True
self.getAnchorData()
self.added = []
self.anchorOn = []
self.showUI = True
self.cs = None
self.name = None
# testing a stupid idea
# self.merzView = merz.MerzView(
# (0, 0, 0, 0),
# delegate=self,
# )
# self.merzView.destroy = Button((3, -26, -3, -3), 'Destroy', callback=self.destroyThis)
# self.merzContainer = self.merzView.getMerzContainer()
# self.getGlyphEditor().addGlyphEditorSubview(self.merzView)
# preferences
self.prefKey = 'com.okaytype.anchorHelper'
initialDefaults = {self.prefKey+'anchorOn': []}
registerExtensionDefaults(initialDefaults)
# glyph view layer
self.glyphEditor = self.getGlyphEditor()
self.anchorPreview = self.glyphEditor.extensionContainer(
identifier='com.okay.anchorHelper.background',
location='background',
clear=True
)
# ui
self.minSize = (-200, 0, 200, 24)
self.anchorController = Group(self.minSize)
self.anchorController.view = Button((3, 0, -30, 22), 'Anchor Helper', sizeStyle='small', callback=self.anchorExpand)
self.anchorController.close = Button((-30, 0, -3, 22), 'X', sizeStyle='small', callback=self.destroyThis)
self.glyphEditor.addGlyphEditorSubview(self.anchorController)
ns = self.anchorController.getNSView()
color = NSColor.colorWithCalibratedRed_green_blue_alpha_(.5, 1, .5, .7)
ns.setBackgroundColor_(color)
self.anchorOn = getExtensionDefault(self.prefKey+'anchorOn')
def destroyThis(self, sender):
if self.toggleOnOff == True:
self.toggleOnOff = False
else:
self.toggleOnOff = True
self.drawAnchors()
self.destroy()
def destroy(self):
setExtensionDefault(self.prefKey + '.anchorOn', self.anchorOn)
self.anchorPreview.clearSublayers()
self.getGlyphEditor().removeGlyphEditorSubview(self.anchorController)
# dumb idea
# self.merzContainer.clearSublayers()
# self.getGlyphEditor().removeGlyphEditorSubview(self.merzView)
# def terminate(self):
# print('terminate')
# # how tf do you manage windows and turn off a tool
# self.anchorControllerWindow.close()
#
#
# events
#
def glyphEditorDidSetGlyph(self, info):
self.glyph = info['glyph']
if self.glyph is None:
return
self.componentsToDraw = self.getGlyphComponentData(self.glyph)
self.drawAnchorControls()
self.drawAnchors()
def glyphDidChange(self, info):
# def glyphEditorGlyphAnchorsDidChange(self, info):
self.glyph = info["glyph"]
if self.glyph is None:
return
self.drawAnchors()
#
#
# build data
#
def getAnchorData(self):
# family-level data
# anchor.name: (list of glyph.names)
self.anchorData = {}
f = CurrentFont()
if f is not None:
glyphOrder = f.glyphOrder
for g in glyphOrder:
glyph = f[g]
if len(glyph.anchors) > 0:
# if len(glyph.anchors) > 0 and len(glyph.components) == 0: # components are harder to draw
for anchor in glyph.anchors:
if anchor.name not in self.anchorData:
self.anchorData[anchor.name] = []
self.anchorData[anchor.name].append(glyph.name)
def getGlyphComponentData(self, glyph):
self.componentsToDraw = {}
if len(glyph.anchors) > 0:
for anchor in glyph.anchors:
mateAnchor = self.mateAnchor(anchor.name)
if mateAnchor not in self.componentsToDraw:
self.componentsToDraw[mateAnchor] = []
for component in self.anchorData[mateAnchor]:
self.componentsToDraw[mateAnchor].append(component)
return self.componentsToDraw
#
#
# helper functions
#
def mateAnchor(self, anchorName):
if '_' == anchorName[0]:
mateAnchor = anchorName.replace('_', '')
else:
mateAnchor = '_' + anchorName
return mateAnchor
def getAnchorByName(self, glyph, anchorName):
return next((
anchor for anchor in glyph.anchors if
anchor.name == anchorName), None)
def italicShiftedX(self, x, y, italicAngle):
if italicAngle != 0:
return x + (tan(-italicAngle * pi / 180) * y)
else:
return x
#
#
# build UI
#
def drawAnchorControls(self):
unit = 22
length = len(self.componentsToDraw)
for i in self.componentsToDraw.values():
length += len(i)+1
height = length * unit + 18
if height < self.minSize[3]:
height = self.minSize[3]
# resize window
x, y, w, h = self.anchorController.getPosSize()
self.maxSize = (x, y, w, height)
if self.showUI == False:
self.anchorController.setPosSize(self.minSize, True)
else:
self.anchorController.setPosSize(self.maxSize, True,)
# remove old stuff
for added in self.added:
if hasattr(self.anchorController, added):
delattr(self.anchorController, added)
# build new stuff
count = 0
y = 25
self.added = []
for anchor in self.componentsToDraw:
count += 1
this = TextBox((24, y+5+unit*.5, -8, unit), anchor, sizeStyle='small')
name = 'Label'+anchor
if hasattr(self.anchorController, name):
delattr(self.anchorController, name)
self.added.append(name)
setattr(self.anchorController, name, this)
y += unit*1.5
for component in self.componentsToDraw[anchor]:
count += 1
check = False
if component in self.anchorOn:
check = True
this = okCheckBox(
(8, y, -8, unit),
component,
component=component,
value=check,
sizeStyle='small',
callback=self.checkAnchorData,
)
name = 'CheckBox'+anchor+component
if hasattr(self.anchorController, name):
delattr(self.anchorController, name)
setattr(self.anchorController, name, this)
self.added.append(name)
y += unit
def checkAnchorData(self, sender):
component = sender.component
if sender.get() == 1 and component not in self.anchorOn:
self.anchorOn.append(component)
if sender.get() == 0 and component in self.anchorOn:
self.anchorOn.remove(component)
self.drawAnchors()
def anchorExpand(self, sender):
if self.showUI == True:
self.showUI = False
self.anchorController.setPosSize(self.minSize, True)
else:
self.showUI = True
self.anchorController.setPosSize(self.maxSize, True)
#
#
# draw anchor preview
#
def drawAnchors(self):
if self.toggleOnOff == False:
self.anchorPreview.clearSublayers()
return
if self.glyph is None:
return
font = self.glyph.font
# prep drawing
self.anchorPreview.clearSublayers()
anchorLayer = self.anchorPreview.getSublayer('anchorLayer')
if anchorLayer is None:
anchorLayer = self.anchorPreview.appendPathSublayer(
name='anchorLayer'
)
# draw
with anchorLayer.drawingTools() as bot:
bot.stroke(None)
bot.fill(*fillColor,)
for anchor in self.componentsToDraw:
for component in self.componentsToDraw[anchor]:
c = font[component]
mateAnchor = self.mateAnchor(anchor)
cAnchor = self.getAnchorByName(c, anchor)
bAnchor = self.getAnchorByName(self.glyph, mateAnchor)
shift = (bAnchor.x-cAnchor.x, bAnchor.y-cAnchor.y)
name = 'CheckBox'+anchor+component
componentVisibility = False
if hasattr(self.anchorController, name):
componentVisibility = getattr(self.anchorController, name).get()
if componentVisibility == True:
component = font[component].copy()
self.name = component
self.cs = RGlyph()
pen = DecompPen(font, self.cs.getPen())
component.draw(pen)
self.cs.moveBy(shift)
bot.drawGlyph(self.cs)
self.drawGuide(self.glyph, bot)
def glyphEditorDidMouseDown(self, info):
lle = info["lowLevelEvents"][0]
point = lle["point"]
cc = lle["clickCount"]
if cc == 3:
path = self.cs.naked().getRepresentation("defconAppKit.NSBezierPath")
if path.containsPoint_(point):
print(self.name)
SetCurrentGlyphByName(self.name.name)
def drawGuide(self, glyph, bot):
font = glyph.font
italicAngle = font.info.italicAngle or 0
italicOffset = font.lib.get('com.typemytype.robofont.italicSlantOffset') or 0
bot.fill(None)
bot.strokeWidth((1))
bot.stroke(0, 1, 0, .8)
# boundsWidth = glyph.bounds[2] - glyph.bounds[0]
# rightX = glyph.width - glyph.angledRightMargin or 0 + font.lib.get('com.typemytype.robofont.italicSlantOffset') or 0
# midX = leftX + (glyph.width - glyph.angledLeftMargin or 0 - glyph.angledRightMargin or 0) / 2.0
Ytop = font.info.ascender + 500
Ybot = font.info.descender - 500
# left
xL = glyph.angledLeftMargin + italicOffset
Xtop = self.italicShiftedX(xL, Ytop, italicAngle)
Xbot = self.italicShiftedX(xL, Ybot, italicAngle)
bot.newPath()
bot.moveTo((Xbot, Ybot))
bot.lineTo((Xtop, Ytop))
bot.closePath()
bot.drawPath()
# right
xR = glyph.width - glyph.angledRightMargin + italicOffset
Xtop = self.italicShiftedX(xR, Ytop, italicAngle)
Xbot = self.italicShiftedX(xR, Ybot, italicAngle)
bot.newPath()
bot.moveTo((Xbot, Ybot))
bot.lineTo((Xtop, Ytop))
bot.closePath()
bot.drawPath()
# middle
xC = (xL + xR)/2
Xtop = self.italicShiftedX(xC, Ytop, italicAngle)
Xbot = self.italicShiftedX(xC, Ybot, italicAngle)
bot.newPath()
bot.moveTo((Xbot, Ybot))
bot.lineTo((Xtop, Ytop))
bot.closePath()
bot.drawPath()
class okCheckBox(CheckBox):
def __init__(self, *args, **kwargs):
self.component = kwargs['component']
del kwargs['component']
super(okCheckBox, self).__init__(*args, **kwargs)
registerGlyphEditorSubscriber(anchorHelper)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment