How to Extract Vector Coordinates in FontForge Python

Extracting vector path coordinates from glyphs is a common task in font engineering, procedural geometry, and digital fabrication. FontForge provides a built-in Python module that offers full programmatic access to glyph outlines, contours, and individual nodes. This guide demonstrates how to open a font, target specific glyphs, iterate through their vector paths, and store the resulting \((x, y)\) coordinate points into standard Python lists.

Understanding the FontForge Geometry Hierarchy

Vector glyphs in FontForge are structured hierarchically:

  1. Glyph: The character container containing multiple layers.
  2. Layer: Typically the foreground layer, which contains the visual artwork.
  3. Contour: An individual path (or outline) made up of interconnected points.
  4. Point: An individual node containing coordinates (x, y) and attributes such as whether it is on-curve or off-curve (control point).

Code Example: Extracting Coordinates to a List

You can run the following script using the FontForge Python interpreter (fontforge -script script.py) or via an external Python environment where the fontforge package is compiled and available.

import fontforge

def extract_glyph_coordinates(font_path, glyph_name):
    """
    Extracts vector coordinates from a specified glyph into a list.
    Returns a list of contours, where each contour is a list of coordinate tuples.
    """
    # Open the font file
    font = fontforge.open(font_path)

    # Verify the glyph exists
    if glyph_name not in font:
        font.close()
        raise ValueError(f"Glyph '{glyph_name}' not found in {font_path}")

    glyph = font[glyph_name]
    
    # Access the active outline layer (foreground)
    layer = glyph.foreground
    
    glyph_paths = []

    # Iterate through each contour in the layer
    for contour in layer:
        contour_points = []
        
        # Iterate through each point in the contour
        for point in contour:
            # Extract x, y coordinates as a tuple
            # You can also capture point.on_curve (True/False) if needed
            contour_points.append((point.x, point.y))
            
        glyph_paths.append(contour_points)

    font.close()
    return glyph_paths

# Example usage:
if __name__ == "__main__":
    font_file = "MyFont.ttf"
    target_glyph = "A"

    coordinates = extract_glyph_coordinates(font_file, target_glyph)
    print(coordinates)

Parsing the Extracted Data

The resulting data structure is a nested list:

[
    [(100.0, 0.0), (250.0, 700.0), (350.0, 700.0), (500.0, 0.0)],  # Outer contour
    [(200.0, 200.0), (300.0, 500.0), (400.0, 200.0)]               # Inner contour (counter)
]
  • Nested List (Contours): Glyphs with cutouts (such as the letter "O" or "A") contain multiple contours. Preserving each contour as a sublist ensures the path winding and individual vector loops remain separated.
  • Flattened List: If you only require a flat list of all vertices regardless of contour boundaries, you can flatten the output using list comprehension:
flat_coordinates = [point for contour in coordinates for point in contour]

Capturing Point Types (On-Curve vs. Off-Curve)

Bézier splines rely on both on-curve anchor points and off-curve control points. If your target application needs to distinguish between anchor points and handles, include the on_curve attribute:

contour_points.append({
    "x": point.x,
    "y": point.y,
    "on_curve": point.on_curve
})

This ensures complete vector fidelity when exporting geometry to formats such as SVG, JSON, or custom CAD workflows.