1use cosmic_text::{
2 Attrs, Buffer, CacheKey, Family, FontSystem, Metrics, Shaping, SwashCache, SwashContent,
3};
4use once_cell::sync::OnceCell;
5use rapidhash::{HashMapExt, RapidHashMap, fast::RapidHasher};
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, u64), TextMetrics>>> = OnceCell::new();
30fn metrics_cache() -> &'static Mutex<Lru<(u64, u32, u64), TextMetrics>> {
31 METRICS_LRU.get_or_init(|| Mutex::new(Lru::new(4096)))
32}
33
34struct Lru<K, V> {
35 map: RapidHashMap<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: RapidHashMap::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 = RapidHasher::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 = RapidHasher::default();
187 k.hash(&mut h);
188 GlyphKey(h.finish())
189}
190
191pub fn shape_line(text: &str, px: f32, font_family: Option<&str>) -> Vec<ShapedGlyph> {
194 let mut eng = engine().lock().unwrap();
195
196 let mut buf = Buffer::new(&mut eng.fs, Metrics::new(px, px * 1.3));
198 {
199 let mut b = buf.borrow_with(&mut eng.fs);
201 b.set_size(None, None);
202 let attrs = match font_family {
203 Some(family) => Attrs::new().family(Family::Name(family)),
204 None => Attrs::new(),
205 };
206 b.set_text(text, &attrs, Shaping::Advanced, None);
207 b.shape_until_scroll(true);
208 }
209
210 let mut out = Vec::new();
211 for run in buf.layout_runs() {
212 for g in run.glyphs {
213 let phys = g.physical((0.0, run.line_y), 1.0);
215 let key = key_from_cachekey(&phys.cache_key);
216 eng.key_map.insert(key, phys.cache_key);
217
218 let img_opt = eng.get_image(phys.cache_key);
220 let (w, h, left, top) = if let Some(img) = img_opt.as_ref() {
221 (
222 img.placement.width as f32,
223 img.placement.height as f32,
224 img.placement.left as f32,
225 img.placement.top as f32,
226 )
227 } else {
228 (0.0, 0.0, 0.0, 0.0)
229 };
230
231 out.push(ShapedGlyph {
232 key,
233 x: g.x + g.x_offset, y: run.line_y, w,
236 h,
237 bearing_x: left,
238 bearing_y: top,
239 advance: g.w,
240 });
241 }
242 }
243 out
244}
245
246pub fn rasterize(key: GlyphKey, _px: f32) -> Option<GlyphBitmap> {
249 let mut eng = engine().lock().unwrap();
250 let &ck = eng.key_map.get(&key)?;
251
252 let img = eng.get_image(ck).as_ref()?.clone();
253 Some(GlyphBitmap {
254 key,
255 w: img.placement.width,
256 h: img.placement.height,
257 content: img.content,
258 data: img.data, })
260}
261
262pub fn lookup_cache_key(key: GlyphKey) -> Option<cosmic_text::CacheKey> {
264 let eng = engine().lock().unwrap();
265 eng.key_map.get(&key).copied()
266}
267
268pub fn extract_outline_commands(
271 cache_key: cosmic_text::CacheKey,
272) -> Option<Box<[cosmic_text::Command]>> {
273 let mut eng = engine().lock().unwrap();
274 let Engine {
275 ref mut cache,
276 ref mut fs,
277 ..
278 } = *eng;
279 cache.get_outline_commands_uncached(fs, cache_key)
280}
281
282pub fn lookup_and_extract_outline(
285 key: GlyphKey,
286) -> Option<(cosmic_text::CacheKey, Box<[cosmic_text::Command]>)> {
287 let mut eng = engine().lock().unwrap();
288 let ck = eng.key_map.get(&key).copied()?;
289 let Engine {
290 ref mut cache,
291 ref mut fs,
292 ..
293 } = *eng;
294 let cmds = cache.get_outline_commands_uncached(fs, ck)?;
295 Some((ck, cmds))
296}
297
298#[derive(Clone)]
300pub struct TextMetrics {
301 pub positions: Vec<f32>, pub byte_offsets: Vec<usize>, }
304
305pub fn metrics_for_textfield(text: &str, px: f32, font_family: Option<&str>) -> TextMetrics {
308 let family_hash = font_family.map(fast_hash).unwrap_or(0);
309 let key = (fast_hash(text), (px * 100.0) as u32, family_hash);
310 if let Some(m) = metrics_cache().lock().unwrap().get(&key).cloned() {
311 return m;
312 }
313 let mut eng = engine().lock().unwrap();
314 let mut buf = Buffer::new(&mut eng.fs, Metrics::new(px, px * 1.3));
315 {
316 let mut b = buf.borrow_with(&mut eng.fs);
317 b.set_size(None, None);
318 let attrs = match font_family {
319 Some(family) => Attrs::new().family(Family::Name(family)),
320 None => Attrs::new(),
321 };
322 b.set_text(text, &attrs, Shaping::Advanced, None);
323 b.shape_until_scroll(true);
324 }
325 let mut edges: Vec<(usize, f32)> = Vec::new();
326 let mut last_x = 0.0f32;
327 for run in buf.layout_runs() {
328 for g in run.glyphs {
329 let right = g.x + g.w;
330 last_x = right.max(last_x);
331 edges.push((g.end, right));
332 }
333 }
334 if edges.last().map(|e| e.0) != Some(text.len()) {
335 edges.push((text.len(), last_x));
336 }
337 let mut positions = Vec::with_capacity(text.graphemes(true).count() + 1);
338 let mut byte_offsets = Vec::with_capacity(positions.capacity());
339 positions.push(0.0);
340 byte_offsets.push(0);
341 let mut last_byte = 0usize;
342 for (b, _) in text.grapheme_indices(true) {
343 positions
344 .push(positions.last().copied().unwrap_or(0.0) + width_between(&edges, last_byte, b));
345 byte_offsets.push(b);
346 last_byte = b;
347 }
348 if *byte_offsets.last().unwrap_or(&0) != text.len() {
349 positions.push(
350 positions.last().copied().unwrap_or(0.0) + width_between(&edges, last_byte, text.len()),
351 );
352 byte_offsets.push(text.len());
353 }
354 let m = TextMetrics {
355 positions,
356 byte_offsets,
357 };
358 metrics_cache().lock().unwrap().put(key, m.clone());
359 m
360}
361
362fn width_between(edges: &[(usize, f32)], start_b: usize, end_b: usize) -> f32 {
363 let x0 = lookup_right(edges, start_b);
364 let x1 = lookup_right(edges, end_b);
365 (x1 - x0).max(0.0)
366}
367fn lookup_right(edges: &[(usize, f32)], b: usize) -> f32 {
368 match edges.binary_search_by_key(&b, |e| e.0) {
369 Ok(i) => edges[i].1,
370 Err(i) => {
371 if i == 0 {
372 0.0
373 } else {
374 edges[i - 1].1
375 }
376 }
377 }
378}
379
380pub fn wrap_lines(
384 text: &str,
385 px: f32,
386 max_width: f32,
387 max_lines: Option<usize>,
388 soft_wrap: bool,
389) -> (Vec<String>, bool) {
390 if text.is_empty() || max_width <= 0.0 {
391 return (vec![String::new()], false);
392 }
393 if !soft_wrap {
394 return (vec![text.to_string()], false);
395 }
396
397 let max_lines_key: u16 = match max_lines {
398 None => 0,
399 Some(n) => {
400 let n = n.min(u16::MAX as usize - 1) as u16;
401 n.saturating_add(1)
402 }
403 };
404 let key = (
405 fast_hash(text),
406 (px * 100.0) as u32,
407 (max_width * 100.0) as u32,
408 max_lines_key,
409 soft_wrap,
410 );
411 if let Some(h) = wrap_cache().lock().unwrap().get(&key).cloned() {
412 return h;
413 }
414
415 let m = metrics_for_textfield(text, px, None);
417 if let Some(&last) = m.positions.last()
419 && last <= max_width + 0.5
420 {
421 return (vec![text.to_string()], false);
422 }
423
424 let width_of = |start_b: usize, end_b: usize| -> f32 {
426 let i0 = match m.byte_offsets.binary_search(&start_b) {
427 Ok(i) | Err(i) => i,
428 };
429 let i1 = match m.byte_offsets.binary_search(&end_b) {
430 Ok(i) | Err(i) => i,
431 };
432 (m.positions.get(i1).copied().unwrap_or(0.0) - m.positions.get(i0).copied().unwrap_or(0.0))
433 .max(0.0)
434 };
435
436 let mut out: Vec<String> = Vec::new();
437 let mut truncated = false;
438
439 let mut line_start = 0usize; let mut best_break = line_start;
441
442 for tok in text.split_word_bounds() {
444 let tok_start = best_break;
445 let tok_end = tok_start + tok.len();
446 let w = width_of(line_start, tok_end);
447
448 if w <= max_width + 0.5 {
449 best_break = tok_end;
450 continue;
451 }
452
453 if best_break > line_start {
455 out.push(text[line_start..best_break].trim_end().to_string());
457 line_start = best_break;
458 } else {
459 let mut cut = tok_start;
461 for g in tok.grapheme_indices(true) {
462 let next = tok_start + g.0 + g.1.len();
463 if width_of(line_start, next) <= max_width + 0.5 {
464 cut = next;
465 } else {
466 break;
467 }
468 }
469 if cut == line_start {
470 if let Some((ofs, grapheme)) = tok.grapheme_indices(true).next() {
472 cut = tok_start + ofs + grapheme.len();
473 }
474 }
475 out.push(text[line_start..cut].to_string());
476 line_start = cut;
477 }
478
479 if let Some(ml) = max_lines
481 && out.len() >= ml
482 {
483 truncated = true;
484 line_start = line_start.min(text.len());
486 break;
487 }
488
489 best_break = line_start;
491
492 if line_start < tok_end {
494 if width_of(line_start, tok_end) <= max_width + 0.5 {
496 best_break = tok_end;
497 } else {
498 }
500 }
501 }
502
503 if line_start < text.len() && max_lines.is_none_or(|ml| out.len() < ml) {
505 out.push(text[line_start..].trim_end().to_string());
506 }
507
508 let res = (out, truncated);
509
510 wrap_cache().lock().unwrap().put(key, res.clone());
511 res
512}
513
514pub fn wrap_line_ranges(
521 text: &str,
522 px: f32,
523 max_width: f32,
524 max_lines: Option<usize>,
525 soft_wrap: bool,
526) -> (Vec<(usize, usize)>, bool) {
527 if text.is_empty() || max_width <= 0.0 {
528 return (vec![(0, 0)], false);
529 }
530 if !soft_wrap {
531 let mut out = Vec::new();
533 let mut start = 0usize;
534 for (i, ch) in text.char_indices() {
535 if ch == '\n' {
536 out.push((start, i));
537 start = i + 1;
538 }
539 }
540 out.push((start, text.len()));
541 return (out, false);
542 }
543
544 let max_lines_key: u16 = match max_lines {
545 None => 0,
546 Some(n) => {
547 let n = n.min(u16::MAX as usize - 1) as u16;
548 n.saturating_add(1)
549 }
550 };
551 let key = (
552 fast_hash(text),
553 (px * 100.0) as u32,
554 (max_width * 100.0) as u32,
555 max_lines_key,
556 soft_wrap,
557 );
558 if let Some(v) = wrap_ranges_cache().lock().unwrap().get(&key).cloned() {
559 return v;
560 }
561
562 let m = metrics_for_textfield(text, px, None);
564
565 let width_of = |start_b: usize, end_b: usize| -> f32 {
567 let i0 = match m.byte_offsets.binary_search(&start_b) {
568 Ok(i) | Err(i) => i,
569 };
570 let i1 = match m.byte_offsets.binary_search(&end_b) {
571 Ok(i) | Err(i) => i,
572 };
573 (m.positions.get(i1).copied().unwrap_or(0.0) - m.positions.get(i0).copied().unwrap_or(0.0))
574 .max(0.0)
575 };
576
577 let mut out: Vec<(usize, usize)> = Vec::new();
578 let mut truncated = false;
579
580 let mut line0_start = 0usize;
582 for (i, ch) in text.char_indices() {
583 if ch == '\n' {
584 let (mut ranges, tr) = wrap_one_hard_line_ranges(
585 text,
586 line0_start,
587 i,
588 max_width,
589 max_lines.map(|ml| ml.saturating_sub(out.len())),
590 &width_of,
591 );
592 out.append(&mut ranges);
593 if tr {
594 truncated = true;
595 break;
596 }
597 line0_start = i + 1;
598
599 if let Some(ml) = max_lines
600 && out.len() >= ml
601 {
602 truncated = true;
603 break;
604 }
605 }
606 }
607 if !truncated {
608 let (mut ranges, tr) = wrap_one_hard_line_ranges(
609 text,
610 line0_start,
611 text.len(),
612 max_width,
613 max_lines.map(|ml| ml.saturating_sub(out.len())),
614 &width_of,
615 );
616 out.append(&mut ranges);
617 truncated = tr;
618 }
619
620 if out.is_empty() {
621 out.push((0, 0));
622 }
623
624 let res = (out, truncated);
625 wrap_ranges_cache().lock().unwrap().put(key, res.clone());
626 res
627}
628
629fn wrap_one_hard_line_ranges(
630 text: &str,
631 start: usize,
632 end: usize,
633 max_width: f32,
634 max_lines: Option<usize>,
635 width_of: &dyn Fn(usize, usize) -> f32,
636) -> (Vec<(usize, usize)>, bool) {
637 let mut out = Vec::new();
638 let mut t = false;
639
640 if start >= end {
641 out.push((start, start));
642 return (out, false);
643 }
644
645 if width_of(start, end) <= max_width + 0.5 {
647 out.push((start, end));
648 return (out, false);
649 }
650
651 let mut line_start = start;
652 let mut best_break = line_start;
653 let mut unconsumed_start = start;
654
655 for tok in text[line_start..end].split_word_bounds() {
656 let tok_abs_start = unconsumed_start;
657 let tok_abs_end = tok_abs_start + tok.len();
658 unconsumed_start = tok_abs_end;
659
660 let w = width_of(line_start, tok_abs_end);
661 if w <= max_width + 0.5 {
662 best_break = tok_abs_end;
663 continue;
664 }
665
666 if best_break > line_start {
668 out.push((line_start, best_break));
669 line_start = best_break;
670 } else {
671 let mut cut = tok_abs_start;
673 for (ofs, g) in tok.grapheme_indices(true) {
674 let next = tok_abs_start + ofs + g.len();
675 if width_of(line_start, next) <= max_width + 0.5 {
676 cut = next;
677 } else {
678 break;
679 }
680 }
681 if cut == line_start
682 && let Some((ofs, gr)) = tok.grapheme_indices(true).next()
683 {
684 cut = tok_abs_start + ofs + gr.len();
685 }
686 out.push((line_start, cut));
687 line_start = cut;
688 }
689
690 if let Some(ml) = max_lines
692 && out.len() >= ml
693 {
694 t = true;
695 break;
696 }
697
698 best_break = line_start;
699 }
700
701 if !t && line_start < end && max_lines.is_none_or(|ml| out.len() < ml) {
703 out.push((line_start, end));
704 }
705
706 (out, t)
707}
708
709pub fn ellipsize_line(text: &str, px: f32, max_width: f32) -> String {
711 if text.is_empty() || max_width <= 0.0 {
712 return String::new();
713 }
714 let key = (
715 fast_hash(text),
716 (px * 100.0) as u32,
717 (max_width * 100.0) as u32,
718 );
719 if let Some(s) = ellip_cache().lock().unwrap().get(&key).cloned() {
720 return s;
721 }
722 let m = metrics_for_textfield(text, px, None);
723 if let Some(&last) = m.positions.last()
724 && last <= max_width + 0.5
725 {
726 return text.to_string();
727 }
728 let _el = "…";
729 let e_w = ellipsis_width(px);
730 if e_w >= max_width {
731 return String::new();
732 }
733 let mut cut_i = 0usize;
735 for i in 0..m.positions.len() {
736 if m.positions[i] + e_w <= max_width {
737 cut_i = i;
738 } else {
739 break;
740 }
741 }
742 let byte = m
743 .byte_offsets
744 .get(cut_i)
745 .copied()
746 .unwrap_or(0)
747 .min(text.len());
748 let mut out = String::with_capacity(byte + 3);
749 out.push_str(&text[..byte]);
750 out.push('…');
751
752 let s = out;
753 ellip_cache().lock().unwrap().put(key, s.clone());
754
755 s
756}
757
758fn ellipsis_width(px: f32) -> f32 {
759 static ELLIP_W_LRU: OnceCell<Mutex<Lru<u32, f32>>> = OnceCell::new();
760 let cache = ELLIP_W_LRU.get_or_init(|| Mutex::new(Lru::new(64)));
761 let key = (px * 100.0) as u32;
762 if let Some(w) = cache.lock().unwrap().get(&key).copied() {
763 return w;
764 }
765 let w = if let Some(g) = crate::shape_line("…", px, None).last() {
766 g.x + g.advance
767 } else {
768 0.0
769 };
770 cache.lock().unwrap().put(key, w);
771 w
772}