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)

Last edited on 15/07/2021