text_layout.rs

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