Skip to main content

gpui/text_system/
line_layout.rs

1use crate::{FontId, GlyphId, Pixels, PlatformTextSystem, Point, SharedString, Size, point, px};
2use collections::FxHashMap;
3use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
4use smallvec::SmallVec;
5use std::{
6    borrow::Borrow,
7    hash::{Hash, Hasher},
8    ops::Range,
9    sync::{
10        Arc,
11        atomic::{AtomicU64, Ordering},
12    },
13};
14
15use super::LineWrapper;
16
17/// A laid out and styled line of text
18#[derive(Default, Debug)]
19pub struct LineLayout {
20    /// The font size for this line
21    pub font_size: Pixels,
22    /// The width of the line
23    pub width: Pixels,
24    /// The ascent of the line
25    pub ascent: Pixels,
26    /// The descent of the line
27    pub descent: Pixels,
28    /// The shaped runs that make up this line
29    pub runs: Vec<ShapedRun>,
30    /// The length of the line in utf-8 bytes
31    pub len: usize,
32}
33
34/// A run of text that has been shaped .
35#[derive(Debug, Clone)]
36pub struct ShapedRun {
37    /// The font id for this run
38    pub font_id: FontId,
39    /// The glyphs that make up this run
40    pub glyphs: Vec<ShapedGlyph>,
41}
42
43/// A single glyph, ready to paint.
44#[derive(Clone, Debug)]
45pub struct ShapedGlyph {
46    /// The ID for this glyph, as determined by the text system.
47    pub id: GlyphId,
48
49    /// The position of this glyph in its containing line.
50    pub position: Point<Pixels>,
51
52    /// The index of this glyph in the original text.
53    pub index: usize,
54
55    /// Whether this glyph is an emoji
56    pub is_emoji: bool,
57}
58
59impl LineLayout {
60    /// The index for the character at the given x coordinate
61    pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
62        if x >= self.width {
63            None
64        } else {
65            for run in self.runs.iter().rev() {
66                for glyph in run.glyphs.iter().rev() {
67                    if glyph.position.x <= x {
68                        return Some(glyph.index);
69                    }
70                }
71            }
72            Some(0)
73        }
74    }
75
76    /// closest_index_for_x returns the character boundary closest to the given x coordinate
77    /// (e.g. to handle aligning up/down arrow keys)
78    pub fn closest_index_for_x(&self, x: Pixels) -> usize {
79        let mut prev_index = 0;
80        let mut prev_x = px(0.);
81
82        for run in self.runs.iter() {
83            for glyph in run.glyphs.iter() {
84                if glyph.position.x >= x {
85                    if glyph.position.x - x < x - prev_x {
86                        return glyph.index;
87                    } else {
88                        return prev_index;
89                    }
90                }
91                prev_index = glyph.index;
92                prev_x = glyph.position.x;
93            }
94        }
95
96        if self.len == 1 {
97            if x > self.width / 2. {
98                return 1;
99            } else {
100                return 0;
101            }
102        }
103
104        self.len
105    }
106
107    /// The x position of the character at the given index
108    pub fn x_for_index(&self, index: usize) -> Pixels {
109        for run in &self.runs {
110            for glyph in &run.glyphs {
111                if glyph.index >= index {
112                    return glyph.position.x;
113                }
114            }
115        }
116        self.width
117    }
118
119    /// The corresponding Font at the given index
120    pub fn font_id_for_index(&self, index: usize) -> Option<FontId> {
121        for run in &self.runs {
122            for glyph in &run.glyphs {
123                if glyph.index >= index {
124                    return Some(run.font_id);
125                }
126            }
127        }
128        None
129    }
130
131    fn compute_wrap_boundaries(
132        &self,
133        text: &str,
134        wrap_width: Pixels,
135        max_lines: Option<usize>,
136    ) -> SmallVec<[WrapBoundary; 1]> {
137        let mut boundaries = SmallVec::new();
138        let mut first_non_whitespace_ix = None;
139        let mut last_candidate_ix = None;
140        let mut last_candidate_x = px(0.);
141        let mut last_boundary = WrapBoundary {
142            run_ix: 0,
143            glyph_ix: 0,
144        };
145        let mut last_boundary_x = px(0.);
146        let mut prev_ch = '\0';
147        let mut glyphs = self
148            .runs
149            .iter()
150            .enumerate()
151            .flat_map(move |(run_ix, run)| {
152                run.glyphs.iter().enumerate().map(move |(glyph_ix, glyph)| {
153                    let character = text[glyph.index..].chars().next().unwrap();
154                    (
155                        WrapBoundary { run_ix, glyph_ix },
156                        character,
157                        glyph.position.x,
158                    )
159                })
160            })
161            .peekable();
162
163        while let Some((boundary, ch, x)) = glyphs.next() {
164            if ch == '\n' {
165                continue;
166            }
167
168            // Here is very similar to `LineWrapper::wrap_line` to determine text wrapping,
169            // but there are some differences, so we have to duplicate the code here.
170            if LineWrapper::is_word_char(ch) {
171                if prev_ch == ' ' && ch != ' ' && first_non_whitespace_ix.is_some() {
172                    last_candidate_ix = Some(boundary);
173                    last_candidate_x = x;
174                }
175            } else {
176                if ch != ' ' && first_non_whitespace_ix.is_some() {
177                    last_candidate_ix = Some(boundary);
178                    last_candidate_x = x;
179                }
180            }
181
182            if ch != ' ' && first_non_whitespace_ix.is_none() {
183                first_non_whitespace_ix = Some(boundary);
184            }
185
186            let next_x = glyphs.peek().map_or(self.width, |(_, _, x)| *x);
187            let width = next_x - last_boundary_x;
188
189            if width > wrap_width && boundary > last_boundary {
190                // When used line_clamp, we should limit the number of lines.
191                if let Some(max_lines) = max_lines
192                    && boundaries.len() >= max_lines - 1
193                {
194                    break;
195                }
196
197                if let Some(last_candidate_ix) = last_candidate_ix.take() {
198                    last_boundary = last_candidate_ix;
199                    last_boundary_x = last_candidate_x;
200                } else {
201                    last_boundary = boundary;
202                    last_boundary_x = x;
203                }
204                boundaries.push(last_boundary);
205            }
206            prev_ch = ch;
207        }
208
209        boundaries
210    }
211}
212
213/// A line of text that has been wrapped to fit a given width
214#[derive(Default, Debug)]
215pub struct WrappedLineLayout {
216    /// The line layout, pre-wrapping.
217    pub unwrapped_layout: Arc<LineLayout>,
218
219    /// The boundaries at which the line was wrapped
220    pub wrap_boundaries: SmallVec<[WrapBoundary; 1]>,
221
222    /// The width of the line, if it was wrapped
223    pub wrap_width: Option<Pixels>,
224}
225
226/// A boundary at which a line was wrapped
227#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
228pub struct WrapBoundary {
229    /// The index in the run just before the line was wrapped
230    pub run_ix: usize,
231    /// The index of the glyph just before the line was wrapped
232    pub glyph_ix: usize,
233}
234
235impl WrappedLineLayout {
236    /// The length of the underlying text, in utf8 bytes.
237    #[allow(clippy::len_without_is_empty)]
238    pub fn len(&self) -> usize {
239        self.unwrapped_layout.len
240    }
241
242    /// The width of this line, in pixels, whether or not it was wrapped.
243    pub fn width(&self) -> Pixels {
244        self.wrap_width
245            .unwrap_or(Pixels::MAX)
246            .min(self.unwrapped_layout.width)
247    }
248
249    /// The size of the whole wrapped text, for the given line_height.
250    /// can span multiple lines if there are multiple wrap boundaries.
251    pub fn size(&self, line_height: Pixels) -> Size<Pixels> {
252        Size {
253            width: self.width(),
254            height: line_height * (self.wrap_boundaries.len() + 1),
255        }
256    }
257
258    /// The ascent of a line in this layout
259    pub fn ascent(&self) -> Pixels {
260        self.unwrapped_layout.ascent
261    }
262
263    /// The descent of a line in this layout
264    pub fn descent(&self) -> Pixels {
265        self.unwrapped_layout.descent
266    }
267
268    /// The wrap boundaries in this layout
269    pub fn wrap_boundaries(&self) -> &[WrapBoundary] {
270        &self.wrap_boundaries
271    }
272
273    /// The font size of this layout
274    pub fn font_size(&self) -> Pixels {
275        self.unwrapped_layout.font_size
276    }
277
278    /// The runs in this layout, sans wrapping
279    pub fn runs(&self) -> &[ShapedRun] {
280        &self.unwrapped_layout.runs
281    }
282
283    /// The index corresponding to a given position in this layout for the given line height.
284    ///
285    /// See also [`Self::closest_index_for_position`].
286    pub fn index_for_position(
287        &self,
288        position: Point<Pixels>,
289        line_height: Pixels,
290    ) -> Result<usize, usize> {
291        self._index_for_position(position, line_height, false)
292    }
293
294    /// The closest index to a given position in this layout for the given line height.
295    ///
296    /// Closest means the character boundary closest to the given position.
297    ///
298    /// See also [`LineLayout::closest_index_for_x`].
299    pub fn closest_index_for_position(
300        &self,
301        position: Point<Pixels>,
302        line_height: Pixels,
303    ) -> Result<usize, usize> {
304        self._index_for_position(position, line_height, true)
305    }
306
307    fn _index_for_position(
308        &self,
309        mut position: Point<Pixels>,
310        line_height: Pixels,
311        closest: bool,
312    ) -> Result<usize, usize> {
313        let wrapped_line_ix = (position.y / line_height) as usize;
314
315        let wrapped_line_start_index;
316        let wrapped_line_start_x;
317        if wrapped_line_ix > 0 {
318            let Some(line_start_boundary) = self.wrap_boundaries.get(wrapped_line_ix - 1) else {
319                return Err(0);
320            };
321            let run = &self.unwrapped_layout.runs[line_start_boundary.run_ix];
322            let glyph = &run.glyphs[line_start_boundary.glyph_ix];
323            wrapped_line_start_index = glyph.index;
324            wrapped_line_start_x = glyph.position.x;
325        } else {
326            wrapped_line_start_index = 0;
327            wrapped_line_start_x = Pixels::ZERO;
328        };
329
330        let wrapped_line_end_index;
331        let wrapped_line_end_x;
332        if wrapped_line_ix < self.wrap_boundaries.len() {
333            let next_wrap_boundary_ix = wrapped_line_ix;
334            let next_wrap_boundary = self.wrap_boundaries[next_wrap_boundary_ix];
335            let run = &self.unwrapped_layout.runs[next_wrap_boundary.run_ix];
336            let glyph = &run.glyphs[next_wrap_boundary.glyph_ix];
337            wrapped_line_end_index = glyph.index;
338            wrapped_line_end_x = glyph.position.x;
339        } else {
340            wrapped_line_end_index = self.unwrapped_layout.len;
341            wrapped_line_end_x = self.unwrapped_layout.width;
342        };
343
344        let mut position_in_unwrapped_line = position;
345        position_in_unwrapped_line.x += wrapped_line_start_x;
346        if position_in_unwrapped_line.x < wrapped_line_start_x {
347            Err(wrapped_line_start_index)
348        } else if position_in_unwrapped_line.x >= wrapped_line_end_x {
349            Err(wrapped_line_end_index)
350        } else {
351            if closest {
352                Ok(self
353                    .unwrapped_layout
354                    .closest_index_for_x(position_in_unwrapped_line.x))
355            } else {
356                Ok(self
357                    .unwrapped_layout
358                    .index_for_x(position_in_unwrapped_line.x)
359                    .unwrap())
360            }
361        }
362    }
363
364    /// Returns the pixel position for the given byte index.
365    pub fn position_for_index(&self, index: usize, line_height: Pixels) -> Option<Point<Pixels>> {
366        let mut line_start_ix = 0;
367        let mut line_end_indices = self
368            .wrap_boundaries
369            .iter()
370            .map(|wrap_boundary| {
371                let run = &self.unwrapped_layout.runs[wrap_boundary.run_ix];
372                let glyph = &run.glyphs[wrap_boundary.glyph_ix];
373                glyph.index
374            })
375            .chain([self.len()])
376            .enumerate();
377        for (ix, line_end_ix) in line_end_indices {
378            let line_y = ix as f32 * line_height;
379            if index < line_start_ix {
380                break;
381            } else if index > line_end_ix {
382                line_start_ix = line_end_ix;
383                continue;
384            } else {
385                let line_start_x = self.unwrapped_layout.x_for_index(line_start_ix);
386                let x = self.unwrapped_layout.x_for_index(index) - line_start_x;
387                return Some(point(x, line_y));
388            }
389        }
390
391        None
392    }
393}
394
395const GLOBAL_CACHE_MAX_ENTRIES: usize = 10_000;
396
397struct GlobalCacheEntry<T> {
398    value: T,
399    last_access: AtomicU64,
400}
401
402pub(crate) struct GlobalLineLayoutCache {
403    lines: RwLock<FxHashMap<Arc<CacheKey>, GlobalCacheEntry<Arc<LineLayout>>>>,
404    wrapped_lines: RwLock<FxHashMap<Arc<CacheKey>, GlobalCacheEntry<Arc<WrappedLineLayout>>>>,
405    access_counter: AtomicU64,
406}
407
408impl GlobalLineLayoutCache {
409    pub fn new() -> Self {
410        Self {
411            lines: RwLock::new(FxHashMap::default()),
412            wrapped_lines: RwLock::new(FxHashMap::default()),
413            access_counter: AtomicU64::new(0),
414        }
415    }
416
417    fn get_line(&self, key: &dyn AsCacheKeyRef) -> Option<Arc<LineLayout>> {
418        let lines = self.lines.read();
419        if let Some(entry) = lines.get(key) {
420            // Relaxed is fine — this is just for approximate LRU ordering
421            entry
422                .last_access
423                .store(self.access_counter.fetch_add(1, Ordering::Relaxed), Ordering::Relaxed);
424            Some(entry.value.clone())
425        } else {
426            None
427        }
428    }
429
430    fn insert_line(&self, key: Arc<CacheKey>, layout: Arc<LineLayout>) {
431        let mut lines = self.lines.write();
432        if lines.len() >= GLOBAL_CACHE_MAX_ENTRIES {
433            Self::evict_oldest(&mut lines);
434        }
435        lines.insert(key, GlobalCacheEntry {
436            value: layout,
437            last_access: AtomicU64::new(self.access_counter.fetch_add(1, Ordering::Relaxed)),
438        });
439    }
440
441    fn get_wrapped_line(&self, key: &dyn AsCacheKeyRef) -> Option<Arc<WrappedLineLayout>> {
442        let wrapped = self.wrapped_lines.read();
443        if let Some(entry) = wrapped.get(key) {
444            entry
445                .last_access
446                .store(self.access_counter.fetch_add(1, Ordering::Relaxed), Ordering::Relaxed);
447            Some(entry.value.clone())
448        } else {
449            None
450        }
451    }
452
453    fn insert_wrapped_line(&self, key: Arc<CacheKey>, layout: Arc<WrappedLineLayout>) {
454        let mut wrapped = self.wrapped_lines.write();
455        if wrapped.len() >= GLOBAL_CACHE_MAX_ENTRIES {
456            Self::evict_oldest(&mut wrapped);
457        }
458        wrapped.insert(key, GlobalCacheEntry {
459            value: layout,
460            last_access: AtomicU64::new(self.access_counter.fetch_add(1, Ordering::Relaxed)),
461        });
462    }
463
464    fn evict_oldest<V>(map: &mut FxHashMap<Arc<CacheKey>, GlobalCacheEntry<V>>) {
465        let half = map.len() / 2;
466        let mut entries: Vec<_> = map
467            .keys()
468            .map(|k| (k.clone(), map.get(k).unwrap().last_access.load(Ordering::Relaxed)))
469            .collect();
470        entries.sort_by_key(|(_, access)| *access);
471        for (key, _) in entries.into_iter().take(half) {
472            map.remove(&key);
473        }
474    }
475}
476
477pub(crate) struct LineLayoutCache {
478    previous_frame: Mutex<FrameCache>,
479    current_frame: RwLock<FrameCache>,
480    platform_text_system: Arc<dyn PlatformTextSystem>,
481    global_cache: Arc<GlobalLineLayoutCache>,
482}
483
484#[derive(Default)]
485struct FrameCache {
486    lines: FxHashMap<Arc<CacheKey>, Arc<LineLayout>>,
487    wrapped_lines: FxHashMap<Arc<CacheKey>, Arc<WrappedLineLayout>>,
488    used_lines: Vec<Arc<CacheKey>>,
489    used_wrapped_lines: Vec<Arc<CacheKey>>,
490}
491
492#[derive(Clone, Default)]
493pub(crate) struct LineLayoutIndex {
494    lines_index: usize,
495    wrapped_lines_index: usize,
496}
497
498impl LineLayoutCache {
499    pub fn new(
500        platform_text_system: Arc<dyn PlatformTextSystem>,
501        global_cache: Arc<GlobalLineLayoutCache>,
502    ) -> Self {
503        Self {
504            previous_frame: Mutex::default(),
505            current_frame: RwLock::default(),
506            platform_text_system,
507            global_cache,
508        }
509    }
510
511    pub fn layout_index(&self) -> LineLayoutIndex {
512        let frame = self.current_frame.read();
513        LineLayoutIndex {
514            lines_index: frame.used_lines.len(),
515            wrapped_lines_index: frame.used_wrapped_lines.len(),
516        }
517    }
518
519    pub fn reuse_layouts(&self, range: Range<LineLayoutIndex>) {
520        let mut previous_frame = &mut *self.previous_frame.lock();
521        let mut current_frame = &mut *self.current_frame.write();
522
523        for key in &previous_frame.used_lines[range.start.lines_index..range.end.lines_index] {
524            if let Some((key, line)) = previous_frame.lines.remove_entry(key) {
525                current_frame.lines.insert(key, line);
526            }
527            current_frame.used_lines.push(key.clone());
528        }
529
530        for key in &previous_frame.used_wrapped_lines
531            [range.start.wrapped_lines_index..range.end.wrapped_lines_index]
532        {
533            if let Some((key, line)) = previous_frame.wrapped_lines.remove_entry(key) {
534                current_frame.wrapped_lines.insert(key, line);
535            }
536            current_frame.used_wrapped_lines.push(key.clone());
537        }
538    }
539
540    pub fn truncate_layouts(&self, index: LineLayoutIndex) {
541        let mut current_frame = &mut *self.current_frame.write();
542        current_frame.used_lines.truncate(index.lines_index);
543        current_frame
544            .used_wrapped_lines
545            .truncate(index.wrapped_lines_index);
546    }
547
548    pub fn finish_frame(&self) {
549        let mut prev_frame = self.previous_frame.lock();
550        let mut curr_frame = self.current_frame.write();
551        std::mem::swap(&mut *prev_frame, &mut *curr_frame);
552        curr_frame.lines.clear();
553        curr_frame.wrapped_lines.clear();
554        curr_frame.used_lines.clear();
555        curr_frame.used_wrapped_lines.clear();
556    }
557
558    pub fn layout_wrapped_line<Text>(
559        &self,
560        text: Text,
561        font_size: Pixels,
562        runs: &[FontRun],
563        wrap_width: Option<Pixels>,
564        max_lines: Option<usize>,
565    ) -> Arc<WrappedLineLayout>
566    where
567        Text: AsRef<str>,
568        SharedString: From<Text>,
569    {
570        let key = &CacheKeyRef {
571            text: text.as_ref(),
572            font_size,
573            runs,
574            wrap_width,
575            force_width: None,
576            letter_spacing: None,
577        } as &dyn AsCacheKeyRef;
578
579        let current_frame = self.current_frame.upgradable_read();
580        if let Some(layout) = current_frame.wrapped_lines.get(key) {
581            return layout.clone();
582        }
583
584        let previous_frame_entry = self.previous_frame.lock().wrapped_lines.remove_entry(key);
585        if let Some((key, layout)) = previous_frame_entry {
586            let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
587            current_frame
588                .wrapped_lines
589                .insert(key.clone(), layout.clone());
590            current_frame.used_wrapped_lines.push(key);
591            layout
592        } else {
593            // Check global cross-window cache
594            if let Some(layout) = self.global_cache.get_wrapped_line(key) {
595                let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
596                let key = Arc::new(CacheKey {
597                    text: SharedString::from(text),
598                    font_size,
599                    runs: SmallVec::from(runs),
600                    wrap_width,
601                    force_width: None,
602                    letter_spacing: None,
603                });
604                current_frame
605                    .wrapped_lines
606                    .insert(key.clone(), layout.clone());
607                current_frame.used_wrapped_lines.push(key);
608                return layout;
609            }
610
611            drop(current_frame);
612            let text = SharedString::from(text);
613            let unwrapped_layout = self.layout_line::<&SharedString>(&text, font_size, runs, None);
614            let wrap_boundaries = if let Some(wrap_width) = wrap_width {
615                unwrapped_layout.compute_wrap_boundaries(text.as_ref(), wrap_width, max_lines)
616            } else {
617                SmallVec::new()
618            };
619            let layout = Arc::new(WrappedLineLayout {
620                unwrapped_layout,
621                wrap_boundaries,
622                wrap_width,
623            });
624            let key = Arc::new(CacheKey {
625                text,
626                font_size,
627                runs: SmallVec::from(runs),
628                wrap_width,
629                force_width: None,
630                letter_spacing: None,
631            });
632
633            let mut current_frame = self.current_frame.write();
634            current_frame
635                .wrapped_lines
636                .insert(key.clone(), layout.clone());
637            current_frame.used_wrapped_lines.push(key.clone());
638            self.global_cache.insert_wrapped_line(key, layout.clone());
639
640            layout
641        }
642    }
643
644    pub fn layout_line<Text>(
645        &self,
646        text: Text,
647        font_size: Pixels,
648        runs: &[FontRun],
649        force_width: Option<Pixels>,
650    ) -> Arc<LineLayout>
651    where
652        Text: AsRef<str>,
653        SharedString: From<Text>,
654    {
655        self.layout_line_with_spacing(text, font_size, runs, force_width, None)
656    }
657
658    pub fn layout_line_with_spacing<Text>(
659        &self,
660        text: Text,
661        font_size: Pixels,
662        runs: &[FontRun],
663        force_width: Option<Pixels>,
664        letter_spacing: Option<Pixels>,
665    ) -> Arc<LineLayout>
666    where
667        Text: AsRef<str>,
668        SharedString: From<Text>,
669    {
670        let key = &CacheKeyRef {
671            text: text.as_ref(),
672            font_size,
673            runs,
674            wrap_width: None,
675            force_width,
676            letter_spacing,
677        } as &dyn AsCacheKeyRef;
678
679        let current_frame = self.current_frame.upgradable_read();
680        if let Some(layout) = current_frame.lines.get(key) {
681            return layout.clone();
682        }
683
684        let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
685        if let Some((key, layout)) = self.previous_frame.lock().lines.remove_entry(key) {
686            current_frame.lines.insert(key.clone(), layout.clone());
687            current_frame.used_lines.push(key);
688            return layout;
689        }
690
691        // Check global cross-window cache
692        if let Some(layout) = self.global_cache.get_line(key) {
693            let key = Arc::new(CacheKey {
694                text: SharedString::from(text),
695                font_size,
696                runs: SmallVec::from(runs),
697                wrap_width: None,
698                force_width,
699                letter_spacing,
700            });
701            current_frame.lines.insert(key.clone(), layout.clone());
702            current_frame.used_lines.push(key);
703            return layout;
704        }
705
706        let text = SharedString::from(text);
707        let mut layout = self
708            .platform_text_system
709            .layout_line(&text, font_size, runs);
710
711        if let Some(force_width) = force_width {
712            let mut glyph_pos = 0;
713            for run in layout.runs.iter_mut() {
714                for glyph in run.glyphs.iter_mut() {
715                    if (glyph.position.x - glyph_pos * force_width).abs() > px(1.) {
716                        glyph.position.x = glyph_pos * force_width;
717                    }
718                    glyph_pos += 1;
719                }
720            }
721        }
722
723        if let Some(spacing) = letter_spacing {
724            let mut glyph_index: usize = 0;
725            for run in layout.runs.iter_mut() {
726                for glyph in run.glyphs.iter_mut() {
727                    glyph.position.x = glyph.position.x + spacing * glyph_index as f32;
728                    glyph_index += 1;
729                }
730            }
731            let total_glyphs = glyph_index;
732            if total_glyphs > 1 {
733                layout.width = layout.width + spacing * (total_glyphs - 1) as f32;
734            }
735        }
736
737        let key = Arc::new(CacheKey {
738            text,
739            font_size,
740            runs: SmallVec::from(runs),
741            wrap_width: None,
742            force_width,
743            letter_spacing,
744        });
745        let layout = Arc::new(layout);
746        current_frame.lines.insert(key.clone(), layout.clone());
747        current_frame.used_lines.push(key.clone());
748        self.global_cache.insert_line(key, layout.clone());
749        layout
750    }
751}
752
753/// A run of text with a single font.
754#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
755pub struct FontRun {
756    pub(crate) len: usize,
757    pub(crate) font_id: FontId,
758}
759
760trait AsCacheKeyRef {
761    fn as_cache_key_ref(&self) -> CacheKeyRef<'_>;
762}
763
764#[derive(Clone, Debug, Eq)]
765struct CacheKey {
766    text: SharedString,
767    font_size: Pixels,
768    runs: SmallVec<[FontRun; 1]>,
769    wrap_width: Option<Pixels>,
770    force_width: Option<Pixels>,
771    letter_spacing: Option<Pixels>,
772}
773
774#[derive(Copy, Clone, PartialEq, Eq, Hash)]
775struct CacheKeyRef<'a> {
776    text: &'a str,
777    font_size: Pixels,
778    runs: &'a [FontRun],
779    wrap_width: Option<Pixels>,
780    force_width: Option<Pixels>,
781    letter_spacing: Option<Pixels>,
782}
783
784impl PartialEq for dyn AsCacheKeyRef + '_ {
785    fn eq(&self, other: &dyn AsCacheKeyRef) -> bool {
786        self.as_cache_key_ref() == other.as_cache_key_ref()
787    }
788}
789
790impl Eq for dyn AsCacheKeyRef + '_ {}
791
792impl Hash for dyn AsCacheKeyRef + '_ {
793    fn hash<H: Hasher>(&self, state: &mut H) {
794        self.as_cache_key_ref().hash(state)
795    }
796}
797
798impl AsCacheKeyRef for CacheKey {
799    fn as_cache_key_ref(&self) -> CacheKeyRef<'_> {
800        CacheKeyRef {
801            text: &self.text,
802            font_size: self.font_size,
803            runs: self.runs.as_slice(),
804            wrap_width: self.wrap_width,
805            force_width: self.force_width,
806            letter_spacing: self.letter_spacing,
807        }
808    }
809}
810
811impl PartialEq for CacheKey {
812    fn eq(&self, other: &Self) -> bool {
813        self.as_cache_key_ref().eq(&other.as_cache_key_ref())
814    }
815}
816
817impl Hash for CacheKey {
818    fn hash<H: Hasher>(&self, state: &mut H) {
819        self.as_cache_key_ref().hash(state);
820    }
821}
822
823impl<'a> Borrow<dyn AsCacheKeyRef + 'a> for Arc<CacheKey> {
824    fn borrow(&self) -> &(dyn AsCacheKeyRef + 'a) {
825        self.as_ref() as &dyn AsCacheKeyRef
826    }
827}
828
829impl AsCacheKeyRef for CacheKeyRef<'_> {
830    fn as_cache_key_ref(&self) -> CacheKeyRef<'_> {
831        *self
832    }
833}