skrifa/outline/glyf/hint/
cleartype.rs1use raw::{tables::gasp::GaspRangeBehavior, TableProvider};
4
5#[derive(Copy, Clone, PartialEq, Eq, Debug)]
14pub struct RasterPolicy {
15 pub symmetric_smoothing: bool,
24 pub grid_fit: bool,
26}
27
28impl RasterPolicy {
29 pub(crate) fn fallback(has_glyph_instructions: bool, ppem: u16) -> Self {
35 Self {
36 symmetric_smoothing: !has_glyph_instructions || ppem > 20,
37 grid_fit: has_glyph_instructions,
38 }
39 }
40
41 pub(crate) fn resolve<'a>(font: &impl TableProvider<'a>, ppem: u16) -> Self {
43 let has_glyph_instructions = font
44 .selected_maxp()
45 .map(|maxp| maxp.max_size_of_instructions().unwrap_or_default() != 0)
46 .unwrap_or_default();
47 let mut policy = Self::fallback(has_glyph_instructions, ppem);
48 if let Ok(gasp) = font.gasp() {
49 if gasp.version() >= 1 {
50 if let Some(range) = gasp
51 .gasp_ranges()
52 .iter()
53 .find(|range| ppem <= range.range_max_ppem())
54 {
55 let behavior = range.range_gasp_behavior();
56 policy.symmetric_smoothing =
57 behavior.contains(GaspRangeBehavior::GASP_SYMMETRIC_SMOOTHING);
58 policy.grid_fit = behavior.contains(GaspRangeBehavior::GASP_SYMMETRIC_GRIDFIT);
59 }
60 }
61 }
62 policy
63 }
64}
65
66impl Default for RasterPolicy {
67 fn default() -> Self {
68 Self {
69 symmetric_smoothing: true,
70 grid_fit: true,
71 }
72 }
73}
74
75#[derive(Copy, Clone, PartialEq, Eq, Debug)]
77pub enum ClearType {
78 Classic,
82 Enhanced,
86}
87
88#[derive(Copy, Clone, Debug)]
90pub struct ClearTypeState {
91 pub mode: ClearType,
93 pub is_hint_reliant: bool,
98 pub policy: RasterPolicy,
100}
101
102impl ClearTypeState {
103 pub fn new(mode: ClearType, is_hint_reliant: bool, policy: RasterPolicy) -> Self {
108 match mode {
109 ClearType::Classic => Self {
110 is_hint_reliant,
111 ..Self::default()
112 },
113 ClearType::Enhanced => Self {
114 mode,
115 is_hint_reliant,
116 policy,
117 },
118 }
119 }
120
121 pub fn is_native_enhanced(&self) -> bool {
124 self.mode == ClearType::Enhanced && !self.is_hint_reliant
125 }
126
127 pub fn symmetric_smoothing(&self) -> bool {
130 self.mode == ClearType::Classic || self.policy.symmetric_smoothing
131 }
132}
133
134impl Default for ClearTypeState {
135 fn default() -> Self {
136 Self {
137 mode: ClearType::Classic,
138 is_hint_reliant: false,
139 policy: RasterPolicy::default(),
140 }
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147 use raw::FontRef;
148
149 #[test]
152 fn policy_from_gasp_v1() {
153 let font = FontRef::new(font_test_data::TINOS_SUBSET).unwrap();
154 for (ppem, symmetric) in [(8, true), (10, true), (11, false), (16, false), (17, true)] {
155 let policy = RasterPolicy::resolve(&font, ppem);
156 assert_eq!(policy.symmetric_smoothing, symmetric, "at {ppem} ppem");
157 assert!(policy.grid_fit, "at {ppem} ppem");
158 }
159 }
160
161 #[test]
164 fn policy_from_permissive_gasp() {
165 let font = FontRef::new(font_test_data::COUSINE_HINT_SUBSET).unwrap();
166 for ppem in [8, 12, 20, 40] {
167 let policy = RasterPolicy::resolve(&font, ppem);
168 assert!(policy.symmetric_smoothing);
169 assert!(policy.grid_fit);
170 }
171 }
172
173 #[test]
176 fn fallback_policy() {
177 for (ppem, symmetric) in [(8, false), (20, false), (21, true), (40, true)] {
178 assert_eq!(
179 RasterPolicy::fallback(true, ppem),
180 RasterPolicy {
181 symmetric_smoothing: symmetric,
182 grid_fit: true,
183 },
184 "at {ppem} ppem"
185 );
186 }
187 for ppem in [8, 20, 21, 40] {
188 assert_eq!(
189 RasterPolicy::fallback(false, ppem),
190 RasterPolicy {
191 symmetric_smoothing: true,
192 grid_fit: false,
193 },
194 "at {ppem} ppem"
195 );
196 }
197 }
198
199 #[test]
202 fn policy_for_uninstructed_font() {
203 let font = FontRef::new(font_test_data::AHEM).unwrap();
204 for ppem in [8, 12, 30] {
205 let policy = RasterPolicy::resolve(&font, ppem);
206 assert!(policy.symmetric_smoothing, "at {ppem} ppem");
207 assert!(!policy.grid_fit, "at {ppem} ppem");
208 }
209 }
210
211 #[test]
214 fn policy_through_the_font_model() {
215 let font = crate::font::Font::new(font_test_data::TINOS_SUBSET.to_vec(), 0).unwrap();
216 let policy = RasterPolicy::resolve(&font.tables(), 12);
217 assert!(!policy.symmetric_smoothing);
218 assert!(policy.grid_fit);
219 }
220
221 #[test]
222 fn classic_state_ignores_the_font() {
223 let font = FontRef::new(font_test_data::TINOS_SUBSET).unwrap();
224 let policy = RasterPolicy::resolve(&font, 12);
225 let state = ClearTypeState::new(ClearType::Classic, true, policy);
226 assert!(state.symmetric_smoothing());
227 assert!(!state.is_native_enhanced());
228 }
229
230 #[test]
231 fn enhanced_state_follows_the_gasp() {
232 let font = FontRef::new(font_test_data::TINOS_SUBSET).unwrap();
233 let policy = RasterPolicy::resolve(&font, 12);
234 let state = ClearTypeState::new(ClearType::Enhanced, false, policy);
235 assert!(!state.symmetric_smoothing());
236 assert!(state.is_native_enhanced());
237 let state = ClearTypeState::new(ClearType::Enhanced, true, policy);
239 assert!(!state.is_native_enhanced());
240 }
241}