1use ahash::{AHashMap, AHasher};
2use cosmic_text::{
3 Attrs, Buffer, CacheKey, FontSystem, Metrics, Shaping, SwashCache, SwashContent,
4};
5use once_cell::sync::OnceCell;
6use std::sync::atomic::{AtomicU64, Ordering};
7use std::{
8 collections::{HashMap, VecDeque},
9 hash::{Hash, Hasher},
10 sync::Mutex,
11};
12use unicode_segmentation::UnicodeSegmentation;
13
14static FRAME_COUNTER: AtomicU64 = AtomicU64::new(0);
16
17pub fn begin_frame() {
19 FRAME_COUNTER.fetch_add(1, Ordering::Relaxed);
20}
21
22pub fn current_frame() -> u64 {
23 FRAME_COUNTER.load(Ordering::Relaxed)
24}
25
26const WRAP_CACHE_CAP: usize = 1024;
27const ELLIP_CACHE_CAP: usize = 2048;
28
29static METRICS_LRU: OnceCell<Mutex<Lru<(u64, u32), TextMetrics>>> = OnceCell::new();
30fn metrics_cache() -> &'static Mutex<Lru<(u64, u32), TextMetrics>> {
31 METRICS_LRU.get_or_init(|| Mutex::new(Lru::new(4096)))
32}
33
34struct Lru<K, V> {
35 map: AHashMap<K, V>,
36 order: VecDeque<K>,
37 cap: usize,
38}
39impl<K: std::hash::Hash + Eq + Clone, V> Lru<K, V> {
40 fn new(cap: usize) -> Self {
41 Self {
42 map: AHashMap::new(),
43 order: VecDeque::new(),
44 cap,
45 }
46 }
47 fn get(&mut self, k: &K) -> Option<&V> {
48 if self.map.contains_key(k) {
49 if let Some(pos) = self.order.iter().position(|x| x == k) {
51 let key = self.order.remove(pos).unwrap();
52 self.order.push_back(key);
53 }
54 }
55 self.map.get(k)
56 }
57 fn put(&mut self, k: K, v: V) {
58 if self.map.contains_key(&k) {
59 self.map.insert(k.clone(), v);
60 if let Some(pos) = self.order.iter().position(|x| x == &k) {
61 let key = self.order.remove(pos).unwrap();
62 self.order.push_back(key);
63 }
64 return;
65 }
66 if self.map.len() >= self.cap
67 && let Some(old) = self.order.pop_front()
68 {
69 self.map.remove(&old);
70 }
71 self.order.push_back(k.clone());
72 self.map.insert(k, v);
73 }
74}
75
76static WRAP_LRU: OnceCell<Mutex<Lru<(u64, u32, u32, u16, bool), (Vec<String>, bool)>>> =
77 OnceCell::new();
78
79static WRAP_RANGES_LRU: OnceCell<
80 Mutex<Lru<(u64, u32, u32, u16, bool), (Vec<(usize, usize)>, bool)>>,
81> = OnceCell::new();
82
83static ELLIP_LRU: OnceCell<Mutex<Lru<(u64, u32, u32), String>>> = OnceCell::new();
84
85fn wrap_cache() -> &'static Mutex<Lru<(u64, u32, u32, u16, bool), (Vec<String>, bool)>> {
86 WRAP_LRU.get_or_init(|| Mutex::new(Lru::new(WRAP_CACHE_CAP)))
87}
88
89fn wrap_ranges_cache()
90-> &'static Mutex<Lru<(u64, u32, u32, u16, bool), (Vec<(usize, usize)>, bool)>> {
91 WRAP_RANGES_LRU.get_or_init(|| Mutex::new(Lru::new(WRAP_CACHE_CAP)))
92}
93
94fn ellip_cache() -> &'static Mutex<Lru<(u64, u32, u32), String>> {
95 ELLIP_LRU.get_or_init(|| Mutex::new(Lru::new(ELLIP_CACHE_CAP)))
96}
97
98fn fast_hash(s: &str) -> u64 {
99 use std::hash::{Hash, Hasher};
100 let mut h = AHasher::default();
101 s.hash(&mut h);
102 h.finish()
103}
104
105#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
106pub struct GlyphKey(pub u64);
107
108pub struct ShapedGlyph {
109 pub key: GlyphKey,
110 pub x: f32,
111 pub y: f32,
112 pub w: f32,
113 pub h: f32,
114 pub bearing_x: f32,
115 pub bearing_y: f32,
116 pub advance: f32,
117}
118
119pub struct GlyphBitmap {
120 pub key: GlyphKey,
121 pub w: u32,
122 pub h: u32,
123 pub content: SwashContent,
124 pub data: Vec<u8>, }
126
127struct Engine {
128 fs: FontSystem,
129 cache: SwashCache,
130 key_map: HashMap<GlyphKey, CacheKey>,
132}
133
134impl Engine {
135 fn get_image(&mut self, key: CacheKey) -> Option<cosmic_text::SwashImage> {
136 self.cache.get_image(&mut self.fs, key).clone()
138 }
139}
140
141static ENGINE: OnceCell<Mutex<Engine>> = OnceCell::new();
142
143fn engine() -> &'static Mutex<Engine> {
144 ENGINE.get_or_init(|| {
145 #[allow(unused_mut)]
146 let mut fs = FontSystem::new();
147
148 let cache = SwashCache::new();
149
150 #[cfg(any(target_os = "android", target_arch = "wasm32"))]
151 {
153 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] =
156 include_bytes!("assets/NotoSansSymbols2-Regular.ttf"); {
158 let db = fs.db_mut();
160 db.load_font_data(FALLBACK_TTF.to_vec());
161 db.set_sans_serif_family("Open Sans".to_string());
162
163 db.load_font_data(FALLBACK_SYMBOLS_TTF.to_vec());
164 db.load_font_data(FALLBACK_EMOJI_TTF.to_vec());
165 }
166 }
167 Mutex::new(Engine {
168 fs,
169 cache,
170 key_map: HashMap::new(),
171 })
172 })
173}
174
175fn key_from_cachekey(k: &CacheKey) -> GlyphKey {
177 let mut h = AHasher::default();
178 k.hash(&mut h);
179 GlyphKey(h.finish())
180}
181
182pub fn shape_line(text: &str, px: f32) -> Vec<ShapedGlyph> {
184 let mut eng = engine().lock().unwrap();
185
186 let mut buf = Buffer::new(&mut eng.fs, Metrics::new(px, px * 1.3));
188 {
189 let mut b = buf.borrow_with(&mut eng.fs);
191 b.set_size(None, None);
192 b.set_text(text, &Attrs::new(), Shaping::Advanced, None);
193 b.shape_until_scroll(true);
194 }
195
196 let mut out = Vec::new();
197 for run in buf.layout_runs() {
198 for g in run.glyphs {
199 let phys = g.physical((0.0, run.line_y), 1.0);
201 let key = key_from_cachekey(&phys.cache_key);
202 eng.key_map.insert(key, phys.cache_key);
203
204 let img_opt = eng.get_image(phys.cache_key);
206 let (w, h, left, top) = if let Some(img) = img_opt.as_ref() {
207 (
208 img.placement.width as f32,
209 img.placement.height as f32,
210 img.placement.left as f32,
211 img.placement.top as f32,
212 )
213 } else {
214 (0.0, 0.0, 0.0, 0.0)
215 };
216
217 out.push(ShapedGlyph {
218 key,
219 x: g.x + g.x_offset, y: run.line_y, w,
222 h,
223 bearing_x: left,
224 bearing_y: top,
225 advance: g.w,
226 });
227 }
228 }
229 out
230}
231
232pub fn rasterize(key: GlyphKey, _px: f32) -> Option<GlyphBitmap> {
235 let mut eng = engine().lock().unwrap();
236 let &ck = eng.key_map.get(&key)?;
237
238 let img = eng.get_image(ck).as_ref()?.clone();
239 Some(GlyphBitmap {
240 key,
241 w: img.placement.width,
242 h: img.placement.height,
243 content: img.content,
244 data: img.data, })
246}
247
248#[derive(Clone)]
250pub struct TextMetrics {
251 pub positions: Vec<f32>, pub byte_offsets: Vec<usize>, }
254
255pub fn metrics_for_textfield(text: &str, px: f32) -> TextMetrics {
257 let key = (fast_hash(text), (px * 100.0) as u32);
258 if let Some(m) = metrics_cache().lock().unwrap().get(&key).cloned() {
259 return m;
260 }
261 let mut eng = engine().lock().unwrap();
262 let mut buf = Buffer::new(&mut eng.fs, Metrics::new(px, px * 1.3));
263 {
264 let mut b = buf.borrow_with(&mut eng.fs);
265 b.set_size(None, None);
266 b.set_text(text, &Attrs::new(), Shaping::Advanced, None);
267 b.shape_until_scroll(true);
268 }
269 let mut edges: Vec<(usize, f32)> = Vec::new();
270 let mut last_x = 0.0f32;
271 for run in buf.layout_runs() {
272 for g in run.glyphs {
273 let right = g.x + g.w;
274 last_x = right.max(last_x);
275 edges.push((g.end, right));
276 }
277 }
278 if edges.last().map(|e| e.0) != Some(text.len()) {
279 edges.push((text.len(), last_x));
280 }
281 let mut positions = Vec::with_capacity(text.graphemes(true).count() + 1);
282 let mut byte_offsets = Vec::with_capacity(positions.capacity());
283 positions.push(0.0);
284 byte_offsets.push(0);
285 let mut last_byte = 0usize;
286 for (b, _) in text.grapheme_indices(true) {
287 positions
288 .push(positions.last().copied().unwrap_or(0.0) + width_between(&edges, last_byte, b));
289 byte_offsets.push(b);
290 last_byte = b;
291 }
292 if *byte_offsets.last().unwrap_or(&0) != text.len() {
293 positions.push(
294 positions.last().copied().unwrap_or(0.0) + width_between(&edges, last_byte, text.len()),
295 );
296 byte_offsets.push(text.len());
297 }
298 let m = TextMetrics {
299 positions,
300 byte_offsets,
301 };
302 metrics_cache().lock().unwrap().put(key, m.clone());
303 m
304}
305
306fn width_between(edges: &[(usize, f32)], start_b: usize, end_b: usize) -> f32 {
307 let x0 = lookup_right(edges, start_b);
308 let x1 = lookup_right(edges, end_b);
309 (x1 - x0).max(0.0)
310}
311fn lookup_right(edges: &[(usize, f32)], b: usize) -> f32 {
312 match edges.binary_search_by_key(&b, |e| e.0) {
313 Ok(i) => edges[i].1,
314 Err(i) => {
315 if i == 0 {
316 0.0
317 } else {
318 edges[i - 1].1
319 }
320 }
321 }
322}
323
324pub fn wrap_lines(
328 text: &str,
329 px: f32,
330 max_width: f32,
331 max_lines: Option<usize>,
332 soft_wrap: bool,
333) -> (Vec<String>, bool) {
334 if text.is_empty() || max_width <= 0.0 {
335 return (vec![String::new()], false);
336 }
337 if !soft_wrap {
338 return (vec![text.to_string()], false);
339 }
340
341 let max_lines_key: u16 = match max_lines {
342 None => 0,
343 Some(n) => {
344 let n = n.min(u16::MAX as usize - 1) as u16;
345 n.saturating_add(1)
346 }
347 };
348 let key = (
349 fast_hash(text),
350 (px * 100.0) as u32,
351 (max_width * 100.0) as u32,
352 max_lines_key,
353 soft_wrap,
354 );
355 if let Some(h) = wrap_cache().lock().unwrap().get(&key).cloned() {
356 return h;
357 }
358
359 let m = metrics_for_textfield(text, px);
361 if let Some(&last) = m.positions.last()
363 && last <= max_width + 0.5
364 {
365 return (vec![text.to_string()], false);
366 }
367
368 let width_of = |start_b: usize, end_b: usize| -> f32 {
370 let i0 = match m.byte_offsets.binary_search(&start_b) {
371 Ok(i) | Err(i) => i,
372 };
373 let i1 = match m.byte_offsets.binary_search(&end_b) {
374 Ok(i) | Err(i) => i,
375 };
376 (m.positions.get(i1).copied().unwrap_or(0.0) - m.positions.get(i0).copied().unwrap_or(0.0))
377 .max(0.0)
378 };
379
380 let mut out: Vec<String> = Vec::new();
381 let mut truncated = false;
382
383 let mut line_start = 0usize; let mut best_break = line_start;
385 let mut _last_w = 0.0;
386
387 for tok in text.split_word_bounds() {
389 let tok_start = best_break;
390 let tok_end = tok_start + tok.len();
391 let w = width_of(line_start, tok_end);
392
393 if w <= max_width + 0.5 {
394 best_break = tok_end;
395 _last_w = w;
396 continue;
397 }
398
399 if best_break > line_start {
401 out.push(text[line_start..best_break].trim_end().to_string());
403 line_start = best_break;
404 } else {
405 let mut cut = tok_start;
407 for g in tok.grapheme_indices(true) {
408 let next = tok_start + g.0 + g.1.len();
409 if width_of(line_start, next) <= max_width + 0.5 {
410 cut = next;
411 } else {
412 break;
413 }
414 }
415 if cut == line_start {
416 if let Some((ofs, grapheme)) = tok.grapheme_indices(true).next() {
418 cut = tok_start + ofs + grapheme.len();
419 }
420 }
421 out.push(text[line_start..cut].to_string());
422 line_start = cut;
423 }
424
425 if let Some(ml) = max_lines
427 && out.len() >= ml
428 {
429 truncated = true;
430 line_start = line_start.min(text.len());
432 break;
433 }
434
435 best_break = line_start;
437 _last_w = 0.0;
438
439 if line_start < tok_end {
441 if width_of(line_start, tok_end) <= max_width + 0.5 {
443 best_break = tok_end;
444 _last_w = width_of(line_start, best_break);
445 } else {
446 }
448 }
449 }
450
451 if line_start < text.len() && max_lines.is_none_or(|ml| out.len() < ml) {
453 out.push(text[line_start..].trim_end().to_string());
454 }
455
456 let res = (out, truncated);
457
458 wrap_cache().lock().unwrap().put(key, res.clone());
459 res
460}
461
462pub fn wrap_line_ranges(
469 text: &str,
470 px: f32,
471 max_width: f32,
472 max_lines: Option<usize>,
473 soft_wrap: bool,
474) -> (Vec<(usize, usize)>, bool) {
475 if text.is_empty() || max_width <= 0.0 {
476 return (vec![(0, 0)], false);
477 }
478 if !soft_wrap {
479 let mut out = Vec::new();
481 let mut start = 0usize;
482 for (i, ch) in text.char_indices() {
483 if ch == '\n' {
484 out.push((start, i));
485 start = i + 1;
486 }
487 }
488 out.push((start, text.len()));
489 return (out, false);
490 }
491
492 let max_lines_key: u16 = match max_lines {
493 None => 0,
494 Some(n) => {
495 let n = n.min(u16::MAX as usize - 1) as u16;
496 n.saturating_add(1)
497 }
498 };
499 let key = (
500 fast_hash(text),
501 (px * 100.0) as u32,
502 (max_width * 100.0) as u32,
503 max_lines_key,
504 soft_wrap,
505 );
506 if let Some(v) = wrap_ranges_cache().lock().unwrap().get(&key).cloned() {
507 return v;
508 }
509
510 let m = metrics_for_textfield(text, px);
512
513 let width_of = |start_b: usize, end_b: usize| -> f32 {
515 let i0 = match m.byte_offsets.binary_search(&start_b) {
516 Ok(i) | Err(i) => i,
517 };
518 let i1 = match m.byte_offsets.binary_search(&end_b) {
519 Ok(i) | Err(i) => i,
520 };
521 (m.positions.get(i1).copied().unwrap_or(0.0) - m.positions.get(i0).copied().unwrap_or(0.0))
522 .max(0.0)
523 };
524
525 let mut out: Vec<(usize, usize)> = Vec::new();
526 let mut truncated = false;
527
528 let mut line0_start = 0usize;
530 for (i, ch) in text.char_indices() {
531 if ch == '\n' {
532 let (mut ranges, tr) = wrap_one_hard_line_ranges(
533 text,
534 line0_start,
535 i,
536 max_width,
537 max_lines.map(|ml| ml.saturating_sub(out.len())),
538 &width_of,
539 );
540 out.append(&mut ranges);
541 if tr {
542 truncated = true;
543 break;
544 }
545 line0_start = i + 1;
546
547 if let Some(ml) = max_lines {
548 if out.len() >= ml {
549 truncated = true;
550 break;
551 }
552 }
553 }
554 }
555 if !truncated {
556 let (mut ranges, tr) = wrap_one_hard_line_ranges(
557 text,
558 line0_start,
559 text.len(),
560 max_width,
561 max_lines.map(|ml| ml.saturating_sub(out.len())),
562 &width_of,
563 );
564 out.append(&mut ranges);
565 truncated = tr;
566 }
567
568 if out.is_empty() {
569 out.push((0, 0));
570 }
571
572 let res = (out, truncated);
573 wrap_ranges_cache().lock().unwrap().put(key, res.clone());
574 res
575}
576
577fn wrap_one_hard_line_ranges(
578 text: &str,
579 start: usize,
580 end: usize,
581 max_width: f32,
582 max_lines: Option<usize>,
583 width_of: &dyn Fn(usize, usize) -> f32,
584) -> (Vec<(usize, usize)>, bool) {
585 let mut out = Vec::new();
586 let mut t = false;
587
588 if start >= end {
589 out.push((start, start));
590 return (out, false);
591 }
592
593 if width_of(start, end) <= max_width + 0.5 {
595 out.push((start, end));
596 return (out, false);
597 }
598
599 let mut line_start = start;
600 let mut best_break = line_start;
601 let mut unconsumed_start = start;
602
603 for tok in text[line_start..end].split_word_bounds() {
604 let tok_abs_start = unconsumed_start;
605 let tok_abs_end = tok_abs_start + tok.len();
606 unconsumed_start = tok_abs_end;
607
608 let w = width_of(line_start, tok_abs_end);
609 if w <= max_width + 0.5 {
610 best_break = tok_abs_end;
611 continue;
612 }
613
614 if best_break > line_start {
616 out.push((line_start, best_break));
617 line_start = best_break;
618 } else {
619 let mut cut = tok_abs_start;
621 for (ofs, g) in tok.grapheme_indices(true) {
622 let next = tok_abs_start + ofs + g.len();
623 if width_of(line_start, next) <= max_width + 0.5 {
624 cut = next;
625 } else {
626 break;
627 }
628 }
629 if cut == line_start {
630 if let Some((ofs, gr)) = tok.grapheme_indices(true).next() {
631 cut = tok_abs_start + ofs + gr.len();
632 }
633 }
634 out.push((line_start, cut));
635 line_start = cut;
636 }
637
638 if let Some(ml) = max_lines {
640 if out.len() >= ml {
641 t = true;
642 break;
643 }
644 }
645
646 best_break = line_start;
647 }
648
649 if !t && line_start < end && max_lines.is_none_or(|ml| out.len() < ml) {
651 out.push((line_start, end));
652 }
653
654 (out, t)
655}
656
657pub fn ellipsize_line(text: &str, px: f32, max_width: f32) -> String {
659 if text.is_empty() || max_width <= 0.0 {
660 return String::new();
661 }
662 let key = (
663 fast_hash(text),
664 (px * 100.0) as u32,
665 (max_width * 100.0) as u32,
666 );
667 if let Some(s) = ellip_cache().lock().unwrap().get(&key).cloned() {
668 return s;
669 }
670 let m = metrics_for_textfield(text, px);
671 if let Some(&last) = m.positions.last()
672 && last <= max_width + 0.5
673 {
674 return text.to_string();
675 }
676 let _el = "…";
677 let e_w = ellipsis_width(px);
678 if e_w >= max_width {
679 return String::new();
680 }
681 let mut cut_i = 0usize;
683 for i in 0..m.positions.len() {
684 if m.positions[i] + e_w <= max_width {
685 cut_i = i;
686 } else {
687 break;
688 }
689 }
690 let byte = m
691 .byte_offsets
692 .get(cut_i)
693 .copied()
694 .unwrap_or(0)
695 .min(text.len());
696 let mut out = String::with_capacity(byte + 3);
697 out.push_str(&text[..byte]);
698 out.push('…');
699
700 let s = out;
701 ellip_cache().lock().unwrap().put(key, s.clone());
702
703 s
704}
705
706fn ellipsis_width(px: f32) -> f32 {
707 static ELLIP_W_LRU: OnceCell<Mutex<Lru<u32, f32>>> = OnceCell::new();
708 let cache = ELLIP_W_LRU.get_or_init(|| Mutex::new(Lru::new(64)));
709 let key = (px * 100.0) as u32;
710 if let Some(w) = cache.lock().unwrap().get(&key).copied() {
711 return w;
712 }
713 let w = if let Some(g) = crate::shape_line("…", px).last() {
714 g.x + g.advance
715 } else {
716 0.0
717 };
718 cache.lock().unwrap().put(key, w);
719 w
720}