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 {
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"); static MATERIAL_SYMBOLS_TTF: &[u8] =
158 include_bytes!("assets/MaterialSymbolsOutlined.ttf"); {
160 let db = fs.db_mut();
162 db.load_font_data(FALLBACK_TTF.to_vec());
163 db.set_sans_serif_family("Open Sans".to_string());
164
165 db.load_font_data(FALLBACK_SYMBOLS_TTF.to_vec());
166 db.load_font_data(FALLBACK_EMOJI_TTF.to_vec());
167 db.load_font_data(MATERIAL_SYMBOLS_TTF.to_vec());
168 }
169 }
170 Mutex::new(Engine {
171 fs,
172 cache,
173 key_map: HashMap::new(),
174 })
175 })
176}
177
178pub fn register_font_data(bytes: &'static [u8]) {
180 let mut eng = engine().lock().unwrap();
181 eng.fs.db_mut().load_font_data(bytes.to_vec());
182}
183
184fn key_from_cachekey(k: &CacheKey) -> GlyphKey {
186 let mut h = AHasher::default();
187 k.hash(&mut h);
188 GlyphKey(h.finish())
189}
190
191pub fn shape_line(text: &str, px: f32) -> Vec<ShapedGlyph> {
193 let mut eng = engine().lock().unwrap();
194
195 let mut buf = Buffer::new(&mut eng.fs, Metrics::new(px, px * 1.3));
197 {
198 let mut b = buf.borrow_with(&mut eng.fs);
200 b.set_size(None, None);
201 b.set_text(text, &Attrs::new(), Shaping::Advanced, None);
202 b.shape_until_scroll(true);
203 }
204
205 let mut out = Vec::new();
206 for run in buf.layout_runs() {
207 for g in run.glyphs {
208 let phys = g.physical((0.0, run.line_y), 1.0);
210 let key = key_from_cachekey(&phys.cache_key);
211 eng.key_map.insert(key, phys.cache_key);
212
213 let img_opt = eng.get_image(phys.cache_key);
215 let (w, h, left, top) = if let Some(img) = img_opt.as_ref() {
216 (
217 img.placement.width as f32,
218 img.placement.height as f32,
219 img.placement.left as f32,
220 img.placement.top as f32,
221 )
222 } else {
223 (0.0, 0.0, 0.0, 0.0)
224 };
225
226 out.push(ShapedGlyph {
227 key,
228 x: g.x + g.x_offset, y: run.line_y, w,
231 h,
232 bearing_x: left,
233 bearing_y: top,
234 advance: g.w,
235 });
236 }
237 }
238 out
239}
240
241pub fn rasterize(key: GlyphKey, _px: f32) -> Option<GlyphBitmap> {
244 let mut eng = engine().lock().unwrap();
245 let &ck = eng.key_map.get(&key)?;
246
247 let img = eng.get_image(ck).as_ref()?.clone();
248 Some(GlyphBitmap {
249 key,
250 w: img.placement.width,
251 h: img.placement.height,
252 content: img.content,
253 data: img.data, })
255}
256
257#[derive(Clone)]
259pub struct TextMetrics {
260 pub positions: Vec<f32>, pub byte_offsets: Vec<usize>, }
263
264pub fn metrics_for_textfield(text: &str, px: f32) -> TextMetrics {
266 let key = (fast_hash(text), (px * 100.0) as u32);
267 if let Some(m) = metrics_cache().lock().unwrap().get(&key).cloned() {
268 return m;
269 }
270 let mut eng = engine().lock().unwrap();
271 let mut buf = Buffer::new(&mut eng.fs, Metrics::new(px, px * 1.3));
272 {
273 let mut b = buf.borrow_with(&mut eng.fs);
274 b.set_size(None, None);
275 b.set_text(text, &Attrs::new(), Shaping::Advanced, None);
276 b.shape_until_scroll(true);
277 }
278 let mut edges: Vec<(usize, f32)> = Vec::new();
279 let mut last_x = 0.0f32;
280 for run in buf.layout_runs() {
281 for g in run.glyphs {
282 let right = g.x + g.w;
283 last_x = right.max(last_x);
284 edges.push((g.end, right));
285 }
286 }
287 if edges.last().map(|e| e.0) != Some(text.len()) {
288 edges.push((text.len(), last_x));
289 }
290 let mut positions = Vec::with_capacity(text.graphemes(true).count() + 1);
291 let mut byte_offsets = Vec::with_capacity(positions.capacity());
292 positions.push(0.0);
293 byte_offsets.push(0);
294 let mut last_byte = 0usize;
295 for (b, _) in text.grapheme_indices(true) {
296 positions
297 .push(positions.last().copied().unwrap_or(0.0) + width_between(&edges, last_byte, b));
298 byte_offsets.push(b);
299 last_byte = b;
300 }
301 if *byte_offsets.last().unwrap_or(&0) != text.len() {
302 positions.push(
303 positions.last().copied().unwrap_or(0.0) + width_between(&edges, last_byte, text.len()),
304 );
305 byte_offsets.push(text.len());
306 }
307 let m = TextMetrics {
308 positions,
309 byte_offsets,
310 };
311 metrics_cache().lock().unwrap().put(key, m.clone());
312 m
313}
314
315fn width_between(edges: &[(usize, f32)], start_b: usize, end_b: usize) -> f32 {
316 let x0 = lookup_right(edges, start_b);
317 let x1 = lookup_right(edges, end_b);
318 (x1 - x0).max(0.0)
319}
320fn lookup_right(edges: &[(usize, f32)], b: usize) -> f32 {
321 match edges.binary_search_by_key(&b, |e| e.0) {
322 Ok(i) => edges[i].1,
323 Err(i) => {
324 if i == 0 {
325 0.0
326 } else {
327 edges[i - 1].1
328 }
329 }
330 }
331}
332
333pub fn wrap_lines(
337 text: &str,
338 px: f32,
339 max_width: f32,
340 max_lines: Option<usize>,
341 soft_wrap: bool,
342) -> (Vec<String>, bool) {
343 if text.is_empty() || max_width <= 0.0 {
344 return (vec![String::new()], false);
345 }
346 if !soft_wrap {
347 return (vec![text.to_string()], false);
348 }
349
350 let max_lines_key: u16 = match max_lines {
351 None => 0,
352 Some(n) => {
353 let n = n.min(u16::MAX as usize - 1) as u16;
354 n.saturating_add(1)
355 }
356 };
357 let key = (
358 fast_hash(text),
359 (px * 100.0) as u32,
360 (max_width * 100.0) as u32,
361 max_lines_key,
362 soft_wrap,
363 );
364 if let Some(h) = wrap_cache().lock().unwrap().get(&key).cloned() {
365 return h;
366 }
367
368 let m = metrics_for_textfield(text, px);
370 if let Some(&last) = m.positions.last()
372 && last <= max_width + 0.5
373 {
374 return (vec![text.to_string()], false);
375 }
376
377 let width_of = |start_b: usize, end_b: usize| -> f32 {
379 let i0 = match m.byte_offsets.binary_search(&start_b) {
380 Ok(i) | Err(i) => i,
381 };
382 let i1 = match m.byte_offsets.binary_search(&end_b) {
383 Ok(i) | Err(i) => i,
384 };
385 (m.positions.get(i1).copied().unwrap_or(0.0) - m.positions.get(i0).copied().unwrap_or(0.0))
386 .max(0.0)
387 };
388
389 let mut out: Vec<String> = Vec::new();
390 let mut truncated = false;
391
392 let mut line_start = 0usize; let mut best_break = line_start;
394 let mut _last_w = 0.0;
395
396 for tok in text.split_word_bounds() {
398 let tok_start = best_break;
399 let tok_end = tok_start + tok.len();
400 let w = width_of(line_start, tok_end);
401
402 if w <= max_width + 0.5 {
403 best_break = tok_end;
404 _last_w = w;
405 continue;
406 }
407
408 if best_break > line_start {
410 out.push(text[line_start..best_break].trim_end().to_string());
412 line_start = best_break;
413 } else {
414 let mut cut = tok_start;
416 for g in tok.grapheme_indices(true) {
417 let next = tok_start + g.0 + g.1.len();
418 if width_of(line_start, next) <= max_width + 0.5 {
419 cut = next;
420 } else {
421 break;
422 }
423 }
424 if cut == line_start {
425 if let Some((ofs, grapheme)) = tok.grapheme_indices(true).next() {
427 cut = tok_start + ofs + grapheme.len();
428 }
429 }
430 out.push(text[line_start..cut].to_string());
431 line_start = cut;
432 }
433
434 if let Some(ml) = max_lines
436 && out.len() >= ml
437 {
438 truncated = true;
439 line_start = line_start.min(text.len());
441 break;
442 }
443
444 best_break = line_start;
446 _last_w = 0.0;
447
448 if line_start < tok_end {
450 if width_of(line_start, tok_end) <= max_width + 0.5 {
452 best_break = tok_end;
453 _last_w = width_of(line_start, best_break);
454 } else {
455 }
457 }
458 }
459
460 if line_start < text.len() && max_lines.is_none_or(|ml| out.len() < ml) {
462 out.push(text[line_start..].trim_end().to_string());
463 }
464
465 let res = (out, truncated);
466
467 wrap_cache().lock().unwrap().put(key, res.clone());
468 res
469}
470
471pub fn wrap_line_ranges(
478 text: &str,
479 px: f32,
480 max_width: f32,
481 max_lines: Option<usize>,
482 soft_wrap: bool,
483) -> (Vec<(usize, usize)>, bool) {
484 if text.is_empty() || max_width <= 0.0 {
485 return (vec![(0, 0)], false);
486 }
487 if !soft_wrap {
488 let mut out = Vec::new();
490 let mut start = 0usize;
491 for (i, ch) in text.char_indices() {
492 if ch == '\n' {
493 out.push((start, i));
494 start = i + 1;
495 }
496 }
497 out.push((start, text.len()));
498 return (out, false);
499 }
500
501 let max_lines_key: u16 = match max_lines {
502 None => 0,
503 Some(n) => {
504 let n = n.min(u16::MAX as usize - 1) as u16;
505 n.saturating_add(1)
506 }
507 };
508 let key = (
509 fast_hash(text),
510 (px * 100.0) as u32,
511 (max_width * 100.0) as u32,
512 max_lines_key,
513 soft_wrap,
514 );
515 if let Some(v) = wrap_ranges_cache().lock().unwrap().get(&key).cloned() {
516 return v;
517 }
518
519 let m = metrics_for_textfield(text, px);
521
522 let width_of = |start_b: usize, end_b: usize| -> f32 {
524 let i0 = match m.byte_offsets.binary_search(&start_b) {
525 Ok(i) | Err(i) => i,
526 };
527 let i1 = match m.byte_offsets.binary_search(&end_b) {
528 Ok(i) | Err(i) => i,
529 };
530 (m.positions.get(i1).copied().unwrap_or(0.0) - m.positions.get(i0).copied().unwrap_or(0.0))
531 .max(0.0)
532 };
533
534 let mut out: Vec<(usize, usize)> = Vec::new();
535 let mut truncated = false;
536
537 let mut line0_start = 0usize;
539 for (i, ch) in text.char_indices() {
540 if ch == '\n' {
541 let (mut ranges, tr) = wrap_one_hard_line_ranges(
542 text,
543 line0_start,
544 i,
545 max_width,
546 max_lines.map(|ml| ml.saturating_sub(out.len())),
547 &width_of,
548 );
549 out.append(&mut ranges);
550 if tr {
551 truncated = true;
552 break;
553 }
554 line0_start = i + 1;
555
556 if let Some(ml) = max_lines {
557 if out.len() >= ml {
558 truncated = true;
559 break;
560 }
561 }
562 }
563 }
564 if !truncated {
565 let (mut ranges, tr) = wrap_one_hard_line_ranges(
566 text,
567 line0_start,
568 text.len(),
569 max_width,
570 max_lines.map(|ml| ml.saturating_sub(out.len())),
571 &width_of,
572 );
573 out.append(&mut ranges);
574 truncated = tr;
575 }
576
577 if out.is_empty() {
578 out.push((0, 0));
579 }
580
581 let res = (out, truncated);
582 wrap_ranges_cache().lock().unwrap().put(key, res.clone());
583 res
584}
585
586fn wrap_one_hard_line_ranges(
587 text: &str,
588 start: usize,
589 end: usize,
590 max_width: f32,
591 max_lines: Option<usize>,
592 width_of: &dyn Fn(usize, usize) -> f32,
593) -> (Vec<(usize, usize)>, bool) {
594 let mut out = Vec::new();
595 let mut t = false;
596
597 if start >= end {
598 out.push((start, start));
599 return (out, false);
600 }
601
602 if width_of(start, end) <= max_width + 0.5 {
604 out.push((start, end));
605 return (out, false);
606 }
607
608 let mut line_start = start;
609 let mut best_break = line_start;
610 let mut unconsumed_start = start;
611
612 for tok in text[line_start..end].split_word_bounds() {
613 let tok_abs_start = unconsumed_start;
614 let tok_abs_end = tok_abs_start + tok.len();
615 unconsumed_start = tok_abs_end;
616
617 let w = width_of(line_start, tok_abs_end);
618 if w <= max_width + 0.5 {
619 best_break = tok_abs_end;
620 continue;
621 }
622
623 if best_break > line_start {
625 out.push((line_start, best_break));
626 line_start = best_break;
627 } else {
628 let mut cut = tok_abs_start;
630 for (ofs, g) in tok.grapheme_indices(true) {
631 let next = tok_abs_start + ofs + g.len();
632 if width_of(line_start, next) <= max_width + 0.5 {
633 cut = next;
634 } else {
635 break;
636 }
637 }
638 if cut == line_start {
639 if let Some((ofs, gr)) = tok.grapheme_indices(true).next() {
640 cut = tok_abs_start + ofs + gr.len();
641 }
642 }
643 out.push((line_start, cut));
644 line_start = cut;
645 }
646
647 if let Some(ml) = max_lines {
649 if out.len() >= ml {
650 t = true;
651 break;
652 }
653 }
654
655 best_break = line_start;
656 }
657
658 if !t && line_start < end && max_lines.is_none_or(|ml| out.len() < ml) {
660 out.push((line_start, end));
661 }
662
663 (out, t)
664}
665
666pub fn ellipsize_line(text: &str, px: f32, max_width: f32) -> String {
668 if text.is_empty() || max_width <= 0.0 {
669 return String::new();
670 }
671 let key = (
672 fast_hash(text),
673 (px * 100.0) as u32,
674 (max_width * 100.0) as u32,
675 );
676 if let Some(s) = ellip_cache().lock().unwrap().get(&key).cloned() {
677 return s;
678 }
679 let m = metrics_for_textfield(text, px);
680 if let Some(&last) = m.positions.last()
681 && last <= max_width + 0.5
682 {
683 return text.to_string();
684 }
685 let _el = "…";
686 let e_w = ellipsis_width(px);
687 if e_w >= max_width {
688 return String::new();
689 }
690 let mut cut_i = 0usize;
692 for i in 0..m.positions.len() {
693 if m.positions[i] + e_w <= max_width {
694 cut_i = i;
695 } else {
696 break;
697 }
698 }
699 let byte = m
700 .byte_offsets
701 .get(cut_i)
702 .copied()
703 .unwrap_or(0)
704 .min(text.len());
705 let mut out = String::with_capacity(byte + 3);
706 out.push_str(&text[..byte]);
707 out.push('…');
708
709 let s = out;
710 ellip_cache().lock().unwrap().put(key, s.clone());
711
712 s
713}
714
715fn ellipsis_width(px: f32) -> f32 {
716 static ELLIP_W_LRU: OnceCell<Mutex<Lru<u32, f32>>> = OnceCell::new();
717 let cache = ELLIP_W_LRU.get_or_init(|| Mutex::new(Lru::new(64)));
718 let key = (px * 100.0) as u32;
719 if let Some(w) = cache.lock().unwrap().get(&key).copied() {
720 return w;
721 }
722 let w = if let Some(g) = crate::shape_line("…", px).last() {
723 g.x + g.advance
724 } else {
725 0.0
726 };
727 cache.lock().unwrap().put(key, w);
728 w
729}