cosmic_text/
glyph_cache.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3bitflags::bitflags! {
4    /// Flags that change rendering
5    #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
6    #[repr(transparent)]
7    pub struct CacheKeyFlags: u32 {
8        /// Skew by 14 degrees to synthesize italic
9        const FAKE_ITALIC = 1;
10    }
11}
12
13/// Key for building a glyph cache
14#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
15pub struct CacheKey {
16    /// Font ID
17    pub font_id: fontdb::ID,
18    /// Glyph ID
19    pub glyph_id: u16,
20    /// `f32` bits of font size
21    pub font_size_bits: u32,
22    /// Binning of fractional X offset
23    pub x_bin: SubpixelBin,
24    /// Binning of fractional Y offset
25    pub y_bin: SubpixelBin,
26    /// [`CacheKeyFlags`]
27    pub flags: CacheKeyFlags,
28}
29
30impl CacheKey {
31    pub fn new(
32        font_id: fontdb::ID,
33        glyph_id: u16,
34        font_size: f32,
35        pos: (f32, f32),
36        flags: CacheKeyFlags,
37    ) -> (Self, i32, i32) {
38        let (x, x_bin) = SubpixelBin::new(pos.0);
39        let (y, y_bin) = SubpixelBin::new(pos.1);
40        (
41            Self {
42                font_id,
43                glyph_id,
44                font_size_bits: font_size.to_bits(),
45                x_bin,
46                y_bin,
47                flags,
48            },
49            x,
50            y,
51        )
52    }
53}
54
55/// Binning of subpixel position for cache optimization
56#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
57pub enum SubpixelBin {
58    Zero,
59    One,
60    Two,
61    Three,
62}
63
64impl SubpixelBin {
65    pub fn new(pos: f32) -> (i32, Self) {
66        let trunc = pos as i32;
67        let fract = pos - trunc as f32;
68
69        if pos.is_sign_negative() {
70            if fract > -0.125 {
71                (trunc, Self::Zero)
72            } else if fract > -0.375 {
73                (trunc - 1, Self::Three)
74            } else if fract > -0.625 {
75                (trunc - 1, Self::Two)
76            } else if fract > -0.875 {
77                (trunc - 1, Self::One)
78            } else {
79                (trunc - 1, Self::Zero)
80            }
81        } else {
82            #[allow(clippy::collapsible_else_if)]
83            if fract < 0.125 {
84                (trunc, Self::Zero)
85            } else if fract < 0.375 {
86                (trunc, Self::One)
87            } else if fract < 0.625 {
88                (trunc, Self::Two)
89            } else if fract < 0.875 {
90                (trunc, Self::Three)
91            } else {
92                (trunc + 1, Self::Zero)
93            }
94        }
95    }
96
97    pub fn as_float(&self) -> f32 {
98        match self {
99            Self::Zero => 0.0,
100            Self::One => 0.25,
101            Self::Two => 0.5,
102            Self::Three => 0.75,
103        }
104    }
105}
106
107#[test]
108fn test_subpixel_bins() {
109    // POSITIVE TESTS
110
111    // Maps to 0.0
112    assert_eq!(SubpixelBin::new(0.0), (0, SubpixelBin::Zero));
113    assert_eq!(SubpixelBin::new(0.124), (0, SubpixelBin::Zero));
114
115    // Maps to 0.25
116    assert_eq!(SubpixelBin::new(0.125), (0, SubpixelBin::One));
117    assert_eq!(SubpixelBin::new(0.25), (0, SubpixelBin::One));
118    assert_eq!(SubpixelBin::new(0.374), (0, SubpixelBin::One));
119
120    // Maps to 0.5
121    assert_eq!(SubpixelBin::new(0.375), (0, SubpixelBin::Two));
122    assert_eq!(SubpixelBin::new(0.5), (0, SubpixelBin::Two));
123    assert_eq!(SubpixelBin::new(0.624), (0, SubpixelBin::Two));
124
125    // Maps to 0.75
126    assert_eq!(SubpixelBin::new(0.625), (0, SubpixelBin::Three));
127    assert_eq!(SubpixelBin::new(0.75), (0, SubpixelBin::Three));
128    assert_eq!(SubpixelBin::new(0.874), (0, SubpixelBin::Three));
129
130    // Maps to 1.0
131    assert_eq!(SubpixelBin::new(0.875), (1, SubpixelBin::Zero));
132    assert_eq!(SubpixelBin::new(0.999), (1, SubpixelBin::Zero));
133    assert_eq!(SubpixelBin::new(1.0), (1, SubpixelBin::Zero));
134    assert_eq!(SubpixelBin::new(1.124), (1, SubpixelBin::Zero));
135
136    // NEGATIVE TESTS
137
138    // Maps to 0.0
139    assert_eq!(SubpixelBin::new(-0.0), (0, SubpixelBin::Zero));
140    assert_eq!(SubpixelBin::new(-0.124), (0, SubpixelBin::Zero));
141
142    // Maps to 0.25
143    assert_eq!(SubpixelBin::new(-0.125), (-1, SubpixelBin::Three));
144    assert_eq!(SubpixelBin::new(-0.25), (-1, SubpixelBin::Three));
145    assert_eq!(SubpixelBin::new(-0.374), (-1, SubpixelBin::Three));
146
147    // Maps to 0.5
148    assert_eq!(SubpixelBin::new(-0.375), (-1, SubpixelBin::Two));
149    assert_eq!(SubpixelBin::new(-0.5), (-1, SubpixelBin::Two));
150    assert_eq!(SubpixelBin::new(-0.624), (-1, SubpixelBin::Two));
151
152    // Maps to 0.75
153    assert_eq!(SubpixelBin::new(-0.625), (-1, SubpixelBin::One));
154    assert_eq!(SubpixelBin::new(-0.75), (-1, SubpixelBin::One));
155    assert_eq!(SubpixelBin::new(-0.874), (-1, SubpixelBin::One));
156
157    // Maps to 1.0
158    assert_eq!(SubpixelBin::new(-0.875), (-1, SubpixelBin::Zero));
159    assert_eq!(SubpixelBin::new(-0.999), (-1, SubpixelBin::Zero));
160    assert_eq!(SubpixelBin::new(-1.0), (-1, SubpixelBin::Zero));
161    assert_eq!(SubpixelBin::new(-1.124), (-1, SubpixelBin::Zero));
162}