Skip to main content

max / quasi-type

5.0 KB · 144 lines History Blame Raw
1 //! The coverage assertion, run once per built face.
2 //!
3 //! That check is in the wrong place: it asks the image whether a face somebody
4 //! else built covers a mark, so a gap surfaces as a failed image build rather
5 //! than as a failed font build, and only for the one consumer that thought to
6 //! grep. Asserting here means every consumer inherits the guarantee, and the
7 //! image is left to assert only that it installed the right family.
8
9 use std::collections::BTreeMap;
10
11 use read_fonts::types::GlyphId;
12
13 use crate::Error;
14 use crate::manifest::format_codepoint;
15
16 /// Every codepoint Alloy's own surfaces put on a screen against the built
17 /// image and recorded in `alloy/docs/FONTS.md`.
18 ///
19 /// A face for the `monospace` alias has to cover all of it or a shipped config
20 /// renders a missing glyph. Kept here as the pipeline's floor rather than in
21 /// the manifest: the manifest says what we draw, this says what a consumer
22 /// needs, and the seven exist only because the two differ.
23 pub const ALLOY_SURFACE: [u32; 18] = [
24 0x00B7, 0x00BB, 0x2014, 0x2022, 0x2026, 0x2191, 0x2192, 0x2193, 0x2195, 0x23CE, 0x2423, 0x2502,
25 0x2588, 0x258F, 0x2591, 0x25B8, 0x25C2, 0x2718,
26 ];
27
28 /// What a **body** face has to cover, which is Alloy's list minus the cell grid.
29 ///
30 /// `│ █ ▏ ░` are a terminal's: they exist to tile, and a proportional face has
31 /// no cell to tile into. Everything else on the list is text a described screen
32 /// sets in body copy as readily as in a status line, so it stays.
33 ///
34 /// The floor is per role rather than per face because that is what it means: it
35 /// says what a consumer in that role emits, and a slot answers to the floor of
36 /// the role it fills.
37 pub const BODY_SURFACE: [u32; 14] = [
38 0x00B7, 0x00BB, 0x2014, 0x2022, 0x2026, 0x2191, 0x2192, 0x2193, 0x2195, 0x23CE, 0x2423, 0x25B8,
39 0x25C2, 0x2718,
40 ];
41
42 /// The sort carets, which Alloy does not emit but every described screen does.
43 pub const SORT_CARETS: [u32; 2] = [0x25B2, 0x25BC];
44
45 pub struct Coverage {
46 pub required: Vec<u32>,
47 pub missing: Vec<u32>,
48 }
49
50 impl Coverage {
51 pub fn ok(&self) -> bool {
52 self.missing.is_empty()
53 }
54 }
55
56 /// Check a built face against everything a consumer is entitled to assume.
57 ///
58 /// `floor` is the role's surface — `ALLOY_SURFACE` for a face that will answer
59 /// `monospace`, `BODY_SURFACE` for one that will answer `sans-serif`.
60 pub fn check(floor: &[u32], mappings: &BTreeMap<u32, GlyphId>, house_set: &[u32]) -> Coverage {
61 let mut required: Vec<u32> = floor
62 .iter()
63 .chain(SORT_CARETS.iter())
64 .chain(house_set.iter())
65 .copied()
66 .collect();
67 required.sort_unstable();
68 required.dedup();
69
70 let missing = required
71 .iter()
72 .copied()
73 .filter(|cp| mappings.get(cp).is_none_or(|gid| gid.to_u32() == 0))
74 .collect();
75
76 Coverage { required, missing }
77 }
78
79 pub fn describe(coverage: &Coverage) -> Result<String, Error> {
80 if coverage.ok() {
81 return Ok(format!("cmap covers all {}", coverage.required.len()));
82 }
83 Err(Error::Coverage(
84 coverage
85 .missing
86 .iter()
87 .map(|cp| format_codepoint(*cp))
88 .collect::<Vec<_>>()
89 .join(" "),
90 ))
91 }
92
93 #[cfg(test)]
94 mod tests {
95 use super::*;
96
97 fn mapping(codepoints: &[u32]) -> BTreeMap<u32, GlyphId> {
98 codepoints
99 .iter()
100 .enumerate()
101 .map(|(i, cp)| (*cp, GlyphId::from(i as u16 + 1)))
102 .collect()
103 }
104
105 #[test]
106 fn the_floor_is_the_twenty_the_done_condition_names() {
107 let coverage = check(&ALLOY_SURFACE, &mapping(&[]), &[]);
108 assert_eq!(
109 coverage.required.len(),
110 20,
111 "Alloy's eighteen plus the two carets"
112 );
113 }
114
115 #[test]
116 fn a_face_missing_a_caret_fails() {
117 let mut all: Vec<u32> = ALLOY_SURFACE.to_vec();
118 all.push(0x25B2);
119 let coverage = check(&ALLOY_SURFACE, &mapping(&all), &[]);
120 assert_eq!(coverage.missing, vec![0x25BC]);
121 assert!(describe(&coverage).is_err());
122 }
123
124 /// A body face is not asked for the cell grid, and is asked for everything
125 /// else. The two floors differ by exactly the four that tile.
126 #[test]
127 fn the_body_floor_is_the_terminal_floor_minus_the_cell_grid() {
128 let terminal: std::collections::BTreeSet<u32> = ALLOY_SURFACE.into_iter().collect();
129 let body: std::collections::BTreeSet<u32> = BODY_SURFACE.into_iter().collect();
130 assert!(body.is_subset(&terminal));
131 let dropped: Vec<u32> = terminal.difference(&body).copied().collect();
132 assert_eq!(dropped, vec![0x2502, 0x2588, 0x258F, 0x2591]);
133 }
134
135 #[test]
136 fn a_notdef_mapping_does_not_count_as_coverage() {
137 let mut mappings = mapping(&ALLOY_SURFACE);
138 mappings.insert(0x25B2, GlyphId::from(0u16));
139 mappings.insert(0x25BC, GlyphId::from(0u16));
140 let coverage = check(&ALLOY_SURFACE, &mappings, &[]);
141 assert_eq!(coverage.missing, vec![0x25B2, 0x25BC]);
142 }
143 }
144