Skip to main content

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) or outline.* (the contour at height Y, with .first/.last to 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, range U+A..U+B, glob *.sc, category {Lu}.
  • = replace · += add · -= remove; !extends default inherits 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

anchorsfactory-0.3.0.tar.gz (46.5 kB view details)

Uploaded Source

Built Distribution

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

anchorsfactory-0.3.0-py3-none-any.whl (34.8 kB view details)

Uploaded Python 3

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

Hashes for anchorsfactory-0.3.0.tar.gz
Algorithm Hash digest
SHA256 f655cbf8d0d1da2023527046928c590b943288df497970c63dd44294da9c78f3
MD5 d53f10e99b18345b8a47fa39f4014ebd
BLAKE2b-256 ca76d16e0224fc6bd67f1841bcc138658bdd0c34ed1c5ededb59bafb90ff758e

See more details on using hashes here.

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

Hashes for anchorsfactory-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 89baafa33759153dace8734092adfab29b14b133cc12033efa6502023f764353
MD5 a5b77e411d488eb1bdd3a4bb7f785c2f
BLAKE2b-256 d7c6794558e8eee10943b3a88f8ee2bada4290e11758ecbdfd4c7e8c06ef6bb4

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