1use ahash::{AHashMap, AHasher};
2use cosmic_text::{
3 Attrs, Buffer, CacheKey, Family, 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, 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: 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, 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
262#[derive(Clone)]
264pub struct TextMetrics {
265 pub positions: Vec<f32>, pub byte_offsets: Vec<usize>, }
268
269pub fn metrics_for_textfield(text: &str, px: f32, font_family: Option<&str>) -> TextMetrics {
272 let family_hash = font_family.map(fast_hash).unwrap_or(0);
273 let key = (fast_hash(text), (px * 100.0) as u32, family_hash);
274 if let Some(m) = metrics_cache().lock().unwrap().get(&key).cloned() {
275 return m;
276 }
277 let mut eng = engine().lock().unwrap();
278 let mut buf = Buffer::new(&mut eng.fs, Metrics::new(px, px * 1.3));
279 {
280 let mut b = buf.borrow_with(&mut eng.fs);
281 b.set_size(None, None);
282 let attrs = match font_family {
283 Some(family) => Attrs::new().family(Family::Name(family)),
284 None => Attrs::new(),
285 };
286 b.set_text(text, &attrs, Shaping::Advanced, None);
287 b.shape_until_scroll(true);
288 }
289 let mut edges: Vec<(usize, f32)> = Vec::new();
290 let mut last_x = 0.0f32;
291 for run in buf.layout_runs() {
292 for g in run.glyphs {
293 let right = g.x + g.w;
294 last_x = right.max(last_x);
295 edges.push((g.end, right));
296 }
297 }
298 if edges.last().map(|e| e.0) != Some(text.len()) {
299 edges.push((text.len(), last_x));
300 }
301 let mut positions = Vec::with_capacity(text.graphemes(true).count() + 1);
302 let mut byte_offsets = Vec::with_capacity(positions.capacity());
303 positions.push(0.0);
304 byte_offsets.push(0);
305 let mut last_byte = 0usize;
306 for (b, _) in text.grapheme_indices(true) {
307 positions
308 .push(positions.last().copied().unwrap_or(0.0) + width_between(&edges, last_byte, b));
309 byte_offsets.push(b);
310 last_byte = b;
311 }
312 if *byte_offsets.last().unwrap_or(&0) != text.len() {
313 positions.push(
314 positions.last().copied().unwrap_or(0.0) + width_between(&edges, last_byte, text.len()),
315 );
316 byte_offsets.push(text.len());
317 }
318 let m = TextMetrics {
319 positions,
320 byte_offsets,
321 };
322 metrics_cache().lock().unwrap().put(key, m.clone());
323 m
324}
325
326fn width_between(edges: &[(usize, f32)], start_b: usize, end_b: usize) -> f32 {
327 let x0 = lookup_right(edges, start_b);
328 let x1 = lookup_right(edges, end_b);
329 (x1 - x0).max(0.0)
330}
331fn lookup_right(edges: &[(usize, f32)], b: usize) -> f32 {
332 match edges.binary_search_by_key(&b, |e| e.0) {
333 Ok(i) => edges[i].1,
334 Err(i) => {
335 if i == 0 {
336 0.0
337 } else {
338 edges[i - 1].1
339 }
340 }
341 }
342}
343
344pub fn wrap_lines(
348 text: &str,
349 px: f32,
350 max_width: f32,
351 max_lines: Option<usize>,
352 soft_wrap: bool,
353) -> (Vec<String>, bool) {
354 if text.is_empty() || max_width <= 0.0 {
355 return (vec![String::new()], false);
356 }
357 if !soft_wrap {
358 return (vec![text.to_string()], false);
359 }
360
361 let max_lines_key: u16 = match max_lines {
362 None => 0,
363 Some(n) => {
364 let n = n.min(u16::MAX as usize - 1) as u16;
365 n.saturating_add(1)
366 }
367 };
368 let key = (
369 fast_hash(text),
370 (px * 100.0) as u32,
371 (max_width * 100.0) as u32,
372 max_lines_key,
373 soft_wrap,
374 );
375 if let Some(h) = wrap_cache().lock().unwrap().get(&key).cloned() {
376 return h;
377 }
378
379 let m = metrics_for_textfield(text, px, None);
381 if let Some(&last) = m.positions.last()
383 && last <= max_width + 0.5
384 {
385 return (vec![text.to_string()], false);
386 }
387
388 let width_of = |start_b: usize, end_b: usize| -> f32 {
390 let i0 = match m.byte_offsets.binary_search(&start_b) {
391 Ok(i) | Err(i) => i,
392 };
393 let i1 = match m.byte_offsets.binary_search(&end_b) {
394 Ok(i) | Err(i) => i,
395 };
396 (m.positions.get(i1).copied().unwrap_or(0.0) - m.positions.get(i0).copied().unwrap_or(0.0))
397 .max(0.0)
398 };
399
400 let mut out: Vec<String> = Vec::new();
401 let mut truncated = false;
402
403 let mut line_start = 0usize; let mut best_break = line_start;
405
406 for tok in text.split_word_bounds() {
408 let tok_start = best_break;
409 let tok_end = tok_start + tok.len();
410 let w = width_of(line_start, tok_end);
411
412 if w <= max_width + 0.5 {
413 best_break = tok_end;
414 continue;
415 }
416
417 if best_break > line_start {
419 out.push(text[line_start..best_break].trim_end().to_string());
421 line_start = best_break;
422 } else {
423 let mut cut = tok_start;
425 for g in tok.grapheme_indices(true) {
426 let next = tok_start + g.0 + g.1.len();
427 if width_of(line_start, next) <= max_width + 0.5 {
428 cut = next;
429 } else {
430 break;
431 }
432 }
433 if cut == line_start {
434 if let Some((ofs, grapheme)) = tok.grapheme_indices(true).next() {
436 cut = tok_start + ofs + grapheme.len();
437 }
438 }
439 out.push(text[line_start..cut].to_string());
440 line_start = cut;
441 }
442
443 if let Some(ml) = max_lines
445 && out.len() >= ml
446 {
447 truncated = true;
448 line_start = line_start.min(text.len());
450 break;
451 }
452
453 best_break = line_start;
455
456 if line_start < tok_end {
458 if width_of(line_start, tok_end) <= max_width + 0.5 {
460 best_break = tok_end;
461 } else {
462 }
464 }
465 }
466
467 if line_start < text.len() && max_lines.is_none_or(|ml| out.len() < ml) {
469 out.push(text[line_start..].trim_end().to_string());
470 }
471
472 let res = (out, truncated);
473
474 wrap_cache().lock().unwrap().put(key, res.clone());
475 res
476}
477
478pub fn wrap_line_ranges(
485 text: &str,
486 px: f32,
487 max_width: f32,
488 max_lines: Option<usize>,
489 soft_wrap: bool,
490) -> (Vec<(usize, usize)>, bool) {
491 if text.is_empty() || max_width <= 0.0 {
492 return (vec![(0, 0)], false);
493 }
494 if !soft_wrap {
495 let mut out = Vec::new();
497 let mut start = 0usize;
498 for (i, ch) in text.char_indices() {
499 if ch == '\n' {
500 out.push((start, i));
501 start = i + 1;
502 }
503 }
504 out.push((start, text.len()));
505 return (out, false);
506 }
507
508 let max_lines_key: u16 = match max_lines {
509 None => 0,
510 Some(n) => {
511 let n = n.min(u16::MAX as usize - 1) as u16;
512 n.saturating_add(1)
513 }
514 };
515 let key = (
516 fast_hash(text),
517 (px * 100.0) as u32,
518 (max_width * 100.0) as u32,
519 max_lines_key,
520 soft_wrap,
521 );
522 if let Some(v) = wrap_ranges_cache().lock().unwrap().get(&key).cloned() {
523 return v;
524 }
525
526 let m = metrics_for_textfield(text, px, None);
528
529 let width_of = |start_b: usize, end_b: usize| -> f32 {
531 let i0 = match m.byte_offsets.binary_search(&start_b) {
532 Ok(i) | Err(i) => i,
533 };
534 let i1 = match m.byte_offsets.binary_search(&end_b) {
535 Ok(i) | Err(i) => i,
536 };
537 (m.positions.get(i1).copied().unwrap_or(0.0) - m.positions.get(i0).copied().unwrap_or(0.0))
538 .max(0.0)
539 };
540
541 let mut out: Vec<(usize, usize)> = Vec::new();
542 let mut truncated = false;
543
544 let mut line0_start = 0usize;
546 for (i, ch) in text.char_indices() {
547 if ch == '\n' {
548 let (mut ranges, tr) = wrap_one_hard_line_ranges(
549 text,
550 line0_start,
551 i,
552 max_width,
553 max_lines.map(|ml| ml.saturating_sub(out.len())),
554 &width_of,
555 );
556 out.append(&mut ranges);
557 if tr {
558 truncated = true;
559 break;
560 }
561 line0_start = i + 1;
562
563 if let Some(ml) = max_lines
564 && out.len() >= ml {
565 truncated = true;
566 break;
567 }
568 }
569 }
570 if !truncated {
571 let (mut ranges, tr) = wrap_one_hard_line_ranges(
572 text,
573 line0_start,
574 text.len(),
575 max_width,
576 max_lines.map(|ml| ml.saturating_sub(out.len())),
577 &width_of,
578 );
579 out.append(&mut ranges);
580 truncated = tr;
581 }
582
583 if out.is_empty() {
584 out.push((0, 0));
585 }
586
587 let res = (out, truncated);
588 wrap_ranges_cache().lock().unwrap().put(key, res.clone());
589 res
590}
591
592fn wrap_one_hard_line_ranges(
593 text: &str,
594 start: usize,
595 end: usize,
596 max_width: f32,
597 max_lines: Option<usize>,
598 width_of: &dyn Fn(usize, usize) -> f32,
599) -> (Vec<(usize, usize)>, bool) {
600 let mut out = Vec::new();
601 let mut t = false;
602
603 if start >= end {
604 out.push((start, start));
605 return (out, false);
606 }
607
608 if width_of(start, end) <= max_width + 0.5 {
610 out.push((start, end));
611 return (out, false);
612 }
613
614 let mut line_start = start;
615 let mut best_break = line_start;
616 let mut unconsumed_start = start;
617
618 for tok in text[line_start..end].split_word_bounds() {
619 let tok_abs_start = unconsumed_start;
620 let tok_abs_end = tok_abs_start + tok.len();
621 unconsumed_start = tok_abs_end;
622
623 let w = width_of(line_start, tok_abs_end);
624 if w <= max_width + 0.5 {
625 best_break = tok_abs_end;
626 continue;
627 }
628
629 if best_break > line_start {
631 out.push((line_start, best_break));
632 line_start = best_break;
633 } else {
634 let mut cut = tok_abs_start;
636 for (ofs, g) in tok.grapheme_indices(true) {
637 let next = tok_abs_start + ofs + g.len();
638 if width_of(line_start, next) <= max_width + 0.5 {
639 cut = next;
640 } else {
641 break;
642 }
643 }
644 if cut == line_start
645 && let Some((ofs, gr)) = tok.grapheme_indices(true).next() {
646 cut = tok_abs_start + ofs + gr.len();
647 }
648 out.push((line_start, cut));
649 line_start = cut;
650 }
651
652 if let Some(ml) = max_lines
654 && out.len() >= ml {
655 t = true;
656 break;
657 }
658
659 best_break = line_start;
660 }
661
662 if !t && line_start < end && max_lines.is_none_or(|ml| out.len() < ml) {
664 out.push((line_start, end));
665 }
666
667 (out, t)
668}
669
670pub fn ellipsize_line(text: &str, px: f32, max_width: f32) -> String {
672 if text.is_empty() || max_width <= 0.0 {
673 return String::new();
674 }
675 let key = (
676 fast_hash(text),
677 (px * 100.0) as u32,
678 (max_width * 100.0) as u32,
679 );
680 if let Some(s) = ellip_cache().lock().unwrap().get(&key).cloned() {
681 return s;
682 }
683 let m = metrics_for_textfield(text, px, None);
684 if let Some(&last) = m.positions.last()
685 && last <= max_width + 0.5
686 {
687 return text.to_string();
688 }
689 let _el = "…";
690 let e_w = ellipsis_width(px);
691 if e_w >= max_width {
692 return String::new();
693 }
694 let mut cut_i = 0usize;
696 for i in 0..m.positions.len() {
697 if m.positions[i] + e_w <= max_width {
698 cut_i = i;
699 } else {
700 break;
701 }
702 }
703 let byte = m
704 .byte_offsets
705 .get(cut_i)
706 .copied()
707 .unwrap_or(0)
708 .min(text.len());
709 let mut out = String::with_capacity(byte + 3);
710 out.push_str(&text[..byte]);
711 out.push('…');
712
713 let s = out;
714 ellip_cache().lock().unwrap().put(key, s.clone());
715
716 s
717}
718
719fn ellipsis_width(px: f32) -> f32 {
720 static ELLIP_W_LRU: OnceCell<Mutex<Lru<u32, f32>>> = OnceCell::new();
721 let cache = ELLIP_W_LRU.get_or_init(|| Mutex::new(Lru::new(64)));
722 let key = (px * 100.0) as u32;
723 if let Some(w) = cache.lock().unwrap().get(&key).copied() {
724 return w;
725 }
726 let w = if let Some(g) = crate::shape_line("…", px, None).last() {
727 g.x + g.advance
728 } else {
729 0.0
730 };
731 cache.lock().unwrap().put(key, w);
732 w
733}