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, 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}