Rule-driven anchor placement for UFO fonts
Project description
AnchorsFactory
Rule-driven anchor placement for UFO fonts. You describe, in a compact text file, where anchors should sit on your glyphs; AnchorsFactory computes the coordinates from each glyph's own geometry and writes the anchors into the font.
It does the pre-marking step of accent handling: place top/bottom/_top/…
anchors consistently across hundreds of glyphs, so a tool like
GlyphConstruction can then
assemble composite glyphs by snapping mark anchors to base anchors.
Install
pip install anchorsfactory
# or, from a checkout:
pip install -e .
Requires Python 3.10+, fontParts and fontTools.
Quick start
# place anchors using the bundled default ruleset, save to font_anchored.ufo
anchorsfactory MyFont.ufo --rules default
# your own rules, overwrite in place, with a backup of existing anchors
anchorsfactory MyFont.ufo --rules my-rules.af --in-place --backup-dir backups/
# a whole folder of UFOs
anchorsfactory masters/ --rules default
By default the source UFO is never overwritten — output goes to
*_anchored.ufo unless you pass --in-place.
The rule language
Rules are stacked: define reusable labels, then mark glyphs with them,
mixing labels and one-off anchors. An anchor is a name and a parenthesised
X Y placement.
# a label
@ = top (box.center capHeight), bottom (box.center 0)
# apply it, by name / unicode / range
A = @, ogonek (outline.right 0)
U+0410..U+044F = @ # all Russian Cyrillic
U+0413 += desc (outline.right 0) # Г also gets a descender anchor
- X is
frame.position:width.*(advance),box.*(bounding box) oroutline.*(the contour at height Y, with.first/.lastto pick a stem). - Y is a number, a font metric (
capHeight,xHeight,ascender, …), or a reference glyph ($H,$H.bottom,$H*5/6). - Selectors: name,
U+XXXX, rangeU+A..U+B, glob*.sc, category{Lu}. =replace ·+=add ·-=remove;!extends defaultinherits a ruleset.
Full reference: docs/anchor-rules.md.
Presets and migration
Bundled rulesets default and default-italics are usable by name in
--rules or !extends. Old .txt rule files (see examples/) convert to the
new syntax — verified lossless:
anchorsfactory-convert examples/default-anchors-list.txt -o my-rules.af
Library API
from anchorsfactory import process_ufo, load_document, apply_document
process_ufo("MyFont.ufo", "default") # high-level: open, apply, save
from fontParts.world import OpenFont
font = OpenFont("MyFont.ufo")
apply_document(font, load_document("my-rules.af"))
font.save()
Compute without mutating (preview)
compute_document is the functional core: it returns the anchors a rule
document would place, keyed by glyph, without touching the font. It owns the
same orchestration as apply_document (suffix expansion, shift_x, rounding,
same-name dedup), so a preview never drifts from what gets written.
from anchorsfactory import compute_document, load_document, accumulate, resolve
doc = load_document("default")
placed = compute_document(font, doc) # {glyph_name: [(anchor, x, y), ...]}
# resolve() is the pure, single-anchor primitive behind it:
specs = accumulate(doc, "A", font["A"].unicodes) # the anchors due on glyph A
x, y = resolve(font, font["A"], specs[0])
For an interactive editor, on_error="collect" never raises — it places what
it can and reports the rest as structured diagnostics:
result = compute_document(font, doc, on_error="collect")
for d in result.diagnostics:
# d.severity == "error" -> anchor skipped (geometry raised)
# d.severity == "warning" -> anchor placed via a fallback, but suspect
print(d.severity, d.glyph, d.anchor, d.reason)
result is a plain dict subclass (so it works anywhere a dict does) carrying
a .diagnostics list of ComputeDiagnostic(glyph, anchor, reason, severity, rule). The default on_error="raise" is unchanged and keeps .diagnostics
empty.
Development
make venv # create .venv, install the package (editable) + dev deps, via uv
make test # run the test suite
make build # build sdist + wheel into dist/
make release # bump minor, update CHANGELOG, build, upload to PyPI, tag + push
Releases are cut with make release: it bumps the minor version, fills in a
CHANGELOG section from the commit log, publishes to PyPI, and
tags vX.Y.Z. Set UV_PUBLISH_TOKEN first; use make release-test to rehearse
against TestPyPI.
License
MIT — see LICENSE.
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 anchorsfactory-0.3.0.tar.gz.
File metadata
- Download URL: anchorsfactory-0.3.0.tar.gz
- Upload date:
- Size: 46.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.9 {"installer":{"name":"uv","version":"0.9.9"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"44","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f655cbf8d0d1da2023527046928c590b943288df497970c63dd44294da9c78f3
|
|
| MD5 |
d53f10e99b18345b8a47fa39f4014ebd
|
|
| BLAKE2b-256 |
ca76d16e0224fc6bd67f1841bcc138658bdd0c34ed1c5ededb59bafb90ff758e
|
File details
Details for the file anchorsfactory-0.3.0-py3-none-any.whl.
File metadata
- Download URL: anchorsfactory-0.3.0-py3-none-any.whl
- Upload date:
- Size: 34.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.9 {"installer":{"name":"uv","version":"0.9.9"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"44","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
89baafa33759153dace8734092adfab29b14b133cc12033efa6502023f764353
|
|
| MD5 |
a5b77e411d488eb1bdd3a4bb7f785c2f
|
|
| BLAKE2b-256 |
d7c6794558e8eee10943b3a88f8ee2bada4290e11758ecbdfd4c7e8c06ef6bb4
|