Observer examples

Reorder contours

from AppKit import NSDragOperationMove
from vanilla import *

from fontTools.pens.cocoaPen import CocoaPen

from defconAppKit.windows.baseWindow import BaseWindowController

from lib.tools.misc import randomColor
from lib.cells.colorCell import ColorCell

from mojo.drawingTools import *
from mojo.events import addObserver, removeObserver
from mojo.UI import UpdateCurrentGlyphView

contourReoderPboardType = "contourReoderPboardType"

class ContourReorder(BaseWindowController):
    
    def __init__(self, glyph):
        self.w = FloatingWindow((230, 300), "Contour Reorder", minSize=(200, 250))
        
        columnDescriptions = [
                    dict(title="contour", width=170), 
                    dict(title="", key="color", cell=ColorCell.alloc().init(), width=60)
                    ]
                
        self.w.contours = List((0, 0, -0, -0), [], columnDescriptions=columnDescriptions,
                            allowsEmptySelection=False, 
                            allowsMultipleSelection=False,
                            enableDelete=True,
                            dragSettings=dict(type=contourReoderPboardType, 
                                              callback=self.dragCallback),
                            selfDropSettings=dict(type=contourReoderPboardType, 
                                                  operation=NSDragOperationMove, 
                                                  callback=self.dropListSelfCallback)
                            )
        
        addObserver(self, "drawBackground", "drawBackground")
        addObserver(self, "currentGlyphChanged", "currentGlyphChanged")
        
        self.setUpBaseWindowBehavior()
        self.setGlyph(glyph)
        UpdateCurrentGlyphView()
        self.w.open()
    
    def setGlyph(self, glyph):
        self._glyph = glyph
        items = [dict(contour=contour, color=randomColor(asNSColor=True, dept=len(glyph), order=i)) 
                        for i, contour in enumerate(glyph)]
        self.w.contours.set(items)
    
    def reorderGlyph(self):
        contours = [item["contour"] for item in self.w.contours.get()]
        
        self._glyph.prepareUndo("Reoder Contours")
        self._glyph.clearContours()
        for contour in contours:
            self._glyph.appendContour(contour)
        
        self._glyph.performUndo()
                    
    def dropListSelfCallback(self, sender, dropInfo):
        isProposal = dropInfo["isProposal"]
        
        if not isProposal:
            indexes = [int(i) for i in sorted(dropInfo["data"])]
            indexes.sort()
            source = dropInfo["source"]
            rowIndex = dropInfo["rowIndex"]

            items = sender.get()

            toMove = [items[index] for index in indexes]

            for index in reversed(indexes):
                del items[index]

            rowIndex -= len([index for index in indexes if index < rowIndex])
            for font in toMove:
                items.insert(rowIndex, font)
                rowIndex += 1

            sender.set(items)
            self.reorderGlyph()
        return True    
        
    def dragCallback(self, sender, indexes):
        return indexes
    
    def drawBackground(self, info):
        scale = info["scale"]
        for item in self.w.contours:
            contour = item["contour"]
            pen = CocoaPen(None)
            contour.draw(pen)
            item["color"].set()
            pen.path.setLineWidth_(10*scale)
            pen.path.stroke()
    
    def currentGlyphChanged(self, info):
        self.setGlyph(CurrentGlyph())
    
    def windowCloseCallback(self, sender):
        removeObserver(self, "drawBackground")
        removeObserver(self, "currentGlyphChanged")
        UpdateCurrentGlyphView()
        super(ContourReorder, self).windowCloseCallback(sender)
        
ContourReorder(CurrentGlyph())
        
        
        

Draw reference glyph

"""
An example script of adding an observers and do *something* 

It draws a simple unicode reference of an existing installed font.
"""

from mojo.events import addObserver
from mojo.drawingTools import *

class DrawReferenceGlyph(object):
    
    def __init__(self):
        addObserver(self, "drawReferenceGlyph", "draw")

    def drawReferenceGlyph(self, info):
        
        glyph = info["glyph"]
        
        r = 0
        g = 0
        b = 0
        a = .5
        
        if glyph is not None and glyph.unicode is not None and glyph.unicode < 0xFFFF:
            t = unichr(glyph.unicode)
            
            font("Georgia", 20)
            stroke(None)
            fill(r, g, b, a)
            text(t, (glyph.width + 10, 10))
            
            
DrawReferenceGlyph()

Show distance between selected points

from mojo.events import addObserver
from mojo.drawingTools import *
from math import hypot

from AppKit import *

class PointDistanceController(object):
    
    def __init__(self):
        # subscribe to the draw event
        addObserver(self, "draw", "draw")
        addObserver(self, "draw", "drawBackground")
        # create a background color
        self.backgroundColor = NSColor.redColor()
        # create a background stroke color
        self.backgroundStrokeColor = NSColor.whiteColor()
        # create a stroke color
        self.strokeColor = NSColor.redColor()
        # setting text attributes
        self.attributes = attributes = {
            NSFontAttributeName : NSFont.boldSystemFontOfSize_(9),
            NSForegroundColorAttributeName : NSColor.whiteColor(),
            }
    
    def draw(self, notification):
        # get the glyph from the notification
        glyph = notification["glyph"]
        # get the selection
        selection = glyph.selection
        # check if the selection is more then 1 and less then 6
        if 2 <= len(selection) <= 5:
            # get the view
            view = notification["view"]
            # get the scale of the view
            scale = notification["scale"] 
            distances = []
            done = []
            # create a path to draw in
            path = NSBezierPath.bezierPath()
            # loop over all the points in the selection
            for p in selection:
                # loope in a the loop again over all the points in the selection
                for p2 in selection:
                    # check if the point is not the same
                    if p == p2:
                        continue
                    # check if we already handled the point
                    if (p, p2) in done:
                        continue
                    if (p2, p) in done:
                        continue
                    # add a line to the path
                    path.moveToPoint_((p.x, p.y))
                    path.lineToPoint_((p2.x, p2.y))
                    # calculate the center point
                    cx = p.x + (p2.x - p.x) * .5
                    cy = p.y + (p2.y - p.y) * .5
                    # calculate the distance
                    dist = hypot(p2.x - p.x, p2.y - p.y)
                    # store the distance and the center point
                    distances.append((cx, cy, dist))
                    # store the points 
                    done.append((p, p2))
            # set the stroke color
            self.strokeColor.set()
            # set the line width of the path
            path.setLineWidth_(scale)
            # stroke the path
            path.stroke()
            
            for x, y, dist in distances:
                # draw the distance as text at the center point
                # this will change to a public callback in the next update (RF 1.5.2)
                if dist.is_integer():
                    t = "%i"
                else:
                    t = "%.2f"
                # in the next update 1.5.2 this will be a public method
                view._drawTextAtPoint(t % dist, self.attributes, (x, y), drawBackground=True, backgroundColor=self.backgroundColor, backgroundStrokeColor=self.backgroundStrokeColor)

PointDistanceController()

Event observer

"""
Observes all objects and displayes with kind of attributes are available in the callback info dict.
"""

from vanilla import *
from AppKit import *
from defconAppKit.windows.baseWindow import BaseWindowController
from mojo.events import addObserver, removeObserver

class NotificationItem(object):
    
    def __init__(self, notification):
        self.notification = notification
    
    def __repr__(self):
        return self.notification["notificationName"]

keyAttributes = {NSFontAttributeName : NSFont.fontWithName_size_("Menlo-Bold", 15)}

valueAttributes = {NSFontAttributeName : NSFont.fontWithName_size_("Menlo-Bold", 10), NSForegroundColorAttributeName : NSColor.darkGrayColor()}

class Observer(BaseWindowController):
    
    def __init__(self):
        
        self.w = Window((400, 400), "mojo.event observer", minSize=(200, 200))
        
        self.w.list = List((10, 10, 200, -40), [], selectionCallback=self.listSelection)
        self.w.info = TextEditor((220, 10, -10, -40), readOnly=True)
        
        self.w.ignoreText = TextBox((10, -30, 100, 22), "Ignore:")
        self.w.ignore = EditText((70, -30, -100, 22), "mouseMoved")
        
        self.w.clear = Button((-70, -30, 60, 22), "Clear", self.clearListCallback)
        
        addObserver(self, "notification", None)
        self.setUpBaseWindowBehavior()
        self.w.open()
    
    def windowCloseCallback(self, sender):
        removeObserver(self, None)
        super(Observer, self).windowCloseCallback(sender)
    
    def listSelection(self, sender):
        sel = sender.getSelection()
        for i in sel:
            item = sender[i]
            notification = item.notification
            
            keys = notification.keys()
            keys.sort()
            
            txt = NSMutableAttributedString.alloc().init()
            
            for key in keys:
                attributedString = NSMutableAttributedString.alloc().initWithString_attributes_(key, keyAttributes)
                txt.appendAttributedString_(attributedString)                    
                
                value = "\n%s\n\n" %notification[key]
                
                attributedString = NSMutableAttributedString.alloc().initWithString_attributes_(value, valueAttributes)
                txt.appendAttributedString_(attributedString)
            
            self.w.info.getNSTextView().textStorage().setAttributedString_(txt)
    
    def clearListCallback(self, sender):
        self.w.list.set([])
        self.w.info.set("")
        
    def notification(self, notification):
        if notification["notificationName"] in self.w.ignore.get().split(" "):
            return
        self.w.list.append(NotificationItem(notification))
        self.w.list.getNSTableView().scrollRowToVisible_(len(self.w.list)- 1)
    
Observer()

Simple draw observer

from mojo.drawingTools import *
from mojo.events import addObserver

class DrawTest:
    def __init__(self):
        ## add an observer for the draw event
        addObserver(self, "drawSomething", "draw")

    def drawSomething(self, info):
        ## draw something the glyph view
        rect(100, 100, 100, 100)

DrawTest()

Simple window observer

import vanilla
from defconAppKit.windows.baseWindow import BaseWindowController

from mojo.events import addObserver, removeObserver
from mojo.drawingTools import *

class SimpleWindowObserver(BaseWindowController):
    
    def __init__(self):
        # create a window        
        self.w = vanilla.Window((300, 45), "Simple Observer")
        # add a button with a title and a callback
        self.w.startStopButton = vanilla.Button((10, 10, -10, 22), "Start", callback=self.startStopButtonCallback)
        # setup basic windwo behavoir (this is an method from the BaseWindowController)
        self.setUpBaseWindowBehavior()
        # open the window
        self.w.open()
    
    
    def startStopButtonCallback(self, sender):
        # button callback, check the title
        if sender.getTitle() == "Start":
            # set "Stop" as title for the button
            sender.setTitle("Stop")
            # add an observer
            addObserver(self, "draw", "draw")
        else:
            # set "Start" as title for the button
            sender.setTitle("Start")
            # remove the observser
            removeObserver(self, "draw")            
    
    def draw(self, notification):
        # get the glyph
        glyph = notification["glyph"]
        # save the state of the canvas
        save()
        # translate the canvase
        translate(glyph.width * 2, 0)
        # scale (flip) the canvas
        scale(-1, 1)
        # set a fill color
        fill(0)
        # draw the glyph
        drawGlyph(glyph)
        # restore the canvas
        restore()
        
    def windowCloseCallback(self, sender):
        # this receives a notification whenever the window is closed
        # remove the observer
        removeObserver(self, "draw")
        # and send the notification to the super
        super(SimpleWindowObserver, self).windowCloseCallback(sender)
    
SimpleWindowObserver()
    

Stencil preview

Works with main shape in the foreground layer and stencil removals in the background layer.

from vanilla import *

from defconAppKit.windows.baseWindow import BaseWindowController

from mojo.glyphPreview import GlyphPreview
from mojo.events import addObserver, removeObserver

from mojo.roboFont import CurrentGlyph

class StencilPreview(BaseWindowController):
    
    def __init__(self):
        self.glyph = CurrentGlyph()
        # create a window        
        self.w = FloatingWindow((400, 400), "Stencil Preview", minSize=(200, 200))
        # add the preview to the window
        self.w.preview = GlyphPreview((0, 0, -0, -0))
        # add an observer to get callbacks when a glyph changes in the glyph view
        addObserver(self, "viewDidChangeGlyph", "viewDidChangeGlyph")
        # open the window
        self.updateGlyph()
        self.w.open()
    
    def viewDidChangeGlyph(self, notification):
        # notification when the glyph changes in the glyph view
        glyph = CurrentGlyph()
        self.unsubscribeGlyph()
        self.subscribeGlyph(glyph)
        self.updateGlyph()
        
    def glyphChanged(self, notification):
        self.updateGlyph()        
        
    def updateGlyph(self):
        glyph = self.glyph
        # if the glyph is None just set None to the preview
        if glyph is None:
            self.w.preview.setGlyph(None)
            return
        # get the foreground layer
        foreground = glyph.getLayer("foreground")
        # get the background layer
        background = glyph.getLayer("background")
        
        # get the substract the background from the foreground layer
        result = foreground % background
        # set the result in the preview view
        self.w.preview.setGlyph(result)
    
    def subscribeGlyph(self, glyph):
        # subscribe the glyph
        self.glyph = glyph
        # add an observer to glyph data changes 
        self.glyph.addObserver(self, "glyphChanged", "Glyph.Changed")
        
    def unsubscribeGlyph(self):
        # unsubscribe the glyph
        if self.glyph is None:
            return
        # remove this observer for the glyph
        self.glyph.removeObserver(self, "Glyph.Changed")
    
    def windowCloseCallback(self, sender):
        # notification when the window get closed
        # remove the view did change glyph in the glyph view observer
        removeObserver(self, "viewDidChangeGlyph")
        # unsubscribe the glyph
        self.unsubscribeGlyph()
        super(StencilPreview, self).windowCloseCallback(sender)

# open the window
OpenWindow(StencilPreview)
        
        

View glyphs in current glyph’s mask

from vanilla import *
from defconAppKit.windows.baseWindow import BaseWindowController

from mojo.events import addObserver, removeObserver
from mojo.UI import UpdateCurrentGlyphView
from mojo.drawingTools import *

class GlobalMaks(BaseWindowController):
    
    def __init__(self, font):
        # create a window
        self.w = Window((170, 300), "The Mask", minSize=(170, 100))
        # add a UI list
        self.w.list = List((0, 0, -0, -0), [], selectionCallback=self.listSelectionCallback)
        
        # set the font 
        self.setFont(font)
        
        # add observers needed
        addObserver(self, "drawBackground", "drawBackground")
        addObserver(self, "drawBackground", "drawInactive")
        addObserver(self, "fontBecameCurrent", "fontBecameCurrent")
        addObserver(self, "fontResignCurrent", "fontResignCurrent")
        
        self.setUpBaseWindowBehavior()
        self.w.open()
    
    def setFont(self, font):
        # set all the possible glyph of the font in the UI list
        self._font = font
        self._glyphs = []
        glyphs = []
        if font is not None:
            glyphs = font.glyphOrder
        
        self.w.list.set(glyphs)
            
    # ui callbacks
    
    def listSelectionCallback(self, sender):
        # called when an item is selected in the UI list
        sel = sender.getSelection()
        self._glyphs = []
        for i in sel:
            glyphName = sender[i]
            self._glyphs.append(self._font[glyphName])
        self.updateGlyphView()
        
    def updateGlyphView(self):
        # update the current glyph view
        UpdateCurrentGlyphView()
            
    
    # notifications
    
    def fontBecameCurrent(self, notification):
        # called when a font became the current font
        font = notification["font"]
        # set the font
        self.setFont(font)
        # update the glyph view
        self.updateGlyphView()
        
    def fontResignCurrent(self, notification):
        # called when a font resigns being the current font
        self.setFont(None)
        self.updateGlyphView()
    
    def drawBackground(self, notification):
        # draw the glyph in the background of the glyph view
        if not self._glyphs:
            return
        
        stroke(1, 0, 0)
        strokeWidth(notification["scale"])
        fill(None)
        for glyph in self._glyphs:
            drawGlyph(glyph)
    
    def windowCloseCallback(self, sender):
        # when the windows closes remove all the added observers
        removeObserver(self, "drawBackground")
        removeObserver(self,"drawInactive")
        removeObserver(self, "fontBecameCurrent")
        removeObserver(self, "fontResignCurrent")

        super(GlobalMaks, self).windowCloseCallback(sender)


# go
GlobalMaks(CurrentFont())

Glyph box preview

Display the glyph’s bounding box when previewing.

from mojo.events import addObserver
from mojo.drawingTools import *

from lib.tools.defaults import getDefault

class DrawVerticalMetricBox(object):
        
    def __init__(self):
        
        addObserver(self, "drawMetricsBox", "drawBackground")
        
        self.color = getDefault("glyphViewMarginColor")
        self.height = getDefault("glyphViewDefaultHeight") / 2
        self.useItalicAngle = getDefault("glyphViewShouldUseItalicAngleForDisplay")
        
        
    def drawMetricsBox(self, info):
        glyph = info["glyph"]
        if glyph is None:
            return
        font = glyph.getParent()
        if font is None:
            return
        
        descender = font.info.descender
        upm = font.info.unitsPerEm
        
        save()
        if self.useItalicAngle:
            angle = font.info.italicAngle
            if angle is not None:
                skew(-angle, 0)
        
        fill(*self.color)
        rect(0, descender, glyph.width, -self.height)
        rect(0, descender + upm, glyph.width, self.height)
        
        restore()
        

DrawVerticalMetricBox()

Preview expanded strokes

from AppKit import *
from vanilla import *

from fontTools.pens.cocoaPen import CocoaPen

from defconAppKit.windows.baseWindow import BaseWindowController

from lib.UI.stepper import SliderEditIntStepper

from mojo.events import addObserver, removeObserver
from mojo.UI import UpdateCurrentGlyphView, CurrentSpaceCenter

class StrokeObserer(BaseWindowController):
    
    defaultColor = NSColor.colorWithCalibratedHue_saturation_brightness_alpha_(.5, .5, .5, .5)
    
    _lineCapStylesMap = dict(
        butt=NSButtLineCapStyle,
        square=NSSquareLineCapStyle,
        round=NSRoundLineCapStyle,
        )
    
    _lineJoinStylesMap = dict(   
        miter=NSMiterLineJoinStyle,
        round=NSRoundLineJoinStyle,
        bevel=NSBevelLineJoinStyle
        )
        
    def __init__(self):
        self.w = FloatingWindow((250, 130), "Stroke It")
        y = 10
        self.w.colorText = TextBox((10, y, 88, 22), "Stroke Color:", alignment="right")
        self.w.color = ColorWell((100, y-5, -10, 30), color=self.defaultColor, callback=self.changedCallback)
        
        y += 30
        self.w.widthText = TextBox((10, y, 88, 22), "Stoke Width:", alignment="right")
        self.w.width = SliderEditIntStepper((100, y, -10, 22), 10, callback=self.changedCallback, minValue=0, maxValue=100)
        
        y += 30
        self.w.lineCapText = TextBox((10, y, 88, 22), "Line Cap:", alignment="right")
        self.w.lineCap = PopUpButton((100, y, -10, 22), self._lineCapStylesMap.keys(), callback=self.changedCallback)
        
        y += 30
        self.w.lineJoinText = TextBox((10, y, 88, 22), "Line Join:", alignment="right")
        self.w.lineJoin = PopUpButton((100, y, -10, 22), self._lineJoinStylesMap.keys(), callback=self.changedCallback)
        
        self.setUpBaseWindowBehavior()
        self.w.open()
        addObserver(self, "draw", "draw")
        addObserver(self, "draw", "drawInactive")
        addObserver(self, "draw", "spaceCenterDraw")
    
    def changedCallback(self, sender):
        UpdateCurrentGlyphView()
        sc = CurrentSpaceCenter()
        if sc:
            sc.refreshAllExept()
    
    def windowCloseCallback(self, sender):
        super(StrokeObserer, self).windowCloseCallback(sender)
        removeObserver(self, "draw")
        removeObserver(self, "drawInactive")
        removeObserver(self, "spaceCenterDraw")

    
    def draw(self, notification):
        glyph = notification["glyph"]
        scale = notification["scale"]
        color = self.w.color.get()
        width = self.w.width.get()
        lineCap = self._lineCapStylesMap[self.w.lineCap.getTitle()]
        lineJoin = self._lineJoinStylesMap[self.w.lineJoin.getTitle()]
        
        #path = glyph.naked().getRepresentation("defconAppKit.NSBezierPath")       
        pen = CocoaPen(glyph.getParent())
        glyph.draw(pen)
        path = pen.path
        
        path.setLineWidth_(width)
        path.setLineCapStyle_(lineCap)
        path.setLineJoinStyle_(lineJoin)
        
        color.set()
        path.stroke()
        
     
StrokeObserer()

Display point count

from mojo.events import addObserver
from mojo.drawingTools import *

class PointCount(object):
    
    def __init__(self):
        addObserver(self, "drawPointCount", "draw")
    
    def drawPointCount(self, info):
        glyph = info["glyph"]
        if glyph is None:
            return
        scale = info["scale"]
                
        pointCount = 0
        for contour in glyph:
            pointCount += len(contour)
        
        selectedCount = len(glyph.selection)
        
        fill(1, 0, 0)
        stroke(None)
        fontSize(10/scale)
        text("points: %s | selected: %s" %(pointCount, selectedCount), (glyph.width+10, 10))
    
    
PointCount()