Skip to main content

gpui_core/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, AtomicUsize, 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    /// Split this layout at a byte index, returning `(prefix, suffix)`.
132    ///
133    /// - `prefix` contains glyphs for bytes `[0, byte_index)` with original positions.
134    ///   Its width equals the x-advance up to the split point.
135    /// - `suffix` contains glyphs for bytes `[byte_index, len)` with positions
136    ///   shifted left so the first glyph starts at x=0, and byte indices rebased to 0.
137    /// - `font_size`, `ascent`, and `descent` are copied to both halves.
138    pub fn split_at(&self, byte_index: usize) -> (LineLayout, LineLayout) {
139        let x_offset = self.x_for_index(byte_index);
140
141        // Partition glyph runs. A single run may contribute glyphs to both halves.
142        let mut left_runs = Vec::new();
143        let mut right_runs = Vec::new();
144
145        for run in &self.runs {
146            let split_pos = run.glyphs.partition_point(|g| g.index < byte_index);
147
148            if split_pos > 0 {
149                left_runs.push(ShapedRun {
150                    font_id: run.font_id,
151                    glyphs: run.glyphs[..split_pos].to_vec(),
152                });
153            }
154
155            if split_pos < run.glyphs.len() {
156                let right_glyphs = run.glyphs[split_pos..]
157                    .iter()
158                    .map(|g| ShapedGlyph {
159                        id: g.id,
160                        position: point(g.position.x - x_offset, g.position.y),
161                        index: g.index - byte_index,
162                        is_emoji: g.is_emoji,
163                    })
164                    .collect();
165                right_runs.push(ShapedRun {
166                    font_id: run.font_id,
167                    glyphs: right_glyphs,
168                });
169            }
170        }
171
172        let left = LineLayout {
173            font_size: self.font_size,
174            width: x_offset,
175            ascent: self.ascent,
176            descent: self.descent,
177            runs: left_runs,
178            len: byte_index,
179        };
180
181        let right = LineLayout {
182            font_size: self.font_size,
183            width: self.width - x_offset,
184            ascent: self.ascent,
185            descent: self.descent,
186            runs: right_runs,
187            len: self.len - byte_index,
188        };
189
190        (left, right)
191    }
192
193    fn compute_wrap_boundaries(
194        &self,
195        text: &str,
196        wrap_width: Pixels,
197        max_lines: Option<usize>,
198    ) -> SmallVec<[WrapBoundary; 1]> {
199        let mut boundaries = SmallVec::new();
200        let mut first_non_whitespace_ix = None;
201        let mut last_candidate_ix = None;
202        let mut last_candidate_x = px(0.);
203        let mut last_boundary = WrapBoundary {
204            run_ix: 0,
205            glyph_ix: 0,
206        };
207        let mut last_boundary_x = px(0.);
208        let mut prev_ch = '\0';
209        let mut glyphs = self
210            .runs
211            .iter()
212            .enumerate()
213            .flat_map(move |(run_ix, run)| {
214                run.glyphs.iter().enumerate().map(move |(glyph_ix, glyph)| {
215                    let character = text[glyph.index..].chars().next().unwrap();
216                    (
217                        WrapBoundary { run_ix, glyph_ix },
218                        character,
219                        glyph.position.x,
220                    )
221                })
222            })
223            .peekable();
224
225        while let Some((boundary, ch, x)) = glyphs.next() {
226            if ch == '\n' {
227                continue;
228            }
229
230            // Here is very similar to `LineWrapper::wrap_line` to determine text wrapping,
231            // but there are some differences, so we have to duplicate the code here.
232            if LineWrapper::is_word_char(ch) {
233                if prev_ch == ' ' && ch != ' ' && first_non_whitespace_ix.is_some() {
234                    last_candidate_ix = Some(boundary);
235                    last_candidate_x = x;
236                }
237            } else {
238                if ch != ' ' && first_non_whitespace_ix.is_some() {
239                    last_candidate_ix = Some(boundary);
240                    last_candidate_x = x;
241                }
242            }
243
244            if ch != ' ' && first_non_whitespace_ix.is_none() {
245                first_non_whitespace_ix = Some(boundary);
246            }
247
248            let next_x = glyphs.peek().map_or(self.width, |(_, _, x)| *x);
249            let width = next_x - last_boundary_x;
250
251            if width > wrap_width && boundary > last_boundary {
252                // When used line_clamp, we should limit the number of lines.
253                if let Some(max_lines) = max_lines
254                    && boundaries.len() >= max_lines.saturating_sub(1)
255                {
256                    break;
257                }
258
259                if let Some(last_candidate_ix) = last_candidate_ix.take() {
260                    last_boundary = last_candidate_ix;
261                    last_boundary_x = last_candidate_x;
262                } else {
263                    last_boundary = boundary;
264                    last_boundary_x = x;
265                }
266                boundaries.push(last_boundary);
267            }
268            prev_ch = ch;
269        }
270
271        boundaries
272    }
273}
274
275/// A line of text that has been wrapped to fit a given width
276#[derive(Default, Debug)]
277pub struct WrappedLineLayout {
278    /// The line layout, pre-wrapping.
279    pub unwrapped_layout: Arc<LineLayout>,
280
281    /// The boundaries at which the line was wrapped
282    pub wrap_boundaries: SmallVec<[WrapBoundary; 1]>,
283
284    /// The width of the line, if it was wrapped
285    pub wrap_width: Option<Pixels>,
286}
287
288/// A boundary at which a line was wrapped
289#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
290pub struct WrapBoundary {
291    /// The index in the run just before the line was wrapped
292    pub run_ix: usize,
293    /// The index of the glyph just before the line was wrapped
294    pub glyph_ix: usize,
295}
296
297impl WrappedLineLayout {
298    /// The length of the underlying text, in utf8 bytes.
299    #[allow(clippy::len_without_is_empty)]
300    pub fn len(&self) -> usize {
301        self.unwrapped_layout.len
302    }
303
304    /// The width of this line, in pixels, whether or not it was wrapped.
305    pub fn width(&self) -> Pixels {
306        self.wrap_width
307            .unwrap_or(Pixels::MAX)
308            .min(self.unwrapped_layout.width)
309    }
310
311    /// The size of the whole wrapped text, for the given line_height.
312    /// can span multiple lines if there are multiple wrap boundaries.
313    pub fn size(&self, line_height: Pixels) -> Size<Pixels> {
314        Size {
315            width: self.width(),
316            height: line_height * (self.wrap_boundaries.len() + 1),
317        }
318    }
319
320    /// The ascent of a line in this layout
321    pub fn ascent(&self) -> Pixels {
322        self.unwrapped_layout.ascent
323    }
324
325    /// The descent of a line in this layout
326    pub fn descent(&self) -> Pixels {
327        self.unwrapped_layout.descent
328    }
329
330    /// The wrap boundaries in this layout
331    pub fn wrap_boundaries(&self) -> &[WrapBoundary] {
332        &self.wrap_boundaries
333    }
334
335    /// The font size of this layout
336    pub fn font_size(&self) -> Pixels {
337        self.unwrapped_layout.font_size
338    }
339
340    /// The runs in this layout, sans wrapping
341    pub fn runs(&self) -> &[ShapedRun] {
342        &self.unwrapped_layout.runs
343    }
344
345    /// The index corresponding to a given position in this layout for the given line height.
346    ///
347    /// See also [`Self::closest_index_for_position`].
348    pub fn index_for_position(
349        &self,
350        position: Point<Pixels>,
351        line_height: Pixels,
352    ) -> Result<usize, usize> {
353        self._index_for_position(position, line_height, false)
354    }
355
356    /// The closest index to a given position in this layout for the given line height.
357    ///
358    /// Closest means the character boundary closest to the given position.
359    ///
360    /// See also [`LineLayout::closest_index_for_x`].
361    pub fn closest_index_for_position(
362        &self,
363        position: Point<Pixels>,
364        line_height: Pixels,
365    ) -> Result<usize, usize> {
366        self._index_for_position(position, line_height, true)
367    }
368
369    fn _index_for_position(
370        &self,
371        mut position: Point<Pixels>,
372        line_height: Pixels,
373        closest: bool,
374    ) -> Result<usize, usize> {
375        let wrapped_line_ix = (position.y / line_height) as usize;
376
377        let wrapped_line_start_index;
378        let wrapped_line_start_x;
379        if wrapped_line_ix > 0 {
380            let Some(line_start_boundary) = self.wrap_boundaries.get(wrapped_line_ix - 1) else {
381                return Err(0);
382            };
383            let run = &self.unwrapped_layout.runs[line_start_boundary.run_ix];
384            let glyph = &run.glyphs[line_start_boundary.glyph_ix];
385            wrapped_line_start_index = glyph.index;
386            wrapped_line_start_x = glyph.position.x;
387        } else {
388            wrapped_line_start_index = 0;
389            wrapped_line_start_x = Pixels::ZERO;
390        };
391
392        let wrapped_line_end_index;
393        let wrapped_line_end_x;
394        if wrapped_line_ix < self.wrap_boundaries.len() {
395            let next_wrap_boundary_ix = wrapped_line_ix;
396            let next_wrap_boundary = self.wrap_boundaries[next_wrap_boundary_ix];
397            let run = &self.unwrapped_layout.runs[next_wrap_boundary.run_ix];
398            let glyph = &run.glyphs[next_wrap_boundary.glyph_ix];
399            wrapped_line_end_index = glyph.index;
400            wrapped_line_end_x = glyph.position.x;
401        } else {
402            wrapped_line_end_index = self.unwrapped_layout.len;
403            wrapped_line_end_x = self.unwrapped_layout.width;
404        };
405
406        let mut position_in_unwrapped_line = position;
407        position_in_unwrapped_line.x += wrapped_line_start_x;
408        if position_in_unwrapped_line.x < wrapped_line_start_x {
409            Err(wrapped_line_start_index)
410        } else if position_in_unwrapped_line.x >= wrapped_line_end_x {
411            Err(wrapped_line_end_index)
412        } else {
413            if closest {
414                Ok(self
415                    .unwrapped_layout
416                    .closest_index_for_x(position_in_unwrapped_line.x))
417            } else {
418                // The shaper can place a trailing zero-width wrap boundary glyph slightly past
419                // the line's width, so the row can extend past where `index_for_x` has glyphs.
420                self.unwrapped_layout
421                    .index_for_x(position_in_unwrapped_line.x)
422                    .ok_or(wrapped_line_end_index)
423            }
424        }
425    }
426
427    /// Returns the pixel position for the given byte index.
428    pub fn position_for_index(&self, index: usize, line_height: Pixels) -> Option<Point<Pixels>> {
429        let mut line_start_ix = 0;
430        let mut line_end_indices = self
431            .wrap_boundaries
432            .iter()
433            .map(|wrap_boundary| {
434                let run = &self.unwrapped_layout.runs[wrap_boundary.run_ix];
435                let glyph = &run.glyphs[wrap_boundary.glyph_ix];
436                glyph.index
437            })
438            .chain([self.len()])
439            .enumerate();
440        for (ix, line_end_ix) in line_end_indices {
441            let line_y = ix as f32 * line_height;
442            if index < line_start_ix {
443                break;
444            } else if index > line_end_ix {
445                line_start_ix = line_end_ix;
446                continue;
447            } else {
448                let line_start_x = self.unwrapped_layout.x_for_index(line_start_ix);
449                let x = self.unwrapped_layout.x_for_index(index) - line_start_x;
450                return Some(point(x, line_y));
451            }
452        }
453
454        None
455    }
456}
457
458struct GlobalCacheEntry<T> {
459    value: T,
460    last_access: AtomicU64,
461}
462
463pub(crate) struct GlobalLineLayoutCache {
464    max_entries: usize,
465    low_watermark: usize,
466    lines: RwLock<FxHashMap<Arc<CacheKey>, GlobalCacheEntry<Arc<LineLayout>>>>,
467    wrapped_lines: RwLock<FxHashMap<Arc<CacheKey>, GlobalCacheEntry<Arc<WrappedLineLayout>>>>,
468    access_counter: AtomicU64,
469}
470
471impl GlobalLineLayoutCache {
472    pub fn new(max_entries: usize, low_watermark: usize) -> Self {
473        let max_entries = max_entries.max(1);
474        // Ensure low_watermark is always less than max_entries to avoid
475        // degenerate eviction behaviour.
476        let low_watermark = low_watermark.min(max_entries.saturating_sub(1));
477        Self {
478            max_entries,
479            low_watermark,
480            lines: RwLock::new(FxHashMap::default()),
481            wrapped_lines: RwLock::new(FxHashMap::default()),
482            access_counter: AtomicU64::new(0),
483        }
484    }
485
486    #[cfg(test)]
487    pub(crate) fn budget_for_test(&self) -> (usize, usize) {
488        (self.max_entries, self.low_watermark)
489    }
490
491    /// Drop every cached line and wrapped-line layout.
492    ///
493    /// Useful when an application enters a low-memory state (for example, the
494    /// last visible window is hidden) and wants to release the heap held by
495    /// shaped text. The cache will repopulate naturally as text is laid out
496    /// again.
497    pub fn clear(&self) {
498        self.lines.write().clear();
499        self.wrapped_lines.write().clear();
500    }
501
502    /// Returns `(line_entries, wrapped_line_entries)` for diagnostics.
503    pub fn entry_counts(&self) -> (usize, usize) {
504        (self.lines.read().len(), self.wrapped_lines.read().len())
505    }
506
507    fn get_line(&self, key: &dyn AsCacheKeyRef) -> Option<Arc<LineLayout>> {
508        let lines = self.lines.read();
509        if let Some(entry) = lines.get(key) {
510            // Relaxed is fine — this is just for approximate LRU ordering
511            entry.last_access.store(
512                self.access_counter.fetch_add(1, Ordering::Relaxed),
513                Ordering::Relaxed,
514            );
515            Some(entry.value.clone())
516        } else {
517            None
518        }
519    }
520
521    fn insert_line(&self, key: Arc<CacheKey>, layout: Arc<LineLayout>) {
522        let mut lines = self.lines.write();
523        if lines.len() >= self.max_entries {
524            Self::evict_to_watermark(&mut lines, self.low_watermark);
525        }
526        lines.insert(
527            key,
528            GlobalCacheEntry {
529                value: layout,
530                last_access: AtomicU64::new(self.access_counter.fetch_add(1, Ordering::Relaxed)),
531            },
532        );
533    }
534
535    fn get_wrapped_line(&self, key: &dyn AsCacheKeyRef) -> Option<Arc<WrappedLineLayout>> {
536        let wrapped = self.wrapped_lines.read();
537        if let Some(entry) = wrapped.get(key) {
538            entry.last_access.store(
539                self.access_counter.fetch_add(1, Ordering::Relaxed),
540                Ordering::Relaxed,
541            );
542            Some(entry.value.clone())
543        } else {
544            None
545        }
546    }
547
548    fn insert_wrapped_line(&self, key: Arc<CacheKey>, layout: Arc<WrappedLineLayout>) {
549        let mut wrapped = self.wrapped_lines.write();
550        if wrapped.len() >= self.max_entries {
551            Self::evict_to_watermark(&mut wrapped, self.low_watermark);
552        }
553        wrapped.insert(
554            key,
555            GlobalCacheEntry {
556                value: layout,
557                last_access: AtomicU64::new(self.access_counter.fetch_add(1, Ordering::Relaxed)),
558            },
559        );
560    }
561
562    /// Evict oldest entries until the map size is at or below `target_size`.
563    fn evict_to_watermark<V>(
564        map: &mut FxHashMap<Arc<CacheKey>, GlobalCacheEntry<V>>,
565        target_size: usize,
566    ) {
567        if map.len() <= target_size {
568            return;
569        }
570        let to_remove = map.len() - target_size;
571        let mut entries: Vec<_> = map
572            .iter()
573            .map(|(k, entry)| (k.clone(), entry.last_access.load(Ordering::Relaxed)))
574            .collect();
575        entries.sort_by_key(|(_, access)| *access);
576        for (key, _) in entries.into_iter().take(to_remove) {
577            map.remove(&key);
578        }
579    }
580}
581
582pub(crate) struct LineLayoutCache {
583    previous_frame: Mutex<FrameCache>,
584    current_frame: RwLock<FrameCache>,
585    platform_text_system: Arc<dyn PlatformTextSystem>,
586    global_cache: Arc<GlobalLineLayoutCache>,
587    /// Advances when [`super::TextSystem::add_fonts`] successfully changes the font database.
588    font_generation: Arc<AtomicUsize>,
589    /// Records the generation represented by both frame caches and the global cache.
590    cached_font_generation: AtomicUsize,
591}
592
593#[derive(Default)]
594struct FrameCache {
595    lines: FxHashMap<Arc<CacheKey>, Arc<LineLayout>>,
596    wrapped_lines: FxHashMap<Arc<CacheKey>, Arc<WrappedLineLayout>>,
597    used_lines: Vec<Arc<CacheKey>>,
598    used_wrapped_lines: Vec<Arc<CacheKey>>,
599}
600
601#[derive(Clone, Default)]
602pub(crate) struct LineLayoutIndex {
603    font_generation: usize,
604    lines_index: usize,
605    wrapped_lines_index: usize,
606}
607
608impl LineLayoutCache {
609    pub fn new(
610        platform_text_system: Arc<dyn PlatformTextSystem>,
611        global_cache: Arc<GlobalLineLayoutCache>,
612        font_generation: Arc<AtomicUsize>,
613    ) -> Self {
614        let cached_font_generation = font_generation.load(Ordering::Acquire);
615        Self {
616            previous_frame: Mutex::default(),
617            current_frame: RwLock::default(),
618            platform_text_system,
619            global_cache,
620            font_generation,
621            cached_font_generation: AtomicUsize::new(cached_font_generation),
622        }
623    }
624
625    pub fn layout_index(&self) -> LineLayoutIndex {
626        let font_generation = self.clear_if_font_generation_changed();
627        let frame = self.current_frame.read();
628        LineLayoutIndex {
629            font_generation,
630            lines_index: frame.used_lines.len(),
631            wrapped_lines_index: frame.used_wrapped_lines.len(),
632        }
633    }
634
635    pub fn reuse_layouts(&self, range: Range<LineLayoutIndex>) {
636        let font_generation = self.clear_if_font_generation_changed();
637        if range.start.font_generation != font_generation
638            || range.end.font_generation != font_generation
639        {
640            return;
641        }
642        let mut current_frame = &mut *self.current_frame.write();
643        let mut previous_frame = &mut *self.previous_frame.lock();
644
645        for key in &previous_frame.used_lines[range.start.lines_index..range.end.lines_index] {
646            if let Some((key, line)) = previous_frame.lines.remove_entry(key) {
647                current_frame.lines.insert(key, line);
648            }
649            current_frame.used_lines.push(key.clone());
650        }
651
652        for key in &previous_frame.used_wrapped_lines
653            [range.start.wrapped_lines_index..range.end.wrapped_lines_index]
654        {
655            if let Some((key, line)) = previous_frame.wrapped_lines.remove_entry(key) {
656                current_frame.wrapped_lines.insert(key, line);
657            }
658            current_frame.used_wrapped_lines.push(key.clone());
659        }
660    }
661
662    pub fn truncate_layouts(&self, index: LineLayoutIndex) {
663        let font_generation = self.clear_if_font_generation_changed();
664        if index.font_generation != font_generation {
665            return;
666        }
667        let mut current_frame = &mut *self.current_frame.write();
668        current_frame.used_lines.truncate(index.lines_index);
669        current_frame
670            .used_wrapped_lines
671            .truncate(index.wrapped_lines_index);
672    }
673
674    pub fn finish_frame(&self) {
675        let _font_generation = self.clear_if_font_generation_changed();
676        let mut curr_frame = self.current_frame.write();
677        let mut prev_frame = self.previous_frame.lock();
678        std::mem::swap(&mut *prev_frame, &mut *curr_frame);
679        curr_frame.lines.clear();
680        curr_frame.wrapped_lines.clear();
681        curr_frame.used_lines.clear();
682        curr_frame.used_wrapped_lines.clear();
683    }
684
685    pub fn layout_wrapped_line<Text>(
686        &self,
687        text: Text,
688        font_size: Pixels,
689        runs: &[FontRun],
690        wrap_width: Option<Pixels>,
691        max_lines: Option<usize>,
692    ) -> Arc<WrappedLineLayout>
693    where
694        Text: AsRef<str>,
695        SharedString: From<Text>,
696    {
697        let _font_generation = self.clear_if_font_generation_changed();
698        let key = &CacheKeyRef {
699            text: text.as_ref(),
700            font_size,
701            runs,
702            wrap_width,
703            force_width: None,
704            letter_spacing: None,
705        } as &dyn AsCacheKeyRef;
706
707        let current_frame = self.current_frame.upgradable_read();
708        if let Some(layout) = current_frame.wrapped_lines.get(key) {
709            return layout.clone();
710        }
711
712        let previous_frame_entry = self.previous_frame.lock().wrapped_lines.remove_entry(key);
713        if let Some((key, layout)) = previous_frame_entry {
714            let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
715            current_frame
716                .wrapped_lines
717                .insert(key.clone(), layout.clone());
718            current_frame.used_wrapped_lines.push(key);
719            layout
720        } else {
721            // Check global cross-window cache
722            if let Some(layout) = self.global_cache.get_wrapped_line(key) {
723                let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
724                let key = Arc::new(CacheKey {
725                    text: SharedString::from(text),
726                    font_size,
727                    runs: SmallVec::from(runs),
728                    wrap_width,
729                    force_width: None,
730                    letter_spacing: None,
731                });
732                current_frame
733                    .wrapped_lines
734                    .insert(key.clone(), layout.clone());
735                current_frame.used_wrapped_lines.push(key);
736                return layout;
737            }
738
739            drop(current_frame);
740            let text = SharedString::from(text);
741            let unwrapped_layout = self.layout_line::<&SharedString>(&text, font_size, runs, None);
742            let wrap_boundaries = if let Some(wrap_width) = wrap_width {
743                unwrapped_layout.compute_wrap_boundaries(text.as_ref(), wrap_width, max_lines)
744            } else {
745                SmallVec::new()
746            };
747            let layout = Arc::new(WrappedLineLayout {
748                unwrapped_layout,
749                wrap_boundaries,
750                wrap_width,
751            });
752            let key = Arc::new(CacheKey {
753                text,
754                font_size,
755                runs: SmallVec::from(runs),
756                wrap_width,
757                force_width: None,
758                letter_spacing: None,
759            });
760
761            let mut current_frame = self.current_frame.write();
762            current_frame
763                .wrapped_lines
764                .insert(key.clone(), layout.clone());
765            current_frame.used_wrapped_lines.push(key.clone());
766            self.global_cache.insert_wrapped_line(key, layout.clone());
767
768            layout
769        }
770    }
771
772    pub fn layout_line<Text>(
773        &self,
774        text: Text,
775        font_size: Pixels,
776        runs: &[FontRun],
777        force_width: Option<Pixels>,
778    ) -> Arc<LineLayout>
779    where
780        Text: AsRef<str>,
781        SharedString: From<Text>,
782    {
783        self.layout_line_with_spacing(text, font_size, runs, force_width, None)
784    }
785
786    pub fn layout_line_with_spacing<Text>(
787        &self,
788        text: Text,
789        font_size: Pixels,
790        runs: &[FontRun],
791        force_width: Option<Pixels>,
792        letter_spacing: Option<Pixels>,
793    ) -> Arc<LineLayout>
794    where
795        Text: AsRef<str>,
796        SharedString: From<Text>,
797    {
798        let _font_generation = self.clear_if_font_generation_changed();
799        let key = &CacheKeyRef {
800            text: text.as_ref(),
801            font_size,
802            runs,
803            wrap_width: None,
804            force_width,
805            letter_spacing,
806        } as &dyn AsCacheKeyRef;
807
808        let current_frame = self.current_frame.upgradable_read();
809        if let Some(layout) = current_frame.lines.get(key) {
810            return layout.clone();
811        }
812
813        let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
814        if let Some((key, layout)) = self.previous_frame.lock().lines.remove_entry(key) {
815            current_frame.lines.insert(key.clone(), layout.clone());
816            current_frame.used_lines.push(key);
817            return layout;
818        }
819
820        // Check global cross-window cache
821        if let Some(layout) = self.global_cache.get_line(key) {
822            let key = Arc::new(CacheKey {
823                text: SharedString::from(text),
824                font_size,
825                runs: SmallVec::from(runs),
826                wrap_width: None,
827                force_width,
828                letter_spacing,
829            });
830            current_frame.lines.insert(key.clone(), layout.clone());
831            current_frame.used_lines.push(key);
832            return layout;
833        }
834
835        let text = SharedString::from(text);
836        let mut layout = self
837            .platform_text_system
838            .layout_line(&text, font_size, runs);
839
840        if let Some(force_width) = force_width {
841            apply_force_width_to_layout(&mut layout, force_width);
842        }
843
844        if let Some(spacing) = letter_spacing {
845            apply_letter_spacing_to_layout(&mut layout, spacing, force_width);
846        }
847
848        let key = Arc::new(CacheKey {
849            text,
850            font_size,
851            runs: SmallVec::from(runs),
852            wrap_width: None,
853            force_width,
854            letter_spacing,
855        });
856        let layout = Arc::new(layout);
857        current_frame.lines.insert(key.clone(), layout.clone());
858        current_frame.used_lines.push(key.clone());
859        self.global_cache.insert_line(key, layout.clone());
860        layout
861    }
862
863    fn clear_if_font_generation_changed(&self) -> usize {
864        let font_generation = self.font_generation.load(Ordering::Acquire);
865        if self.cached_font_generation.load(Ordering::Acquire) == font_generation {
866            return font_generation;
867        }
868
869        let mut current_frame = self.current_frame.write();
870        if self.cached_font_generation.load(Ordering::Acquire) == font_generation {
871            return font_generation;
872        }
873
874        *current_frame = FrameCache::default();
875        *self.previous_frame.lock() = FrameCache::default();
876        // This tree also keeps shaped lines in a cross-window cache. Drop those
877        // entries with the per-window frames so a newly installed font is shaped
878        // again instead of being served from the previous font database.
879        self.global_cache.clear();
880        self.cached_font_generation
881            .store(font_generation, Ordering::Release);
882        font_generation
883    }
884}
885
886// Combining marks are shaped at the same x position as their base character.
887// Forced monospace layout must keep them anchored instead of advancing a cell.
888fn apply_force_width_to_layout(layout: &mut LineLayout, force_width: Pixels) {
889    let mut glyph_pos: usize = 0;
890    let mut last_base_shaped_x = px(f32::NEG_INFINITY);
891    let mut last_base_actual_x = px(0.);
892
893    for run in layout.runs.iter_mut() {
894        for glyph in run.glyphs.iter_mut() {
895            let shaped_x = glyph.position.x;
896
897            if shaped_x > last_base_shaped_x + force_width * 0.5 {
898                let forced_x = glyph_pos * force_width;
899                if (shaped_x - forced_x).abs() > px(1.) {
900                    glyph.position.x = forced_x;
901                }
902                last_base_shaped_x = shaped_x;
903                last_base_actual_x = glyph.position.x;
904                glyph_pos += 1;
905            } else {
906                glyph.position.x = last_base_actual_x + (shaped_x - last_base_shaped_x);
907            }
908        }
909    }
910}
911
912fn apply_letter_spacing_to_layout(
913    layout: &mut LineLayout,
914    spacing: Pixels,
915    force_width: Option<Pixels>,
916) {
917    let glyph_count = if let Some(force_width) = force_width {
918        let mut base_glyph_count: usize = 0;
919        let mut last_base_x = px(f32::NEG_INFINITY);
920        let mut last_base_spacing = px(0.);
921
922        for run in layout.runs.iter_mut() {
923            for glyph in run.glyphs.iter_mut() {
924                let x = glyph.position.x;
925
926                if x > last_base_x + force_width * 0.5 {
927                    last_base_spacing = spacing * base_glyph_count as f32;
928                    glyph.position.x = x + last_base_spacing;
929                    last_base_x = x;
930                    base_glyph_count += 1;
931                } else {
932                    glyph.position.x = x + last_base_spacing;
933                }
934            }
935        }
936
937        base_glyph_count
938    } else {
939        let mut glyph_count: usize = 0;
940        for run in layout.runs.iter_mut() {
941            for glyph in run.glyphs.iter_mut() {
942                glyph.position.x = glyph.position.x + spacing * glyph_count as f32;
943                glyph_count += 1;
944            }
945        }
946
947        glyph_count
948    };
949
950    if glyph_count > 1 {
951        layout.width = layout.width + spacing * (glyph_count - 1) as f32;
952    }
953}
954
955/// A run of text with a single font.
956#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
957#[allow(missing_docs)]
958pub struct FontRun {
959    pub len: usize,
960    pub font_id: FontId,
961}
962
963trait AsCacheKeyRef {
964    fn as_cache_key_ref(&self) -> CacheKeyRef<'_>;
965}
966
967#[derive(Clone, Debug, Eq)]
968struct CacheKey {
969    text: SharedString,
970    font_size: Pixels,
971    runs: SmallVec<[FontRun; 1]>,
972    wrap_width: Option<Pixels>,
973    force_width: Option<Pixels>,
974    letter_spacing: Option<Pixels>,
975}
976
977#[derive(Copy, Clone, PartialEq, Eq, Hash)]
978struct CacheKeyRef<'a> {
979    text: &'a str,
980    font_size: Pixels,
981    runs: &'a [FontRun],
982    wrap_width: Option<Pixels>,
983    force_width: Option<Pixels>,
984    letter_spacing: Option<Pixels>,
985}
986
987impl PartialEq for dyn AsCacheKeyRef + '_ {
988    fn eq(&self, other: &dyn AsCacheKeyRef) -> bool {
989        self.as_cache_key_ref() == other.as_cache_key_ref()
990    }
991}
992
993impl Eq for dyn AsCacheKeyRef + '_ {}
994
995impl Hash for dyn AsCacheKeyRef + '_ {
996    fn hash<H: Hasher>(&self, state: &mut H) {
997        self.as_cache_key_ref().hash(state)
998    }
999}
1000
1001impl AsCacheKeyRef for CacheKey {
1002    fn as_cache_key_ref(&self) -> CacheKeyRef<'_> {
1003        CacheKeyRef {
1004            text: &self.text,
1005            font_size: self.font_size,
1006            runs: self.runs.as_slice(),
1007            wrap_width: self.wrap_width,
1008            force_width: self.force_width,
1009            letter_spacing: self.letter_spacing,
1010        }
1011    }
1012}
1013
1014impl PartialEq for CacheKey {
1015    fn eq(&self, other: &Self) -> bool {
1016        self.as_cache_key_ref().eq(&other.as_cache_key_ref())
1017    }
1018}
1019
1020impl Hash for CacheKey {
1021    fn hash<H: Hasher>(&self, state: &mut H) {
1022        self.as_cache_key_ref().hash(state);
1023    }
1024}
1025
1026impl<'a> Borrow<dyn AsCacheKeyRef + 'a> for Arc<CacheKey> {
1027    fn borrow(&self) -> &(dyn AsCacheKeyRef + 'a) {
1028        self.as_ref() as &dyn AsCacheKeyRef
1029    }
1030}
1031
1032impl AsCacheKeyRef for CacheKeyRef<'_> {
1033    fn as_cache_key_ref(&self) -> CacheKeyRef<'_> {
1034        *self
1035    }
1036}
1037
1038#[cfg(test)]
1039mod tests {
1040    use super::*;
1041    use crate::GlyphId;
1042
1043    fn glyph_at(x: f32, index: usize) -> ShapedGlyph {
1044        ShapedGlyph {
1045            id: GlyphId(0),
1046            position: point(px(x), px(0.)),
1047            index,
1048            is_emoji: false,
1049        }
1050    }
1051
1052    fn make_layout(glyphs: Vec<ShapedGlyph>) -> LineLayout {
1053        LineLayout {
1054            font_size: px(16.),
1055            width: px(100.),
1056            ascent: px(12.),
1057            descent: px(4.),
1058            runs: vec![ShapedRun {
1059                font_id: FontId(0),
1060                glyphs,
1061            }],
1062            len: 0,
1063        }
1064    }
1065
1066    fn glyph_x_positions(layout: &LineLayout) -> Vec<f32> {
1067        layout.runs[0]
1068            .glyphs
1069            .iter()
1070            .map(|glyph| f32::from(glyph.position.x))
1071            .collect()
1072    }
1073
1074    fn make_global_cache_key(text: &str) -> Arc<CacheKey> {
1075        Arc::new(CacheKey {
1076            text: SharedString::from(text.to_owned()),
1077            font_size: px(16.),
1078            runs: SmallVec::new(),
1079            wrap_width: None,
1080            force_width: None,
1081            letter_spacing: None,
1082        })
1083    }
1084
1085    #[test]
1086    fn test_global_layout_cache_clear_drops_all_entries() {
1087        let cache = GlobalLineLayoutCache::new(64, 32);
1088        let layout = Arc::new(make_layout(vec![glyph_at(0., 0)]));
1089        cache.insert_line(make_global_cache_key("hello"), layout.clone());
1090        cache.insert_line(make_global_cache_key("world"), layout);
1091
1092        assert_eq!(cache.entry_counts(), (2, 0));
1093
1094        cache.clear();
1095
1096        assert_eq!(cache.entry_counts(), (0, 0));
1097    }
1098
1099    #[test]
1100    fn test_split_at_partitions_glyphs_and_rebases_suffix() {
1101        let layout = LineLayout {
1102            font_size: px(16.),
1103            width: px(30.),
1104            ascent: px(12.),
1105            descent: px(4.),
1106            runs: vec![ShapedRun {
1107                font_id: FontId(0),
1108                glyphs: vec![glyph_at(0., 0), glyph_at(10., 1), glyph_at(20., 2)],
1109            }],
1110            len: 3,
1111        };
1112
1113        let (left, right) = layout.split_at(1);
1114
1115        assert_eq!(left.len, 1);
1116        assert_eq!(left.width, px(10.));
1117        assert_eq!(left.font_size, layout.font_size);
1118        assert_eq!(left.ascent, layout.ascent);
1119        assert_eq!(left.descent, layout.descent);
1120        assert_eq!(glyph_x_positions(&left), vec![0.]);
1121        assert_eq!(left.runs[0].glyphs[0].index, 0);
1122
1123        assert_eq!(right.len, 2);
1124        assert_eq!(right.width, px(20.));
1125        assert_eq!(glyph_x_positions(&right), vec![0., 10.]);
1126        assert_eq!(
1127            right.runs[0]
1128                .glyphs
1129                .iter()
1130                .map(|glyph| glyph.index)
1131                .collect::<Vec<_>>(),
1132            vec![0, 1]
1133        );
1134    }
1135
1136    #[test]
1137    fn test_split_at_mid_run_and_empty_sides() {
1138        let layout = LineLayout {
1139            font_size: px(16.),
1140            width: px(20.),
1141            ascent: px(12.),
1142            descent: px(4.),
1143            runs: vec![
1144                ShapedRun {
1145                    font_id: FontId(0),
1146                    glyphs: vec![glyph_at(0., 0), glyph_at(8., 1)],
1147                },
1148                ShapedRun {
1149                    font_id: FontId(1),
1150                    glyphs: vec![glyph_at(16., 2)],
1151                },
1152            ],
1153            len: 3,
1154        };
1155
1156        let (left, right) = layout.split_at(2);
1157        assert_eq!(left.runs.len(), 1);
1158        assert_eq!(left.runs[0].glyphs.len(), 2);
1159        assert_eq!(right.runs.len(), 1);
1160        assert_eq!(right.runs[0].font_id, FontId(1));
1161        assert_eq!(glyph_x_positions(&right), vec![0.]);
1162
1163        let (empty_left, all_right) = layout.split_at(0);
1164        assert!(empty_left.runs.is_empty());
1165        assert_eq!(empty_left.width, px(0.));
1166        assert_eq!(all_right.len, 3);
1167        assert_eq!(all_right.runs.len(), 2);
1168
1169        let (all_left, empty_right) = layout.split_at(3);
1170        assert_eq!(all_left.runs.len(), 2);
1171        assert!(empty_right.runs.is_empty());
1172        assert_eq!(empty_right.width, px(0.));
1173    }
1174
1175    #[test]
1176    fn test_force_width_latin_unchanged() {
1177        let cell_width = px(8.);
1178        let mut layout = make_layout(vec![glyph_at(0., 0), glyph_at(8., 1), glyph_at(16., 2)]);
1179
1180        apply_force_width_to_layout(&mut layout, cell_width);
1181
1182        assert_eq!(glyph_x_positions(&layout), vec![0., 8., 16.]);
1183    }
1184
1185    #[test]
1186    fn test_force_width_combining_marks_not_advanced() {
1187        let cell_width = px(8.);
1188        let mut layout = make_layout(vec![glyph_at(0., 0), glyph_at(0., 3)]);
1189
1190        apply_force_width_to_layout(&mut layout, cell_width);
1191
1192        assert_eq!(glyph_x_positions(&layout), vec![0., 0.]);
1193    }
1194
1195    #[test]
1196    fn test_force_width_base_after_combining_mark() {
1197        let cell_width = px(8.);
1198        let mut layout = make_layout(vec![glyph_at(0., 0), glyph_at(0., 3), glyph_at(8., 6)]);
1199
1200        apply_force_width_to_layout(&mut layout, cell_width);
1201
1202        assert_eq!(glyph_x_positions(&layout), vec![0., 0., 8.]);
1203    }
1204
1205    #[test]
1206    fn test_force_width_multiple_combining_marks() {
1207        let cell_width = px(8.);
1208        let mut layout = make_layout(vec![
1209            glyph_at(0., 0),
1210            glyph_at(0., 3),
1211            glyph_at(0., 6),
1212            glyph_at(8., 9),
1213        ]);
1214
1215        apply_force_width_to_layout(&mut layout, cell_width);
1216
1217        assert_eq!(glyph_x_positions(&layout), vec![0., 0., 0., 8.]);
1218    }
1219
1220    #[test]
1221    fn test_force_width_corrects_drifted_base_positions() {
1222        let cell_width = px(8.);
1223        let mut layout = make_layout(vec![glyph_at(0.5, 0), glyph_at(10.2, 1), glyph_at(19.8, 2)]);
1224
1225        apply_force_width_to_layout(&mut layout, cell_width);
1226
1227        assert_eq!(glyph_x_positions(&layout), vec![0.5, 8., 16.]);
1228    }
1229
1230    #[test]
1231    fn test_force_width_combining_mark_after_within_tolerance_base() {
1232        let cell_width = px(8.);
1233        let mut layout = make_layout(vec![glyph_at(0.5, 0), glyph_at(0.5, 3)]);
1234
1235        apply_force_width_to_layout(&mut layout, cell_width);
1236
1237        assert_eq!(glyph_x_positions(&layout), vec![0.5, 0.5]);
1238    }
1239
1240    #[test]
1241    fn test_force_width_with_letter_spacing_keeps_combining_marks_anchored() {
1242        let cell_width = px(8.);
1243        let mut layout = make_layout(vec![glyph_at(0., 0), glyph_at(0., 3), glyph_at(8., 6)]);
1244
1245        apply_force_width_to_layout(&mut layout, cell_width);
1246        apply_letter_spacing_to_layout(&mut layout, px(1.), Some(cell_width));
1247
1248        assert_eq!(glyph_x_positions(&layout), vec![0., 0., 9.]);
1249        assert_eq!(layout.width, px(101.));
1250    }
1251}