Skip to main content

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

ufo_spacing_lib-0.1.0.tar.gz (36.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ufo_spacing_lib-0.1.0-py3-none-any.whl (35.5 kB view details)

Uploaded Python 3

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

Hashes for ufo_spacing_lib-0.1.0.tar.gz
Algorithm Hash digest
SHA256 53e48dcc93b91662f7261b5f8e0c0d8dd5b461a32e52aed80261a2d6138df93a
MD5 258346e6c69af9646dcd7a93be3529f4
BLAKE2b-256 56a75334cc8095991b20de74e5caa5377a1ed22a176e62ccf7c852a87ec041fc

See more details on using hashes here.

File details

Details for the file ufo_spacing_lib-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for ufo_spacing_lib-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5701d469bab2a2929028c1e7de2694807c73c5b6cb278aac56b59bf6b17a62b6
MD5 f98bca965f8d8333530eeb7a36552a07
BLAKE2b-256 7313af775ff25973d150b703452647f2b040e3a737eb85f17cdfa38da64cf53f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page