//! The coverage assertion, run once per built face. //! //! That check is in the wrong place: it asks the image whether a face somebody //! else built covers a mark, so a gap surfaces as a failed image build rather //! than as a failed font build, and only for the one consumer that thought to //! grep. Asserting here means every consumer inherits the guarantee, and the //! image is left to assert only that it installed the right family. use std::collections::BTreeMap; use read_fonts::types::GlyphId; use crate::Error; use crate::manifest::format_codepoint; /// Every codepoint Alloy's own surfaces put on a screen against the built /// image and recorded in `alloy/docs/FONTS.md`. /// /// A face for the `monospace` alias has to cover all of it or a shipped config /// renders a missing glyph. Kept here as the pipeline's floor rather than in /// the manifest: the manifest says what we draw, this says what a consumer /// needs, and the seven exist only because the two differ. pub const ALLOY_SURFACE: [u32; 18] = [ 0x00B7, 0x00BB, 0x2014, 0x2022, 0x2026, 0x2191, 0x2192, 0x2193, 0x2195, 0x23CE, 0x2423, 0x2502, 0x2588, 0x258F, 0x2591, 0x25B8, 0x25C2, 0x2718, ]; /// What a **body** face has to cover, which is Alloy's list minus the cell grid. /// /// `│ █ ▏ ░` are a terminal's: they exist to tile, and a proportional face has /// no cell to tile into. Everything else on the list is text a described screen /// sets in body copy as readily as in a status line, so it stays. /// /// The floor is per role rather than per face because that is what it means: it /// says what a consumer in that role emits, and a slot answers to the floor of /// the role it fills. pub const BODY_SURFACE: [u32; 14] = [ 0x00B7, 0x00BB, 0x2014, 0x2022, 0x2026, 0x2191, 0x2192, 0x2193, 0x2195, 0x23CE, 0x2423, 0x25B8, 0x25C2, 0x2718, ]; /// The sort carets, which Alloy does not emit but every described screen does. pub const SORT_CARETS: [u32; 2] = [0x25B2, 0x25BC]; pub struct Coverage { pub required: Vec, pub missing: Vec, } impl Coverage { pub fn ok(&self) -> bool { self.missing.is_empty() } } /// Check a built face against everything a consumer is entitled to assume. /// /// `floor` is the role's surface — `ALLOY_SURFACE` for a face that will answer /// `monospace`, `BODY_SURFACE` for one that will answer `sans-serif`. pub fn check(floor: &[u32], mappings: &BTreeMap, house_set: &[u32]) -> Coverage { let mut required: Vec = floor .iter() .chain(SORT_CARETS.iter()) .chain(house_set.iter()) .copied() .collect(); required.sort_unstable(); required.dedup(); let missing = required .iter() .copied() .filter(|cp| mappings.get(cp).is_none_or(|gid| gid.to_u32() == 0)) .collect(); Coverage { required, missing } } pub fn describe(coverage: &Coverage) -> Result { if coverage.ok() { return Ok(format!("cmap covers all {}", coverage.required.len())); } Err(Error::Coverage( coverage .missing .iter() .map(|cp| format_codepoint(*cp)) .collect::>() .join(" "), )) } #[cfg(test)] mod tests { use super::*; fn mapping(codepoints: &[u32]) -> BTreeMap { codepoints .iter() .enumerate() .map(|(i, cp)| (*cp, GlyphId::from(i as u16 + 1))) .collect() } #[test] fn the_floor_is_the_twenty_the_done_condition_names() { let coverage = check(&ALLOY_SURFACE, &mapping(&[]), &[]); assert_eq!( coverage.required.len(), 20, "Alloy's eighteen plus the two carets" ); } #[test] fn a_face_missing_a_caret_fails() { let mut all: Vec = ALLOY_SURFACE.to_vec(); all.push(0x25B2); let coverage = check(&ALLOY_SURFACE, &mapping(&all), &[]); assert_eq!(coverage.missing, vec![0x25BC]); assert!(describe(&coverage).is_err()); } /// A body face is not asked for the cell grid, and is asked for everything /// else. The two floors differ by exactly the four that tile. #[test] fn the_body_floor_is_the_terminal_floor_minus_the_cell_grid() { let terminal: std::collections::BTreeSet = ALLOY_SURFACE.into_iter().collect(); let body: std::collections::BTreeSet = BODY_SURFACE.into_iter().collect(); assert!(body.is_subset(&terminal)); let dropped: Vec = terminal.difference(&body).copied().collect(); assert_eq!(dropped, vec![0x2502, 0x2588, 0x258F, 0x2591]); } #[test] fn a_notdef_mapping_does_not_count_as_coverage() { let mut mappings = mapping(&ALLOY_SURFACE); mappings.insert(0x25B2, GlyphId::from(0u16)); mappings.insert(0x25BC, GlyphId::from(0u16)); let coverage = check(&ALLOY_SURFACE, &mappings, &[]); assert_eq!(coverage.missing, vec![0x25B2, 0x25BC]); } }