text_layout.rs

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