Signed-distance-field glyph sets for the Open Waters map styles, served from
https://tiles.openwaters.io/fonts/{fontstack}/{range}.pbf.
A MapLibre style needs a glyphs URL before it can draw a single label. This repo builds those
glyphs from Noto, verifies them, and publishes them to R2. It holds fonts only — sprites are a
different toolchain and get their own home.
The output is shared: every Open Waters map wants the same stacks, so no single product should own them. Release cadence is the deciding argument, though — the seascape tile Worker keys everything to a per-build R2 prefix, and fonts must be release-independent. Serving them from that Worker would tie font availability to bathymetry deploys.
| Stack | Used for |
|---|---|
Noto Sans Italic |
Soundings. S-4 §B-412.1 sets them in sloping sans-serif. |
Noto Sans Regular |
Place names, and soundings of lower reliability — §B-412.4 reserves upright numerals for exactly that. |
Noto Sans Medium, Noto Sans SemiBold |
Emphasis, and legibility at smaller sizes: a halo eats into a light stroke, so weight buys more than size does. |
Noto Sans Medium Italic, Noto Sans SemiBold Italic |
The same, for soundings. |
Weights stop at SemiBold and widths at normal, deliberately. Only the Latin face has italics — no Noto script family ships one — so an italic stack is Latin-italic over upright fallbacks, and the Condensed widths have no non-Latin counterpart at all, which would make a Condensed stack silently tofu outside Latin. Thin and ExtraLight are wrong for a chart on their own terms: hairline strokes vanish under glare, and §B-412.4 reserves hairline numerals for soundings of lower reliability, so the weight would carry a meaning nobody intended.
Each stack is composed from many per-script Noto faces (see fonts.json), so worldwide coverage is a font list rather than a code change. Stack names are the contract with every published style: renaming one breaks every style that asks for it.
# Prebuilt binary (no Rust toolchain needed); or: cargo install build_pbf_glyphs
gh release download cli-v1.5.1 --repo stadiamaps/sdf_font_tools \
--pattern 'build_pbf_glyphs.x86_64-apple-darwin' -O ~/.local/bin/build_pbf_glyphs
chmod +x ~/.local/bin/build_pbf_glyphs
scripts/build.sh # all stacks, or: scripts/build.sh "Noto Sans Italic"
scripts/verify.py # coverage gate
scripts/publish.sh --dry-run # then without the flagThe macOS asset is named x86_64 but ships a native arm64 binary.
build.sh fetches the Noto release pinned in fonts.json (a ~630 MB archive, cached under
build/), so rebuilding the same release is reproducible. Every stack's source paths are
resolved before any glyph is cut, and a missing one is fatal — not every family is in the
monolithic Noto repo (Tibetan is not), and a stack with a silent hole renders as tofu. Set
STAGE_ONLY=1 to run just the fetch and resolve steps, which need no glyph tool.
Faces are merged in listed order, and that order decides who wins an overlapping codepoint — the base Latin face is first so its digits are not shadowed by a script face.
Current output: six stacks, ~12,225 glyphs and 256 ranges each, 43 MB total.
verify.py fails the build when a stack is missing any codepoint the charts actually set, and
this is the reason the repo exists in its present shape. The MapLibre demo fontstack the seascape
style pointed at ships only U+2082–2084 of the subscript block: seven of the ten subscript digits
were absent. Nothing errors when that happens — the glyph is simply not drawn, so a sounding's
decimetre digit silently disappears from the chart. The check is an error, never a warning.
R2 bucket on a custom domain. No Worker:
- Caching — Cloudflare caches from each object's own
Cache-Control, set topublic, max-age=31536000, immutableat upload. Range PBFs for a given release never change. - CORS — configured on the bucket, or as a response-header rule on the hostname.
- TLS and CDN — come with the custom domain.
tiles.openwaters.io already routes /seascape/* to a Worker. A more specific Worker route
takes precedence over a custom domain, so the two coexist; if that proves awkward in practice, a
dedicated hostname costs nothing and changes only the glyphs URL in the styles.
The fonts are SIL Open Font License 1.1 and are redistributed with it. Everything in this repo that is not a font is BSD-3-Clause, matching the rest of Open Waters.