Skip to content

Instantly share code, notes, and snippets.

@frankrolf
Created June 16, 2025 11:06
Show Gist options
  • Save frankrolf/07d09be24d22413e55ba2a9b4d12a2c8 to your computer and use it in GitHub Desktop.
Save frankrolf/07d09be24d22413e55ba2a9b4d12a2c8 to your computer and use it in GitHub Desktop.
Contextual menu items for RoboFont’s font overview window
'''
Contextual menu items for RoboFont’s font overview window
'''
from AppKit import NSPredicate
from mojo import smartSet
from mojo.roboFont import CurrentFont
from mojo.subscriber import (
getRegisteredSubscriberEvents,
registerRoboFontSubscriber,
registerSubscriberEvent,
Subscriber)
from mojo.UI import CurrentFontWindow
from contextual_tools import filter_dependent, make_copy
class FontContextualMenu(Subscriber):
def fontOverviewAdditionalContextualMenuItems(self, notification):
additionContextualMenuItems = notification[
'lowLevelEvents'][0]['additionContextualMenuItems']
font = CurrentFont()
selection = font.templateSelectedGlyphNames
if len(selection) > 0:
myMenuItems = []
if len(selection) == 1:
myMenuItems = [
('Filter same …', [
('mark', self.add_attribute(
self.filter_glyph_attribute, font, 'markColor')),
('width', self.add_attribute(
self.filter_glyph_attribute, font, 'width')),
('anchor count', self.add_attribute(
self.filter_glyph_attribute, font, 'anchors')),
('anchor names', self.add_attribute(
self.filter_glyph_attribute, font, 'anchor_names')),
('component count', self.add_attribute(
self.filter_glyph_attribute, font, 'components')),
('contour count', self.add_attribute(
self.filter_glyph_attribute, font, 'contours')),
])]
myMenuItems.extend([
('Filter …', [
('dependent glyphs', self.add_font(
self.dependent_glyphs, font)),
('L kerning group', self.add_attribute(
self.kerning_glyph_group, font, 'L')),
('R kerning group', self.add_attribute(
self.kerning_glyph_group, font, 'R')),
]),
('Select …', [
('non-suffixed base glyphs', self.add_font(
self.select_baseglyphs, font)),
('same mark color(s)', self.add_font(
self.select_same_mark_colors, font)),
('pure composite glyphs', self.add_font(
self.select_pure_composites, font)),
('mixed composite glyphs', self.add_font(
self.select_mixed_composites, font)),
('glyphs with Unicode', self.add_attribute(
self.select_Unicode, font, True)),
('glyphs without Unicode', self.add_attribute(
self.select_Unicode, font, False)),
]),
('Add alternate(s)', self.add_font(
self.add_alternates, font)),
('Initialize', self.add_font(
self.initialize, font)),
('Invert selection', self.add_font(
self.invert_selection, font)),
('Isolate selection', self.add_font(
self.isolate, font)),
])
else:
myMenuItems = [
('Select …', [
('pure composite glyphs', self.add_font(
self.select_pure_composites, font)),
('mixed composite glyphs', self.add_font(
self.select_mixed_composites, font)),
]),
]
if CurrentFontWindow().getGlyphCollection().getQuery() is not None:
myMenuItems.append(
('Un-Isolate', self.add_font(self.unisolate, font))
)
additionContextualMenuItems.extend(myMenuItems)
def make_query(self, glyph_list):
query_text = 'Name in {"%s"}' % '", "'.join(glyph_list)
query = NSPredicate.predicateWithFormat_(query_text)
CurrentFontWindow().getGlyphCollection().setQuery(query)
def add_attribute(self, func, font, attribute):
def wrapper(sender):
func(sender, font, attribute)
return wrapper
def add_font(self, func, font):
def wrapper(sender):
func(sender, font)
return wrapper
def filter_glyph_attribute(self, sender, font, attribute):
def anchor_names(glyph):
return set([a.name for a in glyph.anchors])
if len(font.selectedGlyphNames):
repr_glyph_name = font.selectedGlyphNames[0]
repr_glyph = font[repr_glyph_name]
if attribute in ['anchors', 'contours', 'components']:
repr_attr = getattr(repr_glyph, attribute)
filter_list = [g.name for g in font if (
len(getattr(g, attribute)) == len(repr_attr))]
elif attribute == 'anchor_names':
filter_list = [
g.name for g in font if
anchor_names(g) == anchor_names(repr_glyph)]
else:
filter_list = [g.name for g in font if (
getattr(g, attribute) == getattr(repr_glyph, attribute))]
self.make_query(filter_list)
def invert_selection(self, sender, font):
combined_selection = (
list(font.selectedGlyphNames) +
list(font.templateSelectedGlyphNames))
all_glyphs = set(
font.glyphOrder) | set([g.name for g in font.templateGlyphs])
invertedSelection = all_glyphs - set(combined_selection)
font.selectedGlyphNames = list(invertedSelection)
def select_pure_composites(self, sender, font):
font.selectedGlyphNames = [
g.name for g in font if g.components and not g.contours]
def select_mixed_composites(self, sender, font):
font.selectedGlyphNames = [
g.name for g in font if g.components and g.contours]
def isolate(self, sender, font):
self.make_query(font.templateSelectedGlyphNames)
def unisolate(self, sender, font):
CurrentFontWindow().getGlyphCollection().setQuery(None)
def add_alternates(self, sender, font):
# XXX this set selection stuff is WIP
try:
selected_set = smartSet.selectedSmartSets()[0] # XXX
set_index = smartSet.getSmartSets().index(selected_set)
ui_index = set_index + 2
# AllGlyphs = 0, DEFAULT SETS = 1
except Exception:
ui_index = 0
for gname in font.selectedGlyphNames:
make_copy(font[gname])
CurrentFontWindow().setSmartListSelection(ui_index)
def initialize(self, sender, font):
'''
initialize template glyph
'''
empty = set(font.templateSelectedGlyphNames) - set(font.selectedGlyphNames)
for gname in empty:
font.newGlyph(gname)
def select_baseglyphs(self, sender, font):
font.selectedGlyphNames = [
gname.split('.')[0] for gname in font.selectedGlyphNames if
font.selectedGlyphNames]
def select_same_mark_colors(self, sender, font):
sel_colors = [font[gn].markColor for gn in font.selectedGlyphNames]
font.selectedGlyphNames = [
gname for gname in font.keys() if
font[gname].markColor in sel_colors]
def select_Unicode(self, sender, font, has_Unicode):
if has_Unicode:
filter_list = [g.name for g in font if g.unicode]
else:
filter_list = [g.name for g in font if not g.unicode]
font.selectedGlyphNames = filter_list
def dependent_glyphs(self, sender, font):
all_dependent_glyphs = []
for gname in font.selectedGlyphNames:
all_dependent_glyphs.extend(filter_dependent(font[gname]))
self.make_query(all_dependent_glyphs)
def kerning_glyph_group(self, sender, font, side):
gname = font.selectedGlyphNames[0]
print('using first selected glyph:', gname)
containing_groups = font.groups.findGlyph(gname)
if containing_groups:
if side == 'L':
kern_group = next((
gr_name for gr_name in containing_groups if
gr_name.startswith('public.kern1.')), None)
if kern_group:
self.make_query(font.groups.get(kern_group))
else:
self.make_query([gname])
elif side == 'R':
kern_group = next((
gr_name for gr_name in containing_groups if
gr_name.startswith('public.kern2.')), None)
if kern_group:
self.make_query(font.groups.get(kern_group))
else:
self.make_query([gname])
eventName = 'fontOverviewAdditionalContextualMenuItems'
allEvents = getRegisteredSubscriberEvents()
if eventName not in allEvents:
registerSubscriberEvent(
subscriberEventName=eventName,
methodName='fontOverviewAdditionalContextualMenuItems',
lowLevelEventNames='fontOverviewAdditionContextualMenuItems',
dispatcher='roboFont',
delay=0,
documentation=(
'This will be called when a mojo.events '
'`fontOverviewAdditionContextualMenuItems` event is posted. '
'Default delay: 0 seconds.')
)
registerRoboFontSubscriber(FontContextualMenu)
'''
tools used by contextual_filters.py, needs to live in the same folder
'''
def filter_dependent(glyph):
component_users = [g for g in glyph.font if g.components]
components_used = {}
for user in component_users:
for component in user.components:
components_used.setdefault(
component.baseGlyph, []).append(user.name)
# the glyph itself
dependent_glyphs = [glyph.name]
# the glyph is used as a component in others
dependent_glyphs.extend(components_used.get(glyph.name, []))
for dep_gname in dependent_glyphs:
dependent_glyphs.extend(components_used.get(dep_gname, []))
return dependent_glyphs
def make_new_name(gname, alt_index=1):
return gname.split('.')[0] + '.' + '{:0>2d}'.format(alt_index)
def make_copy(glyph):
# XXX this could potentially be simplified with g.copy()
font = glyph.font
alt_index = 1
new_gname = make_new_name(glyph.name, alt_index)
while new_gname in font.keys():
alt_index += 1
new_gname = make_new_name(glyph.name, alt_index)
new_glyph = font.newGlyph(new_gname, clear=True)
for layer in [
lname for lname in font.layerOrder if lname != 'background'
]:
layer_glyph = glyph.getLayer(layer)
new_layer_glyph = new_glyph.getLayer(layer)
new_layer_glyph.appendGlyph(layer_glyph)
new_layer_glyph.width = layer_glyph.width
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment