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