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