Skip to content

Instantly share code, notes, and snippets.

@frankrolf
Last active November 26, 2025 21:49
Show Gist options
  • Select an option

  • Save frankrolf/a4fe38d51a05fe8a56743d1472779136 to your computer and use it in GitHub Desktop.

Select an option

Save frankrolf/a4fe38d51a05fe8a56743d1472779136 to your computer and use it in GitHub Desktop.
Filter font overview by Unicode category in Robofont 3
from mojo.UI import CurrentFontWindow
from mojo.subscriber import (
Subscriber, listRegisteredSubscribers,
registerRoboFontSubscriber, unregisterRoboFontSubscriber)
import unicodedata
import AppKit
from vanilla import CheckBox, FloatingWindow, List
category_to_codepoints = {}
for code_point in range(0, 0xFFFF + 1):
category = unicodedata.category(chr(code_point))
category_to_codepoints.setdefault(category, []).append(code_point)
all_categories = {
'Cc': 'Other, Control',
'Cf': 'Other, Format',
'Cn': 'Other, Not Assigned',
'Co': 'Other, Private Use',
'Cs': 'Other, Surrogate',
'LC': 'Letter, Cased',
'Ll': 'Letter, Lowercase',
'Lm': 'Letter, Modifier',
'Lo': 'Letter, Other',
'Lt': 'Letter, Titlecase',
'Lu': 'Letter, Uppercase',
'Mc': 'Mark, Spacing Combining',
'Me': 'Mark, Enclosing',
'Mn': 'Mark, Nonspacing',
'Nd': 'Number, Decimal Digit',
'Nl': 'Number, Letter',
'No': 'Number, Other',
'Pc': 'Punctuation, Connector',
'Pd': 'Punctuation, Dash',
'Pe': 'Punctuation, Close',
'Pf': 'Punctuation, Final quote',
'Pi': 'Punctuation, Initial quote',
'Po': 'Punctuation, Other',
'Ps': 'Punctuation, Open',
'Sc': 'Symbol, Currency',
'Sk': 'Symbol, Modifier',
'Sm': 'Symbol, Math',
'So': 'Symbol, Other',
'Zl': 'Separator, Line',
'Zp': 'Separator, Paragraph',
'Zs': 'Separator, Space',
}
class CatList(Subscriber):
def build(self):
self.select_suffix = False
self.selected_cats = []
# initialize window and list with dummy values,
# which are replaced on update()
self.w = FloatingWindow((350, 100), title='uni😸😸😸')
self.w.myList = List(
(0, 0, -0, 0),
[],
columnDescriptions=[
{'title': 'cat name'},
{'title': 'description'}
],
selectionCallback=self.sel_callback,
allowsMultipleSelection=True
)
self.update(CurrentFont())
self.w.bind('close', self.close_callback)
self.w.myCheckBox = CheckBox(
(10, -23, -10, 20),
"Show Suffixed",
callback=self.check_callback,
value=False)
# xxx trying to get the window to toggle quicker, however
# this does not do anything
# self.w.getNSWindow().setAnimationBehavior_(1)
self.w.open()
# select the first item in the list and filter the glyph set
# according to that selection
self.w.myList.setSelection([0])
self.sel_callback(self.w.myList)
def update(self, font):
self.f = font
self.all_glyphs = [g for g in font] + list(font.templateGlyphs)
self.cat_list = [] # use internally
self.display_cat_list = [] # use in window
cat_names = self.collect_cat_names()
for cat_name in cat_names:
description = all_categories.get(cat_name, '')
self.cat_list.append(cat_name)
self.display_cat_list.append({
'cat name': cat_name,
'description': description,
})
self.list_height = 24 + 18 * (len(self.display_cat_list) + 1)
self.window_height = self.list_height + 24
self.w.resize(350, self.window_height)
self.w.myList.set(self.display_cat_list)
self.w.myList.resize(-0, self.list_height)
def collect_cat_names(self):
'''
Find Unicode categories found in the font at hand
'''
cp_list = [g.unicodes for g in self.all_glyphs if g.unicodes]
all_codepoints = sum(cp_list, ())
cat_names = []
for cat_name, cpoint_list in category_to_codepoints.items():
if set(all_codepoints) & set(cpoint_list):
cat_names.append(cat_name)
return sorted(cat_names)
def sel_callback(self, sender):
cat_selections = sender.getSelection()
cat_names = []
for sel_index in cat_selections:
cat_name = self.cat_list[sel_index]
cat_names.append(cat_name)
self.selected_cats = cat_names
self.select_cats(cat_names)
def close_callback(self, sender):
print('unregistering')
if CurrentFontWindow():
CurrentFontWindow().getGlyphCollection().setQuery(None)
# self.destroy()
for s in listRegisteredSubscribers(subscriberClassName='CatList'):
unregisterRoboFontSubscriber(s)
def check_callback(self, sender):
self.select_suffix = sender.get()
if self.selected_cats:
self.select_cats(self.selected_cats)
else:
self.select_cats(['Ll'])
def select_cats(self, cat_names):
g_name_list = []
for cat_name in cat_names:
g_name_list.extend(
g.name for g in self.all_glyphs if
set(g.unicodes) & set(category_to_codepoints[cat_name]))
if self.select_suffix:
sel_list = [
g.name for g in self.all_glyphs if
g.name.split('.')[0] in g_name_list]
else:
sel_list = g_name_list
query = 'Name in {"%s"}' % '", "'.join(sel_list)
query = AppKit.NSPredicate.predicateWithFormat_(query)
CurrentFontWindow().getGlyphCollection().setQuery(query)
def fontDocumentDidBecomeCurrent(self, sender):
self.update(sender.get('font'))
def roboFontDidSwitchCurrentGlyph(self, sender):
glyph = sender.get('glyph')
if glyph:
self.update(glyph.font)
def destroy(self):
# unregisterRoboFontSubscriber(self)
print('destruction')
if __name__ == '__main__':
if CurrentFont() is not None:
registerRoboFontSubscriber(CatList)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment