par_term_render/cell_renderer/
font.rs1use super::CellRenderer;
2
3pub(crate) struct FontState {
5 pub(crate) base_font_size: f32,
7 pub(crate) line_spacing: f32,
8 pub(crate) char_spacing: f32,
9 pub(crate) font_ascent: f32,
11 pub(crate) font_descent: f32,
12 pub(crate) font_leading: f32,
13 pub(crate) font_size_pixels: f32,
14 pub(crate) char_advance: f32,
15 #[allow(dead_code)] pub(crate) enable_text_shaping: bool,
21 #[allow(dead_code)] pub(crate) enable_ligatures: bool,
23 #[allow(dead_code)] pub(crate) enable_kerning: bool,
25 pub(crate) font_antialias: bool,
28 pub(crate) font_hinting: bool,
30 pub(crate) font_thin_strokes: par_term_config::ThinStrokesMode,
32 pub(crate) minimum_contrast: f32,
35}
36
37const DARK_BACKGROUND_THRESHOLD: f32 = 0.5;
39
40const CONTRAST_CHANGE_EPSILON: f32 = 0.001;
43
44impl CellRenderer {
45 pub fn update_font_antialias(&mut self, enabled: bool) -> bool {
48 if self.font.font_antialias != enabled {
49 self.font.font_antialias = enabled;
50 self.clear_glyph_cache();
51 self.dirty_rows.fill(true);
52 true
53 } else {
54 false
55 }
56 }
57
58 pub fn update_font_hinting(&mut self, enabled: bool) -> bool {
61 if self.font.font_hinting != enabled {
62 self.font.font_hinting = enabled;
63 self.clear_glyph_cache();
64 self.dirty_rows.fill(true);
65 true
66 } else {
67 false
68 }
69 }
70
71 pub fn update_font_thin_strokes(&mut self, mode: par_term_config::ThinStrokesMode) -> bool {
74 if self.font.font_thin_strokes != mode {
75 self.font.font_thin_strokes = mode;
76 self.clear_glyph_cache();
77 self.dirty_rows.fill(true);
78 true
79 } else {
80 false
81 }
82 }
83
84 pub fn update_minimum_contrast(&mut self, value: f32) -> bool {
87 let value = value.clamp(0.0, 1.0);
89 if (self.font.minimum_contrast - value).abs() > CONTRAST_CHANGE_EPSILON {
90 self.font.minimum_contrast = value;
91 self.dirty_rows.fill(true);
92 true
93 } else {
94 false
95 }
96 }
97
98 pub(crate) fn ensure_minimum_contrast(&self, fg: [f32; 4], bg: [f32; 4]) -> [f32; 4] {
104 let min_contrast = self.font.minimum_contrast;
105 if min_contrast <= 0.0 {
107 return fg;
108 }
109
110 fn perceived_brightness(r: f32, g: f32, b: f32) -> f32 {
112 0.30 * r + 0.59 * g + 0.11 * b
113 }
114
115 let fg_brightness = perceived_brightness(fg[0], fg[1], fg[2]);
116 let bg_brightness = perceived_brightness(bg[0], bg[1], bg[2]);
117 let brightness_diff = (fg_brightness - bg_brightness).abs();
118
119 if brightness_diff >= min_contrast {
121 return fg;
122 }
123
124 let error = min_contrast - brightness_diff;
126 let mut target_brightness = if fg_brightness < bg_brightness {
127 fg_brightness - error
129 } else {
130 fg_brightness + error
132 };
133
134 if target_brightness < 0.0 {
136 let alternative = bg_brightness + min_contrast;
137 let base_contrast = bg_brightness;
138 let alt_contrast = alternative.min(1.0) - bg_brightness;
139 if alt_contrast > base_contrast {
140 target_brightness = alternative;
141 }
142 } else if target_brightness > 1.0 {
143 let alternative = bg_brightness - min_contrast;
144 let base_contrast = 1.0 - bg_brightness;
145 let alt_contrast = bg_brightness - alternative.max(0.0);
146 if alt_contrast > base_contrast {
147 target_brightness = alternative;
148 }
149 }
150
151 target_brightness = target_brightness.clamp(0.0, 1.0);
152
153 let k: f32 = if fg_brightness < target_brightness {
156 1.0 } else {
158 0.0 };
160
161 let denom = perceived_brightness(k - fg[0], k - fg[1], k - fg[2]);
162 let p = if denom.abs() < 1e-10 {
163 0.0
164 } else {
165 ((target_brightness - perceived_brightness(fg[0], fg[1], fg[2])) / denom)
166 .clamp(0.0, 1.0)
167 };
168
169 [
170 p * k + (1.0 - p) * fg[0],
171 p * k + (1.0 - p) * fg[1],
172 p * k + (1.0 - p) * fg[2],
173 fg[3],
174 ]
175 }
176
177 pub(crate) fn should_use_thin_strokes(&self) -> bool {
179 use par_term_config::ThinStrokesMode;
180
181 let is_retina = self.scale_factor > 1.5;
183
184 let bg_brightness =
186 (self.background_color[0] + self.background_color[1] + self.background_color[2]) / 3.0;
187 let is_dark_background = bg_brightness < DARK_BACKGROUND_THRESHOLD;
188
189 match self.font.font_thin_strokes {
190 ThinStrokesMode::Never => false,
191 ThinStrokesMode::Always => true,
192 ThinStrokesMode::RetinaOnly => is_retina,
193 ThinStrokesMode::DarkBackgroundsOnly => is_dark_background,
194 ThinStrokesMode::RetinaDarkBackgroundsOnly => is_retina && is_dark_background,
195 }
196 }
197}