Skip to main content

FakeLab

Test-driven development for FontLab Studio 5 Python macros and modules.

FakeLab is a FontLab Studio 5 replacement for testing Python code.

Everything is only implemented so far as to make FontLab objects importable outside of FontLab Studio 5, and run tests.

It is suggested to install FakeLab in a virtual environment so FontLab won't accidentally import the fake module when running the scripts in actual FontLab Studio 5. FakeLab will run in Python 3; you have to live with any incompatibilites between Python 2.7 and 3 that may occur in your scripts.

Loading and saving VFBs is supported via vfbLib.

The implementation of FakeLab is based on the invaluable Unofficial FontLab/Python API Reference, and from running scripts in FontLab Studio and checking what they do, apart from crashing the application.

Installation

When you have activated your virtual environment:

$ pip install -e .

FL is then importable outside of FontLab Studio:

from FL import fl, Font

# Make an empty font
f = Font()

# Add the font to the mock app
fl.Add(f)

# Close the font
fl.Close()

If your are running in a virtual environment, and need to make your FontLab modules importable, add a .pth file in your virtual environment's site-packages directory:

# fl5modules.pth
/Users/<yourname>/Code/FLMacros/System/Modules

uv

To use tox with uv for testing, install and run like this:

uv sync --group test
uv run tox

A word of caution

FakeLab is a work in progress, and only implemented as far as I needed it in my own scripts. I have to admit that the bulk of them still has no tests.

If you encounter a NotImplementedError while writing tests, this is where you can excel at helping to improve this project ;) I'll be happy to accept your pull requests. Alternatively, open an issue, buy me a coffee, and hope that my kids leave me alone so I can find some time to work on your issue.

Writing tests

Developing scripts without automated testing is really only for very small projects. To be sure of the outcomes of a module or script, you should always write tests. This is usually done using pytest.

Tests example

Let's assume you have a FontLab script to select glyphs containing components. If you have your own tools collection for FontLab, this script may consist of two parts: One script that is listed in FontLab's macro toolbar, and one Python module implementing the logic, which is called by the toolbar script.

Studio 5
+- Macros
   +- Selection
      +- Select Composites.py
   +- System
      +- Modules
         +- fakeLabDemo
            +- selection
               +- __init__.py
               +- composites.py

The Select Composites.py script looks like this:

#FLM: Select composites
# Studio 5/Macros/Selection/Select Composites.py
from fakeLabDemo.selection.composites import selectComposites
selectComposites(fl.font)

And the module:

# Studio 5/Macros/System/Modules/fakeLabDemo/selection/composites.py
from __future__ import absolute_import, division, print_function

from FL import fl


def getFontIndex(font):
    """
    Get the index of the supplied font.
    We must iterate through the open fonts and compare file names,
    because the == operator can not compare the font objects directly.
    (FL font objects get a different id() each time they are called)

    :param font: A title for the dialog.
    :type font:  :py:class:`FL.Font`
    """
    for i in range(len(fl)):
        cf = fl[i]
        if cf.file_name == font.file_name:
            if font.file_name is None:
                if (
                    cf.family_name == font.family_name
                    and cf.style_name == font.style_name
                ):
                    return (cf, i)
            else:
                return (cf, i)
    # Font was not found, probably there are no open fonts
    return (None, -1)


def setSelection(font, glyph_names):
    """
    Set glyphs from the glyph_names list as selected in the font window.
    """
    f, i = getFontIndex(font)
    if i > -1:
        fl.ifont = i
        fl.Unselect()
        for n in glyph_names:
            fl.Select(n)


def selectComposites(font):
    """
    Select composites in font.
    """
    setSelection(
        font,
        [
            glyph.name
            for glyph in font.glyphs
            if glyph.components
        ]
    )

How can we be sure this script does what it is supposed to do? For pytest, we add another parallel folder structure to the existing structure:

Studio 5
+- Macros
   +- Selection
      +- Select Composites.py
   +- System
      +- Modules
         +- fakeLabDemo
            +- selection
               +- __init__.py
               +- composites.py
         +- tests
            +- fakeLabDemo
               +- selection
                  +- composites_test.py

The file composites_test.py, which is named analogous to the module file it relates to, is where we will implement our tests:

# Studio 5/Macros/System/Modules/tests/fakeLabDemo/selection/composites_test.py
import pytest

from FL import fl, Component, Font, Glyph, Point
from fakeLabDemo.selection.composites import selectComposites


def test_selectComposites():
    # Construct a fake FontLab font object
    font = Font()
    g = Glyph(1)
    g.name = "A"
    g.width = 500
    g.unicode = 0x41
    font.glyphs.append(g)

    g = Glyph(1)
    g.name = "dieresis"
    g.width = 500
    g.unicode = 0xA8
    font.glyphs.append(g)

    g = Glyph(1)
    g.name = "Adieresis"
    g.width = 500
    g.unicode = 0xC4
    g.components.append(Component(0))
    g.components.append(Component(1, Point(0, 300)))
    font.glyphs.append(g)

    # Add the font to the FL object
    fl.Add(font)
    fl.UpdateFont()

    # Run our script to be tested on the font
    selectComposites(fl.font)

    # You could save the fake font to JSON instead of VFB.
    # fl.font.Save("test_composites.vfb.json")

    # Test if the correct glyphs have been selected
    assert fl.Selected(0) == 0
    assert fl.Selected(1) == 0
    assert fl.Selected(2) == 1

    # Close the fake font
    fl.Close()

As you see, you can use the objects just as you would inside FontLab.

You can open a font from an existing VFB, or you can construct a test font using the FL Python API.

Invoke the test script in a Terminal window while your virtual environment is active:

cd "Studio 5/Macros/System/Modules"
python -m pytest tests/fakeLabDemo/selection/composites_test.py

If everything works out, you will see some output like this:

============================ test session starts ===============================
platform darwin -- Python 3.8.7, pytest-6.0.1, py-1.9.0, pluggy-0.13.1
rootdir: /Users/jens/Code/fakelab
collected 1 item

tests/fakeLabDemo/selection/composites_test.py .                          [100%]

============================= 1 passed in 0.02s ================================

Metadata

Release files for fakelab 0.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fakelab 0.3.1
File Size Uploaded
fakelab-0.3.1.tar.gz 142.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fakelab 0.3.1
File Interpreter ABI Platform
fakelab-0.3.1-py3-none-any.whl Python 3 none any Details

Total release size: 312.0 kB

Release files / fakelab-0.3.1.tar.gz

Download URL fakelab-0.3.1.tar.gz
Size 142.8 kB
Tags Source
SHA-256 checksum
How to use checksums
60e39075717e6ba56ce5bc0f6f26f960853e55b517ca790dd34d897a582fcbe8
BLAKE2b-256 checksum
How to use checksums
e89aec64c4805d5caf1273b404da01b602da7bccf6e227ac871cf653dae857a0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / fakelab-0.3.1-py3-none-any.whl

Download URL fakelab-0.3.1-py3-none-any.whl
Size 169.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8309a12ea68ba83f43a1b47006831ef9769a706760117c16b0413c8500695f24
BLAKE2b-256 checksum
How to use checksums
8fb7103a0254668eb57d17296a3eef17acf424be20125566c9cc54fcbb9cf99f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.3.1 This release

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page