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