use guiduck_scene::paint::{Color, FontData}; use guiduck_scene::{Glyph, GlyphRun, Synthesis}; use skrifa::MetadataProvider; use super::*; const SIZE: f32 = 48.0; /// One `l` from the sample face at the origin: a tall upright stem, so a lean /// shows as its top moving and its foot staying put. fn stem(synthesis: Synthesis, hint: bool) -> GlyphRun { let data = guiduck_samples::sample_font(); let font = FontRef::from_index(data.as_ref(), 0).expect("the sample face"); let id = font.charmap().map('l').expect("the face has an l").to_u32(); GlyphRun { font: FontData::new(data, 0), size: SIZE, brush: Color::BLACK.into(), glyphs: vec![Glyph { id, x: 0.0, y: 0.0 }], normalized_coords: Vec::new(), hint, synthesis, } } fn path_bounds(run: &GlyphRun) -> tiny_skia::Rect { let placement = RunPlacement::of(run, Affine::IDENTITY); run_to_path(run, &placement, &mut GlyphCache::default()) .expect("draws") .expect("has ink") .bounds() } const ITALIC: Synthesis = Synthesis { skew: Some(14.0), embolden: false, }; const BOLD: Synthesis = Synthesis { skew: None, embolden: true, }; #[test] fn a_synthesized_italic_leans_the_outline_forward() { for hint in [false, true] { let upright = path_bounds(&stem(Synthesis::default(), hint)); let leaned = path_bounds(&stem(ITALIC, hint)); // The foot sits on the baseline and does not move; the top, the // stem's height above it, moves right by tan(14°) of that height. let height = f64::from(upright.bottom() - upright.top()); let expected = height * 14f64.to_radians().tan(); let moved = f64::from(leaned.right() - upright.right()); assert!( (moved - expected).abs() < 1.0, "hint {hint}: the top moved {moved}, expected about {expected}" ); assert!( (leaned.left() - upright.left()).abs() < 0.5, "hint {hint}: the foot stayed put" ); assert_eq!(leaned.top(), upright.top(), "hint {hint}: no taller"); } } #[test] fn a_synthesized_bold_grows_the_outline_on_every_side() { let grow = f64::from(SIZE) * Synthesis::EMBOLDEN_PER_SIDE_EM; for hint in [false, true] { let regular = path_bounds(&stem(Synthesis::default(), hint)); let bold = path_bounds(&stem(BOLD, hint)); for (side, delta) in [ ("left", regular.left() - bold.left()), ("right", bold.right() - regular.right()), ("top", regular.top() - bold.top()), ("bottom", bold.bottom() - regular.bottom()), ] { assert!( (f64::from(delta) - grow).abs() < 0.05, "hint {hint}: {side} grew {delta}, expected {grow}" ); } } } /// The raster cache keys on the synthesis: the same glyph at the same size, /// asked for upright and then leaned, is two rasters rather than one reused. #[test] fn the_raster_cache_tells_synthesized_glyphs_apart() { let mut cache = GlyphCache::default(); let black = [0, 0, 0, 255]; let bounds = |cache: &mut GlyphCache, synthesis| { cache .hinted_run_bounds(&stem(synthesis, true), 1.0, 0.0, 0.0, black) .expect("draws") .expect("has ink") }; let upright = bounds(&mut cache, Synthesis::default()); let leaned = bounds(&mut cache, ITALIC); let bold = bounds(&mut cache, BOLD); assert!( leaned.x1 > upright.x1, "the leaned raster reaches further right" ); assert!(bold.width() > upright.width(), "the bold raster is wider"); }