This example shows a floating window that displays a list of all open fonts. The window knows when the user has changed the open fonts (opened, closed, created a new one) and updates accordingly.

The tool subscribes to font info and features. It uses the merz module to draw a little graph in the “glyph” panel.

import vanilla
import merz
from mojo.subscriber import Subscriber, WindowController, registerRoboFontSubscriber
from mojo.roboFont import AllFonts

WHITE = (1, 1, 1, 1)

class RoboFontWithUISubscriberDemo(Subscriber, WindowController):

    debug = True

    def build(self):
        self.fontList = []

        self.w = vanilla.FloatingWindow((0, 500), "RoboFont Useless Data")

        columnDescriptions = [
            dict(title="name")
        ]
        self.fontList = vanilla.List(
            "auto",
            [],
            columnDescriptions=columnDescriptions,
            showColumnTitles=False,
            allowsEmptySelection=False,
            allowsMultipleSelection=False,
            selectionCallback=self.fontListSelectionCallback
        )

        self.infoList = vanilla.List(
            "auto",
            []
        )

        self.featureList = vanilla.List(
            "auto",
            []
        )

        self.namesView = merz.MerzView(
            "auto",
            backgroundColor=(0, 0, 0, 1)
        )

        columnDescriptions = [
            dict(
                width=300
            ),
            {},
            {},
            dict(
                width=100
            ),
        ]
        rows = [
            dict(
                height=25,
                cells=(
                    vanilla.TextBox("auto", "Fonts:"),
                    vanilla.TextBox("auto", "Info:"),
                    vanilla.TextBox("auto", "Features:"),
                    vanilla.TextBox("auto", "Glyphs:"),
                )
            ),

            dict(
                height=400,
                cells=(
                    self.fontList,
                    self.infoList,
                    self.featureList,
                    self.namesView
                ),
            ),
        ]
        self.w.gridView = vanilla.GridView(
            "auto",
            rows,
            columnDescriptions=columnDescriptions,
            columnWidth=150,
            columnSpacing=10,
            rowHeight=25,
            rowSpacing=0
        )
        metrics = dict(
            margin=15
        )
        rules = [
            "H:|-margin-[gridView]-margin-|",
            "V:|-margin-[gridView]-margin-|"
        ]
        self.w.addAutoPosSizeRules(rules, metrics)

    def started(self):
        for font in AllFonts():
            self.fontList.append(
                dict(
                    name=f"{font.info.familyName}-{font.info.styleName}",
                    font=font
                )
            )
        self.w.open()

    # ---- callbacks with delays ---- #
    fontDocumentDidOpenNewDelay = 0

    def fontDocumentDidOpenNew(self, info):
        font = info["font"]
        self.fontList.append(
            dict(
                name="New Family-New Style",
                font=font
            )
        )

    fontDocumentDidOpenDelay = 0

    def fontDocumentDidOpen(self, info):
        font = info["font"]
        self.fontList.append(
            dict(
                name=f"{font.info.familyName}-{font.info.styleName}",
                font=font
            )
        )

    fontDocumentWillCloseDelay = 0

    def fontDocumentWillClose(self, info):
        font = info["font"]
        for i, item in enumerate(self.fontList.get()):
            if item["font"] == font:
                break
        del self.fontList[i]

    def fontListSelectionCallback(self, sender):
        selection = sender.getSelection()
        if not selection:
            font = None
        else:
            font = sender[selection[0]]["font"]
        self.updateInfoList(font)
        self.updateFeatureList(font)
        self.updateNamesView(font)

    # ---- update functions ---- #
    def updateInfoList(self, font):
        items = []
        if font is not None:
            text = []
            if font.info.copyright is not None:
                text.append(font.info.copyright)
            if font.info.trademark is not None:
                text.append(font.info.trademark)
            if font.info.license is not None:
                text.append(font.info.license)
            text = "\n".join(text)
            if text:
                items = text.replace("\n", " ").split(" ")
                items = [i for i in items if i]
                items.sort()
        self.infoList.set(items)

    def updateFeatureList(self, font):
        items = []
        if font is not None:
            text = font.features.text
            if text:
                items = text.replace("\n", " ").split(" ")
                items = [i for i in items if i]
                items.sort()
        self.featureList.set(items)

    def updateNamesView(self, font):
        if font is None:
            names = []
        else:
            names = font.glyphOrder
        view = self.namesView
        container = view.getMerzContainer()
        container.clearSublayers()
        if not names:
            return
        lengths = [len(name) for name in names]
        minLength = min(lengths)
        maxLength = max(lengths)
        xSteps = maxLength - minLength
        ySteps = len(names)
        width = view.width()
        height = view.height()
        offset = 10
        if xSteps == 0:
            xStep = 0
        else:
            xStep = (width - (offset * 2)) / xSteps
        yStep = (height - (offset * 2)) / ySteps
        pathLayer = container.appendPathSublayer(
            fillColor=None,
            strokeColor=WHITE,
            strokeWidth=1
        )
        pen = pathLayer.getPen()
        pen.moveTo((offset, offset))
        y = offset
        for length in lengths:
            x = offset + (xStep * length)
            pen.lineTo((x, y))
            y += yStep
        pen.endPath()


registerRoboFontSubscriber(RoboFontWithUISubscriberDemo)

Last edited on 01/09/2021