CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-fonttools

Tools to manipulate font files

Pending
Overview
Eval results
Files

variable-fonts.mddocs/

Variable Fonts

Build and manipulate variable fonts using design space documents, supporting multi-axis font variations and instance generation. Variable fonts allow a single font file to contain multiple styles and weights along continuous axes.

Capabilities

Variable Font Building

Core functions for building variable fonts from master sources and design space documents.

def build(designspace, master_finder=lambda s: s, exclude=[], optimize=True, **kwargs):
    """
    Build variable font from designspace document.

    Parameters:
    - designspace: str or DesignSpaceDocument, path to .designspace file or document object
    - master_finder: callable, function to locate master font files
    - exclude: List[str], OpenType tables to exclude from variation  
    - optimize: bool, enable variation optimization
    - kwargs: additional build options

    Returns:
    TTFont: Variable font with variation tables
    """

def build_many(designspace, master_finder=lambda s: s, exclude=[], **kwargs):
    """
    Build multiple variable fonts from designspace v5 document.

    Parameters:
    - designspace: str or DesignSpaceDocument, designspace file or document
    - master_finder: callable, function to locate master font files
    - exclude: List[str], tables to exclude
    - kwargs: additional build options

    Returns:
    List[TTFont]: List of built variable fonts
    """

def load_designspace(designspace):
    """
    Parse and validate designspace document.

    Parameters:
    - designspace: str, path to designspace file

    Returns:
    DesignSpaceDocument: Parsed and validated document
    """

def load_masters(designspace, master_finder):
    """
    Load master fonts referenced in designspace.

    Parameters:
    - designspace: DesignSpaceDocument, design space document
    - master_finder: callable, function to resolve master font paths

    Returns:
    Dict[str, TTFont]: Master fonts keyed by source identifier
    """

Basic Variable Font Building

from fontTools.varLib import build
from fontTools.designspaceLib import DesignSpaceDocument

# Build from designspace file
variable_font = build("MyFont.designspace")
variable_font.save("MyFont-VF.ttf")

# Build with custom master finder
def find_masters(source_path):
    """Custom logic to locate master fonts."""
    return f"masters/{source_path}"

variable_font = build("MyFont.designspace", master_finder=find_masters)

Advanced Building with Options

# Build with optimization and exclusions
variable_font = build(
    "MyFont.designspace",
    exclude=['DSIG', 'hdmx'],  # Exclude problematic tables
    optimize=True,              # Enable delta optimization
    overlap_tolerance=0.5,      # Overlap detection tolerance
    cff_version=2              # Use CFF2 for PostScript outlines
)

# Build multiple fonts from v5 designspace
fonts = build_many("MyFamily.designspace")
for i, font in enumerate(fonts):
    font.save(f"MyFamily-VF-{i}.ttf")

Design Space Documents

Classes for creating and manipulating design space documents that define variable font parameters.

class DesignSpaceDocument:
    def __init__(self):
        """Initialize empty design space document."""

    def read(self, path):
        """
        Read designspace from file.

        Parameters:
        - path: str, path to .designspace file
        """

    def write(self, path):
        """
        Write designspace to file.

        Parameters:
        - path: str, output file path
        """

    def addAxisDescriptor(self, descriptor):
        """
        Add variation axis definition.

        Parameters:
        - descriptor: AxisDescriptor, axis definition
        """

    def addSourceDescriptor(self, descriptor):
        """
        Add master font source.

        Parameters:
        - descriptor: SourceDescriptor, source definition
        """

    def addInstanceDescriptor(self, descriptor):
        """
        Add named instance definition.

        Parameters:
        - descriptor: InstanceDescriptor, instance definition
        """

    def addRuleDescriptor(self, descriptor):
        """
        Add conditional substitution rule.

        Parameters:
        - descriptor: RuleDescriptor, rule definition
        """

Creating Design Space Documents

from fontTools.designspaceLib import (
    DesignSpaceDocument, AxisDescriptor, SourceDescriptor, InstanceDescriptor
)

# Create new design space
doc = DesignSpaceDocument()

# Define weight axis
weight_axis = AxisDescriptor()
weight_axis.name = "Weight"
weight_axis.tag = "wght"
weight_axis.minimum = 100
weight_axis.default = 400
weight_axis.maximum = 900
doc.addAxisDescriptor(weight_axis)

# Define width axis
width_axis = AxisDescriptor()
width_axis.name = "Width"
width_axis.tag = "wdth"
width_axis.minimum = 75
width_axis.default = 100
width_axis.maximum = 125
doc.addAxisDescriptor(width_axis)

# Add master sources
light_source = SourceDescriptor()
light_source.filename = "MyFont-Light.ufo"
light_source.location = {"Weight": 100, "Width": 100}
doc.addSourceDescriptor(light_source)

regular_source = SourceDescriptor()
regular_source.filename = "MyFont-Regular.ufo"
regular_source.location = {"Weight": 400, "Width": 100}
regular_source.copyInfo = True  # Copy font info from this master
doc.addSourceDescriptor(regular_source)

bold_source = SourceDescriptor()
bold_source.filename = "MyFont-Bold.ufo"
bold_source.location = {"Weight": 900, "Width": 100}
doc.addSourceDescriptor(bold_source)

# Add named instances
regular_instance = InstanceDescriptor()
regular_instance.name = "Regular"
regular_instance.location = {"Weight": 400, "Width": 100}
doc.addInstanceDescriptor(regular_instance)

bold_instance = InstanceDescriptor()
bold_instance.name = "Bold"
bold_instance.location = {"Weight": 700, "Width": 100}
doc.addInstanceDescriptor(bold_instance)

# Save design space
doc.write("MyFont.designspace")

Descriptor Classes

Classes that define the components of a design space document.

class AxisDescriptor:
    def __init__(self):
        """Initialize axis descriptor."""
        
    name: str          # Human-readable axis name
    tag: str           # 4-character axis tag
    minimum: float     # Minimum axis value
    default: float     # Default axis value
    maximum: float     # Maximum axis value
    hidden: bool       # Hide axis from user interfaces
    labelNames: Dict   # Localized axis names

class SourceDescriptor:
    def __init__(self):
        """Initialize source descriptor."""
        
    filename: str           # Path to master font file
    location: Dict          # Axis coordinates for this master
    copyInfo: bool          # Copy font info from this master
    copyGroups: bool        # Copy glyph groups
    copyLib: bool           # Copy font lib
    copyFeatures: bool      # Copy OpenType features
    muteKerning: bool       # Disable kerning from this master
    mutedGlyphNames: List   # Glyphs to exclude from this master

class InstanceDescriptor:
    def __init__(self):
        """Initialize instance descriptor."""
        
    name: str              # Instance name
    location: Dict         # Axis coordinates
    styleName: str         # Style name for naming table
    familyName: str        # Family name override
    postScriptFontName: str # PostScript name
    styleMapFamilyName: str # Style map family name
    styleMapStyleName: str  # Style map style name
    
class RuleDescriptor:
    def __init__(self):
        """Initialize rule descriptor."""
        
    name: str                    # Rule name
    conditionSets: List[Dict]    # Conditions when rule applies
    subs: List[Tuple[str, str]]  # Glyph substitutions (from, to)

Variable Font Utilities

Utility functions for variable font manipulation and optimization.

def set_default_weight_width_slant(font, axes_config=None):
    """
    Set default axis values in STAT table.

    Parameters:
    - font: TTFont, variable font
    - axes_config: Dict, axis configuration overrides
    """

def drop_implied_oncurve_points(font):
    """
    Optimize TrueType curves by dropping implied on-curve points.

    Parameters:
    - font: TTFont, font to optimize
    """

def addGSUBFeatureVariations(font, conditional_substitutions):
    """
    Add GSUB feature variations for conditional substitutions.

    Parameters:
    - font: TTFont, variable font
    - conditional_substitutions: Dict, feature variation definitions
    """

Variable Font Optimization

from fontTools.varLib import set_default_weight_width_slant, drop_implied_oncurve_points

# Optimize variable font
variable_font = build("MyFont.designspace")

# Set default axes values for better browser support
set_default_weight_width_slant(variable_font, {
    'wght': 400,  # Regular weight as default
    'wdth': 100   # Normal width as default
})

# Optimize TrueType curves
drop_implied_oncurve_points(variable_font)

variable_font.save("MyFont-VF-Optimized.ttf")

Master Font Finding

Utilities for locating and validating master font files.

class MasterFinder:
    def __init__(self, template):
        """
        Template-based master font finder.

        Parameters:
        - template: str, path template with placeholders
        """

    def __call__(self, source_path):
        """
        Find master font file.

        Parameters:
        - source_path: str, source file path from designspace

        Returns:
        str: Resolved master font path
        """

Custom Master Finding

from fontTools.varLib import MasterFinder

# Template-based finder
finder = MasterFinder("masters/{stem}.ufo")

# Custom finder function
def custom_finder(source_path):
    """Custom logic for finding masters."""
    import os
    
    # Look in multiple directories
    search_paths = ["masters/", "sources/", "fonts/"]
    
    for search_dir in search_paths:
        full_path = os.path.join(search_dir, source_path)
        if os.path.exists(full_path):
            return full_path
    
    raise FileNotFoundError(f"Master not found: {source_path}")

# Use with build
variable_font = build("MyFont.designspace", master_finder=custom_finder)

Feature Variations

Support for conditional OpenType feature substitutions in variable fonts.

# Add feature variations for different weights
feature_variations = {
    'liga': [
        {
            'conditions': [{'wght': (400, 900)}],  # Bold weights
            'substitutions': [('f_i', 'f_i.bold')]  # Use bold ligature
        }
    ]
}

addGSUBFeatureVariations(variable_font, feature_variations)

Error Handling

Variable font building may raise various exceptions:

  • FileNotFoundError: Master font files not found
  • DesignSpaceDocumentError: Invalid designspace document
  • VarLibError: Variable font building errors
  • InterpolationError: Incompatible masters for interpolation

Integration Examples

def build_font_family_variable(designspace_path, output_dir):
    """Build variable font family with proper naming."""
    import os
    from pathlib import Path
    
    # Load and validate designspace
    doc = load_designspace(designspace_path)
    
    # Build variable font
    vf = build(doc, optimize=True)
    
    # Set up proper naming
    name_table = vf['name']
    family_name = doc.default.familyName or "Variable Font"
    
    # Update name table entries
    name_table.setName(family_name, 1, 3, 1, 0x409)  # Family name
    name_table.setName("Variable", 2, 3, 1, 0x409)   # Subfamily name
    
    # Save with proper filename
    output_path = Path(output_dir) / f"{family_name.replace(' ', '')}-VF.ttf"
    vf.save(output_path)
    
    return output_path

def validate_masters_compatibility(designspace_path):
    """Validate that masters are compatible for interpolation."""
    doc = load_designspace(designspace_path)
    masters = load_masters(doc, lambda s: s)
    
    # Check glyph compatibility
    reference_glyphs = None
    for source, font in masters.items():
        glyph_set = set(font.getGlyphOrder())
        
        if reference_glyphs is None:
            reference_glyphs = glyph_set
        elif glyph_set != reference_glyphs:
            missing = reference_glyphs - glyph_set
            extra = glyph_set - reference_glyphs
            print(f"Master {source} compatibility issues:")
            if missing:
                print(f"  Missing glyphs: {missing}")
            if extra:
                print(f"  Extra glyphs: {extra}")
    
    return len(masters)

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