Subscribing to current font events ↩
This example shows a floating window that displays information about the current font. The window knows when the user has switched the current font and updates accordingly. The displayed information is updated when the data within the current font is changed.

The tool uses the Subscriber module to subscribe to font kerning, info, features changed and to all outline changes. It also draws a small graph using merz
.
import vanilla
import merz
import defcon
from mojo.subscriber import Subscriber, WindowController, registerCurrentFontSubscriber
WHITE = (1, 1, 1, 1)
RED = (1, 0, 0, 1)
BLUE = (0, 0, 1, 1)
GREEN = (0, 1, 0, 1)
YELLOW = (1, 1, 0, 1)
CYAN = (0, 1, 1, 1)
class CurrentFontWithUISubscriberDemo(Subscriber, WindowController):
debug = True
def build(self):
self.w = vanilla.FloatingWindow((0, 0), "Useless Font Data")
self.fontNameTextBox = vanilla.TextBox(posSize="auto", text="")
self.kerningView = merz.MerzView(posSize="auto")
self.infoView = merz.MerzView(posSize="auto")
self.featuresList = vanilla.List(
"auto",
items=[],
columnDescriptions=[
dict(
title="character",
width=30
),
dict(
title="count"
)
],
showColumnTitles=False
)
self.pointsView = merz.MerzView(
"auto"
)
columnDescriptions = [
dict(
columnPlacement="trailing"
),
dict(
width=100,
columnPlacement="fill"
)
]
rows = [
(
vanilla.TextBox("auto", "Font:"),
self.fontNameTextBox
),
dict(
height=100,
cells=(
vanilla.TextBox("auto", "Kerning:"),
self.kerningView
)
),
dict(
height=100,
cells=(
vanilla.TextBox("auto", "Font Info:"),
self.infoView
)
),
dict(
height=100,
cells=(
vanilla.TextBox("auto", "Features:"),
self.featuresList
)
),
dict(
height=100,
cells=(
vanilla.TextBox("auto", "Outline:"),
self.pointsView
)
),
]
self.w.gridView = vanilla.GridView(
"auto",
rows,
columnDescriptions=columnDescriptions,
columnWidth=75,
columnSpacing=10,
rowHeight=25,
rowSpacing=10
)
metrics = dict(
margin=15
)
rules = ["H:|-margin-[gridView]-margin-|",
"V:|-margin-[gridView]-margin-|"]
self.w.addAutoPosSizeRules(rules, metrics)
def started(self):
self.w.open()
def currentFontDidSetFont(self, info):
font = info["font"]
if font is not None:
info["layer"] = font.defaultLayer
if font is None:
name = ""
else:
name = f"{font.info.familyName}-{font.info.styleName}"
self.fontNameTextBox.set(name)
self.currentFontInfoDidChange(info)
self.currentFontKerningDidChange(info)
self.currentFontFeaturesDidChange(info)
self.currentFontLayerDidChange(info)
def currentFontInfoDidChange(self, info):
font = info["font"]
container = self.infoView.getMerzContainer()
container.clearSublayers()
if font is None:
return
base = container.appendRectangleSublayer(
name="base",
position=(0, 0),
size=(100, 100),
fillColor=WHITE
)
copyright = font.info.copyright
if copyright is None:
copyright = ""
copyright = len(copyright)
trademark = font.info.trademark
if trademark is None:
trademark = ""
trademark = len(trademark)
license = font.info.openTypeNameLicense
if license is None:
license = ""
license = len(license)
total = copyright + trademark + license
if total:
copyright /= total
copyright *= 100
base.appendRectangleSublayer(
position=(0, 0),
size=(100, copyright),
fillColor=RED
)
trademark /= total
trademark *= 100
base.appendRectangleSublayer(
position=(0, copyright),
size=(100, trademark),
fillColor=GREEN
)
license /= total
license *= 100
base.appendRectangleSublayer(
position=(0, copyright + trademark),
size=(100, license),
fillColor=BLUE
)
def currentFontKerningDidChange(self, info):
font = info["font"]
container = self.kerningView.getMerzContainer()
container.clearSublayers()
if font is None:
return
negative = 0
positive = 0
zero = 0
for value in font.kerning.values():
if value > 0:
positive += 1
elif value < 0:
negative += 1
else:
zero += 1
total = sum((negative, positive, zero))
negativeAngle = 0
zeroAngle = 0
if total:
if negative:
negativeAngle = negative / total
negativeAngle *= 360
if zero:
zeroAngle = zero / total
zeroAngle *= 360
zeroAngle += negativeAngle
slices = [
dict(
startAngle=0,
endAngle=negativeAngle,
fillColor=RED
),
dict(
startAngle=negativeAngle,
endAngle=zeroAngle,
fillColor=YELLOW
),
dict(
startAngle=zeroAngle,
endAngle=0,
fillColor=GREEN
)
]
makePieChart(container, slices)
def currentFontFeaturesDidChange(self, info):
font = info["font"]
items = []
if font is not None:
text = font.features.text
counter = {}
if text:
for line in text.splitlines():
for c in line:
if c not in counter:
counter[c] = 0
counter[c] += 1
for character, count in sorted(counter.items()):
item = dict(character=character, count=str(count))
items.append(item)
self.featuresList.set(items)
def currentFontLayerDidChange(self, info):
font = info["font"]
container = self.pointsView.getMerzContainer()
container.clearSublayers()
if font is None:
return
layer = font.defaultLayer
counter = {}
for glyph in layer:
glyphCounter = glyph.getRepresentation("com.robofont.subscriberDemo.pointCounts")
for type, count in glyphCounter.items():
if type not in counter:
counter[type] = 0
counter[type] += count
colors = dict(
move=YELLOW,
line=GREEN,
curve=BLUE,
qCurve=RED,
offcurve=CYAN,
)
if not counter:
slices = []
else:
slices = []
total = sum(counter.values())
previousAngle = 0
for type, color in colors.items():
count = counter.get(type)
if not count:
continue
angle = previousAngle + (360 * count / total)
slices.append(dict(
startAngle=previousAngle,
endAngle=angle,
fillColor=color
))
previousAngle = angle
makePieChart(container, slices)
def makePieChart(container, slices):
base = container.appendOvalSublayer(
position=(0, 0),
size=(100, 100),
fillColor=WHITE
)
for slice in slices:
fillColor = slice["fillColor"]
startAngle = slice["startAngle"]
endAngle = slice["endAngle"]
pathLayer = base.appendPathSublayer(
fillColor=fillColor
)
pen = pathLayer.getPen()
pen.moveTo((50, 50))
pen.arc((50, 50), 50, startAngle, endAngle, clockwise=False)
pen.closePath()
def getPointCountsFactory(glyph):
counter = {}
for contour in glyph:
for point in contour:
t = point.segmentType
if t is None:
t = "offcurve"
if t not in counter:
counter[t] = 0
counter[t] += 1
return counter
defcon.registerRepresentationFactory(
defcon.Glyph,
"com.robofont.subscriberDemo.pointCounts",
getPointCountsFactory,
destructiveNotifications=[
"Glyph.ContoursChanged"
]
)
registerCurrentFontSubscriber(CurrentFontWithUISubscriberDemo)