Skip to content

Instantly share code, notes, and snippets.

@ryanbugden
Last active May 3, 2025 06:51
Show Gist options
  • Select an option

  • Save ryanbugden/7b885e69768f5c618ae9cec418f102a6 to your computer and use it in GitHub Desktop.

Select an option

Save ryanbugden/7b885e69768f5c618ae9cec418f102a6 to your computer and use it in GitHub Desktop.
(An ezui implementation) Copy glyphs from another source that don't exist in one of your UFOs.
# menuTitle: Copy Missing Glyphs from Other Source
import ezui
from mojo.UI import PostBannerNotification, getDefault
from mojo.subscriber import Subscriber
import drawBot
import functools
@functools.cache
def colorImage(color, width=20, height=12):
if color is None:
return None
drawBot.newDrawing()
drawBot.newPage(width, height)
drawBot.fill(*color)
drawBot.rect(0, 0, width, height)
im = drawBot.saveImage("NSImage")
drawBot.endDrawing()
return im
class CopyMissingGlyphs(Subscriber, ezui.WindowController):
def build(self):
self.default_mark_colors = [value for (value, name) in getDefault("markColors")]
content = """
* TwoColumnForm @form
> :Source Font:
> ( ...) @popUpSrce
> :Destination Font:
> ( ...) @popUpDest
> -----------------
> :Mark Color:
> (( ...)) @markColors
> :Fit to:
> (X) Cap-Height @heights
> ( ) X-Height
# (Scales contours and anchors, not components)
> -----------------
> :
> [ ] Only fill template glyphs @templateFill
* HorizontalStack @bottomRow
> %% @progressSpinner
> (Copy Missing Glyphs) @commit
"""
title_width = 105
item_width = 190
descriptionData = dict(
form=dict(
titleColumnWidth=title_width,
itemColumnWidth=item_width
),
popUpSrce=dict(
items=[],
selected=1,
width=item_width,
),
popUpDest=dict(
items=[],
selected=0, # not necessary
width=item_width,
),
progressSpinner=dict(
showWhenStopped=False,
gravity="trailing"
),
commit=dict(
width=item_width,
gravity="trailing"
),
bottomRow=dict(
distribution="gravity",
)
)
self.w = ezui.EZPanel(
content=content,
descriptionData=descriptionData,
title="Copy Missing Glyphs",
controller=self
)
self.pop_up_srce = self.w.getItem("popUpSrce")
self.pop_up_dest = self.w.getItem("popUpDest")
self.mark_colors = self.w.getItem("markColors")
self.mark_colors.setItemDescriptions(
[dict(image=colorImage(value), text=name) for (value, name) in getDefault("markColors")]
)
self.spinner = self.w.getItem("progressSpinner")
self.getAllFonts()
def started(self):
self.w.open()
def getAllFonts(self):
self.a = AllFonts()
print("-1")
self.list_of_fonts = [] # empty field if no fonts
if self.a:
for f in self.a:
if f.info.familyName and f.info.styleName:
self.list_of_fonts.append(f.info.familyName + " - " + f.info.styleName)
else: # show "Untitled #" or whatever the new font display name is
self.list_of_fonts.append(f._document._windowTitleDisplayName())
# set the menu
self.pop_up_srce.setItems(self.list_of_fonts)
self.pop_up_dest.setItems(self.list_of_fonts)
self.pop_up_dest.set(0)
self.pop_up_srce.set(0)
if len(self.list_of_fonts) > 1:
self.pop_up_srce.set(1)
def fontDocumentDidOpen(self, info):
self.getAllFonts()
def fontDocumentDidOpenNew(self, info):
self.getAllFonts()
def commitCallback(self, sender):
self.spinner.start()
srce_f = self.a[self.w.getItem("popUpSrce").get()]
dest_f = self.a[self.w.getItem("popUpDest").get()]
scale_by = dest_f.info.capHeight / srce_f.info.capHeight
if self.w.getItem("heights").get() == 1:
scale_by = dest_f.info.xHeight / srce_f.info.xHeight
mark_color_index = self.w.getItem("markColors").get()
r,g,b,a = getDefault("markColors")[mark_color_index][0]
for srce_g_name in srce_f.glyphOrder:
for layer in srce_f.layers:
if srce_g_name in layer.keys():
srce_g = layer[srce_g_name]
# Match the default layers
if layer == srce_f.defaultLayer:
dest_layer = dest_f.defaultLayer
else:
if not layer in dest_f.layerOrder:
dest_f.newLayer(layer.name)
dest_layer = dest_f.getLayer(layer.name)
if srce_g.name not in dest_layer.keys():
# Skip copying the glyph if it's not a template glyph in the destination font, and you've chosen that option in the window
if self.w.getItem("templateFill").get() == True:
if not srce_g.name in dest_f.templateGlyphOrder:
continue
dest_layer.newGlyph(srce_g.name)
copied_glyph = srce_g.copy()
# Only scale contours + anchors
for c in copied_glyph.contours:
c.scaleBy(scale_by, origin=(0,0))
for anc in copied_glyph.anchors:
anc.scaleBy(scale_by, origin=(0,0))
copied_glyph.width = srce_g.width * scale_by
dest_layer.insertGlyph(copied_glyph, name=srce_g.name)
dest_layer[srce_g.name].markColor = (r,g,b,a)
dest_f.changed()
self.spinner.stop()
# Notify what just happened
message = f"Copying glyphs from {srce_f.info.familyName} {srce_f.info.styleName} into empty glyphs slots of {dest_f.info.familyName} {dest_f.info.styleName}."
print(message)
PostBannerNotification("Copied Missing Glyphs", message)
CopyMissingGlyphs()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment