Subscribing to glyph editor events ↩
This example shows how to visualize data in the glyph editor. The visualization is done within the glyph editing space with the merz
library. The visualization is done atop the glyph editor with the vanilla library. The visualization is updated automatically when the glyph in the editor is switched and when the data within the glyph is changed. Different timings (events coalescing) are used to demonstrate optimization possibilities.
The tool subscribes to the current glyph contour, components and anchor changes and count them while drawing some info with merz
.
"""
Visualizing data in the glyph editor
This example shows how to visualize data in the glyph editor.
The visualization is done within the glyph editing space with
the merz library. The visualization is done atop the glyph editor
with the vanilla library. The visualization is updated automatically
when the glyph in the editor is switched and when the data within
the glyph is changed. Different timings are used to demonstrate
optimization possibilities.
"""
from mojo.subscriber import Subscriber, registerGlyphEditorSubscriber
class GlyphEditorWithUISubscriberDemo(Subscriber):
debug = True
def build(self):
glyphEditor = self.getGlyphEditor()
self.backgroundContainer = glyphEditor.extensionContainer(
identifier="com.roboFont.GlyphEditorDrawingSubscriberDemo.background",
location="background",
clear=True
)
self.foregroundContainer = glyphEditor.extensionContainer(
identifier="com.roboFont.GlyphEditorDrawingSubscriberDemo.foreground",
location="foreground",
clear=True
)
def destroy(self):
self.backgroundContainer.clearSublayers()
self.foregroundContainer.clearSublayers()
def glyphEditorDidSetGlyph(self, info):
self.backgroundContainer.clearSublayers()
self.foregroundContainer.clearSublayers()
glyph = info["glyph"]
if glyph is None:
return
self.glyphEditorGlyphDidChange(info)
self.glyphEditorGlyphDidChangeInfo(info)
self.glyphEditorGlyphDidChangeOutline(info)
self.glyphEditorGlyphDidChangeComponents(info)
self.glyphEditorGlyphDidChangeAnchors(info)
self.glyphEditorGlyphDidChangeGuidelines(info)
self.glyphEditorGlyphDidChangeImage(info)
self.glyphEditorGlyphDidChangeMetrics(info)
self.glyphEditorGlyphDidChangeContours(info)
def glyphEditorGlyphDidChange(self, info):
pass
# - editor overlay in upper left corner:
# -- changes since started editing
def glyphEditorGlyphDidChangeInfo(self, info):
pass
# - editor overlay in lower left corner:
# -- glyph name and unicodes
glyphEditorGlyphDidChangeMetricsDelay = 0
def glyphEditorGlyphDidChangeMetrics(self, info):
glyph = info["glyph"]
font = glyph.font
verticalMetrics = [
font.info.descender,
0,
font.info.xHeight,
font.info.capHeight,
font.info.ascender
]
bottom = min(verticalMetrics) - 100
top = max(verticalMetrics) + 100
middle = (bottom + top) / 2
height = top - bottom
headSegment = height * 0.125
y1 = bottom
y2 = bottom + headSegment
y3 = middle
y4 = top - headSegment
y5 = top
l1 = 0
l2 = -200
l3 = -250
r1 = glyph.width
r2 = r1 + 200
r3 = r1 + 250
pathLayer = self.backgroundContainer.getSublayer("metrics")
if pathLayer is None:
pathLayer = self.backgroundContainer.appendPathSublayer(
fillColor=(1, 1, 0, 0.6),
name="metrics"
)
pen = pathLayer.getPen()
pen.moveTo((l1, y3))
pen.lineTo((l2, y1))
pen.lineTo((l2, y2))
pen.lineTo((l3, y2))
pen.lineTo((l3, y4))
pen.lineTo((l2, y4))
pen.lineTo((l2, y5))
pen.closePath()
pen.moveTo((r1, y3))
pen.lineTo((r2, y1))
pen.lineTo((r2, y2))
pen.lineTo((r3, y2))
pen.lineTo((r3, y4))
pen.lineTo((r2, y4))
pen.lineTo((r2, y5))
pen.closePath()
def glyphEditorGlyphDidChangeOutline(self, info):
pass
# - editor overlay in upper right corner:
# -- list of contours with segment counts and bounds
# -- list of components and bounds
glyphEditorGlyphDidChangeContoursChangeDelay = 0
def glyphEditorGlyphDidChangeContours(self, info):
glyph = info["glyph"]
containerLayer = self.backgroundContainer.getSublayer("contours")
if containerLayer is not None:
containerLayer.clearSublayers()
else:
containerLayer = self.backgroundContainer.appendBaseSublayer(
name="contours"
)
segmentColors = [
(1, 0, 0, 0.5),
(0, 1, 0, 0.5),
(0, 0, 1, 0.5),
(1, 1, 0, 0.5),
(1, 0, 1, 0.5),
(0, 1, 1, 0.5)
]
pens = {}
for color in segmentColors:
pathLayer = containerLayer.appendPathSublayer(
fillColor=None,
strokeColor=color,
strokeWidth=10
)
pens[color] = pathLayer.getPen()
for contour in glyph.contours:
if not len(contour.segments):
continue
colors = list(segmentColors)
previous = contour.segments[-1]
for segment in contour.segments:
color = colors.pop(0)
if previous.onCurve is not None:
pen = pens[color]
previousX = previous.onCurve.x
previousY = previous.onCurve.y
pen.moveTo((previousX, previousY))
points = [(point.x, point.y) for point in segment]
if segment.type == "line":
pen.lineTo(*points)
elif segment.type == "curve":
pen.curveTo(*points)
previous = segment
colors.append(color)
for pen in pens.values():
pen.endPath()
glyphEditorGlyphDidChangeComponentsDelay = 0
def glyphEditorGlyphDidChangeComponents(self, info):
glyph = info["glyph"]
componentLayer = self.backgroundContainer.getSublayer("components")
if componentLayer is not None:
componentLayer.clearSublayers()
else:
componentLayer = self.backgroundContainer.appendBaseSublayer(
name="components"
)
for component in glyph.components:
pathLayer = componentLayer.appendPathSublayer(
fillColor=(0, 0, 1, 0.1)
)
x, y, xMax, yMax = component.bounds
w = xMax - x
h = yMax - y
pen = pathLayer.getPen()
pen.rect((x, y, w, h))
def glyphEditorGlyphDidChangeAnchors(self, info):
glyph = info["glyph"]
anchorLayer = self.backgroundContainer.getSublayer("anchors")
if anchorLayer is not None:
anchorLayer.clearSublayers()
else:
anchorLayer = self.backgroundContainer.appendBaseSublayer(
name="anchors",
opacity=0.4
)
for anchor in glyph.anchors:
x1 = anchor.x
y1 = anchor.y
x2 = x1 - 100
y2 = y1 - 100
anchorLayer.appendLineSublayer(
startPoint=(x1, y1),
endPoint=(x2, y2),
strokeColor=(0, 1, 0, 1),
strokeWidth=5,
startSymbol=dict(
name="star",
pointCount=14,
size=(40, 40),
fillColor=(0, 1, 0, 1)
),
endSymbol=dict(
name="oval",
size=(20, 20),
fillColor=(0, 1, 0, 1)
)
)
text = f"{anchor.name}\n({anchor.x}, {anchor.y})"
anchorLayer.appendTextLineSublayer(
position=(x2, y2),
offset=(-10, -10),
text=text,
pointSize=15,
weight="bold",
horizontalAlignment="right",
verticalAlignment="top",
fillColor=(0, 1, 0, 1)
)
def glyphEditorGlyphDidChangeGuidelines(self, info):
glyph = info["glyph"]
guidelineLayer = self.backgroundContainer.getSublayer("guidelines")
if guidelineLayer is not None:
guidelineLayer.clearSublayers()
else:
guidelineLayer = self.backgroundContainer.appendBaseSublayer(
name="guidelines",
opacity=0.4
)
for guideline in glyph.guidelines:
x1 = guideline.x
y1 = guideline.y
x2 = x1 + 100
y2 = y1 + 100
guidelineLayer.appendLineSublayer(
startPoint=(x1, y1),
endPoint=(x2, y2),
strokeColor=(1, 0, 0, 1),
strokeWidth=5,
startSymbol=dict(
name="triangle",
size=(20, 40),
fillColor=(1, 0, 0, 1)
)
)
text = f"{guideline.name}\n({guideline.x}, {guideline.y})\n{guideline.angle}°"
guidelineLayer.appendTextLineSublayer(
position=(x2, y2),
offset=(-10, -10),
padding=(10, 10),
cornerRadius=15,
text=text,
pointSize=15,
weight="bold",
horizontalAlignment="left",
verticalAlignment="bottom",
fillColor=(1, 1, 1, 1),
backgroundColor=(1, 0, 0, 1)
)
def glyphEditorGlyphDidChangeImage(self, info):
glyph = info["glyph"]
image = glyph.image
textLayer = self.foregroundContainer.getSublayer("image")
if textLayer is None:
textLayer = self.foregroundContainer.appendTextBoxSublayer(
name="image",
pointSize=15,
fillColor=(0, 0, 0, 1)
)
data = image.data
if data is None:
textLayer.setText("")
return
data = str(data)[:5000]
x, y = image.offset
w = 200
h = 200
textLayer.setPosition((x, y))
textLayer.setSize((w, h))
textLayer.setText(data)
if __name__ == '__main__':
registerGlyphEditorSubscriber(GlyphEditorWithUISubscriberDemo)