{"record":{"id":"8cbde26d00d47c3b","repo":"louis-e/arnis","slug":"bundled-font-atlas-is-valid","errorCode":null,"errorMessage":"bundled font atlas is valid","messagePattern":"bundled font atlas is valid","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"info","filePath":"src/decals/font.rs","lineNumber":67,"sourceCode":"    line_height: u8,\n    glyphs: HashMap<char, Glyph>,\n}\n\nmacro_rules! font_assets {\n    ($($size:literal),*) => {\n        [$((\n            include_bytes!(concat!(\"../../assets/decorations/font/dejavu_bold_\", $size, \".png\")) as &[u8],\n            include_bytes!(concat!(\"../../assets/decorations/font/dejavu_bold_\", $size, \".bin\")) as &[u8],\n        )),*]\n    };\n}\n\nstatic FONT_ASSETS: [(&[u8], &[u8]); 5] = font_assets!(\"12\", \"18\", \"28\", \"44\", \"64\");\n\nstatic FONTS: Lazy<Vec<Font>> = Lazy::new(|| {\n    FONT_ASSETS\n        .iter()\n        .map(|(png, idx)| Font::parse(png, idx).expect(\"bundled font atlas is valid\"))\n        .collect()\n});\n\nimpl Font {\n    fn parse(png: &[u8], idx: &[u8]) -> Result<Font, String> {\n        let atlas = image::load_from_memory(png)\n            .map_err(|e| format!(\"font atlas: {e}\"))?\n            .to_luma8();\n        if idx.len() < 8 || &idx[0..4] != b\"AFN1\" {\n            return Err(\"font index: bad magic\".to_string());\n        }\n        let line_height = idx[4];\n        let count = u16::from_le_bytes([idx[6], idx[7]]) as usize;\n        let mut glyphs = HashMap::with_capacity(count);\n        let mut off = 8;\n        for _ in 0..count {\n            if off + 9 > idx.len() {\n                return Err(\"font index: truncated\".to_string());","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/louis-e/arnis/blob/34048924d9365795fb0d832e76140a3fbdc413d9/src/decals/font.rs#L49-L85","documentation":"The bundled font atlas is decoded at startup via `FONT_ASSETS` -> `Font::parse(png, idx).expect(\"bundled font atlas is valid\")`. The library authors consider the embedded PNG/index pairs compile-time constants that must always parse, so a failure panics instead of returning an error. Hitting it means the bundled binary assets are corrupt or `Font::parse` regressed on them.","triggerScenarios":"Practically only when the font asset bytes were replaced or corrupted (custom build, bad merge, build script mangling `include_bytes!` sources) or a `Font::parse` code change broke decoding of a previously valid atlas format.","commonSituations":"Patching the bundled asset files or the image/decoding dependency to an incompatible version; a build step that rewrites or compresses assets; partial checkout/corrupted repository.","solutions":["Restore the original bundled font PNG/index assets (git checkout the asset files)","Rebuild from a clean checkout to rule out corrupted build artifacts","If a dependency (image decoder) was upgraded, downgrade or fix `Font::parse` for the new decoder output","Replace `expect` with proper error propagation during development of custom fonts"],"exampleFix":"// before\nFont::parse(png, idx).expect(\"bundled font atlas is valid\")\n// after\nFont::parse(png, idx).map_err(|e| format!(\"font atlas {name:?} invalid: {e}\"))?","handlingStrategy":"fallback","validationCode":"let parsed: Result<Vec<_>, String> = FONT_ASSETS.iter().map(|(p,i)| Font::parse(p,i)).collect();","typeGuard":null,"tryCatchPattern":"// Wrap FONTS initialization so a parse failure logs and falls back to a default font instead of panicking at first use","preventionTips":["Do not modify bundled assets without running the font tests","Pin/lock image-decoder dependency versions","CI check: parse all bundled atlases in a unit test"],"tags":["panic","asset-loading","font"],"backgroundTag":"embedded-asset-corrupt","analyzedSha":"34048924d9365795fb0d832e76140a3fbdc413d9","analyzedAt":"2026-09-03T14:05:17.283Z","contentChangedAt":"2026-09-03T14:05:17.283Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}