1use ahash::{AHashMap, AHasher};
2use cosmic_text::{
3 Attrs, Buffer, CacheKey, FontSystem, LayoutRunIter, Metrics, Shaping, SwashCache, SwashContent,
4};
5use once_cell::sync::OnceCell;
6use std::{
7 collections::{HashMap, VecDeque},
8 hash::{Hash, Hasher},
9 sync::Mutex,
10};
11use unicode_segmentation::UnicodeSegmentation;
12
13const WRAP_CACHE_CAP: usize = 1024;
14const ELLIP_CACHE_CAP: usize = 2048;
15
16static METRICS_LRU: OnceCell<Mutex<Lru<(u64, u32), TextMetrics>>> = OnceCell::new();
17fn metrics_cache() -> &'static Mutex<Lru<(u64, u32), TextMetrics>> {
18 METRICS_LRU.get_or_init(|| Mutex::new(Lru::new(4096)))
19}
20
21struct Lru<K, V> {
22 map: AHashMap<K, V>,
23 order: VecDeque<K>,
24 cap: usize,
25}
26impl<K: std::hash::Hash + Eq + Clone, V> Lru<K, V> {
27 fn new(cap: usize) -> Self {
28 Self {
29 map: AHashMap::new(),
30 order: VecDeque::new(),
31 cap,
32 }
33 }
34 fn get(&mut self, k: &K) -> Option<&V> {
35 if self.map.contains_key(k) {
36 if let Some(pos) = self.order.iter().position(|x| x == k) {
38 let key = self.order.remove(pos).unwrap();
39 self.order.push_back(key);
40 }
41 }
42 self.map.get(k)
43 }
44 fn put(&mut self, k: K, v: V) {
45 if self.map.contains_key(&k) {
46 self.map.insert(k.clone(), v);
47 if let Some(pos) = self.order.iter().position(|x| x == &k) {
48 let key = self.order.remove(pos).unwrap();
49 self.order.push_back(key);
50 }
51 return;
52 }
53 if self.map.len() >= self.cap {
54 if let Some(old) = self.order.pop_front() {
55 self.map.remove(&old);
56 }
57 }
58 self.order.push_back(k.clone());
59 self.map.insert(k, v);
60 }
61}
62
63static WRAP_LRU: OnceCell<Mutex<Lru<(u64, u32, u32, u16, bool), (Vec<String>, bool)>>> =
64 OnceCell::new();
65static ELLIP_LRU: OnceCell<Mutex<Lru<(u64, u32, u32), String>>> = OnceCell::new();
66
67fn wrap_cache() -> &'static Mutex<Lru<(u64, u32, u32, u16, bool), (Vec<String>, bool)>> {
68 WRAP_LRU.get_or_init(|| Mutex::new(Lru::new(WRAP_CACHE_CAP)))
69}
70fn ellip_cache() -> &'static Mutex<Lru<(u64, u32, u32), String>> {
71 ELLIP_LRU.get_or_init(|| Mutex::new(Lru::new(ELLIP_CACHE_CAP)))
72}
73
74fn fast_hash(s: &str) -> u64 {
75 use std::hash::{Hash, Hasher};
76 let mut h = AHasher::default();
77 s.hash(&mut h);
78 h.finish()
79}
80
81#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
82pub struct GlyphKey(pub u64);
83
84pub struct ShapedGlyph {
85 pub key: GlyphKey,
86 pub x: f32,
87 pub y: f32,
88 pub w: f32,
89 pub h: f32,
90 pub bearing_x: f32,
91 pub bearing_y: f32,
92 pub advance: f32,
93}
94
95pub struct GlyphBitmap {
96 pub key: GlyphKey,
97 pub w: u32,
98 pub h: u32,
99 pub content: SwashContent,
100 pub data: Vec<u8>, }
102
103struct Engine {
104 fs: FontSystem,
105 cache: SwashCache,
106 key_map: HashMap<GlyphKey, CacheKey>,
108}
109
110impl Engine {
111 fn get_image(&mut self, key: CacheKey) -> Option<cosmic_text::SwashImage> {
112 self.cache.get_image(&mut self.fs, key).clone()
114 }
115}
116
117static ENGINE: OnceCell<Mutex<Engine>> = OnceCell::new();
118
119fn engine() -> &'static Mutex<Engine> {
120 ENGINE.get_or_init(|| {
121 #[allow(unused_mut)]
122 let mut fs = FontSystem::new();
123
124 let cache = SwashCache::new();
125
126 #[cfg(target_os = "android")] {
128 static FALLBACK_TTF: &[u8] = include_bytes!("assets/OpenSans-Regular.ttf"); static FALLBACK_EMOJI_TTF: &[u8] = include_bytes!("assets/NotoColorEmoji-Regular.ttf"); static FALLBACK_SYMBOLS_TTF: &[u8] =
131 include_bytes!("assets/NotoSansSymbols2-Regular.ttf"); {
133 let db = fs.db_mut();
135 db.load_font_data(FALLBACK_TTF.to_vec());
136 db.set_sans_serif_family("Open Sans".to_string());
137
138 db.load_font_data(FALLBACK_SYMBOLS_TTF.to_vec());
139 db.load_font_data(FALLBACK_EMOJI_TTF.to_vec());
140 }
141 }
142 Mutex::new(Engine {
143 fs,
144 cache,
145 key_map: HashMap::new(),
146 })
147 })
148}
149
150fn key_from_cachekey(k: &CacheKey) -> GlyphKey {
152 let mut h = AHasher::default();
153 k.hash(&mut h);
154 GlyphKey(h.finish())
155}
156
157pub fn shape_line(text: &str, px: f32) -> Vec<ShapedGlyph> {
159 let mut eng = engine().lock().unwrap();
160
161 let mut buf = Buffer::new(&mut eng.fs, Metrics::new(px, px * 1.3));
163 {
164 let mut b = buf.borrow_with(&mut eng.fs);
166 b.set_size(None, None);
167 b.set_text(text, &Attrs::new(), Shaping::Advanced, None);
168 b.shape_until_scroll(true);
169 }
170
171 let mut out = Vec::new();
172 for run in buf.layout_runs() {
173 for g in run.glyphs {
174 let phys = g.physical((0.0, run.line_y), 1.0);
176 let key = key_from_cachekey(&phys.cache_key);
177 eng.key_map.insert(key, phys.cache_key);
178
179 let img_opt = eng.get_image(phys.cache_key);
181 let (w, h, left, top) = if let Some(img) = img_opt.as_ref() {
182 (
183 img.placement.width as f32,
184 img.placement.height as f32,
185 img.placement.left as f32,
186 img.placement.top as f32,
187 )
188 } else {
189 (0.0, 0.0, 0.0, 0.0)
190 };
191
192 out.push(ShapedGlyph {
193 key,
194 x: g.x + g.x_offset, y: run.line_y, w,
197 h,
198 bearing_x: left,
199 bearing_y: top,
200 advance: g.w,
201 });
202 }
203 }
204 out
205}
206
207pub fn rasterize(key: GlyphKey, _px: f32) -> Option<GlyphBitmap> {
210 let mut eng = engine().lock().unwrap();
211 let &ck = eng.key_map.get(&key)?;
212
213 let img = eng.get_image(ck).as_ref()?.clone();
214 Some(GlyphBitmap {
215 key,
216 w: img.placement.width,
217 h: img.placement.height,
218 content: img.content,
219 data: img.data, })
221}
222
223#[derive(Clone)]
225pub struct TextMetrics {
226 pub positions: Vec<f32>, pub byte_offsets: Vec<usize>, }
229
230pub fn metrics_for_textfield(text: &str, px: f32) -> TextMetrics {
232 let key = (fast_hash(text), (px * 100.0) as u32);
233 if let Some(m) = metrics_cache().lock().unwrap().get(&key).cloned() {
234 return m;
235 }
236 let mut eng = engine().lock().unwrap();
237 let mut buf = Buffer::new(&mut eng.fs, Metrics::new(px, px * 1.3));
238 {
239 let mut b = buf.borrow_with(&mut eng.fs);
240 b.set_size(None, None);
241 b.set_text(text, &Attrs::new(), Shaping::Advanced, None);
242 b.shape_until_scroll(true);
243 }
244 let mut edges: Vec<(usize, f32)> = Vec::new();
245 let mut last_x = 0.0f32;
246 for run in buf.layout_runs() {
247 for g in run.glyphs {
248 let right = g.x + g.w;
249 last_x = right.max(last_x);
250 edges.push((g.end, right));
251 }
252 }
253 if edges.last().map(|e| e.0) != Some(text.len()) {
254 edges.push((text.len(), last_x));
255 }
256 let mut positions = Vec::with_capacity(text.graphemes(true).count() + 1);
257 let mut byte_offsets = Vec::with_capacity(positions.capacity());
258 positions.push(0.0);
259 byte_offsets.push(0);
260 let mut last_byte = 0usize;
261 for (b, _) in text.grapheme_indices(true) {
262 positions
263 .push(positions.last().copied().unwrap_or(0.0) + width_between(&edges, last_byte, b));
264 byte_offsets.push(b);
265 last_byte = b;
266 }
267 if *byte_offsets.last().unwrap_or(&0) != text.len() {
268 positions.push(
269 positions.last().copied().unwrap_or(0.0) + width_between(&edges, last_byte, text.len()),
270 );
271 byte_offsets.push(text.len());
272 }
273 let m = TextMetrics {
274 positions,
275 byte_offsets,
276 };
277 metrics_cache().lock().unwrap().put(key, m.clone());
278 m
279}
280
281fn width_between(edges: &[(usize, f32)], start_b: usize, end_b: usize) -> f32 {
282 let x0 = lookup_right(edges, start_b);
283 let x1 = lookup_right(edges, end_b);
284 (x1 - x0).max(0.0)
285}
286fn lookup_right(edges: &[(usize, f32)], b: usize) -> f32 {
287 match edges.binary_search_by_key(&b, |e| e.0) {
288 Ok(i) => edges[i].1,
289 Err(i) => {
290 if i == 0 {
291 0.0
292 } else {
293 edges[i - 1].1
294 }
295 }
296 }
297}
298
299pub fn wrap_lines(
303 text: &str,
304 px: f32,
305 max_width: f32,
306 max_lines: Option<usize>,
307 soft_wrap: bool,
308) -> (Vec<String>, bool) {
309 if text.is_empty() || max_width <= 0.0 {
310 return (vec![String::new()], false);
311 }
312 if !soft_wrap {
313 return (vec![text.to_string()], false);
314 }
315
316 let max_lines_key: u16 = match max_lines {
317 None => 0,
318 Some(n) => {
319 let n = n.min(u16::MAX as usize - 1) as u16;
320 n.saturating_add(1)
321 }
322 };
323 let key = (
324 fast_hash(text),
325 (px * 100.0) as u32,
326 (max_width * 100.0) as u32,
327 max_lines_key,
328 soft_wrap,
329 );
330 if let Some(h) = wrap_cache().lock().unwrap().get(&key).cloned() {
331 return h;
332 }
333
334 let m = metrics_for_textfield(text, px);
336 if let Some(&last) = m.positions.last() {
338 if last <= max_width + 0.5 {
339 return (vec![text.to_string()], false);
340 }
341 }
342
343 let width_of = |start_b: usize, end_b: usize| -> f32 {
345 let i0 = match m.byte_offsets.binary_search(&start_b) {
346 Ok(i) | Err(i) => i,
347 };
348 let i1 = match m.byte_offsets.binary_search(&end_b) {
349 Ok(i) | Err(i) => i,
350 };
351 (m.positions.get(i1).copied().unwrap_or(0.0) - m.positions.get(i0).copied().unwrap_or(0.0))
352 .max(0.0)
353 };
354
355 let mut out: Vec<String> = Vec::new();
356 let mut truncated = false;
357
358 let mut line_start = 0usize; let mut best_break = line_start;
360 let mut last_w = 0.0;
361
362 for tok in text.split_word_bounds() {
364 let tok_start = best_break;
365 let tok_end = tok_start + tok.len();
366 let w = width_of(line_start, tok_end);
367
368 if w <= max_width + 0.5 {
369 best_break = tok_end;
370 last_w = w;
371 continue;
372 }
373
374 if best_break > line_start {
376 out.push(text[line_start..best_break].trim_end().to_string());
378 line_start = best_break;
379 } else {
380 let mut cut = tok_start;
382 for g in tok.grapheme_indices(true) {
383 let next = tok_start + g.0 + g.1.len();
384 if width_of(line_start, next) <= max_width + 0.5 {
385 cut = next;
386 } else {
387 break;
388 }
389 }
390 if cut == line_start {
391 if let Some((ofs, grapheme)) = tok.grapheme_indices(true).next() {
393 cut = tok_start + ofs + grapheme.len();
394 }
395 }
396 out.push(text[line_start..cut].to_string());
397 line_start = cut;
398 }
399
400 if let Some(ml) = max_lines {
402 if out.len() >= ml {
403 truncated = true;
404 line_start = line_start.min(text.len());
406 break;
407 }
408 }
409
410 best_break = line_start;
412 last_w = 0.0;
413
414 if line_start < tok_end {
416 if width_of(line_start, tok_end) <= max_width + 0.5 {
418 best_break = tok_end;
419 last_w = width_of(line_start, best_break);
420 } else {
421 }
423 }
424 }
425
426 if line_start < text.len() && max_lines.map_or(true, |ml| out.len() < ml) {
428 out.push(text[line_start..].trim_end().to_string());
429 }
430
431 let res = (out, truncated);
432
433 wrap_cache().lock().unwrap().put(key, res.clone());
434 res
435}
436
437pub fn ellipsize_line(text: &str, px: f32, max_width: f32) -> String {
439 if text.is_empty() || max_width <= 0.0 {
440 return String::new();
441 }
442 let key = (
443 fast_hash(text),
444 (px * 100.0) as u32,
445 (max_width * 100.0) as u32,
446 );
447 if let Some(s) = ellip_cache().lock().unwrap().get(&key).cloned() {
448 return s;
449 }
450 let m = metrics_for_textfield(text, px);
451 if let Some(&last) = m.positions.last() {
452 if last <= max_width + 0.5 {
453 return text.to_string();
454 }
455 }
456 let el = "…";
457 let e_w = ellipsis_width(px);
458 if e_w >= max_width {
459 return String::new();
460 }
461 let mut cut_i = 0usize;
463 for i in 0..m.positions.len() {
464 if m.positions[i] + e_w <= max_width {
465 cut_i = i;
466 } else {
467 break;
468 }
469 }
470 let byte = m
471 .byte_offsets
472 .get(cut_i)
473 .copied()
474 .unwrap_or(0)
475 .min(text.len());
476 let mut out = String::with_capacity(byte + 3);
477 out.push_str(&text[..byte]);
478 out.push('…');
479
480 let s = out;
481 ellip_cache().lock().unwrap().put(key, s.clone());
482
483 s
484}
485
486fn ellipsis_width(px: f32) -> f32 {
487 static ELLIP_W_LRU: OnceCell<Mutex<Lru<u32, f32>>> = OnceCell::new();
488 let cache = ELLIP_W_LRU.get_or_init(|| Mutex::new(Lru::new(64)));
489 let key = (px * 100.0) as u32;
490 if let Some(w) = cache.lock().unwrap().get(&key).copied() {
491 return w;
492 }
493 let w = if let Some(g) = crate::shape_line("…", px).last() {
494 g.x + g.advance
495 } else {
496 0.0
497 };
498 cache.lock().unwrap().put(key, w);
499 w
500}