//! Cutting a face from a variable base, which is the capability itself. //! //! The base is Atkinson Hyperlegible Mono: `wght` 200-800 in one file, and the //! base `quasi-mono` has been cut from since. What is asserted here is what //! the axis adds — that it survives the cut, that the marks ride it, and that //! a mark calibrated at rest is still the right weight at the far end, which //! is the failure mode a static base could not have. //! //! Skipped rather than failed when the base is not cached, same as the static //! cut. `cargo run -- params --base atkinson-mono` once and it runs for good. use quasi_type::base::{self, BaseFace}; use quasi_type::compose::{self, Identity}; use quasi_type::manifest::WeightResponse; use quasi_type::manifest::{GlyphSpec, Manifest}; use quasi_type::pins::{Base, Pins}; use quasi_type::{HOUSE_SET, PINS, assert as coverage, woff2}; use read_fonts::types::{GlyphId, Tag}; use read_fonts::{FontRef, TableProvider}; use skrifa::MetadataProvider; use skrifa::instance::Size; const BASE: &str = "atkinson-mono"; /// The family the slot names. Read here rather than through `pins.slot` because /// this file is about the base and cuts from it directly. const FAMILY: &str = "Quasi Mono"; fn root() -> std::path::PathBuf { std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) } fn pins() -> Pins { Pins::parse(PINS).unwrap() } fn base_face() -> Option<(BaseFace, &'static Base)> { let pins: &'static Pins = Box::leak(Box::new(pins())); let base = pins.base(BASE).unwrap(); match base::load(base, &base::cache_dir(&root()), true) { Ok(faces) => faces.into_iter().next().map(|face| (face, base)), Err(_) => { eprintln!("skipped: {BASE} is not cached. Run `cargo run -- params --base {BASE}`"); None } } } fn cut_as(style: &str) -> Option, Vec), String>> { let (face, base) = base_face()?; let manifest = Manifest::parse(HOUSE_SET).unwrap(); let specs = manifest.glyphs; let refs: Vec<&GlyphSpec> = specs.iter().collect(); let id = Identity { family: FAMILY, style, version: "2.2.001", set_version: manifest.set.version, base, }; Some( compose::build(&face.bytes, &refs, &id) .map(|built| (built.bytes, specs)) .map_err(|e| e.to_string()), ) } fn cut() -> Option<(Vec, Vec)> { Some(cut_as("ExtraLight")?.expect("the face cuts")) } /// The whole point: the cut face still varies, and so do the marks in it. #[test] fn the_axis_survives_the_cut_and_the_marks_ride_it() { let Some((bytes, specs)) = cut() else { return }; let font = FontRef::new(&bytes).unwrap(); for tag in [b"fvar", b"gvar", b"avar", b"STAT", b"HVAR"] { assert!( font.table_data(Tag::new(tag)).is_some(), "{} was dropped, so the face no longer varies the way its base did", String::from_utf8_lossy(tag) ); } let axes = font.axes(); assert_eq!(axes.len(), 1); let axis = axes.get(0).unwrap(); assert_eq!(axis.tag(), Tag::new(b"wght")); assert_eq!((axis.min_value(), axis.max_value()), (200.0, 800.0)); assert_eq!( font.named_instances().len(), 7, "the base's seven named weights" ); // Every mark that answers weight has variation data, and it is data the // base did not have. A cell fill is exactly half the cell at every weight, // so it correctly carries none: `gvar` says "does not vary" with an empty // entry, and that is the honest record rather than a gap. let mappings = compose::coverage(&bytes).unwrap(); let gvar = font.gvar().unwrap(); for spec in &specs { if spec.shape.weight_response() == WeightResponse::Holds { continue; } let gid = mappings[&spec.codepoint]; let data = gvar .glyph_variation_data(gid) .unwrap_or_else(|e| panic!("{} has no readable variation data: {e:?}", spec.name)); assert!( data.is_some_and(|d| d.tuples().count() > 0), "{} carries no variation, so it would draw at wght 200 across the whole axis", spec.name ); } } /// The done condition, in the form it was written in: a mark drawn at the bold /// end of the axis is drawn at the bold stroke weight. /// /// Read back through the axis rather than off the deltas, so what is measured is /// what a rasteriser would actually put on a screen. #[test] fn a_mark_at_the_heavy_end_is_heavier_than_at_the_light_end() { let Some((bytes, specs)) = cut() else { return }; for spec in &specs { // Except the cell fills, which are the one thing in the set that // deliberately does not answer weight: `▀` is exactly half the cell at // wght 200 and at 800, and a half that grew would stop tiling. if spec.shape.weight_response() == WeightResponse::Holds { continue; } let light = ink(&bytes, spec.codepoint, 200.0); let heavy = ink(&bytes, spec.codepoint, 800.0); assert!( heavy > light * 1.10, "{} puts {light:.0} units of ink down at wght 200 and {heavy:.0} at 800, \ which is not a face answering its own axis", spec.name ); } } /// The two kinds of mark answer the axis differently, and the discriminator is /// how much of its own box a mark inks rather than how wide it gets. /// /// Extent alone does not separate them here and should not be expected to. The /// static cut compares Regular to Bold, 400 to 700; this axis runs 200 to 800 /// and Atkinson's stroke goes 55 units to 139, so a stroked mark's band term /// carries it 1.15x wider on its own. Fill separates them cleanly, because it is /// what the two responses actually mean: a solid mark scales, so it inks the /// same share of a larger box, and a stroked mark thickens inside a box that /// barely moves. #[test] fn solid_marks_scale_along_the_axis_and_stroked_marks_thicken() { use quasi_type::manifest::WeightResponse; let Some((bytes, specs)) = cut() else { return }; for spec in &specs { // Cell furniture is measured by the test below instead. A bar's // bounding box *is* the bar, so it inks all of it at every weight, and // a fill fraction says nothing about whether it thickened. if spec.shape.is_cell_furniture() { continue; } let light = fill(&bytes, spec.codepoint, 200.0); let heavy = fill(&bytes, spec.codepoint, 800.0); match spec.shape.weight_response() { // Cell fills are exact fractions of the cell at every weight, so // they neither grow nor thicken. Nothing to assert but that. WeightResponse::Holds => { assert!( (width(&bytes, spec.codepoint, 800.0) - width(&bytes, spec.codepoint, 200.0)) .abs() < 1.0, "{} is a cell fill and must not move with the axis", spec.name ); } WeightResponse::Grows => { assert!( (heavy - light).abs() < 0.01, "{} inks {light:.3} of its box at wght 200 and {heavy:.3} at 800. \ A solid mark scales, so its fill is the shape's own constant.", spec.name ); let growth = width(&bytes, spec.codepoint, 800.0) / width(&bytes, spec.codepoint, 200.0); assert!( growth > 1.10, "{} grew {growth:.2}x across the whole axis, which is not a mark \ following a base whose stroke goes 55 units to 139", spec.name ); } WeightResponse::Thickens => assert!( heavy > light * 1.4, "{} inks {light:.3} of its box at wght 200 and {heavy:.3} at 800, so its \ stroke is not following the base's", spec.name ), } } } /// The calibration holds at the far end of the axis, which is where adopting /// this base broke it. /// /// Both numbers here are read off the base at each end rather than written /// down as constants, because that is what the coefficients in the manifest /// are: a relationship to the base, refitted when the base moves. /// /// - the ballot X is heavier than the base's own `×` and not far heavier. A /// coefficient carried over from another base can leave it *lighter*. /// - the space render keeps its counter open. A coefficient that is too heavy /// at `wght` 800 inks most of its own bounding box, which rasterises as a /// blob rather than a box. #[test] fn the_marks_are_still_calibrated_at_the_heavy_end() { let Some((bytes, _)) = cut() else { return }; for wght in [200.0, 800.0] { let base_cross = base_fill(0x00D7, wght); let ours = fill(&bytes, 0x2718, wght); assert!( ours > base_cross && ours < base_cross + 0.04, "at wght {wght} the ballot X inks {ours:.3} of its box against the base's own \ cross at {base_cross:.3}. HEAVY BALLOT X is heavier than `×`, and not far \ heavier: a flat multiple of the base's bar read as a blob." ); // The counter is what makes an open box a box. Measured off the drawing // rather than off a fill fraction, since fill cannot tell a thick box // from a closed one. let bar = base_stroke(wght); let across = width(&bytes, 0x2423, wght) - 2.0 * bar; let up = height(&bytes, 0x2423, wght) - bar; assert!( across / width(&bytes, 0x2423, wght) > 0.45 && up / height(&bytes, 0x2423, wght) > 0.45, "at wght {wght} the space render's counter is {across:.0} x {up:.0} units inside \ a {:.0} x {:.0} box, which closes up rather than reading as a box", width(&bytes, 0x2423, wght), height(&bytes, 0x2423, wght), ); } } /// The base's own fill for a codepoint, so a calibration is stated against the /// face it was fitted to rather than against a number somebody copied. fn base_fill(codepoint: u32, wght: f32) -> f32 { let (face, _) = base_face().expect("the base is cached, since the cut above needed it"); fill(&face.bytes, codepoint, wght) } /// The base's horizontal stroke at a location, read off `-` the way the recipes /// read it. fn base_stroke(wght: f32) -> f32 { height(&base_face().expect("cached").0.bytes, 0x002D, wght) } /// The done condition of the variable work, in the words it was written in: a /// bold table border draws at the bold stroke weight. /// /// Measured on the bar itself rather than on a fill fraction. A box-drawing /// stem is a rectangle, so its bounding box is the bar and it inks all of it at /// every weight; what has to move is the bar's own thickness, and it has to /// move by what the base moved — `-` goes 55 units to 139 across this axis. #[test] fn a_bold_border_draws_at_the_bold_stroke_weight() { let Some((bytes, _)) = cut() else { return }; // A horizontal rule's thickness is its height; a vertical's is its width. let thin = height(&bytes, 0x2500, 200.0); let thick = height(&bytes, 0x2500, 800.0); let ratio = thick / thin; let base_ratio = 139.0 / 55.0; assert!( (ratio - base_ratio).abs() < 0.15, "the light horizontal goes {thin:.0} to {thick:.0} ({ratio:.2}x) while the base's \ own bar goes 55 to 139 ({base_ratio:.2}x)" ); let thin = width(&bytes, 0x2502, 200.0); let thick = width(&bytes, 0x2502, 800.0); assert!( thick > thin * 2.0, "the light vertical goes {thin:.0} to {thick:.0}, and the base's stem goes 54 to 158" ); // And heavy stays twice light at both ends of the axis, or the two weights // of border stop being distinguishable at one end. for wght in [200.0, 800.0] { let light = height(&bytes, 0x2500, wght); let heavy = height(&bytes, 0x2501, wght); assert!( (heavy / light - 2.0).abs() < 0.1, "at wght {wght} heavy is {:.2}x light", heavy / light ); } // A cell fill holds: `█` is the whole cell at every weight. assert!( (height(&bytes, 0x2588, 800.0) - height(&bytes, 0x2588, 200.0)).abs() < 1.0, "the full block moved with the axis" ); } /// The ExtraLight trap, as a gate rather than as a warning in a comment. /// /// Keeping the axis means keeping the base's default instance, and this base's /// default is `wght` 200. A cut that called itself Regular would be a file every /// naive `@font-face` and every `fc-match` believes. #[test] fn a_face_that_misnames_the_default_instance_is_refused() { let Some(result) = cut_as("Regular") else { return; }; let err = result.expect_err("Regular is not this base's default instance"); assert!(err.contains("ExtraLight"), "{err}"); assert!(err.contains("wght 200"), "{err}"); } /// The names other tables point at by number survive, which is what makes the /// seven named instances still have names. #[test] fn the_names_the_axis_points_at_come_across() { let Some((bytes, _)) = cut() else { return }; let font = FontRef::new(&bytes).unwrap(); let instances: Vec = font .named_instances() .iter() .map(|instance| { font.localized_strings(instance.subfamily_name_id()) .english_or_first() .map(|s| s.chars().collect()) .unwrap_or_default() }) .collect(); assert!( instances.iter().any(|n| n == "Bold") && instances.iter().any(|n| n == "ExtraLight"), "the instances lost their names: {instances:?}" ); // The house name reaches the records a menu reads, and the typographic pair // states what the file really is. let names = |id: u16| -> String { font.localized_strings(skrifa::string::StringId::new(id)) .english_or_first() .map(|s| s.chars().collect()) .unwrap_or_default() }; assert_eq!(names(1), "Quasi Mono ExtraLight"); assert_eq!(names(2), "Regular"); assert_eq!(names(16), "Quasi Mono"); assert_eq!(names(17), "ExtraLight"); assert_eq!(names(25), "QuasiMono"); assert!(names(10).contains("Atkinson Hyperlegible Mono 2.001")); } /// Atkinson ships no box drawing, no block elements and none of the four /// arrows; a face cut from it covers the whole floor a consumer is entitled to /// assume, which is what lets the mono slot point at this base. #[test] fn the_cut_face_is_fit_for_the_mono_slot() { let Some((bytes, specs)) = cut() else { return }; let house: Vec = specs.iter().map(|g| g.codepoint).collect(); let result = coverage::check( &coverage::ALLOY_SURFACE, &compose::coverage(&bytes).unwrap(), &house, ); assert!( result.ok(), "missing {:?}", result .missing .iter() .map(|c| format!("U+{c:04X}")) .collect::>() ); // The four the base has none of, one from each tier that had to be built: // a box-drawing stem, a fill, an eighth, and a shade. for cell_furniture in [0x2502, 0x2588, 0x258F, 0x2591] { assert!( house.contains(&cell_furniture), "U+{cell_furniture:04X} is not in the set, so the base must have it \ and Atkinson does not" ); } } #[test] fn the_base_keeps_every_glyph_and_every_codepoint_it_had() { let Some((face, _)) = base_face() else { return }; let before = base::mappings(&face.bytes).unwrap(); let base_glyphs = FontRef::new(&face.bytes) .unwrap() .maxp() .unwrap() .num_glyphs(); let (bytes, specs) = cut().unwrap(); let after = compose::coverage(&bytes).unwrap(); for (codepoint, gid) in &before { assert_eq!(after.get(codepoint), Some(gid), "U+{codepoint:04X} moved"); } assert_eq!( FontRef::new(&bytes).unwrap().maxp().unwrap().num_glyphs(), base_glyphs + specs.len() as u16 ); } #[test] fn the_same_checkout_cuts_the_same_bytes() { let Some((first, _)) = cut() else { return }; let (second, _) = cut().unwrap(); assert_eq!(first, second, "the build is not reproducible"); } #[test] fn the_woff2_still_encodes() { let Some((bytes, _)) = cut() else { return }; let web = woff2::encode(&bytes).unwrap(); assert_eq!(&web[0..4], b"wOF2"); assert!(web.len() < bytes.len()); } /// A glyph's outline at one point on the axis, as a `(bbox width, ink area)`. fn outline(bytes: &[u8], codepoint: u32, wght: f32) -> (f32, f32, f32) { let font = FontRef::new(bytes).unwrap(); let gid: GlyphId = compose::coverage(bytes).unwrap()[&codepoint]; let location = font.axes().location([("wght", wght)]); let mut pen = Trace::default(); font.outline_glyphs() .get(gid) .unwrap() .draw( skrifa::outline::DrawSettings::unhinted(Size::unscaled(), &location), &mut pen, ) .unwrap(); (pen.x1 - pen.x0, pen.y1 - pen.y0, pen.area.abs() / 2.0) } fn width(bytes: &[u8], codepoint: u32, wght: f32) -> f32 { outline(bytes, codepoint, wght).0 } fn height(bytes: &[u8], codepoint: u32, wght: f32) -> f32 { outline(bytes, codepoint, wght).1 } fn ink(bytes: &[u8], codepoint: u32, wght: f32) -> f32 { outline(bytes, codepoint, wght).2 } /// How much of its own bounding box a mark inks in. fn fill(bytes: &[u8], codepoint: u32, wght: f32) -> f32 { let (width, height, ink) = outline(bytes, codepoint, wght); ink / (width * height) } /// Accumulates a drawn outline's extent and its shoelace area. Every mark in the /// set is straight-edged, so summing the segments is exact rather than an /// approximation of a curve. #[derive(Default)] struct Trace { x0: f32, x1: f32, y0: f32, y1: f32, area: f32, start: (f32, f32), at: (f32, f32), started: bool, } impl Trace { fn point(&mut self, x: f32, y: f32) { if !self.started { self.x0 = x; self.x1 = x; self.y0 = y; self.y1 = y; self.started = true; } self.x0 = self.x0.min(x); self.x1 = self.x1.max(x); self.y0 = self.y0.min(y); self.y1 = self.y1.max(y); } fn edge(&mut self, x: f32, y: f32) { self.area += self.at.0 * y - x * self.at.1; self.at = (x, y); self.point(x, y); } } impl skrifa::outline::OutlinePen for Trace { fn move_to(&mut self, x: f32, y: f32) { self.point(x, y); self.start = (x, y); self.at = (x, y); } fn line_to(&mut self, x: f32, y: f32) { self.edge(x, y); } fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) { self.edge(cx0, cy0); self.edge(x, y); } fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) { self.edge(cx0, cy0); self.edge(cx1, cy1); self.edge(x, y); } fn close(&mut self) { let (x, y) = self.start; self.edge(x, y); } }