Tools to manipulate font files
—
Font optimization, subsetting, and merging tools for reducing file sizes, removing unused features, and combining multiple fonts into collections. These tools are essential for web font optimization and font distribution workflows.
Remove unused glyphs, features, and tables to create optimized fonts with smaller file sizes.
class Subsetter:
def __init__(self, options=None):
"""
Initialize font subsetter.
Parameters:
- options: Options, subsetting configuration (default: default options)
"""
def subset(self, font):
"""
Apply subsetting to TTFont instance.
Parameters:
- font: TTFont, font to subset (modified in-place)
"""
class Options:
def __init__(self, **kwargs):
"""
Subsetting configuration options.
Parameters:
- text: str, characters to keep (default: None)
- unicodes: List[int], Unicode codepoints to keep
- glyphs: List[str], glyph names to keep
- layout_features: List[str], OpenType features to keep
- name_IDs: List[int], name table entries to keep
- name_languages: List[int], name table languages to keep
- drop_tables: List[str], tables to remove
- passthrough_tables: bool, keep unhandled tables
- hinting: bool, keep hinting information
- legacy_kern: bool, keep legacy kern table
- symbol_cmap: bool, keep symbol character maps
- ignore_missing_glyphs: bool, continue if glyphs missing
- ignore_missing_unicodes: bool, continue if Unicode missing
"""from fontTools.subset import Subsetter, Options
from fontTools.ttLib import TTFont
# Load font
font = TTFont("input.ttf")
# Create subsetter with options
options = Options(
text="Hello World!", # Keep only these characters
layout_features=['kern', 'liga'], # Keep kerning and ligatures
hinting=True, # Preserve hinting
ignore_missing_glyphs=True # Don't fail on missing glyphs
)
subsetter = Subsetter(options)
subsetter.subset(font)
# Save subsetted font
font.save("output_subset.ttf")
font.close()# Subset by Unicode ranges
options = Options(
unicodes=list(range(0x20, 0x7F)) + list(range(0xA0, 0xFF)), # Basic Latin + Latin-1
layout_features=['kern', 'liga', 'clig', 'frac'],
drop_tables=['DSIG', 'GSUB', 'GPOS'], # Remove specific tables
name_IDs=[1, 2, 3, 4, 5, 6], # Keep essential name entries
hinting=False # Remove hinting for smaller size
)
subsetter = Subsetter(options)
subsetter.subset(font)def optimize_for_web(input_path, output_path, text_content):
"""Optimize font for web usage."""
font = TTFont(input_path)
# Web-optimized subsetting
options = Options(
text=text_content,
layout_features=['kern', 'liga'], # Common web features
drop_tables=['DSIG', 'prep', 'fpgm', 'cvt '], # Remove hinting tables
hinting=False, # Remove hinting
desubroutinize=True, # Flatten CFF subroutines
passthrough_tables=False # Drop unknown tables
)
subsetter = Subsetter(options)
subsetter.subset(font)
font.save(output_path)
font.close()
print(f"Optimized font saved to {output_path}")Combine multiple fonts into a single font or font collection.
class Merger:
def __init__(self, options=None):
"""
Initialize font merger.
Parameters:
- options: Options, merging configuration
"""
def merge(self, fontfiles):
"""
Merge multiple fonts into single font.
Parameters:
- fontfiles: List[str], paths to font files to merge
Returns:
TTFont: Merged font
"""
class Options:
def __init__(self, **kwargs):
"""
Font merging options.
Parameters:
- verbose: bool, enable verbose output
- timing: bool, report timing information
- ignore_missing_glyphs: bool, continue on missing glyphs
- drop_tables: List[str], tables to exclude from merge
"""from fontTools.merge import Merger, Options
from fontTools.ttLib import TTFont
# Merge multiple fonts
merger = Merger()
merged_font = merger.merge(["font1.ttf", "font2.ttf", "font3.ttf"])
# Save merged result
merged_font.save("merged_font.ttf")
merged_font.close()# Configure merging options
options = Options(
verbose=True,
ignore_missing_glyphs=True,
drop_tables=['DSIG'] # Drop digital signatures
)
merger = Merger(options)
# Merge fonts with specific character ranges
latin_font = TTFont("latin.ttf")
cyrillic_font = TTFont("cyrillic.ttf")
arabic_font = TTFont("arabic.ttf")
merged_font = merger.merge([latin_font, cyrillic_font, arabic_font])
merged_font.save("multilingual.ttf")Convert fonts to/from TTX XML format for human-readable font analysis and modification.
def main(args=None):
"""
Convert OpenType fonts to XML and back.
Parameters:
- args: List[str], command line arguments
Common options:
- -d: dump font to XML
- -m: merge XML back to font
- -t: specify tables to dump/compile
- -x: exclude specific tables
- -s: split tables into separate files
- -i: don't disassemble TT instructions
- -z: use WOFF2 compression
"""import subprocess
# Convert font to XML
subprocess.run(["fonttools", "ttx", "font.ttf"]) # Creates font.ttx
# Convert specific tables only
subprocess.run(["fonttools", "ttx", "-t", "cmap", "-t", "name", "font.ttf"])
# Convert XML back to font
subprocess.run(["fonttools", "ttx", "font.ttx"]) # Creates font.ttf
# Split each table into separate XML file
subprocess.run(["fonttools", "ttx", "-s", "font.ttf"])FontTools provides command-line tools for batch processing:
def main(args=None):
"""
Font subsetting command-line interface.
Parameters:
- args: List[str], command line arguments
"""# Subset font to specific text
fonttools subset font.ttf --text="Hello World"
# Subset with Unicode ranges
fonttools subset font.ttf --unicodes="U+0020-007F,U+00A0-00FF"
# Web font optimization
fonttools subset font.ttf --text-file=content.txt --layout-features="kern,liga" --no-hinting
# Merge fonts
fonttools merge font1.ttf font2.ttf font3.ttf
# TTX conversion
fonttools ttx font.ttf # to XML
fonttools ttx font.ttx # to binaryclass SubsettingError(Exception):
"""Base subsetting exception."""
class MissingGlyphsSubsettingError(SubsettingError):
"""Required glyphs are missing from font."""
class MissingUnicodesSubsettingError(SubsettingError):
"""Required Unicode codepoints are missing from font."""def process_font_directory(input_dir, output_dir, text_content):
"""Process all fonts in a directory."""
import os
from pathlib import Path
input_path = Path(input_dir)
output_path = Path(output_dir)
output_path.mkdir(exist_ok=True)
for font_file in input_path.glob("*.ttf"):
try:
font = TTFont(font_file)
# Apply subsetting
options = Options(text=text_content, hinting=False)
subsetter = Subsetter(options)
subsetter.subset(font)
# Save with suffix
output_file = output_path / f"{font_file.stem}_subset.ttf"
font.save(output_file)
font.close()
print(f"Processed: {font_file.name} -> {output_file.name}")
except Exception as e:
print(f"Error processing {font_file.name}: {e}")
# Usage
process_font_directory("input_fonts/", "output_fonts/", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")def optimize_subsetting_performance(font_paths, common_options):
"""Optimize subsetting for multiple fonts with shared options."""
# Reuse subsetter instance for better performance
subsetter = Subsetter(common_options)
for font_path in font_paths:
font = TTFont(font_path)
# Create a copy for subsetting (preserves original)
font_copy = TTFont()
font_copy.importXML(font.saveXML())
subsetter.subset(font_copy)
output_path = font_path.replace('.ttf', '_subset.ttf')
font_copy.save(output_path)
font.close()
font_copy.close()def create_web_font_variants(font_path, text_samples):
"""Create multiple optimized variants for different use cases."""
base_font = TTFont(font_path)
variants = {}
# Full feature set (headlines)
headline_options = Options(
text=text_samples['headlines'],
layout_features=['kern', 'liga', 'clig', 'dlig'],
hinting=True
)
variants['headline'] = create_subset(base_font, headline_options)
# Basic features (body text)
body_options = Options(
text=text_samples['body'],
layout_features=['kern'],
hinting=False # Smaller size for body text
)
variants['body'] = create_subset(base_font, body_options)
# Minimal (fallback)
fallback_options = Options(
text=text_samples['essential'],
layout_features=[],
hinting=False
)
variants['fallback'] = create_subset(base_font, fallback_options)
return variants
def create_subset(base_font, options):
"""Helper to create font subset."""
# Create deep copy to avoid modifying original
font = TTFont()
font.importXML(base_font.saveXML())
subsetter = Subsetter(options)
subsetter.subset(font)
return fontInstall with Tessl CLI
npx tessl i tessl/pypi-fonttools