max / quasi-type
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
3 files changed,
+72 insertions,
-4 deletions
| @@ -27,6 +27,17 @@ | |||
| 27 | 27 | pub family: &'a str, | |
| 28 | 28 | pub style: &'a str, | |
| 29 | 29 | pub version: &'a str, | |
| 30 | + | /// `[set] version` from the glyph manifest, stated in the description on | |
| 31 | + | /// its own rather than only folded into `version`. | |
| 32 | + | /// | |
| 33 | + | /// A cut face is a composition of a base and a glyph set, and the set | |
| 34 | + | /// versions independently of the base and of this pipeline. Two faces over | |
| 35 | + | /// the same base carrying different sets are different fonts, so "which set | |
| 36 | + | /// is this face carrying" has to be answerable from the file. It was not: | |
| 37 | + | /// `version` is `{set}.{base}`, and nothing in the face says where the | |
| 38 | + | /// boundary falls, so `1.2.5.0` is unreadable without knowing that Plex Mono | |
| 39 | + | /// happened to be at 2.5.0. | |
| 40 | + | pub set_version: u32, | |
| 30 | 41 | pub base: &'a Base, | |
| 31 | 42 | } | |
| 32 | 43 | ||
| @@ -341,17 +352,31 @@ | |||
| 341 | 352 | ); | |
| 342 | 353 | // OFL 1.1 requires the base's copyright and licence to travel with a | |
| 343 | 354 | // modified build, and asks that the lineage be stated. It goes here rather | |
| 344 | - | // than in the family name, which the Reserved Font Name forbids. | |
| 355 | + | // than in the family name, which a Reserved Font Name would forbid. | |
| 356 | + | // | |
| 357 | + | // The glyph set version is stated alongside the base because a cut face is | |
| 358 | + | // a composition of the two and the set moves on its own schedule. Naming | |
| 359 | + | // both is what makes "does this installed face have the octants" a question | |
| 360 | + | // about the file rather than about which build produced it. | |
| 345 | 361 | let description = format!( | |
| 346 | - | "Derived from {} {}, by drawing the quasi house glyph set into it. \ | |
| 362 | + | "Derived from {} {}, by drawing quasi house glyph set v{} into it. \ | |
| 347 | 363 | The letterforms are unmodified.", | |
| 348 | - | id.base.family, id.base.version | |
| 364 | + | id.base.family, id.base.version, id.set_version | |
| 349 | 365 | ); | |
| 350 | 366 | let records = vec![ | |
| 351 | 367 | record(NameId::COPYRIGHT_NOTICE, &id.base.copyright), | |
| 352 | 368 | record(NameId::FAMILY_NAME, id.family), | |
| 353 | 369 | record(NameId::SUBFAMILY_NAME, id.style), | |
| 354 | - | record(NameId::UNIQUE_ID, &format!("{full}; {}", id.version)), | |
| 370 | + | // Names both halves of the composition rather than the packed version, | |
| 371 | + | // so the record that exists to identify one exact build identifies it | |
| 372 | + | // without a decoder ring. | |
| 373 | + | record( | |
| 374 | + | NameId::UNIQUE_ID, | |
| 375 | + | &format!( | |
| 376 | + | "{full}; house glyph set v{}; {} {}", | |
| 377 | + | id.set_version, id.base.family, id.base.version | |
| 378 | + | ), | |
| 379 | + | ), | |
| 355 | 380 | record(NameId::FULL_NAME, &full), | |
| 356 | 381 | record(NameId::VERSION_STRING, &format!("Version {}", id.version)), | |
| 357 | 382 | record(NameId::POSTSCRIPT_NAME, &postscript), |
| @@ -154,6 +154,7 @@ | |||
| 154 | 154 | family: &slot.family, | |
| 155 | 155 | style: &face.style, | |
| 156 | 156 | version: &version, | |
| 157 | + | set_version: manifest.set.version, | |
| 157 | 158 | base: base_pin, | |
| 158 | 159 | }; | |
| 159 | 160 | let built = compose::build(&face.bytes, &selected, &id)?; |
| @@ -52,6 +52,7 @@ | |||
| 52 | 52 | family: &slot.family, | |
| 53 | 53 | style, | |
| 54 | 54 | version: "1.2.5.0", | |
| 55 | + | set_version: 1, | |
| 55 | 56 | base, | |
| 56 | 57 | }; | |
| 57 | 58 | let built = compose::build(&face.bytes, &refs, &id).expect("the face cuts"); | |
| @@ -155,6 +156,47 @@ | |||
| 155 | 156 | assert!(copyright.unwrap().contains("IBM")); | |
| 156 | 157 | } | |
| 157 | 158 | ||
| 159 | + | #[test] | |
| 160 | + | fn the_face_states_which_glyph_set_it_carries() { | |
| 161 | + | // A cut face is a composition of a base and a glyph set, and the set | |
| 162 | + | // versions independently of both the base and this pipeline. So "which set | |
| 163 | + | // is this face carrying" has to be answerable from the file rather than | |
| 164 | + | // from knowing which build produced it — otherwise an installed face that | |
| 165 | + | // predates a set version is indistinguishable from one that does not. | |
| 166 | + | let Some((bytes, _)) = cut("Regular") else { | |
| 167 | + | return; | |
| 168 | + | }; | |
| 169 | + | let font = FontRef::new(&bytes).unwrap(); | |
| 170 | + | let name = font.name().unwrap(); | |
| 171 | + | let mut description = None; | |
| 172 | + | let mut unique_id = None; | |
| 173 | + | for record in name.name_record() { | |
| 174 | + | let value = record | |
| 175 | + | .string(name.string_data()) | |
| 176 | + | .map(|s| s.chars().collect::<String>()) | |
| 177 | + | .unwrap_or_default(); | |
| 178 | + | match record.name_id().to_u16() { | |
| 179 | + | 3 => unique_id = Some(value), | |
| 180 | + | 10 => description = Some(value), | |
| 181 | + | _ => {} | |
| 182 | + | } | |
| 183 | + | } | |
| 184 | + | let description = description.expect("a description"); | |
| 185 | + | let unique_id = unique_id.expect("a unique id"); | |
| 186 | + | ||
| 187 | + | assert!( | |
| 188 | + | description.contains("house glyph set v1"), | |
| 189 | + | "the description names the set version: {description}" | |
| 190 | + | ); | |
| 191 | + | // The unique id identifies one exact build, so it names both halves rather | |
| 192 | + | // than the packed `{set}.{base}` version, which cannot be split without | |
| 193 | + | // already knowing where the base's version starts. | |
| 194 | + | assert!( | |
| 195 | + | unique_id.contains("house glyph set v1") && unique_id.contains("IBM Plex Mono 2.5.0"), | |
| 196 | + | "the unique id names both halves of the composition: {unique_id}" | |
| 197 | + | ); | |
| 198 | + | } | |
| 199 | + | ||
| 158 | 200 | #[test] | |
| 159 | 201 | fn the_signature_over_the_old_bytes_is_dropped() { | |
| 160 | 202 | let Some((bytes, _)) = cut("Regular") else { |