# Around typeface Around is Cameron's custom geometric unicase display face for [`cameron.stream`](https://cameron.stream). It was drawn and refined as a **cosmological instrument**: precise, austere, slightly uncanny, and more like an artifact than a conventional brand font. This repository contains everything required to rebuild and revisit it. No FontLab, Glyphs, Illustrator, or private source file exists elsewhere. ## Source of truth | File | Role | | --- | --- | | `scripts/generate-around-font.py` | Canonical glyph geometry, metrics, mappings, kerning, metadata, and font build | | `scripts/requirements-font-build.txt` | Shared pinned build-only Python dependencies | | `public/fonts/around-regular.ttf` | Generated desktop font committed for serving and download | | `public/fonts/around-regular.woff2` | Generated web font committed for serving | | `src/components/type-specimen.tsx` | Public `/type` specimen and design statement | | `public/type.css` | Specimen composition and responsive behavior | | `public/site.css` | Site-wide `@font-face`, display-font integration, and font cache key | | `src/components/shell.tsx` | Shared WOFF2 preload and its matching cache key | | `src/index.tsx` | Immutable font response policy | The Python generator is the editable source. The TTF and WOFF2 files are build artifacts and should never be edited directly. ## Build from a clean checkout The font toolchain is deliberately separate from the website's Node runtime. ```bash python3 -m venv .venv-font .venv-font/bin/python -m pip install -r scripts/requirements-font-build.txt .venv-font/bin/python scripts/generate-around-font.py ``` The generator writes both files under `public/fonts/`. Builds are reproducible: it uses a fixed `SOURCE_DATE_EPOCH` unless that standard environment variable is explicitly supplied by the caller. Rebuilding unchanged source should therefore leave the committed font binaries unchanged. After a real font change, increment the cache query attached to both font URLs in `public/site.css` and the matching WOFF2 preload in `src/components/shell.tsx`. Font responses are cached for one year with `immutable`, so changing the query is what makes browsers request a new binary. Browsers can otherwise retain an older WOFF2 while the server is already serving the new one. ## Design grammar - **1000 units per em** with an **800-unit cap/unicase body**. - **70-unit monoline construction** is the default skeleton grammar. - Letters are built from lines, sampled arcs, cubic curves, and a small number of precision geometries converted into TrueType outlines through Shapely. - Uppercase and lowercase Unicode codepoints map to the same unicase glyphs. - Character widths are proportional. Around is geometric, not monospaced. - The ring `○` is a first-class glyph and recurring visual primitive. - Inter remains the body and navigation face. Around is for names, titles, statements, and display-scale moments. The generator has two construction layers: 1. `GLYPHS` defines centerline skeletons for the broad family. 2. `precision_geometry()` replaces forms that need optical or contour-level control after a generic stroked skeleton proves insufficient. That distinction is intentional. Do not force every glyph through one clever abstraction. The face became good when the system allowed optical exceptions. ## Settled forms and relationships These are design decisions, not unfinished cleanup: - **S** is the characteristic form. Preserve its distributed curvature and current silhouette unless a real-scale proof exposes a concrete problem. - **R** is deliberately upright and left-anchored. Its diagonal leg must not extend beyond the bowl's right edge; the generator enforces this invariant. - **P and R share bowl dimensions** through `PR_BOWL_RX` and `PR_BOWL_RY`. - The **M, J, and 1** retain their unusual constructions. A review proposed conventionalizing them; proofs did not justify losing their character. - Current word spacing is **330 units**. The original 430-unit space made sentences fragment into isolated word objects. - Kerning is sparse and optical. Existing pairs should be judged in words, rather than expanded mechanically into a large generic table. The current R leg centerline is `[(535, 500), (810, -50)]`. Treat that as a protected relationship to the bowl, not an arbitrary pair of coordinates. ## How to refine it 1. Change the Python source, never the generated binaries. 2. Regenerate both font formats. 3. Open `/type` and inspect the face at hero, sentence, alphabet, numeral, and small-display scales. 4. Inspect real site routes too. At minimum: `/`, `/travel`, `/blog`, one post with a long title, and `/annotations`. 5. Check desktop, ordinary mobile, and a narrow 320px viewport. The specimen contains intentionally oversized compositions, so hidden clipping can look like successful overflow handling. 6. Proof changes in words containing the affected pair. For example, a `P/A` change should be judged in `SPACE`, `PAPER`, and `PAIR`, not only `PA`. 7. Run the checks below, inspect the binary diff, and bump the font cache key. Useful proof strings: ```text SPACE DISPLAY PAPER TYPEFACE PAIR APPROACH SHAPE OPEN AVATAR WAYWARD YOUTH ATLAS LOVE OVER MOVE ROUND NEVER MEMORY SIGNAL SYSTEM LA TYPE TO TA FO PA PO PE PR ``` Do not assess only enlarged isolated glyphs. Several forms that look peculiar alone become coherent in words, while spacing defects often appear only in a sentence or on a real page. ## Validation Run the site type checker: ```bash pnpm typecheck git diff --check ``` Confirm that the generated files can be fully parsed and contain the expected metadata: ```bash .venv-font/bin/python - <<'PY' from fontTools.ttLib import TTFont for path in ("public/fonts/around-regular.ttf", "public/fonts/around-regular.woff2"): font = TTFont(path) font.ensureDecompiled() cmap = font.getBestCmap() required = ( set(range(ord("A"), ord("Z") + 1)) | set(range(ord("a"), ord("z") + 1)) | set(range(ord("0"), ord("9") + 1)) | {0x25CB, 0x2018, 0x2019, 0x201C, 0x201D} ) assert required <= set(cmap) assert font["OS/2"].usWeightClass == 400 assert "GPOS" in font print(path, len(font.getGlyphOrder()), "glyphs", len(cmap), "codepoints") PY ``` At the time this document was written, both formats contain **58 glyphs** and map **88 codepoints**. A deliberate character-set expansion will change those figures; update this document when it does. ## Known limits - Around is a display font, not a general-purpose text face. - It currently covers the Latin alphabet, decimal numerals, selected punctuation, typographic quotes, and the ring. It is not broad multilingual coverage. - There is one regular weight and no variable axis. - Browser and page-level proofing remains part of the build. Binary validity cannot tell whether a word rhythm or silhouette is actually good. ## Deployment The font and specimen deploy with the rest of `cameron.stream`: ```bash fly deploy ``` After deployment, verify `https://cameron.stream/type` and confirm the WOFF2 request uses the new cache key from `public/site.css`.