1#![forbid(unsafe_code)]
2
3pub mod alloc_budget;
23pub mod ansi;
24pub mod arena;
25pub mod budget;
26pub mod buffer;
27pub mod cell;
28pub mod counting_writer;
29pub mod diff;
30pub mod diff_strategy;
31pub mod drawing;
32pub mod fit_metrics;
33pub mod frame;
34pub mod frame_guardrails;
35pub mod grapheme_pool;
36pub mod headless;
37pub mod link_registry;
38pub mod presenter;
39pub mod render_certificate;
40
41pub mod quotient_filter;
42pub mod roaring_bitmap;
43pub mod sanitize;
44pub mod spatial_hit_index;
45pub mod terminal_model;
46
47pub use ftui_core::terminal_capabilities::ColorDepth;
51
52pub(crate) use ftui_core::text_width::{char_width, display_width, grapheme_width};
54
55#[cfg(test)]
56mod tests {
57 use super::{char_width, display_width, grapheme_width};
58
59 #[test]
62 fn display_width_matches_expected_samples() {
63 let samples = [
68 ("hello", 5usize),
69 ("😀", 2usize),
70 ("👩💻", 2usize),
71 ("🇺🇸", 2usize),
72 ("⭐", 2usize),
73 ("A😀B", 4usize),
74 ("ok ✅", 5usize),
75 ];
76 for (sample, expected) in samples {
77 assert_eq!(
78 display_width(sample),
79 expected,
80 "display width mismatch for {sample:?}"
81 );
82 }
83 }
84
85 #[test]
86 fn display_width_empty_string() {
87 assert_eq!(display_width(""), 0);
88 }
89
90 #[test]
91 fn display_width_single_ascii_char() {
92 assert_eq!(display_width("x"), 1);
93 assert_eq!(display_width(" "), 1);
94 }
95
96 #[test]
97 fn display_width_pure_ascii_fast_path() {
98 assert_eq!(display_width("Hello, World!"), 13);
99 assert_eq!(display_width("fn main() {}"), 12);
100 }
101
102 #[test]
103 fn display_width_ascii_with_tabs() {
104 assert_eq!(display_width("a\tb"), 3);
105 assert_eq!(display_width("\n"), 1);
106 }
107
108 #[test]
109 fn display_width_mixed_ascii_emoji() {
110 assert_eq!(display_width("hi 🎉"), 5);
111 assert_eq!(display_width("🚀start"), 7);
112 }
113
114 #[test]
115 fn display_width_zero_width_chars_in_string() {
116 let s = "a\u{00AD}b";
117 assert_eq!(display_width(s), 2);
118 }
119
120 #[test]
121 fn display_width_combining_characters() {
122 let s = "e\u{0301}";
123 assert_eq!(display_width(s), 1);
124 }
125
126 #[test]
127 fn display_width_multiple_emoji() {
128 assert_eq!(display_width("😀😀😀"), 6);
129 }
130
131 #[test]
134 fn grapheme_width_matches_expected_samples() {
135 let samples = [
139 ("a", 1usize),
140 ("😀", 2usize),
141 ("👩💻", 2usize),
142 ("🇺🇸", 2usize),
143 ("👍🏽", 2usize),
144 ("⭐", 2usize),
145 ];
146 for (grapheme, expected) in samples {
147 assert_eq!(
148 grapheme_width(grapheme),
149 expected,
150 "grapheme width mismatch for {grapheme:?}"
151 );
152 }
153 }
154
155 #[test]
156 fn grapheme_width_ascii_space() {
157 assert_eq!(grapheme_width(" "), 1);
158 }
159
160 #[test]
161 fn grapheme_width_ascii_tilde() {
162 assert_eq!(grapheme_width("~"), 1);
163 }
164
165 #[test]
166 fn grapheme_width_tab() {
167 assert_eq!(grapheme_width("\t"), 1);
168 }
169
170 #[test]
171 fn grapheme_width_newline() {
172 assert_eq!(grapheme_width("\n"), 1);
173 }
174
175 #[test]
176 fn grapheme_width_combining_accent() {
177 assert_eq!(grapheme_width("e\u{0301}"), 1);
178 }
179
180 #[test]
181 fn grapheme_width_zero_width_space() {
182 assert_eq!(grapheme_width("\u{200B}"), 0);
183 }
184
185 #[test]
186 fn grapheme_width_zero_width_joiner() {
187 assert_eq!(grapheme_width("\u{200D}"), 0);
188 }
189
190 #[test]
191 fn grapheme_width_skin_tone_modifier() {
192 assert_eq!(grapheme_width("👍🏿"), 2);
193 }
194
195 #[test]
198 fn char_width_ascii_printable() {
199 assert_eq!(char_width('A'), 1);
200 assert_eq!(char_width('z'), 1);
201 assert_eq!(char_width(' '), 1);
202 assert_eq!(char_width('~'), 1);
203 assert_eq!(char_width('!'), 1);
204 }
205
206 #[test]
207 fn char_width_ascii_whitespace() {
208 assert_eq!(char_width('\t'), 1);
209 assert_eq!(char_width('\n'), 1);
210 assert_eq!(char_width('\r'), 1);
211 }
212
213 #[test]
214 fn char_width_ascii_control() {
215 assert_eq!(char_width('\x00'), 0);
216 assert_eq!(char_width('\x01'), 0);
217 assert_eq!(char_width('\x1F'), 0);
218 assert_eq!(char_width('\x7F'), 0);
219 }
220
221 #[test]
222 fn char_width_zero_width_combining() {
223 assert_eq!(char_width('\u{0300}'), 0);
224 assert_eq!(char_width('\u{0301}'), 0);
225 }
226
227 #[test]
228 fn char_width_zero_width_special() {
229 assert_eq!(char_width('\u{200B}'), 0);
230 assert_eq!(char_width('\u{200D}'), 0);
231 assert_eq!(char_width('\u{FEFF}'), 0);
232 assert_eq!(char_width('\u{00AD}'), 0);
233 }
234
235 #[test]
236 fn char_width_variation_selectors() {
237 assert_eq!(char_width('\u{FE00}'), 0);
238 assert_eq!(char_width('\u{FE0F}'), 0);
239 }
240
241 #[test]
242 fn char_width_bidi_controls() {
243 assert_eq!(char_width('\u{200E}'), 0);
244 assert_eq!(char_width('\u{200F}'), 0);
245 }
246
247 #[test]
248 fn char_width_normal_non_ascii() {
249 assert_eq!(char_width('é'), 1);
250 assert_eq!(char_width('ñ'), 1);
251 }
252
253 #[test]
254 fn char_width_euro_sign() {
255 assert_eq!(char_width('€'), 1);
256 }
257}