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,
18 pub(crate) enable_ligatures: bool,
19 pub(crate) enable_kerning: bool,
20 pub(crate) font_antialias: bool,
23 pub(crate) font_hinting: bool,
25 pub(crate) font_thin_strokes: par_term_config::ThinStrokesMode,
27 pub(crate) minimum_contrast: f32,
30}
31
32const DARK_BACKGROUND_THRESHOLD: f32 = 0.5;
34
35const CONTRAST_CHANGE_EPSILON: f32 = 0.001;
38
39impl CellRenderer {
40 pub fn update_font_antialias(&mut self, enabled: bool) -> bool {
43 if self.font.font_antialias != enabled {
44 self.font.font_antialias = enabled;
45 self.clear_glyph_cache();
46 self.dirty_rows.fill(true);
47 true
48 } else {
49 false
50 }
51 }
52
53 pub fn update_font_hinting(&mut self, enabled: bool) -> bool {
56 if self.font.font_hinting != enabled {
57 self.font.font_hinting = enabled;
58 self.clear_glyph_cache();
59 self.dirty_rows.fill(true);
60 true
61 } else {
62 false
63 }
64 }
65
66 pub fn update_font_thin_strokes(&mut self, mode: par_term_config::ThinStrokesMode) -> bool {
69 if self.font.font_thin_strokes != mode {
70 self.font.font_thin_strokes = mode;
71 self.clear_glyph_cache();
72 self.dirty_rows.fill(true);
73 true
74 } else {
75 false
76 }
77 }
78
79 pub fn update_minimum_contrast(&mut self, value: f32) -> bool {
82 let value = value.clamp(0.0, 1.0);
84 if (self.font.minimum_contrast - value).abs() > CONTRAST_CHANGE_EPSILON {
85 self.font.minimum_contrast = value;
86 self.dirty_rows.fill(true);
87 true
88 } else {
89 false
90 }
91 }
92
93 pub(crate) fn ensure_minimum_contrast(&self, fg: [f32; 4], bg: [f32; 4]) -> [f32; 4] {
99 let min_contrast = self.font.minimum_contrast;
100 if min_contrast <= 0.0 {
102 return fg;
103 }
104
105 fn perceived_brightness(r: f32, g: f32, b: f32) -> f32 {
107 0.30 * r + 0.59 * g + 0.11 * b
108 }
109
110 let fg_brightness = perceived_brightness(fg[0], fg[1], fg[2]);
111 let bg_brightness = perceived_brightness(bg[0], bg[1], bg[2]);
112 let brightness_diff = (fg_brightness - bg_brightness).abs();
113
114 if brightness_diff >= min_contrast {
116 return fg;
117 }
118
119 let error = min_contrast - brightness_diff;
121 let mut target_brightness = if fg_brightness < bg_brightness {
122 fg_brightness - error
124 } else {
125 fg_brightness + error
127 };
128
129 if target_brightness < 0.0 {
131 let alternative = bg_brightness + min_contrast;
132 let base_contrast = bg_brightness;
133 let alt_contrast = alternative.min(1.0) - bg_brightness;
134 if alt_contrast > base_contrast {
135 target_brightness = alternative;
136 }
137 } else if target_brightness > 1.0 {
138 let alternative = bg_brightness - min_contrast;
139 let base_contrast = 1.0 - bg_brightness;
140 let alt_contrast = bg_brightness - alternative.max(0.0);
141 if alt_contrast > base_contrast {
142 target_brightness = alternative;
143 }
144 }
145
146 target_brightness = target_brightness.clamp(0.0, 1.0);
147
148 let k: f32 = if fg_brightness < target_brightness {
151 1.0 } else {
153 0.0 };
155
156 let denom = perceived_brightness(k - fg[0], k - fg[1], k - fg[2]);
157 let p = if denom.abs() < 1e-10 {
158 0.0
159 } else {
160 ((target_brightness - perceived_brightness(fg[0], fg[1], fg[2])) / denom)
161 .clamp(0.0, 1.0)
162 };
163
164 [
165 p * k + (1.0 - p) * fg[0],
166 p * k + (1.0 - p) * fg[1],
167 p * k + (1.0 - p) * fg[2],
168 fg[3],
169 ]
170 }
171
172 pub(crate) fn should_use_thin_strokes(&self) -> bool {
174 use par_term_config::ThinStrokesMode;
175
176 let is_retina = self.scale_factor > 1.5;
178
179 let bg_brightness =
181 (self.background_color[0] + self.background_color[1] + self.background_color[2]) / 3.0;
182 let is_dark_background = bg_brightness < DARK_BACKGROUND_THRESHOLD;
183
184 match self.font.font_thin_strokes {
185 ThinStrokesMode::Never => false,
186 ThinStrokesMode::Always => true,
187 ThinStrokesMode::RetinaOnly => is_retina,
188 ThinStrokesMode::DarkBackgroundsOnly => is_dark_background,
189 ThinStrokesMode::RetinaDarkBackgroundsOnly => is_retina && is_dark_background,
190 }
191 }
192}