How to Transform Glyphs Using Matrix in FontForge

This guide explains how to apply mathematical 2D transformation matrices to glyph contours in FontForge. You will learn the mechanics behind FontForge's six-element affine transformation arrays, how to input custom matrix values through the graphical user interface, and how to automate the transformation process using the FontForge Python scripting environment.

Understanding the FontForge Transformation Matrix

FontForge represents 2D affine transformations using a six-element array: [a, b, c, d, x, y]. This array maps an original coordinate point \((X, Y)\) to a new coordinate point \((X', Y')\) using the following linear equations:

  • \(X' = a \cdot X + c \cdot Y + x\)
  • \(Y' = b \cdot X + d \cdot Y + y\)

In this structure:

  • \(a\): Horizontal scaling factor.
  • \(b\): Vertical shear (skew along the Y-axis).
  • \(c\): Horizontal shear (skew along the X-axis).
  • \(d\): Vertical scaling factor.
  • \(x\): Horizontal translation (movement along the X-axis in font units).
  • \(y\): Vertical translation (movement along the Y-axis in font units).

Standard Matrix Configurations

  • Identity (No Change): [1, 0, 0, 1, 0, 0]
  • Translation: [1, 0, 0, 1, dx, dy]
  • Scaling: [sx, 0, 0, sy, 0, 0]
  • Horizontal Slant (Italicize): [1, 0, tan(θ), 1, 0, 0] where \(\theta\) is the slant angle.
  • Rotation: [cos(θ), sin(θ), -sin(θ), cos(θ), 0, 0] where \(\theta\) is the rotation angle in radians.

Applying Matrices via the Graphical User Interface

  1. Open your font file in FontForge.
  2. Select one or more glyphs in the Font View, or double-click a single glyph to open the Glyph View.
  3. Navigate to the top menu and select Element > Transformations > Transform... (or press Ctrl+Shift+E / Cmd+Shift+E).
  4. In the transformation dialog window, change the first drop-down menu from its default (such as Origin or Move) to Matrix.
  5. Input your six matrix parameters into the designated fields:
    • [ a ] [ c ] [ x ]
    • [ b ] [ d ] [ y ]
  6. Click OK to execute the transformation on the selected contours.

Applying Matrices via Python Scripting

FontForge includes a built-in Python module named psMat designed for matrix generation, concatenation, and transformation.

Example: Custom Matrix Transformation

To apply a custom matrix directly to a glyph, pass a tuple of six numerical values to the glyph.transform() method:

import fontforge

# Open the font file
font = fontforge.open("MyFont.sfd")

# Select a target glyph
glyph = font["A"]

# Define a matrix: [a, b, c, d, x, y]
# This example scales X by 1.2, shears X by 0.2, and shifts Y by 50 units
custom_matrix = (1.2, 0.0, 0.2, 1.0, 0.0, 50.0)

# Apply the transformation
glyph.transform(custom_matrix)

# Save changes
font.save()

Example: Combining Transformations with psMat

The psMat module allows you to compose complex matrices without manual calculation:

import fontforge
import psMat
import math

font = fontforge.open("MyFont.sfd")
glyph = font["B"]

# Generate component matrices
scale = psMat.scale(1.1, 0.9)          # Scale X by 110%, Y by 90%
rotate = psMat.rotate(math.radians(15)) # Rotate 15 degrees counter-clockwise
translate = psMat.translate(20, -10)    # Move 20 units right, 10 units down

# Multiply matrices together (applied from right to left)
combined_matrix = psMat.compose(scale, rotate)
final_matrix = psMat.compose(combined_matrix, translate)

# Apply to glyph
glyph.transform(final_matrix)

font.save()

Post-Transformation Clean-Up

Matrix operations often alter point positioning in ways that disrupt font standards. After applying transformations:

  1. Round Coordinates to Integers: PostScript and TrueType outlines typically require whole integer coordinates. Run Element > Round > To Int (or glyph.round() in Python).
  2. Correct Extrema: Rotations and shears move on-curve extrema. Run Element > Add Extrema (or glyph.addExtrema()) to maintain proper curve rendering.
  3. Correct Direction: Reflection matrices (negative scaling factors) invert path winding order. Run Element > Correct Direction (or glyph.correctDirection()) to ensure internal counters render as cutouts rather than solid fills.