Font spacing management library for UFO-compatible fonts (kerning, margins, groups)
Project description
UFO Spacing Library
A framework-agnostic Python library for managing font spacing (kerning and margins) with full undo/redo support. Designed to work with UFO-compatible font objects.
Features
- Framework Independent: Works with any font editor that provides compatible font objects
- Undo/Redo Support: Full command pattern implementation with unlimited history
- Multi-Font Operations: Support for linked/interpolated fonts with per-font scaling
- Preview/Simulation: VirtualFont wrapper for testing changes without modifying real font
- Composite Propagation: Automatic margin propagation to composite glyphs
- Well Documented: Comprehensive docstrings and type hints throughout
Installation
# From PyPI (when published)
pip install ufo-spacing-lib
# From source
pip install -e .
# Or with uv
uv pip install -e .
Quick Start
Kerning Operations
from ufo_spacing_lib import (
KerningEditor,
FontContext,
AdjustKerningCommand,
SetKerningCommand,
RemoveKerningCommand,
)
# Create an editor
editor = KerningEditor()
# Create a context for your font
context = FontContext.from_single_font(font)
# Adjust kerning by a delta
cmd = AdjustKerningCommand(pair=('A', 'V'), delta=-10)
result = editor.execute(cmd, context)
# Set kerning to absolute value
cmd = SetKerningCommand(pair=('A', 'V'), value=-50)
editor.execute(cmd, context)
# Remove a kerning pair
cmd = RemoveKerningCommand(pair=('A', 'V'))
editor.execute(cmd, context)
# Undo/Redo
editor.undo()
editor.redo()
Margins Operations
from ufo_spacing_lib import (
MarginsEditor,
FontContext,
AdjustMarginCommand,
SetMarginCommand,
)
editor = MarginsEditor()
context = FontContext.from_single_font(font)
# Adjust left margin (propagates to composites by default)
cmd = AdjustMarginCommand(
glyph_name='A',
side='left',
delta=10,
propagate_to_composites=True
)
editor.execute(cmd, context)
# Set right margin to absolute value
cmd = SetMarginCommand(
glyph_name='A',
side='right',
value=50
)
editor.execute(cmd, context)
Multi-Font Operations (Interpolation)
# Create context for multiple fonts with scaling
context = FontContext.from_linked_fonts(
fonts=[light_master, regular_master, bold_master],
primary=regular_master,
scales={
light_master: 0.8,
regular_master: 1.0,
bold_master: 1.3
}
)
# Command applies to all fonts with appropriate scaling
cmd = AdjustKerningCommand(pair=('A', 'V'), delta=-10)
editor.execute(cmd, context)
# light_master: -8, regular_master: -10, bold_master: -13
Event Callbacks
def on_kerning_change(command, result):
print(f"Kerning changed: {command.description}")
refresh_ui()
editor.on_change = on_kerning_change
editor.on_undo = on_kerning_change
editor.on_redo = on_kerning_change
Preview/Simulation (VirtualFont)
from ufo_spacing_lib import VirtualFont, FontContext, AdjustKerningCommand
# Create virtual copy - isolates kerning/groups changes
virtual = VirtualFont.from_font(font)
# Work as usual - changes only affect virtual.kerning/groups
context = FontContext.from_single_font(virtual)
cmd = AdjustKerningCommand(pair=('A', 'V'), delta=-10)
editor.execute(cmd, context)
# Glyphs are live references - changes in font visible through virtual
print(virtual['A'].leftMargin) # Same as font['A'].leftMargin
# Check what changed
if virtual.has_changes():
for pair, (old, new) in virtual.get_kerning_diff().items():
print(f"{pair}: {old} -> {new}")
# Apply to real font when ready, or reset
virtual.apply_to(font) # Writes changes to font
# virtual.reset() # Discards all changes
Architecture
ufo_spacing_lib/
├── __init__.py # Main exports
├── contexts.py # FontContext class
├── groups_core.py # FontGroupsManager, KernPairInfo, resolve_kern_pair
├── virtual.py # VirtualFont for preview/simulation
├── commands/
│ ├── __init__.py
│ ├── base.py # Command ABC, CommandResult
│ ├── kerning.py # Kerning commands
│ └── margins.py # Margins commands
└── editors/
├── __init__.py
├── kerning.py # KerningEditor
└── margins.py # MarginsEditor
Font Object Interface
The library is designed to work with any font object that implements this interface:
For Kerning Operations
class FontKerning:
"""Dict-like kerning access."""
def __getitem__(self, pair: Tuple[str, str]) -> int: ...
def __setitem__(self, pair: Tuple[str, str], value: int): ...
def __delitem__(self, pair: Tuple[str, str]): ...
def __contains__(self, pair: Tuple[str, str]) -> bool: ...
def get(self, pair: Tuple[str, str], default=None) -> Optional[int]: ...
class Font:
kerning: FontKerning
For Margins Operations
class Glyph:
leftMargin: Optional[int]
rightMargin: Optional[int]
width: int
components: List[Component] # Optional
def moveBy(self, delta: Tuple[int, int]): ...
def changed(self): ... # Optional
class Component:
offset: Tuple[int, int]
def moveBy(self, delta: Tuple[int, int]): ...
class Font:
def __getitem__(self, glyph_name: str) -> Glyph: ...
def __contains__(self, glyph_name: str) -> bool: ...
def getReverseComponentMapping(self) -> Dict[str, List[str]]: ... # Optional
Commands Reference
Kerning Commands
| Command | Description |
|---|---|
SetKerningCommand(pair, value) |
Set kerning to absolute value |
AdjustKerningCommand(pair, delta) |
Adjust kerning by delta |
RemoveKerningCommand(pair) |
Remove a kerning pair |
CreateExceptionCommand(pair, value, side) |
Create kerning exception |
Margins Commands
| Command | Description |
|---|---|
SetMarginCommand(glyph, side, value) |
Set margin to absolute value |
AdjustMarginCommand(glyph, side, delta) |
Adjust margin by delta |
All commands support:
- Multi-font operations via
FontContext - Per-font scaling
- Full undo/redo
Testing
The library includes 100+ unit tests covering all components.
# Run all tests
PYTHONPATH=src python3 -m unittest discover -s tests -v
# Run specific test module
PYTHONPATH=src python3 -m unittest tests.test_kerning_commands -v
# With pytest (if installed)
PYTHONPATH=src python3 -m pytest tests/ -v
Test Coverage
| Module | Tests | Coverage |
|---|---|---|
| Kerning Commands | 25 | SetKerning, AdjustKerning, RemoveKerning, CreateException |
| Editors | 20 | KerningEditor, MarginsEditor, undo/redo, callbacks |
| Groups Manager | 30 | FontGroupsManager, add/remove/delete/rename groups |
| VirtualFont | 27 | Creation, isolation, glyph access, diff tracking, apply/reset |
License
MIT License
Author
Alexander Lubovenko lubovenko@gmail.com github.com/typedev
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ufo_spacing_lib-0.1.0.tar.gz.
File metadata
- Download URL: ufo_spacing_lib-0.1.0.tar.gz
- Upload date:
- Size: 36.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
53e48dcc93b91662f7261b5f8e0c0d8dd5b461a32e52aed80261a2d6138df93a
|
|
| MD5 |
258346e6c69af9646dcd7a93be3529f4
|
|
| BLAKE2b-256 |
56a75334cc8095991b20de74e5caa5377a1ed22a176e62ccf7c852a87ec041fc
|
File details
Details for the file ufo_spacing_lib-0.1.0-py3-none-any.whl.
File metadata
- Download URL: ufo_spacing_lib-0.1.0-py3-none-any.whl
- Upload date:
- Size: 35.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5701d469bab2a2929028c1e7de2694807c73c5b6cb278aac56b59bf6b17a62b6
|
|
| MD5 |
f98bca965f8d8333530eeb7a36552a07
|
|
| BLAKE2b-256 |
7313af775ff25973d150b703452647f2b040e3a737eb85f17cdfa38da64cf53f
|