Convert TrueType fonts to stencil-ready versions by automatically adding bridges to enclosed contours.
Stencilizer is a Python CLI tool that transforms regular fonts into stencil fonts by detecting "islands" (enclosed contours) in glyphs and adding bridges to connect them. This is essential for creating fonts suitable for stencil cutting, where disconnected parts would fall out.
Characters like O, A, B, D, P, R, Q, 4, 6, 8, 9, @ and others often have enclosed contours that need bridges to remain connected during cutting.
- Automatic Island Detection: Intelligently identifies enclosed contours using contour hierarchy analysis
- Smart Bridge Placement: Places bridges optimally based on contour geometry
- Parallel Processing: Leverages multicore CPUs for fast processing of large fonts
- Flexible Configuration: Control bridge width and parallel workers
- Font Coexistence: Output fonts get a "Stenciled" suffix in their internal name table, allowing installation alongside the original font
- Multiple Output Modes:
- Full processing (default)
- Dry-run analysis
- Island listing
- Rich CLI Output: Beautiful console output with progress tracking
- Detailed Logging: Optional file logging for debugging and analysis
- Format Support: Works with TTF and OTF fonts containing TrueType outlines (see Font Format Support)
git clone https://github.com/cosmix/stencilizer.git
cd stencilizer
uv pip install -e .The desktop GUI needs PySide6, which ships in the optional gui extra:
uv pip install -e ".[gui]"git clone https://github.com/cosmix/stencilizer.git
cd stencilizer
uv pip install -e .
stencilizer Roboto-Regular.ttfThis creates Roboto-Regular-stenciled.ttf in the same directory.
Example output:
Stencilizer v1.0.0
────────────────────────────────────────────
▸ Loading font
Roboto-Regular.ttf (TrueType)
897 glyphs · 2,048 UPM
▸ Analyzing glyphs
42 glyphs with islands
▸ Processing
8 workers (auto) · Ctrl+C to cancel
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:02
Complete with unbridged islands in 2.1s
Roboto-Regular-stenciled.ttf (156 KB)
42 glyphs · 38 bridges · 0 errors
4 islands remained unbridged
45.2ms avg (12.1–98.3ms range)
The bridge count records islands actually connected. Islands that the geometry cannot bridge are
reported separately, including with --quiet. If a glyph worker or font write fails, the command
exits with an error and does not publish a partial output file.
# Convert with default settings
stencilizer input.ttf
# Specify output path
stencilizer input.ttf -o output.ttf
# Adjust bridge width (30-110% of a reference stroke of 10% of font UPM)
stencilizer input.ttf --bridge-width 70# List all glyphs with islands
stencilizer input.ttf --list-islands
# Dry run (analyze without modifying)
stencilizer input.ttf --dry-run
# Verbose output
stencilizer input.ttf --verbose
# Quiet mode
stencilizer input.ttf --quiet# Control parallel workers
stencilizer input.ttf --workers 4
# Use all available cores (default)
stencilizer input.ttf# Enable file logging
stencilizer input.ttf --log-file stencilizer.log
# Set log level
stencilizer input.ttf --log-level DEBUGLato-Black with a live preview of the bridges added to B.
# Launch the window, optionally opening a font straight away
stencilizer-gui [font]A top bar holds "Open Font…" and "Stencilize & Save…", next to the name and details of the open font. Open a TTF/OTF font through it (or the command-line argument) and the window lists its island glyphs as thumbnails. Selecting one shows the glyph before and after stencilization side by side at one shared scale, recomputed as you change the bridge width (30-110 %) or the spanning-bridges toggle in the sidebar. The worker slider below them sets the number of processes used for the save; its left end is "Auto". "Stencilize & Save…" writes the full font and shows its progress as a percentage and bar in the status bar. It refuses to overwrite the input font, and refuses a source file that changed on disk since it was opened. Variable fonts and CFF2 fonts are rejected on open. The window uses a light or a dark theme and follows the system setting, switching when the system does.
The grid also lists composite glyphs (accented letters such as Aacute) that draw an island
glyph: they inherit their bridges from the base glyph. The direction picker sets Auto, Vertical
or Horizontal for the selected glyph (an O loses its top and bottom strokes under Vertical, its
left and right strokes under Horizontal); a composite follows its base glyph's direction and its
picker is disabled. Choices apply to the preview and the saved font for the session, and are not
written to a file. Glyphs where no bridge can be placed are marked red in the grid and the
preview says "no bridge could be placed".
Configuration is controlled via CLI options (see the usage examples above).
If you are using Stencilizer as a library, create a StencilizerSettings instance and set
values directly before passing it to the processor:
from stencilizer.config import StencilizerSettings
settings = StencilizerSettings()
settings.bridge.width_percent = 70.0
settings.processing.max_workers = 4Stencilizer analyzes each glyph to identify its contour hierarchy:
- Outer contours: Main glyph shapes (counter-clockwise winding)
- Inner contours: Enclosed areas (clockwise winding)
- Islands: Inner contours that are fully enclosed and need bridges
For each island, the algorithm:
- Analyzes stroke geometry between inner and outer contours
- Determines optimal bridge orientation (vertical or horizontal)
- Calculates bridge width as a percentage of a reference stroke of 10% of the font's UPM
- Places bridges to connect the island to the outer contour
Bridges are added by cutting notches into both the island and outer contour, creating connection points while preserving the overall glyph structure.
Glyphs are processed in parallel using Python's ProcessPoolExecutor, enabling efficient utilization of multi-core systems.
stencilizer Roboto-Regular.ttf \
--bridge-width 80 \
-o Roboto-Stencil.ttf# Check which glyphs have islands
stencilizer Roboto-Regular.ttf --list-islands
# See what would be done
stencilizer Roboto-Regular.ttf --dry-runDry-run output:
▸ Loading font
Roboto-Regular.ttf (TrueType)
897 glyphs · 2,048 UPM
▸ Analyzing (dry run)
Analysis
Glyphs with islands 42
Total islands 67
Estimated bridges 67
Bridge width 60% of a reference stroke of 10% of font UPM
✓ Dry run complete – no changes made
stencilizer Roboto-Regular.ttf \
--log-file processing.log \
--log-level DEBUG \
--verbose- Python 3.11 or higher
- fonttools >= 4.65.0
- pydantic >= 2.13.5
- rich >= 15.0.0
- structlog >= 26.1.0
- typer >= 0.27.2
# Clone repository
git clone https://github.com/cosmix/stencilizer.git
cd stencilizer
# Install with development dependencies
uv pip install -e ".[dev]"# Run all tests
pytest
# Run with coverage
pytest --cov=stencilizer --cov-report=html
# Run specific test modules
pytest tests/unit/test_analyzer.py# Type checking
mypy src/stencilizer tests
# Linting and formatting
ruff check src tests
ruff format src testsEnsure your font file is a valid TTF file (or OTF with TrueType outlines) and not corrupted. See Font Format Support for details on supported formats.
Some fonts may not have enclosed contours. Use --list-islands to check which glyphs have islands.
Adjust the --bridge-width parameter (range: 30-110% of a reference stroke of 10% of the font's UPM). Default is 60%.
Enable detailed logging to diagnose issues:
stencilizer input.ttf --log-file debug.log --log-level DEBUGStencilizer supports the following font formats:
| Format | Extension | Outline Type | Status |
|---|---|---|---|
| TrueType | .ttf |
TrueType (glyf table) |
✅ Fully supported |
| OpenType with TrueType outlines | .otf |
TrueType (glyf table) |
✅ Fully supported |
| OpenType with CFF outlines | .otf |
PostScript (CFF table) |
✅ Fully supported |
| OpenType with CFF2 outlines | .otf |
PostScript (CFF2 table) |
❌ Not supported |
| Variable fonts | .ttf/.otf |
Variable (fvar table) |
❌ Not supported |
Most .ttf files use TrueType outlines and will work. For .otf files, the situation is more nuanced:
- OTF with TrueType outlines: Some foundries package TrueType outlines in an OpenType container. These are fully supported.
- OTF with CFF outlines: Traditional PostScript-based OpenType fonts. These are fully supported.
- OTF with CFF2 outlines: Modern variable OpenType fonts use CFF2. These are not yet supported.
If you're unsure about your font's format, try processing it—Stencilizer will report an error if the format is unsupported.
- OpenType CFF2 font support
- Variable font support (fonts with
fvartable)
MIT License - see the LICENSE file for details.
