1use crate::{
2 color::Color,
3 fonts::{FontId, GlyphId, Underline},
4 geometry::{
5 rect::RectF,
6 vector::{vec2f, Vector2F},
7 },
8 platform,
9 platform::FontSystem,
10 scene, PaintContext,
11};
12use ordered_float::OrderedFloat;
13use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
14use smallvec::SmallVec;
15use std::{
16 borrow::Borrow,
17 collections::HashMap,
18 hash::{Hash, Hasher},
19 iter,
20 sync::Arc,
21};
22
23pub struct TextLayoutCache {
24 prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
25 curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
26 fonts: Arc<dyn platform::FontSystem>,
27}
28
29#[derive(Copy, Clone, Debug, PartialEq, Eq)]
30pub struct RunStyle {
31 pub color: Color,
32 pub font_id: FontId,
33 pub underline: Underline,
34}
35
36impl TextLayoutCache {
37 pub fn new(fonts: Arc<dyn platform::FontSystem>) -> Self {
38 Self {
39 prev_frame: Mutex::new(HashMap::new()),
40 curr_frame: RwLock::new(HashMap::new()),
41 fonts,
42 }
43 }
44
45 pub fn finish_frame(&self) {
46 let mut prev_frame = self.prev_frame.lock();
47 let mut curr_frame = self.curr_frame.write();
48 std::mem::swap(&mut *prev_frame, &mut *curr_frame);
49 curr_frame.clear();
50 }
51
52 pub fn layout_str<'a>(
53 &'a self,
54 text: &'a str,
55 font_size: f32,
56 runs: &'a [(usize, RunStyle)],
57 ) -> Line {
58 let key = &CacheKeyRef {
59 text,
60 font_size: OrderedFloat(font_size),
61 runs,
62 } as &dyn CacheKey;
63 let curr_frame = self.curr_frame.upgradable_read();
64 if let Some(layout) = curr_frame.get(key) {
65 return Line::new(layout.clone(), runs);
66 }
67
68 let mut curr_frame = RwLockUpgradableReadGuard::upgrade(curr_frame);
69 if let Some((key, layout)) = self.prev_frame.lock().remove_entry(key) {
70 curr_frame.insert(key, layout.clone());
71 Line::new(layout, runs)
72 } else {
73 let layout = Arc::new(self.fonts.layout_line(text, font_size, runs));
74 let key = CacheKeyValue {
75 text: text.into(),
76 font_size: OrderedFloat(font_size),
77 runs: SmallVec::from(runs),
78 };
79 curr_frame.insert(key, layout.clone());
80 Line::new(layout, runs)
81 }
82 }
83}
84
85trait CacheKey {
86 fn key(&self) -> CacheKeyRef;
87}
88
89impl<'a> PartialEq for (dyn CacheKey + 'a) {
90 fn eq(&self, other: &dyn CacheKey) -> bool {
91 self.key() == other.key()
92 }
93}
94
95impl<'a> Eq for (dyn CacheKey + 'a) {}
96
97impl<'a> Hash for (dyn CacheKey + 'a) {
98 fn hash<H: Hasher>(&self, state: &mut H) {
99 self.key().hash(state)
100 }
101}
102
103#[derive(Eq)]
104struct CacheKeyValue {
105 text: String,
106 font_size: OrderedFloat<f32>,
107 runs: SmallVec<[(usize, RunStyle); 1]>,
108}
109
110impl CacheKey for CacheKeyValue {
111 fn key(&self) -> CacheKeyRef {
112 CacheKeyRef {
113 text: self.text.as_str(),
114 font_size: self.font_size,
115 runs: self.runs.as_slice(),
116 }
117 }
118}
119
120impl PartialEq for CacheKeyValue {
121 fn eq(&self, other: &Self) -> bool {
122 self.key().eq(&other.key())
123 }
124}
125
126impl Hash for CacheKeyValue {
127 fn hash<H: Hasher>(&self, state: &mut H) {
128 self.key().hash(state);
129 }
130}
131
132impl<'a> Borrow<dyn CacheKey + 'a> for CacheKeyValue {
133 fn borrow(&self) -> &(dyn CacheKey + 'a) {
134 self as &dyn CacheKey
135 }
136}
137
138#[derive(Copy, Clone)]
139struct CacheKeyRef<'a> {
140 text: &'a str,
141 font_size: OrderedFloat<f32>,
142 runs: &'a [(usize, RunStyle)],
143}
144
145impl<'a> CacheKey for CacheKeyRef<'a> {
146 fn key(&self) -> CacheKeyRef {
147 *self
148 }
149}
150
151impl<'a> PartialEq for CacheKeyRef<'a> {
152 fn eq(&self, other: &Self) -> bool {
153 self.text == other.text
154 && self.font_size == other.font_size
155 && self.runs.len() == other.runs.len()
156 && self.runs.iter().zip(other.runs.iter()).all(
157 |((len_a, style_a), (len_b, style_b))| {
158 len_a == len_b && style_a.font_id == style_b.font_id
159 },
160 )
161 }
162}
163
164impl<'a> Hash for CacheKeyRef<'a> {
165 fn hash<H: Hasher>(&self, state: &mut H) {
166 self.text.hash(state);
167 self.font_size.hash(state);
168 for (len, style_id) in self.runs {
169 len.hash(state);
170 style_id.font_id.hash(state);
171 }
172 }
173}
174
175#[derive(Default, Debug, Clone)]
176pub struct Line {
177 layout: Arc<LineLayout>,
178 style_runs: SmallVec<[(u32, Color, Underline); 32]>,
179}
180
181#[derive(Default, Debug)]
182pub struct LineLayout {
183 pub width: f32,
184 pub ascent: f32,
185 pub descent: f32,
186 pub runs: Vec<Run>,
187 pub len: usize,
188 pub font_size: f32,
189}
190
191#[derive(Debug)]
192pub struct Run {
193 pub font_id: FontId,
194 pub glyphs: Vec<Glyph>,
195}
196
197#[derive(Clone, Debug)]
198pub struct Glyph {
199 pub id: GlyphId,
200 pub position: Vector2F,
201 pub index: usize,
202 pub is_emoji: bool,
203}
204
205impl Line {
206 fn new(layout: Arc<LineLayout>, runs: &[(usize, RunStyle)]) -> Self {
207 let mut style_runs = SmallVec::new();
208 for (len, style) in runs {
209 style_runs.push((*len as u32, style.color, style.underline));
210 }
211 Self { layout, style_runs }
212 }
213
214 pub fn runs(&self) -> &[Run] {
215 &self.layout.runs
216 }
217
218 pub fn width(&self) -> f32 {
219 self.layout.width
220 }
221
222 pub fn font_size(&self) -> f32 {
223 self.layout.font_size
224 }
225
226 pub fn x_for_index(&self, index: usize) -> f32 {
227 for run in &self.layout.runs {
228 for glyph in &run.glyphs {
229 if glyph.index >= index {
230 return glyph.position.x();
231 }
232 }
233 }
234 self.layout.width
235 }
236
237 pub fn font_for_index(&self, index: usize) -> Option<FontId> {
238 for run in &self.layout.runs {
239 for glyph in &run.glyphs {
240 if glyph.index >= index {
241 return Some(run.font_id);
242 }
243 }
244 }
245
246 None
247 }
248
249 pub fn len(&self) -> usize {
250 self.layout.len
251 }
252
253 pub fn is_empty(&self) -> bool {
254 self.layout.len == 0
255 }
256
257 pub fn index_for_x(&self, x: f32) -> Option<usize> {
258 if x >= self.layout.width {
259 None
260 } else {
261 for run in self.layout.runs.iter().rev() {
262 for glyph in run.glyphs.iter().rev() {
263 if glyph.position.x() <= x {
264 return Some(glyph.index);
265 }
266 }
267 }
268 Some(0)
269 }
270 }
271
272 pub fn paint(
273 &self,
274 origin: Vector2F,
275 visible_bounds: RectF,
276 line_height: f32,
277 cx: &mut PaintContext,
278 ) {
279 let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
280 let baseline_offset = vec2f(0., padding_top + self.layout.ascent);
281
282 let mut style_runs = self.style_runs.iter();
283 let mut run_end = 0;
284 let mut color = Color::black();
285 let mut underline = None;
286
287 for run in &self.layout.runs {
288 let max_glyph_width = cx
289 .font_cache
290 .bounding_box(run.font_id, self.layout.font_size)
291 .x();
292
293 for glyph in &run.glyphs {
294 let glyph_origin = origin + baseline_offset + glyph.position;
295 if glyph_origin.x() > visible_bounds.upper_right().x() {
296 break;
297 }
298
299 let mut finished_underline = None;
300 if glyph.index >= run_end {
301 if let Some((run_len, run_color, run_underline)) = style_runs.next() {
302 if let Some((_, underline_style)) = underline {
303 if *run_underline != underline_style {
304 finished_underline = underline.take();
305 }
306 }
307 if run_underline.thickness.into_inner() > 0. {
308 underline.get_or_insert((
309 vec2f(
310 glyph_origin.x(),
311 origin.y() + baseline_offset.y() + 0.618 * self.layout.descent,
312 ),
313 Underline {
314 color: Some(run_underline.color.unwrap_or(*run_color)),
315 thickness: run_underline.thickness,
316 squiggly: run_underline.squiggly,
317 },
318 ));
319 }
320
321 run_end += *run_len as usize;
322 color = *run_color;
323 } else {
324 run_end = self.layout.len;
325 finished_underline = underline.take();
326 }
327 }
328
329 if glyph_origin.x() + max_glyph_width < visible_bounds.origin().x() {
330 continue;
331 }
332
333 if let Some((underline_origin, underline_style)) = finished_underline {
334 cx.scene.push_underline(scene::Underline {
335 origin: underline_origin,
336 width: glyph_origin.x() - underline_origin.x(),
337 thickness: underline_style.thickness.into(),
338 color: underline_style.color.unwrap(),
339 squiggly: underline_style.squiggly,
340 });
341 }
342
343 if glyph.is_emoji {
344 cx.scene.push_image_glyph(scene::ImageGlyph {
345 font_id: run.font_id,
346 font_size: self.layout.font_size,
347 id: glyph.id,
348 origin: glyph_origin,
349 });
350 } else {
351 cx.scene.push_glyph(scene::Glyph {
352 font_id: run.font_id,
353 font_size: self.layout.font_size,
354 id: glyph.id,
355 origin: glyph_origin,
356 color,
357 });
358 }
359 }
360 }
361
362 if let Some((underline_start, underline_style)) = underline.take() {
363 let line_end_x = origin.x() + self.layout.width;
364 cx.scene.push_underline(scene::Underline {
365 origin: underline_start,
366 width: line_end_x - underline_start.x(),
367 color: underline_style.color.unwrap(),
368 thickness: underline_style.thickness.into(),
369 squiggly: underline_style.squiggly,
370 });
371 }
372 }
373
374 pub fn paint_wrapped(
375 &self,
376 origin: Vector2F,
377 visible_bounds: RectF,
378 line_height: f32,
379 boundaries: impl IntoIterator<Item = ShapedBoundary>,
380 cx: &mut PaintContext,
381 ) {
382 let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
383 let baseline_origin = vec2f(0., padding_top + self.layout.ascent);
384
385 let mut boundaries = boundaries.into_iter().peekable();
386 let mut color_runs = self.style_runs.iter();
387 let mut color_end = 0;
388 let mut color = Color::black();
389
390 let mut glyph_origin = vec2f(0., 0.);
391 let mut prev_position = 0.;
392 for run in &self.layout.runs {
393 for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
394 if boundaries.peek().map_or(false, |b| b.glyph_ix == glyph_ix) {
395 boundaries.next();
396 glyph_origin = vec2f(0., glyph_origin.y() + line_height);
397 } else {
398 glyph_origin.set_x(glyph_origin.x() + glyph.position.x() - prev_position);
399 }
400 prev_position = glyph.position.x();
401
402 if glyph.index >= color_end {
403 if let Some(next_run) = color_runs.next() {
404 color_end += next_run.0 as usize;
405 color = next_run.1;
406 } else {
407 color_end = self.layout.len;
408 color = Color::black();
409 }
410 }
411
412 let glyph_bounds = RectF::new(
413 origin + glyph_origin,
414 cx.font_cache
415 .bounding_box(run.font_id, self.layout.font_size),
416 );
417 if glyph_bounds.intersects(visible_bounds) {
418 if glyph.is_emoji {
419 cx.scene.push_image_glyph(scene::ImageGlyph {
420 font_id: run.font_id,
421 font_size: self.layout.font_size,
422 id: glyph.id,
423 origin: glyph_bounds.origin() + baseline_origin,
424 });
425 } else {
426 cx.scene.push_glyph(scene::Glyph {
427 font_id: run.font_id,
428 font_size: self.layout.font_size,
429 id: glyph.id,
430 origin: glyph_bounds.origin() + baseline_origin,
431 color,
432 });
433 }
434 }
435 }
436 }
437 }
438}
439
440impl Run {
441 pub fn glyphs(&self) -> &[Glyph] {
442 &self.glyphs
443 }
444}
445
446#[derive(Copy, Clone, Debug, PartialEq, Eq)]
447pub struct Boundary {
448 pub ix: usize,
449 pub next_indent: u32,
450}
451
452#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
453pub struct ShapedBoundary {
454 pub run_ix: usize,
455 pub glyph_ix: usize,
456}
457
458impl Boundary {
459 fn new(ix: usize, next_indent: u32) -> Self {
460 Self { ix, next_indent }
461 }
462}
463
464pub struct LineWrapper {
465 font_system: Arc<dyn FontSystem>,
466 pub(crate) font_id: FontId,
467 pub(crate) font_size: f32,
468 cached_ascii_char_widths: [f32; 128],
469 cached_other_char_widths: HashMap<char, f32>,
470}
471
472impl LineWrapper {
473 pub const MAX_INDENT: u32 = 256;
474
475 pub fn new(font_id: FontId, font_size: f32, font_system: Arc<dyn FontSystem>) -> Self {
476 Self {
477 font_system,
478 font_id,
479 font_size,
480 cached_ascii_char_widths: [f32::NAN; 128],
481 cached_other_char_widths: HashMap::new(),
482 }
483 }
484
485 pub fn wrap_line<'a>(
486 &'a mut self,
487 line: &'a str,
488 wrap_width: f32,
489 ) -> impl Iterator<Item = Boundary> + 'a {
490 let mut width = 0.0;
491 let mut first_non_whitespace_ix = None;
492 let mut indent = None;
493 let mut last_candidate_ix = 0;
494 let mut last_candidate_width = 0.0;
495 let mut last_wrap_ix = 0;
496 let mut prev_c = '\0';
497 let mut char_indices = line.char_indices();
498 iter::from_fn(move || {
499 for (ix, c) in char_indices.by_ref() {
500 if c == '\n' {
501 continue;
502 }
503
504 if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
505 last_candidate_ix = ix;
506 last_candidate_width = width;
507 }
508
509 if c != ' ' && first_non_whitespace_ix.is_none() {
510 first_non_whitespace_ix = Some(ix);
511 }
512
513 let char_width = self.width_for_char(c);
514 width += char_width;
515 if width > wrap_width && ix > last_wrap_ix {
516 if let (None, Some(first_non_whitespace_ix)) = (indent, first_non_whitespace_ix)
517 {
518 indent = Some(
519 Self::MAX_INDENT.min((first_non_whitespace_ix - last_wrap_ix) as u32),
520 );
521 }
522
523 if last_candidate_ix > 0 {
524 last_wrap_ix = last_candidate_ix;
525 width -= last_candidate_width;
526 last_candidate_ix = 0;
527 } else {
528 last_wrap_ix = ix;
529 width = char_width;
530 }
531
532 let indent_width =
533 indent.map(|indent| indent as f32 * self.width_for_char(' '));
534 width += indent_width.unwrap_or(0.);
535
536 return Some(Boundary::new(last_wrap_ix, indent.unwrap_or(0)));
537 }
538 prev_c = c;
539 }
540
541 None
542 })
543 }
544
545 pub fn wrap_shaped_line<'a>(
546 &'a mut self,
547 str: &'a str,
548 line: &'a Line,
549 wrap_width: f32,
550 ) -> impl Iterator<Item = ShapedBoundary> + 'a {
551 let mut first_non_whitespace_ix = None;
552 let mut last_candidate_ix = None;
553 let mut last_candidate_x = 0.0;
554 let mut last_wrap_ix = ShapedBoundary {
555 run_ix: 0,
556 glyph_ix: 0,
557 };
558 let mut last_wrap_x = 0.;
559 let mut prev_c = '\0';
560 let mut glyphs = line
561 .runs()
562 .iter()
563 .enumerate()
564 .flat_map(move |(run_ix, run)| {
565 run.glyphs()
566 .iter()
567 .enumerate()
568 .map(move |(glyph_ix, glyph)| {
569 let character = str[glyph.index..].chars().next().unwrap();
570 (
571 ShapedBoundary { run_ix, glyph_ix },
572 character,
573 glyph.position.x(),
574 )
575 })
576 })
577 .peekable();
578
579 iter::from_fn(move || {
580 while let Some((ix, c, x)) = glyphs.next() {
581 if c == '\n' {
582 continue;
583 }
584
585 if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
586 last_candidate_ix = Some(ix);
587 last_candidate_x = x;
588 }
589
590 if c != ' ' && first_non_whitespace_ix.is_none() {
591 first_non_whitespace_ix = Some(ix);
592 }
593
594 let next_x = glyphs.peek().map_or(line.width(), |(_, _, x)| *x);
595 let width = next_x - last_wrap_x;
596 if width > wrap_width && ix > last_wrap_ix {
597 if let Some(last_candidate_ix) = last_candidate_ix.take() {
598 last_wrap_ix = last_candidate_ix;
599 last_wrap_x = last_candidate_x;
600 } else {
601 last_wrap_ix = ix;
602 last_wrap_x = x;
603 }
604
605 return Some(last_wrap_ix);
606 }
607 prev_c = c;
608 }
609
610 None
611 })
612 }
613
614 fn is_boundary(&self, prev: char, next: char) -> bool {
615 (prev == ' ') && (next != ' ')
616 }
617
618 #[inline(always)]
619 fn width_for_char(&mut self, c: char) -> f32 {
620 if (c as u32) < 128 {
621 let mut width = self.cached_ascii_char_widths[c as usize];
622 if width.is_nan() {
623 width = self.compute_width_for_char(c);
624 self.cached_ascii_char_widths[c as usize] = width;
625 }
626 width
627 } else {
628 let mut width = self
629 .cached_other_char_widths
630 .get(&c)
631 .copied()
632 .unwrap_or(f32::NAN);
633 if width.is_nan() {
634 width = self.compute_width_for_char(c);
635 self.cached_other_char_widths.insert(c, width);
636 }
637 width
638 }
639 }
640
641 fn compute_width_for_char(&self, c: char) -> f32 {
642 self.font_system
643 .layout_line(
644 &c.to_string(),
645 self.font_size,
646 &[(
647 1,
648 RunStyle {
649 font_id: self.font_id,
650 color: Default::default(),
651 underline: Default::default(),
652 },
653 )],
654 )
655 .width
656 }
657}
658
659#[cfg(test)]
660mod tests {
661 use super::*;
662 use crate::fonts::{Properties, Weight};
663
664 #[crate::test(self)]
665 fn test_wrap_line(cx: &mut crate::AppContext) {
666 let font_cache = cx.font_cache().clone();
667 let font_system = cx.platform().fonts();
668 let family = font_cache
669 .load_family(&["Courier"], &Default::default())
670 .unwrap();
671 let font_id = font_cache.select_font(family, &Default::default()).unwrap();
672
673 let mut wrapper = LineWrapper::new(font_id, 16., font_system);
674 assert_eq!(
675 wrapper
676 .wrap_line("aa bbb cccc ddddd eeee", 72.0)
677 .collect::<Vec<_>>(),
678 &[
679 Boundary::new(7, 0),
680 Boundary::new(12, 0),
681 Boundary::new(18, 0)
682 ],
683 );
684 assert_eq!(
685 wrapper
686 .wrap_line("aaa aaaaaaaaaaaaaaaaaa", 72.0)
687 .collect::<Vec<_>>(),
688 &[
689 Boundary::new(4, 0),
690 Boundary::new(11, 0),
691 Boundary::new(18, 0)
692 ],
693 );
694 assert_eq!(
695 wrapper.wrap_line(" aaaaaaa", 72.).collect::<Vec<_>>(),
696 &[
697 Boundary::new(7, 5),
698 Boundary::new(9, 5),
699 Boundary::new(11, 5),
700 ]
701 );
702 assert_eq!(
703 wrapper
704 .wrap_line(" ", 72.)
705 .collect::<Vec<_>>(),
706 &[
707 Boundary::new(7, 0),
708 Boundary::new(14, 0),
709 Boundary::new(21, 0)
710 ]
711 );
712 assert_eq!(
713 wrapper
714 .wrap_line(" aaaaaaaaaaaaaa", 72.)
715 .collect::<Vec<_>>(),
716 &[
717 Boundary::new(7, 0),
718 Boundary::new(14, 3),
719 Boundary::new(18, 3),
720 Boundary::new(22, 3),
721 ]
722 );
723 }
724
725 #[crate::test(self, retries = 5)]
726 fn test_wrap_shaped_line(cx: &mut crate::AppContext) {
727 // This is failing intermittently on CI and we don't have time to figure it out
728 let font_cache = cx.font_cache().clone();
729 let font_system = cx.platform().fonts();
730 let text_layout_cache = TextLayoutCache::new(font_system.clone());
731
732 let family = font_cache
733 .load_family(&["Helvetica"], &Default::default())
734 .unwrap();
735 let font_id = font_cache.select_font(family, &Default::default()).unwrap();
736 let normal = RunStyle {
737 font_id,
738 color: Default::default(),
739 underline: Default::default(),
740 };
741 let bold = RunStyle {
742 font_id: font_cache
743 .select_font(
744 family,
745 &Properties {
746 weight: Weight::BOLD,
747 ..Default::default()
748 },
749 )
750 .unwrap(),
751 color: Default::default(),
752 underline: Default::default(),
753 };
754
755 let text = "aa bbb cccc ddddd eeee";
756 let line = text_layout_cache.layout_str(
757 text,
758 16.0,
759 &[(4, normal), (5, bold), (6, normal), (1, bold), (7, normal)],
760 );
761
762 let mut wrapper = LineWrapper::new(font_id, 16., font_system);
763 assert_eq!(
764 wrapper
765 .wrap_shaped_line(text, &line, 72.0)
766 .collect::<Vec<_>>(),
767 &[
768 ShapedBoundary {
769 run_ix: 1,
770 glyph_ix: 3
771 },
772 ShapedBoundary {
773 run_ix: 2,
774 glyph_ix: 3
775 },
776 ShapedBoundary {
777 run_ix: 4,
778 glyph_ix: 2
779 }
780 ],
781 );
782 }
783}