// SPDX-License-Identifier: MIT OR Apache-2.0 /// Key for building a glyph cache #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct CacheKey { /// Font ID pub font_id: fontdb::ID, /// Glyph ID pub glyph_id: u16, /// `f32` bits of font size pub font_size_bits: u32, /// Binning of fractional X offset pub x_bin: SubpixelBin, /// Binning of fractional Y offset pub y_bin: SubpixelBin, } impl CacheKey { pub fn new( font_id: fontdb::ID, glyph_id: u16, font_size: f32, pos: (f32, f32), ) -> (Self, i32, i32) { let (x, x_bin) = SubpixelBin::new(pos.0); let (y, y_bin) = SubpixelBin::new(pos.1); ( Self { font_id, glyph_id, font_size_bits: font_size.to_bits(), x_bin, y_bin, }, x, y, ) } } /// Binning of subpixel position for cache optimization #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum SubpixelBin { Zero, One, Two, Three, } impl SubpixelBin { pub fn new(pos: f32) -> (i32, Self) { let (fract, truncf) = libm::modff(pos); let trunc = truncf as i32; if pos.is_sign_negative() { if fract > -0.125 { (trunc, Self::Zero) } else if fract > -0.375 { (trunc - 1, Self::Three) } else if fract > -0.625 { (trunc - 1, Self::Two) } else if fract > -0.875 { (trunc - 1, Self::One) } else { (trunc - 1, Self::Zero) } } else { #[allow(clippy::collapsible_else_if)] if fract < 0.125 { (trunc, Self::Zero) } else if fract < 0.375 { (trunc, Self::One) } else if fract < 0.625 { (trunc, Self::Two) } else if fract < 0.875 { (trunc, Self::Three) } else { (trunc + 1, Self::Zero) } } } pub fn as_float(&self) -> f32 { match self { Self::Zero => 0.0, Self::One => 0.25, Self::Two => 0.5, Self::Three => 0.75, } } } #[test] fn test_subpixel_bins() { // POSITIVE TESTS // Maps to 0.0 assert_eq!(SubpixelBin::new(0.0), (0, SubpixelBin::Zero)); assert_eq!(SubpixelBin::new(0.124), (0, SubpixelBin::Zero)); // Maps to 0.25 assert_eq!(SubpixelBin::new(0.125), (0, SubpixelBin::One)); assert_eq!(SubpixelBin::new(0.25), (0, SubpixelBin::One)); assert_eq!(SubpixelBin::new(0.374), (0, SubpixelBin::One)); // Maps to 0.5 assert_eq!(SubpixelBin::new(0.375), (0, SubpixelBin::Two)); assert_eq!(SubpixelBin::new(0.5), (0, SubpixelBin::Two)); assert_eq!(SubpixelBin::new(0.624), (0, SubpixelBin::Two)); // Maps to 0.75 assert_eq!(SubpixelBin::new(0.625), (0, SubpixelBin::Three)); assert_eq!(SubpixelBin::new(0.75), (0, SubpixelBin::Three)); assert_eq!(SubpixelBin::new(0.874), (0, SubpixelBin::Three)); // Maps to 1.0 assert_eq!(SubpixelBin::new(0.875), (1, SubpixelBin::Zero)); assert_eq!(SubpixelBin::new(0.999), (1, SubpixelBin::Zero)); assert_eq!(SubpixelBin::new(1.0), (1, SubpixelBin::Zero)); assert_eq!(SubpixelBin::new(1.124), (1, SubpixelBin::Zero)); // NEGATIVE TESTS // Maps to 0.0 assert_eq!(SubpixelBin::new(-0.0), (0, SubpixelBin::Zero)); assert_eq!(SubpixelBin::new(-0.124), (0, SubpixelBin::Zero)); // Maps to 0.25 assert_eq!(SubpixelBin::new(-0.125), (-1, SubpixelBin::Three)); assert_eq!(SubpixelBin::new(-0.25), (-1, SubpixelBin::Three)); assert_eq!(SubpixelBin::new(-0.374), (-1, SubpixelBin::Three)); // Maps to 0.5 assert_eq!(SubpixelBin::new(-0.375), (-1, SubpixelBin::Two)); assert_eq!(SubpixelBin::new(-0.5), (-1, SubpixelBin::Two)); assert_eq!(SubpixelBin::new(-0.624), (-1, SubpixelBin::Two)); // Maps to 0.75 assert_eq!(SubpixelBin::new(-0.625), (-1, SubpixelBin::One)); assert_eq!(SubpixelBin::new(-0.75), (-1, SubpixelBin::One)); assert_eq!(SubpixelBin::new(-0.874), (-1, SubpixelBin::One)); // Maps to 1.0 assert_eq!(SubpixelBin::new(-0.875), (-1, SubpixelBin::Zero)); assert_eq!(SubpixelBin::new(-0.999), (-1, SubpixelBin::Zero)); assert_eq!(SubpixelBin::new(-1.0), (-1, SubpixelBin::Zero)); assert_eq!(SubpixelBin::new(-1.124), (-1, SubpixelBin::Zero)); }