The font download in `build_support`

Updated Oct 13, 2026

build_support::font fetches Fira Sans at build time and verifies it against the FONT_SHA256 constant, so the font need not be committed. Two details of that path are deliberate.

`to_lower_hex` instead of `{:x}`

build_support::hex::to_lower_hex renders digest bytes as lowercase hexadecimal. It exists because sha2 0.11 changed Sha256::digest and Sha256::finalize to return hybrid_array::Array<u8, _>, which does not implement core::fmt::LowerHex; format!("{digest:x}") no longer compiles against it. The same release dropped the std::io::Write impl on the hasher, so io::copy(&mut reader, &mut hasher) would also need replacing with a buffered read loop that calls hasher.update. No such call site exists here today, but the sibling RustCrypto crates move in lockstep — sha1, sha3, and md-5 to 0.11, and hmac, hkdf, and pbkdf2 to 0.13 — so expect the same break there.

A crate-internal encoder is preferred over a hex dependency: the whole requirement is two table lookups per byte, and build_support runs on every build.

The download is size-capped

read_capped bounds the response body at MAX_FONT_BYTES (8 MiB, against a ~400 KiB font). The current fetch_font_data and FontFetcher::fetch contracts return Vec<u8>, so an accepted response is fully materialized before checksum verification. SHA-256 itself supports incremental updates; the cap bounds memory used by this full-body interface if the origin misbehaves.

The reader is capped at MAX_FONT_BYTES + 1 rather than MAX_FONT_BYTES. A plain cap cannot distinguish a body sitting exactly on the limit from one silently truncated by it; the extra byte makes an oversized body observable so it is reported rather than hashed as a short font.