diff --git a/src/swash.rs b/src/swash.rs index 43d088b..8ff411a 100644 --- a/src/swash.rs +++ b/src/swash.rs @@ -33,11 +33,10 @@ fn swash_image( .size(f32::from_bits(cache_key.font_size_bits)) .hint(!cache_key.flags.contains(CacheKeyFlags::DISABLE_HINTING)); if let Some(variation) = variable_width { - scaler = scaler.variations(std::iter::once(swash::Setting { - tag: swash::Tag::from_be_bytes(*b"wght"), - value: f32::from(cache_key.font_weight.0) - .clamp(variation.min_value(), variation.max_value()), - })); + scaler = scaler.normalized_coords(font.as_swash().variations().normalized_coords([( + swash::Tag::from_be_bytes(*b"wght"), + f32::from(cache_key.font_weight.0).clamp(variation.min_value(), variation.max_value()), + )])); } let mut scaler = scaler.build(); @@ -100,11 +99,10 @@ fn swash_outline_commands( .size(f32::from_bits(cache_key.font_size_bits)) .hint(!cache_key.flags.contains(CacheKeyFlags::DISABLE_HINTING)); if let Some(variation) = variable_width { - scaler = scaler.variations(std::iter::once(swash::Setting { - tag: swash::Tag::from_be_bytes(*b"wght"), - value: f32::from(cache_key.font_weight.0) - .clamp(variation.min_value(), variation.max_value()), - })); + scaler = scaler.normalized_coords(font.as_swash().variations().normalized_coords([( + swash::Tag::from_be_bytes(*b"wght"), + f32::from(cache_key.font_weight.0).clamp(variation.min_value(), variation.max_value()), + )])); } let mut scaler = scaler.build(); @@ -244,3 +242,60 @@ impl SwashCache { } } } + +#[cfg(test)] +mod test { + use super::*; + use swash::{FontRef, Setting, Tag}; + + // variations() resizes context.coords in place (stale values persist), + // whereas using normalized_coords() clears and replaces them. + #[test] + fn no_coord_leakage_across_fonts() { + let [Ok(sfns), Ok(sfns_italic)] = [ + "/System/Library/Fonts/SFNS.ttf", + "/System/Library/Fonts/SFNSItalic.ttf", + ] + .map(std::fs::read) else { + return; + }; + let regular = FontRef::from_index(&sfns, 0).unwrap(); + let italic = FontRef::from_index(&sfns_italic, 0).unwrap(); + let wght = Tag::from_be_bytes(*b"wght"); + + let render = |ctx: &mut ScaleContext, font: FontRef, weight: f32, use_normalized| { + let mut b = ctx.builder(font).size(16.0).hint(true); + if use_normalized { + b = b.normalized_coords(font.variations().normalized_coords([(wght, weight)])); + } else { + b = b.variations(std::iter::once(Setting { + tag: wght, + value: weight, + })); + } + Render::new(&[Source::Outline]) + .format(Format::Alpha) + .render(&mut b.build(), 36) + }; + + // reference: regular@400 with no prior context + let mut ctx = ScaleContext::new(); + let reference = render(&mut ctx, regular, 400.0, false).map(|i| i.data); + + // variations(): pollute ctx with italic@700, then render regular@400 + let mut ctx = ScaleContext::new(); + render(&mut ctx, italic, 700.0, false); + let not_normalized = render(&mut ctx, regular, 400.0, false).map(|i| i.data); + + // normalized_coords(): same sequence + let mut ctx = ScaleContext::new(); + render(&mut ctx, italic, 700.0, true); + let normalized = render(&mut ctx, regular, 400.0, true).map(|i| i.data); + + assert_ne!(not_normalized, reference, "variations leak across fonts"); + assert_eq!( + normalized, reference, + "normalized_coords match clean render" + ); + } +}