
rotulo
Monoline typeface generated from skeletons: strokes of lines and arcs expanded with a round pen.
Every letter is a skeleton of straight lines and arcs drawn in code, never a hand-drawn outline. The generator expands each skeleton with a round pen and writes TrueType and WOFF2 fonts. Weight is the pen width, so the family is a variable font from 200 to 700. Floating details (dots, accents, the zero slash, punctuation) are a second color layer. A reading cut, Lectura, adds weight, spacing and letters that are hard to mirror.
Install
npm install @npmegar/rotulo
pip install rotulo
| Package | What it is | Needs |
|---|---|---|
@npmegar/rotulo |
WOFF2 files and a stylesheet with the @font-face rules |
A bundler that resolves CSS imports, or a plain <link> |
rotulo |
The generator: builds every font from the skeletons | Python 3.12 or later |
The TTF files are attached to each GitHub release.
Quick start
@import '@npmegar/rotulo';
body {
font-family: 'Rotulo', system-ui, sans-serif;
font-weight: 200;
}
.reading {
font-family: 'Rotulo Lectura', 'Rotulo', sans-serif;
}
python -m rotulo --out build --web build/web
Fonts
| Family | File | Weight | Use |
|---|---|---|---|
Rotulo |
Rotulo-Variable.woff2, .ttf |
200 to 700 | Map labels, interface, titles |
Rotulo |
Rotulo-Display.ttf |
200, static and hinted | Renderers without variable fonts, such as GeoServer or PDF libraries |
Rotulo Lectura |
Rotulo-Lectura.woff2, .ttf |
500, static and hinted | Long reading |
Color details
Dots, accents, the zero slash and punctuation sit on a color layer (COLR/CPAL). The body takes the text color; the detail takes the palette color. Renderers without color fonts draw the whole letter in the text color.
| Palette | Detail color | CSS |
|---|---|---|
| 0, any background | #3d8a8f |
default; readable on light and dark |
| 1, dark backgrounds | #7fb7b5 |
font-palette: --rotulo-dark; the stylesheet applies it under prefers-color-scheme: dark |
Override the detail color with @font-palette-values and override-colors: 0 <color>.
Reading forms
ss01 switches four letters to the forms of Lectura: l with a tail, q with a crossed tail, a curved
flag on 1 and a straight 9.
.labels {
font-feature-settings: 'ss01';
}
Using the fonts outside the web
| Where | How |
|---|---|
| GeoServer | Copy Rotulo-Display.ttf into the font directory and name Rotulo in the SLD font-family |
Embed Rotulo-Display.ttf or Rotulo-Lectura.ttf |
Parameters
Params holds every design decision, in units of a 1000 em:
| Field | Default | What it controls |
|---|---|---|
weight |
29 | Pen width |
x_height, cap_height |
420, 700 | Body and capital heights |
ascender, descender |
800, −260 | Shared by every letter with a tail |
corner |
70 | Radius used to round every skeleton vertex |
gap |
110 | Space between a stroke and a floating detail |
spacing, word_spacing |
1.56, 1.0 | Side bearings and space width |
narrow_air |
0.3 | Extra room around narrow letters such as i, l, t |
from dataclasses import replace
from pathlib import Path
from rotulo import DISPLAY, build_font
print(build_font(replace(DISPLAY, weight=50), "Medium", 400, Path("build")))
Known limitations
- Latin script: Spanish, Portuguese, French and Catalan, figures and common symbols.
- The variable font keeps overlapping strokes and is not hinted; use the static files at small sizes on Windows.
- Detail colors need COLR support. Safari, Chrome, Firefox and Windows render them; older renderers draw one color.
Development
python -m venv .venv && . .venv/bin/activate
pip install -e '.[dev]'
pytest
ruff check src tests && ruff format --check src tests
mypy
The tests build every font and check heights against the skeleton, kerning, color layers, ss01,
hinting and that the variable font interpolates every glyph.
Contributing
Issues are welcome. Please open one before sending a pull request.
License
OFL-1.1 © GHEgar
Metadata
Release files for rotulo 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| rotulo-0.1.1.tar.gz | 97.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| rotulo-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 124.3 kB
Release files / rotulo-0.1.1.tar.gz
| Download URL | rotulo-0.1.1.tar.gz |
|---|---|
| Size | 97.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
77e829767fbdc77d4e17c927c675078a4ec82f83381670c908730e889c232cca
|
|
BLAKE2b-256 checksum How to use checksums |
fa44d9cd7fbde9c0c44591d54fdac550a712e048b7d9a6daf2d4f2417b0a33dc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency logRelease files / rotulo-0.1.1-py3-none-any.whl
| Download URL | rotulo-0.1.1-py3-none-any.whl |
|---|---|
| Size | 27.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1517e7460d05b3593639c08710dee3be89e4904e7076d5efdc184b0cc9671078
|
|
BLAKE2b-256 checksum How to use checksums |
227efe8b0230b743855835a034d422c865698e0680f607dd3f411648285b07ce
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency log