CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-fonttools

Tools to manipulate font files

Pending
Overview
Eval results
Files

font-building.mddocs/

Font Building

Build fonts from scratch using a builder pattern, supporting both TrueType and CFF/PostScript font creation with comprehensive table setup methods. FontBuilder provides a structured approach to creating fonts programmatically with proper table dependencies and validation.

Capabilities

FontBuilder Class

The primary class for constructing fonts from scratch using a builder pattern with sequential setup methods.

class FontBuilder:
    def __init__(self, unitsPerEm=None, font=None, isTTF=True):
        """
        Initialize font builder.

        Parameters:
        - unitsPerEm: int, units per em (default: 1000)
        - font: TTFont, existing font to modify (default: new font)
        - isTTF: bool, True for TrueType, False for CFF/PostScript
        """

    def setupGlyphOrder(self, glyphOrder):
        """
        Set glyph names list (must be called first).

        Parameters:
        - glyphOrder: List[str], ordered list of glyph names
        """

    def setupCharacterMap(self, cmap):
        """
        Set character-to-glyph mapping.

        Parameters:
        - cmap: Dict[int, str], Unicode codepoint to glyph name mapping
        """

    def setupGlyf(self, glyphs):
        """
        Set TrueType glyph data (for TrueType fonts).

        Parameters:
        - glyphs: Dict[str, Glyph], glyph name to glyph object mapping
        """

    def setupCFF(self, psName, charStrings, charStringsType=2):
        """
        Set CFF/PostScript data (for OpenType fonts).

        Parameters:
        - psName: str, PostScript font name
        - charStrings: Dict[str, CharString], glyph name to CharString mapping
        - charStringsType: int, CharString format version (1 or 2)
        """

    def setupHorizontalMetrics(self, metrics):
        """
        Set glyph advance widths and left side bearings.

        Parameters:
        - metrics: Dict[str, Tuple[int, int]], glyph name to (advance, lsb) mapping
        """

    def setupHorizontalHeader(self, ascent=None, descent=None):
        """
        Set horizontal metrics header.

        Parameters:
        - ascent: int, font ascent (default: calculated from metrics)
        - descent: int, font descent (default: calculated from metrics)
        """

    def setupNameTable(self, nameStrings, mac=True):
        """
        Set font naming information.

        Parameters:
        - nameStrings: Dict[str, str], name ID to string mapping
        - mac: bool, include Mac platform names
        """

    def setupOS2(self, sTypoAscender=None, sTypoDescender=None, usWinAscent=None, usWinDescent=None):
        """
        Set OS/2 table with font metrics and properties.

        Parameters:
        - sTypoAscender: int, typographic ascender
        - sTypoDescender: int, typographic descender  
        - usWinAscent: int, Windows ascent
        - usWinDescent: int, Windows descent
        """

    def setupPost(self, keepGlyphNames=True, extraNames=[]):
        """
        Set PostScript table.

        Parameters:
        - keepGlyphNames: bool, include glyph names in font
        - extraNames: List[str], additional glyph names beyond standard set
        """

Basic Font Building Example

from fontTools.fontBuilder import FontBuilder
from fontTools.pens.ttGlyphPen import TTGlyphPen

# Initialize builder for TrueType font
fb = FontBuilder(unitsPerEm=1000, isTTF=True)

# 1. Set glyph order (must be first)
glyph_order = [".notdef", "A", "B", "space"]
fb.setupGlyphOrder(glyph_order)

# 2. Set character mapping
cmap = {
    ord('A'): 'A',
    ord('B'): 'B', 
    ord(' '): 'space'
}
fb.setupCharacterMap(cmap)

# 3. Create glyphs using pen
glyphs = {}

# Create .notdef glyph
pen = TTGlyphPen(None)
pen.moveTo((100, 0))
pen.lineTo((100, 750))
pen.lineTo((400, 750))
pen.lineTo((400, 0))
pen.closePath()
glyphs[".notdef"] = pen.glyph()

# Create letter A
pen = TTGlyphPen(None)
pen.moveTo((200, 0))
pen.lineTo((100, 700))
pen.lineTo((150, 700))
pen.lineTo((300, 700))
pen.lineTo((350, 700))
pen.lineTo((250, 0))
pen.closePath()
# Add crossbar
pen.moveTo((175, 350))
pen.lineTo((275, 350))
pen.lineTo((275, 400))
pen.lineTo((175, 400))
pen.closePath()
glyphs["A"] = pen.glyph()

# Simple rectangle for B
pen = TTGlyphPen(None)
pen.moveTo((100, 0))
pen.lineTo((100, 700))
pen.lineTo((300, 700))
pen.lineTo((300, 0))
pen.closePath()
glyphs["B"] = pen.glyph()

# Empty glyph for space
pen = TTGlyphPen(None)
glyphs["space"] = pen.glyph()

fb.setupGlyf(glyphs)

# 4. Set metrics (advance width, left side bearing)
metrics = {
    ".notdef": (500, 100),
    "A": (450, 100),
    "B": (400, 100),
    "space": (250, 0)
}
fb.setupHorizontalMetrics(metrics)

# 5. Set font header
fb.setupHorizontalHeader(ascent=800, descent=-200)

# 6. Set font names
names = {
    "familyName": "TestFont",
    "styleName": "Regular",
    "uniqueFontIdentifier": "TestFont-Regular",
    "fullName": "TestFont Regular",
    "psName": "TestFont-Regular",
    "version": "1.0"
}
fb.setupNameTable(names)

# 7. Set OS/2 table
fb.setupOS2(sTypoAscender=800, sTypoDescender=-200, usWinAscent=800, usWinDescent=200)

# 8. Set PostScript table
fb.setupPost()

# Get the built font
font = fb.font
font.save("test_font.ttf")

Variable Font Setup

Additional methods for creating variable fonts with multiple axes and instances.

def setupFvar(self, axes, instances):
    """
    Set up font variations (variable font axes and instances).

    Parameters:
    - axes: List[Dict], axis definitions with name, tag, min, default, max values
    - instances: List[Dict], named instances with coordinates and names
    """

def setupAvar(self, axes):
    """
    Set up axis variation mapping (non-linear axis mapping).

    Parameters:
    - axes: Dict[str, List[Tuple[float, float]]], axis tag to mapping pairs
    """

def setupGvar(self, variations):
    """
    Set up glyph variations (delta coordinates for each axis).

    Parameters:
    - variations: Dict[str, Dict], glyph name to variation deltas mapping
    """

Variable Font Example

# Build variable font with weight axis
fb = FontBuilder(unitsPerEm=1000, isTTF=True)

# ... basic setup steps as above ...

# Define weight axis
axes = [{
    'name': 'Weight',
    'tag': 'wght',
    'minValue': 100,
    'defaultValue': 400,
    'maxValue': 900
}]

# Define instances
instances = [{
    'name': 'Light',
    'coordinates': {'wght': 200}
}, {
    'name': 'Regular', 
    'coordinates': {'wght': 400}
}, {
    'name': 'Bold',
    'coordinates': {'wght': 700}
}]

fb.setupFvar(axes, instances)

# Set up glyph variations (simplified example)
variations = {
    'A': {
        'wght': [
            # Delta coordinates for different weight values
            {'coordinates': [10, 0, 15, 0, 20, 0], 'support': [0, 1, 1]}
        ]
    }
}
fb.setupGvar(variations)

Color Font Setup

Methods for creating color fonts using COLR/CPAL tables.

def setupCOLR(self, colorLayers):
    """
    Set up color layer definitions (COLR table).

    Parameters:
    - colorLayers: Dict[str, List[Tuple[str, int]]], base glyph to layers mapping
    """

def setupCPAL(self, palettes):
    """
    Set up color palettes (CPAL table).

    Parameters:
    - palettes: List[List[Tuple[float, float, float, float]]], color palettes with RGBA values
    """

Color Font Example

# Create color font with emoji-style glyphs
fb = FontBuilder(unitsPerEm=1000, isTTF=True)

# ... basic setup ...

# Define color layers
color_layers = {
    'heart': [
        ('heart.base', 0),  # Base shape uses palette color 0
        ('heart.highlight', 1)  # Highlight uses palette color 1
    ]
}
fb.setupCOLR(color_layers)

# Define color palette
palettes = [[
    (1.0, 0.0, 0.0, 1.0),  # Red (RGBA)
    (1.0, 0.5, 0.5, 1.0)   # Light red
]]
fb.setupCPAL(palettes)

Font Validation

FontBuilder automatically handles many validation requirements, but additional checks can be performed:

# Validate required tables are present
font = fb.font
required_tables = ['head', 'hhea', 'hmtx', 'maxp', 'name', 'cmap', 'post']

for table_tag in required_tables:
    if table_tag not in font:
        print(f"Warning: Missing required table {table_tag}")

# Validate glyph metrics consistency
if 'hmtx' in font and 'hhea' in font:
    hmtx = font['hmtx']
    hhea = font['hhea']
    if hhea.numberOfHMetrics != len(hmtx.metrics):
        print("Warning: Inconsistent horizontal metrics count")

Common Font Building Patterns

Building from Vector Data

from fontTools.misc.bezierTools import calcBounds

def build_glyph_from_contours(contours):
    """Build glyph from list of contour point lists."""
    pen = TTGlyphPen(None)
    
    for contour in contours:
        if not contour:
            continue
            
        pen.moveTo(contour[0])
        for point in contour[1:]:
            pen.lineTo(point)
        pen.closePath()
    
    return pen.glyph()

# Calculate bounding box for metrics
def calc_glyph_bounds(glyph):
    """Calculate bounding box for glyph."""
    if hasattr(glyph, 'coordinates'):
        coords = glyph.coordinates
        if coords:
            return calcBounds(coords)
    return (0, 0, 0, 0)

Batch Font Generation

def create_font_family(base_glyphs, family_name, styles):
    """Create multiple fonts in a family."""
    fonts = {}
    
    for style_name, style_params in styles.items():
        fb = FontBuilder(unitsPerEm=1000, isTTF=True)
        
        # Apply style-specific modifications to glyphs
        styled_glyphs = apply_style_to_glyphs(base_glyphs, style_params)
        
        # ... setup steps ...
        
        names = {
            "familyName": family_name,
            "styleName": style_name,
            "fullName": f"{family_name} {style_name}",
            # ... other names
        }
        fb.setupNameTable(names)
        
        fonts[style_name] = fb.font
    
    return fonts

Error Handling

FontBuilder methods may raise exceptions for invalid parameters or missing dependencies:

  • ValueError: Invalid parameters or missing required data
  • KeyError: Referenced glyph names not in glyph order
  • TypeError: Incorrect parameter types
  • AttributeError: Missing required attributes in glyph objects

Always ensure proper setup sequence and validate input data before font building operations.

Install with Tessl CLI

npx tessl i tessl/pypi-fonttools

docs

core-font-operations.md

drawing-pens.md

font-building.md

font-processing.md

index.md

utilities-tools.md

variable-fonts.md

tile.json