FontForge Freezing During Python Batch Scripts

When executing large batch operations in FontForge using Python, the program can become unresponsive due to memory leaks in its underlying C architecture, hidden graphical user interface prompts, or corrupt glyph outlines causing algorithmic deadlocks. This guide outlines the exact causes of these freezes and provides actionable solutions—such as running FontForge headlessly, isolating tasks with multiprocessing, managing memory explicitly, and logging file progression—to ensure your automated font conversions and modifications run to completion without manual intervention.

Run in Headless CLI Mode

FontForge often hangs if it attempts to render UI elements or display error dialogs in an environment that expects them. Never run batch scripts through the FontForge GUI interface. Instead, run the script from the command line using the standalone binary or as a direct Python module import:

fontforge -script your_script.py

If you installed FontForge via system package managers that include the Python extension (e.g., python3-fontforge on Ubuntu), run the script directly through standard Python:

python3 your_script.py

Running in headless mode ensures that non-fatal font warnings do not trigger interactive blocking pop-ups.

Isolate Fonts Using Subprocesses

FontForge's internal C-bindings do not always release memory back to the operating system when processing hundreds of files sequentially inside a single loop. Over time, the memory footprint balloons, leading to severe slowdowns or a complete freeze.

To prevent this, use Python's subprocess module to spawn an isolated, fresh FontForge process for each font or small batch:

import subprocess
import glob

font_files = glob.glob("path/to/fonts/*.ttf")

for font_path in font_files:
    # Runs an isolated script for a single font file
    result = subprocess.run(
        ["fontforge", "-script", "process_single_font.py", font_path],
        capture_output=True,
        text=True,
        timeout=120 # Kill the process if it hangs longer than 2 minutes
    )
    if result.returncode != 0:
        print(f"Failed processing {font_path}: {result.stderr}")

Setting a timeout parameter ensures that if an individual font causes an infinite calculation loop, the runner terminates it and proceeds to the next file instead of hanging indefinitely.

Force Memory Cleanup Explicitly

If you must process files within a single long-running Python script, explicitly close the font objects and invoke Python's garbage collector after every iteration. Merely letting a variable fall out of scope often fails to free the C-level font data:

import fontforge
import gc

for font_path in font_list:
    font = fontforge.open(font_path)
    try:
        # Perform font operations here
        font.generate("output/" + font.fontname + ".otf")
    finally:
        font.close()
        del font
        gc.collect()

Bypass Problematic Path Operations

Complex operations such as font.removeOverlap() or automatic hint generation (font.autoHint()) can enter endless loops when processing malformed Bezier curves or self-intersecting contours.

  1. Wrap High-Risk Calls: Isolate calls like removeOverlap() or simplify() within standard try...except blocks.

  2. Set Validation Flags: When saving, disable extensive validation checks that can freeze on corrupted glyphs:

    font.generate("output.ttf", flags=('opentype', 'omit-instructions'))
  3. Skip Broken Glyphs: If a specific glyph is locking the thread, iterate through font.glyphs() individually so you can catch errors per glyph rather than failing the entire file.

Implement Verbose Logging

Determine whether the script is genuinely frozen or simply working slowly through heavy outline calculations by writing timestamps to a log file or terminal before and after critical milestones:

import sys

def log(message):
    print(message, file=sys.stderr, flush=True)

log(f"Opening {font_path}...")
font = fontforge.open(font_path)
log("Generating outlines...")
font.generate(output_path)
log("Done.")

Using flush=True prevents print buffers from delaying status updates, allowing you to see the exact file and operation that triggered the freeze.