Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A very simple, very rough interpolated spacecenter window for Robofont
# menuTitle : Interpolated Preview
'''
v 0.2
jackson @ okaytype
to do:
- clean up imports
- better performance
- needs a smarter, cheaper redraw trigger
- only redraw/reinterpolate the current glyph when changed, keep others
- match spacecenter window styling
- on open
- when space center style changes (size, lineheight, etc)
- sort font list by weight / width / angle
- uncompatible glyphs feedback (placeholder glyph? fallback glyph? nothing?)
- bounce option (animate/bounce through axis to look cool)
'''
from mojo.UI import MultiLineView, CurrentSpaceCenter
from mojo.canvas import CanvasGroup
from vanilla import Window, PopUpButton, Slider
from defconAppKit.windows.baseWindow import BaseWindowController
from mojo.glyphPreview import GlyphPreview
from mojo.events import addObserver, removeObserver
from mojo.roboFont import CurrentGlyph
from mojo.drawingTools import rect, fill
from mojo.pens import DecomposePointPen
sliderMin = 0
sliderMax = 1
w = 1120
# limit number of glyphs to help performance
maxglyphs = 7
class InterpolatedPreview(BaseWindowController):
def __init__(self):
self.font = CurrentFont()
self.w = Window((w, w/2), "Interpolated Preview", minSize=(200, 200))
self.w.lineView = MultiLineView(
(0, 0, -0, -0),
pointSize=30,
lineHeight=1,
)
self.w.lineView.setFont(self.font)
self.w.g = CanvasGroup((0, -45, -0, 45), delegate=CanvasStuff(self.w))
self.fonts = self.fontlist()
self.w.g.masterA = PopUpButton(
(0, -45, w/2, 22),
self.fonts,
callback=self.updateFont)
self.w.g.masterB = PopUpButton(
(w/2, -45, w/2, 22),
self.fonts,
callback=self.updateFont)
self.w.g.slider = Slider(
(0, -20, w, 22),
minValue=sliderMin,
maxValue=sliderMax,
value=(sliderMax+sliderMin)/2,
tickMarkCount=None,
callback=self.updateFont
)
self.w.g.masterA.set(0)
self.w.g.masterB.set(1)
self.w.g.masterA.show(False)
self.w.g.masterB.show(False)
self.w.g.slider.show(False)
glyphs = ['o', 'n', 'i', 'o', 'n']
glyphs = [self.font[glyphName] for glyphName in glyphs]
self.w.lineView.set(glyphs)
self.w.bind("close", self.windowClose)
self.w.open()
self.updateFont(None)
addObserver(self, "updateFont", "currentGlyphChanged")
addObserver(self, "viewDidChangeGlyph", "viewDidChangeGlyph")
addObserver(self, 'syncSC', 'spaceCenterDrawLineView')
def windowClose(self, sender):
removeObserver(self, "currentGlyphChanged")
removeObserver(self, "viewDidChangeGlyph")
removeObserver(self, "spaceCenterDrawLineView")
def fontlist(self):
fontlist = []
for f in AllFonts():
fontlist.append(f.info.familyName + ' ' + f.info.styleName)
return fontlist
def updateFont(self, sender):
self.masterA = self.fonts[self.w.g.masterA.get()]
self.masterB = self.fonts[self.w.g.masterB.get()]
for f in AllFonts():
if f.info.familyName != None:
fname = f.info.familyName + ' ' + f.info.styleName
if self.masterA == fname:
self.masterA = f
if self.masterB == fname:
self.masterB = f
self.botherinterpolating = False
if self.masterA != self.masterB:
self.botherinterpolating = True
self.updateWindow()
def updateWindow(self):
lv = self.w.lineView
sc = CurrentSpaceCenter()
glyphs = ['o', 'n', 'i', 'o', 'n']
if sc != None:
glyphs = sc.get()
lv.setPointSize(sc.getPointSize())
lv.setLineHeight(sc.getLineHeight())
cleanglyphs = []
# limit number of glyphs to help performance
glyphs = glyphs[:maxglyphs]
if self.botherinterpolating == True:
for glyphName in glyphs:
if glyphName in self.masterA.glyphOrder and glyphName in self.masterB.glyphOrder:
if self.masterA[glyphName].isCompatible(self.masterB[glyphName]):
cleanglyphs.append(self.interpolateG(glyphName))
else:
print('not compatible', glyphName)
else:
for glyphName in glyphs:
if glyphName in self.masterA.glyphOrder:
cleanglyphs.append(self.masterA[glyphName])
lv.set(cleanglyphs)
def interpolateG(self, glyphName):
factor = self.w.g.slider.get()
ig = RGlyph()
gA = self.masterA[glyphName]
gB = self.masterB[glyphName]
# decompose if necessary
if len(gA.components) > 0:
gA = self.masterA[glyphName].copy()
for component in reversed(gA.components):
decomposePen = DecomposePointPen(self.masterA, gA.getPointPen())
component.drawPoints(decomposePen)
gA.removeComponent(component)
if len(gB.components) > 0:
gB = self.masterB[glyphName].copy()
for component in reversed(gB.components):
decomposePen = DecomposePointPen(self.masterB, gB.getPointPen())
component.drawPoints(decomposePen)
gB.removeComponent(component)
ig.interpolate(factor, gA, gB)
ig.name = gA.name
# ig.unicode = self.masterA[glyphName].unicode
return ig
def viewDidChangeGlyph(self, notification):
self.glyph = CurrentGlyph()
if self.glyph is None:
return
self.unsubscribeGlyph()
self.subscribeGlyph(self.glyph)
self.updateFont(None)
def subscribeGlyph(self, glyph=None):
if self.glyph is None:
return
self.glyph = glyph
self.glyph.addObserver(self, "updateFont", "Glyph.Changed")
def unsubscribeGlyph(self):
if self.glyph is None:
return
self.glyph.removeObserver(self, "Glyph.Changed")
sText = ''
def syncSC(self, sender):
sc = CurrentSpaceCenter().get()
if sc == self.sText:
return
self.sText = sc
self.updateFont(None)
class CanvasStuff(object):
def __init__(self, window):
self.w = window
self.bg = False
def opaque(self):
return False
def shouldDrawBackground(self):
return False
def draw(self):
if self.bg == True:
fill(1, 1, 1, .5)
rect(0, 0, w, 100)
def mouseEntered(self, event):
self.w.g.masterA.show(True)
self.w.g.masterB.show(True)
self.w.g.slider.show(True)
self.bg = True
def mouseExited(self, event):
self.w.g.masterA.show(False)
self.w.g.masterB.show(False)
self.w.g.slider.show(False)
self.bg = False
OpenWindow(InterpolatedPreview)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment