Skip to content

New in FontLab 8: Scripts & extensions

Tired of click-click-click? Automate excellence with FontLab’s comprehensive scripting.

Write simple Python 3.11 scripts, or perform complex batch work and flexible glyph transformations with powerful extensions like the TypeRig package.

New in FontLab 8: Write and use Python 3 scripts. Seamlessly interchange with other font editing apps like Glyphs or RoboFont.Perform global transformations with the powerful TypeRig library. Create fonts for the entire Unicode 15 standard.

New In FontLab 8.3 every font export can run your own post-processing script.

Python

Python 3.11

New FontLab 8.2 uses Python 3.11 instead of Python 3.10 (in FontLab 8.0) or Python 2.7 (in earlier versions). Python 3.11 is 10-60% faster than 3.10 and contains various improvements.

FontLab 8 uses its own built-in version of Python 3.11 on both macOS and Windows. You don’t have to install Python yourself. Place your FontLab-specific packages in the python/3.11/site-packages folder inside the user data folder.

If Python is working correctly, the About box (FontLab 8 > About FontLab 8 on macOS, Help > About FontLab 8 on Windows) now shows which Python version FontLab is using.

As a result of the switch from Python 2.7 to 3.11, scripting works several times faster.

Python scripting on macOS

FontLab 8 or macOS uses its own built-in Python. You don’t have to install Python yourself.

If you want to use pip to install additional Python packages that you import into your FontLab scripts, you may also install the most recent Python 3.11.x from Python.org, or the most recent Python 3.11.x from Homebrew with brew install python@3.11.

Then, in Terminal, install general-purpose packages with python3 -m pip install PACKAGENAME or python3 -m pip install --user packagename, and FontLab should recognize them.

FontLab 8 for macOS looks for Python packages and modules in these folders, in this sequence:

  • python/3.11/site-packages inside the user data folder, which by default is ~/Library/Application Support/FontLab/FontLab 8/ and which you can customize in Preferences > General
  • Contents/Resources/python/3.11/site-packages inside the FontLab 8.app
  • ~/Library/Python/3.11/lib/python/site-packages, which is the location for packages installed with python3 -m pip install --user if you have Python 3.11 installed from Python.org or Homebrew
  • /usr/local/lib/python3.11/site-packages, which is the location for packages installed with python3 -m pip install if you have Python 3.11 installed from Homebrew
  • /Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/, which is the location for packages installed with python3 -m pip install if you have Python 3.11 installed from Python.org

Python scripting on Windows

FontLab 8 or macOS uses its own built-in Python. You don’t have to install Python yourself.

To use pip to install additional Python packages that can you import into your FontLab scripts, install the most recent Python 3.11.x from Python.org. Install the 64-bit (x64) or 32-bit (x86) version of Python, depending on the version of FontLab 8 that you use.

After the installation, you may want to click Start, in the search box type environment and choose Edit the system environment variables. In the System Properties dialog, choose Environment Variables and in the System variables section, double-click Path. Click New and paste %USERPROFILE%\AppData\Local\Programs\Python\Python310\. Click OK to close all dialogs.

Then, in the Command Prompt, install general-purpose packages with python -m pip install PACKAGENAME or python -m pip install --user packagename, and FontLab should recognize them.

FontLab 8 for Windows looks for Python packages and modules in these folders, in this sequence:

  • python/3.11/site-packages inside the user data folder, which by default is C:/Users/%USERNAME%/Documents/FontLab/FontLab 8/ and which you can customize in Preferences > General
  • Resources/python/3.11/site-packages inside the FontLab 8 app folder such as C:/Program Files/Fontlab/FontLab 8/
  • C:/Users/%USERNAME%/AppData/Local/Programs/Python/Python310/lib/site-packages, which is the location for packages installed with python -m pip install if you have Python 3.11 installed from Python.org
  • Lib folder inside the FontLab 8 app folder
  • C:/Users/%USERNAME%/AppData/Roaming/Python/Python310/site-packages, which is the location for packages installed with python -m pip install --user if you have Python 3.11 installed from Python.org

Troubleshooting your Python packages

  1. To use Python in FontLab 8 on macOS 10.13 High Sierra, you need to apply a small fix, read more.
  2. FontLab 8 uses Python 3.11 — not Python 3.9 or 3.11 or any other version. If you have any other version of Python installed, and you want to use pip-installed packages in FontLab, install the most recent Python 3.11.x from Python.org, and re-install the Python packages for this Python version.
  3. You may manipulate the sys.path list in startupScript.py to add or remove paths in which FontLab looks for packages and modules.

Installing and using scripts

New When you choose Scripts > Update / Install Scripts, the new Extend FontLab website opens in your browser.

You can download and install TypeRig and other scripts and resources from the website. We will be adding new scripts and resources there.

TypeRig

New Vassil Kateliev has updated his powerful TypeRig Python package to work in FontLab 8. Visit Extend FontLab website to download and install TypeRig.

fontTools

New FontLab 8.2 now has its own built-in copy of the fontTools package, named fontlab_private.fontTools, in version 4.39.4 (FontLab 8.0 used 4.29.1, FontLab 7 used 3.43.2). FontLab uses fontlab_private.fontTools to export variable OpenType fonts and to compile/decompile TTX table data from/to the Tables panel.

You may use import fontlab_private.fontTools for your own scripting from within FontLab. Alternatively, if you’ve installed a standalone Python 3.11 distribution, you can run python3.11 -m pip install --upgrade fontTools[ufo,lxml,woff,unicode,interpolatable,plot,symfont,type1,pathops,repacker], and then use import fontTools.

FontLab will always use its own built-in copy of fontTools, but you can use the pip-installed version in your own scripting, like so:

try:
    # Try to use pip-installed fontTools
    from fontTools import ttLib
except ImportError:
    # If it's not available, use FontLab-bundled fontTools
    from fontlab_private.fontTools import ttLib

Preferences in Python

Direct access to preferences

The fontlab built-in module contains the flPreferences class that offers direct access to over 340 FontLab preference attributes.

For example, if you use the dark UI theme, you can toggle the Glyph window between the light and the dark theme:

from fontlab import *
pref = flPreferences()
pref.generalDarkGlyph = not pref.generalDarkGlyph

Extended access to preferences

New FontLab now also offers extended access to over 400 preference attributes. You can save() the current preferences to a Python dict, modify the values of the dict and load() the dict back into the preferences. For example:

from fontlab import *
prefd = flPreferences().save()
prefd["general.dark_glyph"] = not prefd["general.dark_glyph"]
flPreferences().load(prefd)
flWorkspace.instance().updateActiveViewport()

We recommend that you use direct access to preferences. Use extended access only if direct access does not expose the preferences that you need. For example, prefd.get("profiles", tuple()) returns a Python tuple with all custom export profiles.

Run actions and action sets in Python

The code below shows a way of running action sets (same as in Tools > Actions) from Python, using the native FontLab Python API.

This Python code defines a function run_actions() that runs FontLab actions on specified glyphs and layers of a specified or current font, or on all glyphs and masters of a specified or current font. The function takes in a list of action names or dict of action names and parameters, and optionally: a specified fgFont font, a list of glyph names or objects, a list of layer names (or False if current layer, or True if all masters), plus a boolean value for whether the actions will be undoable.

The function calls flPackage.runActions() to run the actions, using the get_fl_action_syntax() function to create a list of JSON strings that contain dictionaries in the format compatible with the JSON action set notation, and the get_fl_glyphs() function to build a list of flGlyph objects from a list of glyph names or objects.

Finally, the function performs actions on current layer, specified layers or all masters of the specified or current font. In this example, the function is called with a list of actions, which flatten and decompose all masters in all glyphs, and remove overlaps in them.

A more pythonic method will be incorporated into the TypeRig package in the future.

from collections.abc import Iterable, Mapping
from json import dumps
from typing import List

from fontgate import fgFont, fgGlyph
from fontlab import CurrentFont, flGlyph, flPackage

def get_fl_action_syntax(actions: Mapping | Iterable) -> list():
    """
    Creates a list of JSON strings that contain dictionaries in the format used by `flPackage.runActions`.

    `actions_list` will contain dictionaries where the `id` key is the action name and additional keys specify
    parameters. For syntax specifics, export an Action set from Tools > Actions and check the saved JSON file.

    Args:
        actions (Mapping | Iterable): List or dict of action names to be performed.

    Returns:
        list: A list of JSON strings that contain dictionaries in the format used by `flPackage.runActions`.
    """

    # Create list of actions to run
    actions_list = []

    # If `actions` is a dictionary, creates `actions_list` with parameters
    if isinstance(actions, Mapping):
        for action, params in actions.items():
            action_rec = {"id": action}
            if isinstance(params, Mapping):
                action_rec |= params
            actions_list.append(dumps(action_rec))

    # If `actions` is a list of simple actions without parameters, creates simple `actions_list`
    elif isinstance(actions, Iterable):
        actions_list.extend(dumps({"id": action}) for action in actions)

    return actions_list

def get_fl_glyphs(font: fgFont, glyphs: List[str | fgGlyph | flGlyph] = None) -> list():
    """
    Builds a list of `flGlyph` objects from a list of glyph names, `flGlyph` objects or `fgGlyph` objects.

    Args:
        font (fgFont): FontGate object to be acted upon.
        glyphs (List[str | fgGlyph | flGlyph], optional): List of names, fgGlyph or flGlyph objects to be acted upon. All glyphs if absent. Defaults to None.

    Returns:
        list: List of `flGlyph` objects.
    """

    # If `glyphs` are specified, builds a list of `flGlyph` objects
    if glyphs:
        glyphs_fl = []
        for glyph in glyphs:
            if isinstance(glyph, flGlyph):
                glyphs_fl.append(glyph)
            elif isinstance(glyph, fgGlyph):
                glyphs_fl.append(flGlyph(glyph, font))
            elif isinstance(glyph, str):
                glyphs_fl.append(flPackage(font).findName(glyph))
    else:
        glyphs_fl = [flGlyph(glyph, font) for glyph in font.glyphs]
    return glyphs_fl

def run_actions(
    actions: Mapping | Iterable, # List of action names or dict of action names and params to be performed
    font: fgFont = None, # `fgFont` font or `CurrentFont()` if absent
    glyphs: List[str | fgGlyph | flGlyph] = None, # List of glyph names, fgGlyph or flGlyph objects, all glyphs if absent
    layers: bool | List[str] = False, # List of layer names, or True if all masters, or False (default) if current masters
    undo: bool = True, # Will actions be undoable? True by default
) -> None:

    """
    Runs a list of FontLab actions on specified glyphs and layers, or on all glyphs and masters
    of a specified or current font. Action specs use the same syntax as action sets exported from Tools > Actions.

    Args:
        actions (Mapping | Iterable): List of action names or dict of action names and params to be performed
        font (fgFont, optional): `fgFont` font or `CurrentFont()` if absent
        glyphs (List[str | fgGlyph | flGlyph], optional): List of glyph names, fgGlyph or flGlyph objects, all glyphs if absent
        layers (List[str] | bool, optional): List of layer names or False if current master or True (default) if all masters
        undo (bool, optional): Will actions be undoable? True by default
    """

    # `font` is current font or specified fgFont object
    font = font or CurrentFont()

    actions_list = get_fl_action_syntax(actions)
    glyphs_fl = get_fl_glyphs(font, glyphs)

    # If `layers` were specified, makes them visible and stores their previous visibility
    if isinstance(layers, Iterable):
        # Stores the previous layer visibility of each layer
        layer_visibility = {key: -1 for key in flPackage(font).fontLayerOrder}
        # Sets layer_scope for `flFont.runActions` to 1 (visible layers)
        layer_scope = 1
        for glyph in glyphs_fl:
            for layer in glyph.fgGlyph.layers:
                if layer_visibility[layer.name] == -1:
                    layer_visibility[layer.name] = int(layer.visible)
                if layer_visibility[layer.name] > -1:
                    layer.visible = layer.name in layers
    elif layers:
        # Sets layer_scope for `flFont.runActions` to 2 (all masters)
        layer_scope = 2
    else:
        # Sets layer_scope for `flFont.runActions` to 0 (current master)
        layer_scope = 0

    # Calls `fontlab.flPackage.runActions()` to run the actions
    flPackage(font).runActions(glyphs_fl, actions_list, layer_scope, undo)

    # If a `layers` list was specified, restores previous layer visibility
    if layer_scope == 1:
        for glyph in glyphs_fl:
            for layer in glyph.fgGlyph.layers:
                layer.visible = bool(layer_visibility[layer.name])

# Expands filters and transformations, decomposes components and removes overlaps in all masters
run_actions([
    "flatten_glyph", # Expands filters and transformations.
    "decompose", # Decomposes components.
    "remove_overlap" # Removes overlapping portions of contours
], layers=True)

Scripting panel

Clearing Output panel whenever you run a script

New If you turn on the new Clear Output Panel toggle in the menu of the Scripting panel, every time you run a script that you have open in the panel, FontLab clears the Output panel. This is useful as you develop your script.

Run current script from collapsed Scripting panel

New If you collapse the Scripting panel, its header (if it’s horizontal) shows a Run button. This way, you can quickly run the same script again and again.

If you’ve loaded a script from the panel’s sidebar, or if you’ve written a script and have saved it, the collapsed panel also shows the name of the script.

Find next in Scripting panel

New If you’re in the Scripting panel and you press CmdF or CtrlF, FontLab jumps to the panel search box. Type a string and press Enter to find the first occurrence of the string. Press CmdG or CtrlG to find the next occurrence.

Python hooks

If you place a file named startupScript.py directly inside your user data folder, FontLab will execute the code in this file at the beginning of each Python script that you run from the Scripts menu or the Scripting panel. You may place commonly-used imports there.

New If you place the file named initScript.py in your user data folder, FontLab will execute this script once, right after you run the FontLab app. You may place code there that registers custom handlers, connects functions to Qt signals, checks for updates for your own code etc.

Output panel

HTML in Output panel

New If you use print in Python to print text into the Output panel, and the text contains the characters < and >, FontLab interprets the text as HTML and renders that HTML in the panel.

New If you use the print function in Python to print text into the Output panel, and the text contains HTML links formed like <a href="protocol:content">text</a>, the links in the panel will be clickable.

  • If protocol is glyph, then content can be a glyph name. If you click such a link and the glyph exists, FontLab opens the glyph in the current window.
  • If protocol is class, then content can be a class name. If you click the link and the class exists, FontLab opens the Classes panel and highlights the class.
  • If protocol is file, then content can be a file or folder path. If you click the click, FontLab reveals the path in the Finder / File Explorer. FontLab does not open the file in the associated app, only navigates to the file in the system file browser. When you use the file protocol, the path can immediately follow the :. You don’t need to use // or /// after the colon, or to escape spaces.

protocol can be any URL scheme protocol such as https. On macOS, it can be any custom URI scheme registered with the system. If you click the link, FontLab will delegate the handling of the link to the system. For example:

  • If the link is <a href="https://fontlab.com">website</a>, FontLab will open the link in the default browser.
  • On macOS, if the link is <a href="x-dictionary:‽:com.apple.dictionary.Wikipedia">‽</a>">look up</a> and you click it, macOS opens the Dictionary app and looks up the character (interrobang) in the Wikipedia.
  • On macOS, if the link is <a href="x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility">open Accessibility preferences</a> and you click it, macOS opens System Preferences > Security & Privacy > Privacy > Accessibility.

If you run a script that prints links multiple times, only the links from the last run will be clickable.

Integrated libraries

New FontLab 8 uses 2022 versions of the ICU, HarfBuzz and FreeType libraries. Thanks to that, FontLab supports new writing systems added in Unicode 14, and includes other improvements.

New FontLab 8.2 uses the Qt Framework version 5.15.13. It contains dozens of minor fixes compared to the version 5.15.9 used in FontLab 8.0, and 5.9 used in FontLab 7. Thanks to that, FontLab handles high-DPI system settings on Windows better, and includes other improvements.