Skip to main content

gpui/text_system/
line_layout.rs

1use crate::{
2    Bounds, FontId, GlyphId, InlineBidiScope, InlineBoxRequest, InlineLayoutRequest,
3    InlineTextMetrics, InlineTextStyle, InlineVisualLine, Pixels, PlatformTextSystem, Point,
4    SharedString, Size, StrikethroughStyle, TextLayoutOptions, TextLayoutRequest, TextRun,
5    UnderlineStyle, VerticalAlign,
6};
7use collections::FxHashMap;
8use palette::Hsla;
9use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
10use smallvec::SmallVec;
11use std::{
12    borrow::Borrow,
13    hash::{Hash, Hasher},
14    ops::Range,
15    sync::{
16        Arc,
17        atomic::{AtomicU64, Ordering},
18    },
19};
20
21/// The shaped and laid-out form of a string of text, ready to measure and paint.
22///
23/// `LineLayout` is what the text backend returns for one piece of text. Most
24/// code gets it indirectly through [`ShapedLine`](crate::ShapedLine) or
25/// [`ShapedText`](crate::ShapedText), and reads it through the methods on
26/// [`ShapedTextLayout`]. Use the public fields when you need the raw geometry,
27/// such as the width of the widest visual line.
28#[derive(Clone, Debug)]
29pub struct LineLayout {
30    /// The base font size the text was shaped at, in logical pixels.
31    pub font_size: Pixels,
32    /// The width of the widest visual line, in logical pixels.
33    pub width: Pixels,
34    /// The distance from the baseline up to the top of the tallest glyphs.
35    pub ascent: Pixels,
36    /// The distance from the baseline down to the bottom of the lowest glyphs.
37    pub descent: Pixels,
38    /// The visual lines in top-to-bottom order. Without soft wraps or hard breaks, there is one line.
39    pub visual_lines: SmallVec<[VisualLine; 1]>,
40    /// The runs of glyphs that share one style, referenced by each visual line.
41    pub paint_fragments: Vec<PaintFragment>,
42    /// The length of the source text in UTF-8 bytes.
43    pub len: usize,
44    /// The backend's interaction model, used for caret movement and hit testing.
45    pub platform_layout: Arc<dyn PlatformTextLayout>,
46}
47
48/// An atomic box from an inline layout, with the position the layout gave it.
49#[derive(Clone, Copy, Debug, Default, PartialEq)]
50pub struct PositionedInlineBox {
51    /// The identifier from the matching [`InlineBoxRequest::id`].
52    pub id: u64,
53    /// The index of the visual line that contains this box.
54    pub line_index: usize,
55    /// The bounds of the box, relative to the complete inline layout.
56    pub bounds: Bounds<Pixels>,
57}
58
59/// The area that a text range covers on one visual line.
60#[derive(Clone, Copy, Debug, Default, PartialEq)]
61pub struct InlineRangeGeometry {
62    /// The bounds of the range on this line, relative to the complete layout.
63    pub bounds: Bounds<Pixels>,
64    /// The index of the visual line that contains `bounds`.
65    pub visual_line_index: usize,
66}
67
68/// Text and atomic boxes laid out together, following inline paragraph flow.
69///
70/// Use [`WindowTextSystem::layout_inline`](crate::WindowTextSystem::layout_inline)
71/// to build one. Paint it with
72/// [`InlineLayout::paint_background`] and [`InlineLayout::paint`]. Place each box from
73/// [`InlineLayout::boxes`] in the surrounding element yourself; GPUI paints the text but does
74/// not render the box contents.
75#[derive(Clone, Debug)]
76pub struct InlineLayout {
77    /// The shaped text, which the ordinary paint pipeline draws.
78    pub layout: Arc<LineLayout>,
79    /// The position and height of each visual line.
80    pub lines: Vec<InlineVisualLine>,
81    /// The position of each atomic box.
82    pub boxes: Vec<PositionedInlineBox>,
83    /// The horizontal offset that aligns every visual line in the layout.
84    pub alignment_offset: Pixels,
85    /// The natural size of the complete layout.
86    pub size: Size<Pixels>,
87}
88
89fn base_inline_line_bounds(metrics: InlineTextMetrics, line_height: Pixels) -> (Pixels, Pixels) {
90    let half_leading = ((line_height - metrics.ascent - metrics.descent) / 2.).max(Pixels::ZERO);
91
92    (
93        -metrics.ascent - half_leading,
94        metrics.descent + half_leading,
95    )
96}
97
98fn expand_inline_line_for_box(
99    top: &mut Pixels,
100    bottom: &mut Pixels,
101    top_box_height: &mut Pixels,
102    bottom_box_height: &mut Pixels,
103    height: Pixels,
104    metrics: InlineTextMetrics,
105    align: VerticalAlign,
106) {
107    match align {
108        VerticalAlign::Baseline => *top = (*top).min(-height),
109        VerticalAlign::Middle => {
110            let middle = -metrics.x_height / 2.;
111            *top = (*top).min(middle - height / 2.);
112            *bottom = (*bottom).max(middle + height / 2.);
113        }
114        VerticalAlign::Top => *top_box_height = (*top_box_height).max(height),
115        VerticalAlign::Bottom => *bottom_box_height = (*bottom_box_height).max(height),
116    }
117}
118
119fn aligned_inline_box_y(
120    line: InlineVisualLine,
121    metrics: InlineTextMetrics,
122    height: Pixels,
123    align: VerticalAlign,
124) -> Pixels {
125    match align {
126        VerticalAlign::Baseline => line.baseline - height,
127        VerticalAlign::Middle => line.baseline - metrics.x_height / 2. - height / 2.,
128        VerticalAlign::Top => Pixels::ZERO,
129        VerticalAlign::Bottom => line.size.height - height,
130    }
131}
132
133/// Applies CSS-like vertical alignment to boxes after a text backend assigns them to lines.
134pub fn align_inline_boxes(
135    lines: &mut [InlineVisualLine],
136    boxes: &mut [PositionedInlineBox],
137    size: &mut Size<Pixels>,
138    requests: &[InlineBoxRequest],
139    line_metrics: &[InlineTextMetrics],
140    text_bounds: &[(Pixels, Pixels)],
141    fallback_metrics: InlineTextMetrics,
142    line_height: Pixels,
143) {
144    let request_alignments = requests
145        .iter()
146        .map(|request| (request.id, request.vertical_align))
147        .collect::<FxHashMap<_, _>>();
148    let mut boxes_by_line: FxHashMap<_, Vec<_>> = FxHashMap::default();
149
150    for inline_box in boxes
151        .iter_mut()
152        .filter(|inline_box| inline_box.line_index < lines.len())
153    {
154        let align = request_alignments
155            .get(&inline_box.id)
156            .copied()
157            .unwrap_or(VerticalAlign::Baseline);
158
159        boxes_by_line
160            .entry(inline_box.line_index)
161            .or_default()
162            .push((inline_box, align));
163    }
164
165    let mut line_y = Pixels::ZERO;
166
167    for (line_index, line) in lines.iter_mut().enumerate() {
168        let line_boxes = boxes_by_line.remove(&line_index).unwrap_or_default();
169        let metrics = line_metrics
170            .get(line_index)
171            .copied()
172            .unwrap_or(fallback_metrics);
173        let (mut top, mut bottom) = text_bounds
174            .get(line_index)
175            .copied()
176            .unwrap_or_else(|| base_inline_line_bounds(metrics, line_height));
177        let mut top_box_height = Pixels::ZERO;
178        let mut bottom_box_height = Pixels::ZERO;
179
180        for (inline_box, align) in &line_boxes {
181            expand_inline_line_for_box(
182                &mut top,
183                &mut bottom,
184                &mut top_box_height,
185                &mut bottom_box_height,
186                inline_box.bounds.size.height,
187                metrics,
188                *align,
189            );
190        }
191
192        bottom = bottom.max(top + top_box_height);
193        top = top.min(bottom - bottom_box_height);
194        line.origin.y = line_y;
195        line.size.height = bottom - top;
196        line.baseline = -top;
197
198        for (inline_box, align) in line_boxes {
199            inline_box.bounds.origin.y =
200                line_y + aligned_inline_box_y(*line, metrics, inline_box.bounds.size.height, align);
201        }
202
203        line_y += line.size.height;
204    }
205
206    size.height = line_y;
207}
208
209/// The text interaction model that a backend provides for one laid-out string.
210///
211/// GPUI calls these methods for hit testing, caret movement, and selection. The
212/// backend does this work because only it knows the glyph clusters, bidi
213/// runs, and line breaks. Application code does not implement or call this trait
214/// directly. Use the methods on [`ShapedTextLayout`] instead.
215///
216/// Byte positions are UTF-8 boundaries. Geometry is in GPUI layout coordinates,
217/// using the caller-provided line height. Implementations must preserve visual
218/// order and caret affinity.
219///
220/// A cluster is one backend-defined caret step. It may contain several Unicode
221/// scalar values, such as a combining sequence or an emoji ZWJ sequence. Logical
222/// clusters are independent of visual direction and soft-wrapped rows.
223pub trait PlatformTextLayout: Send + Sync + std::fmt::Debug {
224    /// Returns the length of the source text in UTF-8 bytes.
225    fn len(&self) -> usize;
226
227    /// Returns the number of visual lines, after wrapping and clamping.
228    fn line_count(&self) -> usize;
229
230    /// Returns the natural size of the layout that the backend reports.
231    fn size(&self) -> Size<Pixels>;
232
233    /// Returns the UTF-8 byte index of the cluster under a point in GPUI layout coordinates.
234    ///
235    /// Returns `Ok` when the point is inside a visual row. Otherwise it returns
236    /// `Err` with the closest index.
237    fn byte_index_from_pixel_point(
238        &self,
239        pixel_point: Point<Pixels>,
240        line_height: Pixels,
241    ) -> Result<usize, usize>;
242
243    /// Returns the closest caret for a point in GPUI layout coordinates.
244    /// Points outside a visual row return `Err`.
245    fn caret_from_pixel_point(
246        &self,
247        pixel_point: Point<Pixels>,
248        line_height: Pixels,
249    ) -> Result<CaretPosition, CaretPosition>;
250
251    /// Returns the caret bounds in GPUI layout coordinates.
252    fn caret_bounds(&self, caret: CaretPosition, line_height: Pixels) -> Option<Bounds<Pixels>>;
253
254    /// Snaps a caret to a native cluster boundary.
255    fn normalized_caret(&self, caret: CaretPosition) -> CaretPosition;
256
257    /// Returns the adjacent caret stop in visual order.
258    fn adjacent_visual_caret(
259        &self,
260        caret: CaretPosition,
261        direction: VisualDirection,
262    ) -> Option<CaretPosition>;
263
264    /// Returns bounds for a UTF-8 byte range in visual order.
265    fn selection_bounds(
266        &self,
267        byte_range: Range<usize>,
268        line_height: Pixels,
269    ) -> Vec<Bounds<Pixels>>;
270
271    /// Native range rectangles and their visual line indices, without selection-only extensions.
272    ///
273    /// Returns `None` for an empty input range. A nonempty range can produce an empty vector when
274    /// it has no drawable geometry. Backends supporting inline flow should preserve actual
275    /// vertical metrics here.
276    fn inline_geometry(&self, range: Range<usize>) -> Option<Vec<InlineRangeGeometry>> {
277        if range.is_empty() {
278            return None;
279        }
280
281        let line_height = self.size().height / self.line_count().max(1) as f32;
282
283        Some(
284            self.selection_bounds(range, line_height)
285                .into_iter()
286                .map(|bounds| InlineRangeGeometry {
287                    visual_line_index: (bounds.origin.y / line_height) as usize,
288                    bounds,
289                })
290                .collect(),
291        )
292    }
293
294    /// Returns native geometry for several ranges in input order.
295    fn inline_geometry_for_ranges(&self, ranges: &[Range<usize>]) -> Vec<Vec<InlineRangeGeometry>> {
296        ranges
297            .iter()
298            .map(|range| self.inline_geometry(range.clone()).unwrap_or_default())
299            .collect()
300    }
301
302    /// Returns the atomic logical cluster before the caret.
303    fn logical_cluster_before(&self, caret: CaretPosition) -> Option<Range<usize>>;
304
305    /// Returns the atomic logical cluster after the caret.
306    fn logical_cluster_after(&self, caret: CaretPosition) -> Option<Range<usize>>;
307
308    /// Returns the caret and retained horizontal coordinate after applying a movement.
309    fn caret_movement(
310        &self,
311        caret: CaretPosition,
312        movement: TextMovement,
313        vertical_navigation_x: Option<Pixels>,
314    ) -> CaretMovement;
315
316    /// Returns the word or line selected at a point in GPUI layout coordinates.
317    fn selection_from_pixel_point(
318        &self,
319        pixel_point: Point<Pixels>,
320        line_height: Pixels,
321        kind: TextSelectionKind,
322    ) -> Range<usize>;
323}
324
325/// A direction in visual order, which is left and right on screen, regardless of text direction.
326#[derive(Clone, Copy, Debug, Eq, PartialEq)]
327pub enum VisualDirection {
328    /// Toward the left edge of the screen.
329    Left,
330    /// Toward the right edge of the screen.
331    Right,
332}
333
334/// The direction of a semantic text movement, such as an arrow key or a Home key.
335#[derive(Clone, Copy, Debug, Eq, PartialEq)]
336pub enum TextDirection {
337    /// Move toward the visual left.
338    Left,
339    /// Move toward the visual right.
340    Right,
341    /// Move to the visual row above.
342    Up,
343    /// Move to the visual row below.
344    Down,
345    /// Move to the start of a line or document boundary.
346    Start,
347    /// Move to the end of a line or document boundary.
348    End,
349}
350
351/// The unit that a semantic text movement stops at.
352#[derive(Clone, Copy, Debug, Eq, PartialEq)]
353pub enum TextBoundary {
354    /// One backend-defined caret step, such as one grapheme cluster.
355    ///
356    /// The backend decides what a cluster is. [`PlatformTextLayout::normalized_caret`]
357    /// and [`PlatformTextLayout::adjacent_visual_caret`] find visual stops, and
358    /// [`PlatformTextLayout::logical_cluster_before`] and
359    /// [`PlatformTextLayout::logical_cluster_after`] return logical ranges.
360    /// Layouts from the Parley backend, which the platforms use, step by grapheme.
361    Cluster,
362    /// A word boundary.
363    Word,
364    /// One visual row produced by line breaking, including soft wrapping.
365    VisualLine,
366    /// A line delimited by a hard break.
367    HardLine,
368    /// The complete text document.
369    Document,
370}
371
372/// A caret movement described by a direction and a unit, such as "to the end of the word".
373///
374/// `direction` chooses where to move, and `boundary` chooses the unit. Consecutive vertical
375/// movements reuse the returned preferred horizontal coordinate, so moving up or down keeps
376/// the column you started in. Build one with [`TextDirection::with_boundary`].
377#[derive(Clone, Copy, Debug, Eq, PartialEq)]
378pub struct TextMovement {
379    /// The direction in which to move.
380    pub direction: TextDirection,
381    /// The boundary at which to stop.
382    pub boundary: TextBoundary,
383}
384
385impl TextDirection {
386    /// Returns a [`TextMovement`] in this direction that stops at `boundary`.
387    pub const fn with_boundary(self, boundary: TextBoundary) -> TextMovement {
388        TextMovement {
389            direction: self,
390            boundary,
391        }
392    }
393}
394
395/// The unit selected by a double or triple click at a point.
396#[derive(Clone, Copy, Debug, Eq, PartialEq)]
397pub enum TextSelectionKind {
398    /// The word under the point.
399    Word,
400    /// The soft-wrapped visual row under the point.
401    VisualLine,
402    /// The line between hard breaks under the point.
403    HardLine,
404}
405
406/// One row of text produced by shaping and line breaking.
407///
408/// Soft wrapping can split one logical line across several rows. Bidirectional
409/// ordering changes the visual order of glyphs but not the logical UTF-8 byte ranges.
410#[derive(Clone, Debug, Default, PartialEq)]
411pub struct VisualLine {
412    /// The logical UTF-8 byte range assigned to this row.
413    pub text_range: Range<usize>,
414    /// Indices into [`LineLayout::paint_fragments`] for the positioned glyph runs
415    /// painted on this row, ordered by visual position.
416    pub paint_fragment_range: Range<usize>,
417    /// Horizontal distance consumed by the row's shaped content before alignment,
418    /// measured to the backend's final text pen position. Inline layouts include
419    /// advances from embedded boxes.
420    pub advance_width: Pixels,
421    /// Horizontal offset assigned by backend layout.
422    pub offset: Pixels,
423    /// Base direction used to order and align this visual line.
424    pub direction: crate::ResolvedDirection,
425}
426
427/// The paint properties of one run of text: color, background, underline, and strikethrough.
428///
429/// These come from the [`TextRun`] that covers the glyphs. They are kept apart from
430/// the shaping, so changing them does not reshape the text.
431#[derive(Clone, Debug, Default, PartialEq)]
432pub struct PaintStyle {
433    /// Glyph foreground color.
434    pub color: Hsla,
435    /// Optional background color.
436    pub background_color: Option<Hsla>,
437    /// Optional underline style.
438    pub underline: Option<UnderlineStyle>,
439    /// Optional strikethrough style.
440    pub strikethrough: Option<StrikethroughStyle>,
441}
442
443impl From<&TextRun> for PaintStyle {
444    fn from(run: &TextRun) -> Self {
445        Self {
446            color: run.color,
447            background_color: run.background_color,
448            underline: run.underline,
449            strikethrough: run.strikethrough,
450        }
451    }
452}
453
454/// A run of glyphs that share one font and one style, positioned on a visual line.
455#[derive(Clone, Debug, PartialEq)]
456pub struct PaintFragment {
457    /// Index of the input text run that supplies paint properties.
458    pub source_run: usize,
459    /// Canonical font used by the glyphs.
460    pub font_id: FontId,
461    /// Resolved size of this shaped run.
462    pub font_size: Pixels,
463    /// Positioned glyphs local to the visual line.
464    pub glyphs: Arc<[ShapedGlyph]>,
465    /// Horizontal bounds of this fragment.
466    pub x_range: Range<Pixels>,
467    /// Paint properties shared by every glyph in the fragment.
468    pub style: PaintStyle,
469    /// Resolved underline offset from the baseline.
470    pub underline_offset: Option<Pixels>,
471    /// Resolved strikethrough offset from the baseline.
472    pub strikethrough_offset: Option<Pixels>,
473}
474
475/// One glyph in a shaped line, ready to paint.
476#[derive(Clone, Debug, PartialEq)]
477pub struct ShapedGlyph {
478    /// The glyph's ID in the font, as chosen by the text system.
479    pub id: GlyphId,
480
481    /// The glyph origin, relative to the start of its visual line.
482    pub position: Point<Pixels>,
483
484    /// Whether the glyph is a color emoji, which paints through the emoji path.
485    pub is_emoji: bool,
486}
487
488/// Determines which logical neighbor owns a caret at a shared text boundary.
489///
490/// At a soft wrap, downstream places the caret at the next row's start, and upstream places it at
491/// the previous row's end. At a bidirectional boundary, affinities can place the same byte index at
492/// different horizontal positions. They refer to logical order, not visual left and right.
493#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
494pub enum CaretAffinity {
495    /// The caret attaches to the logically following cluster.
496    #[default]
497    Downstream,
498    /// The caret attaches to the logically preceding cluster.
499    Upstream,
500}
501
502/// Hard line breaks that place an inserted-text caret downstream.
503static HARD_LINE_BREAK_CHARACTERS: phf::Set<char> = phf::phf_set! {
504    '\n',
505    '\r',
506    '\u{001c}',
507    '\u{001d}',
508    '\u{001e}',
509    '\u{0085}',
510    '\u{2028}',
511    '\u{2029}',
512};
513
514/// Returns whether `character` separates Unicode text paragraphs.
515///
516/// Line separator (`U+2028`) is a hard line break within the current paragraph. Consumers are
517/// responsible for treating a CRLF pair as one paragraph separator.
518pub fn is_paragraph_separator(character: char) -> bool {
519    character != '\u{2028}' && HARD_LINE_BREAK_CHARACTERS.contains(&character)
520}
521
522impl CaretAffinity {
523    /// Returns the affinity for a caret placed right after `text` was inserted.
524    ///
525    /// Returns [`CaretAffinity::Downstream`] when `text` is empty or ends with a hard line break,
526    /// so the caret starts the next line. Otherwise it returns [`CaretAffinity::Upstream`].
527    pub fn for_inserted_text(text: &str) -> Self {
528        Self::from(text)
529    }
530}
531
532impl From<&str> for CaretAffinity {
533    fn from(text: &str) -> Self {
534        let Some(last_character) = text.chars().next_back() else {
535            return Self::Downstream;
536        };
537
538        if HARD_LINE_BREAK_CHARACTERS.contains(&last_character) {
539            return Self::Downstream;
540        }
541
542        Self::Upstream
543    }
544}
545
546/// A position between characters, given as a UTF-8 byte index plus the side it attaches to.
547///
548/// The index alone is ambiguous at a soft wrap or a bidi boundary, because the
549/// same byte can sit at the end of one row or the start of the next. The
550/// affinity decides which.
551#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
552pub struct CaretPosition {
553    /// The logical UTF-8 byte index.
554    pub index: usize,
555    /// The logical neighbor that owns this position.
556    pub affinity: CaretAffinity,
557}
558
559impl From<(usize, CaretAffinity)> for CaretPosition {
560    fn from((index, affinity): (usize, CaretAffinity)) -> Self {
561        Self { index, affinity }
562    }
563}
564
565impl CaretPosition {
566    /// Creates a caret attached to the next logical cluster.
567    pub const fn attached_to_next_cluster(index: usize) -> Self {
568        Self {
569            index,
570            affinity: CaretAffinity::Downstream,
571        }
572    }
573
574    /// Creates a caret attached to the previous logical cluster.
575    pub const fn attached_to_previous_cluster(index: usize) -> Self {
576        Self {
577            index,
578            affinity: CaretAffinity::Upstream,
579        }
580    }
581}
582
583/// A text selection with an anchor and a caret, each with its own affinity.
584///
585/// The anchor stays fixed while the caret moves. The caret can be on either side
586/// of the anchor, so use [`CaretSelection::byte_range`] to get the range in logical order.
587#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
588pub struct CaretSelection {
589    /// The fixed position of the selection, which may be its logical start or end.
590    pub anchor: CaretPosition,
591    /// The active position of the selection. When it equals `anchor`, the selection is empty.
592    pub caret: CaretPosition,
593}
594
595impl From<usize> for CaretSelection {
596    fn from(index: usize) -> Self {
597        CaretPosition::attached_to_next_cluster(index).into()
598    }
599}
600
601impl From<CaretPosition> for CaretSelection {
602    fn from(caret: CaretPosition) -> Self {
603        Self {
604            anchor: caret,
605            caret,
606        }
607    }
608}
609
610impl From<Range<usize>> for CaretSelection {
611    /// Creates a selection whose caret is at `range.start` and whose anchor is at `range.end`.
612    fn from(range: Range<usize>) -> Self {
613        Self {
614            anchor: CaretPosition::attached_to_next_cluster(range.end),
615            caret: CaretPosition::attached_to_next_cluster(range.start),
616        }
617    }
618}
619
620impl CaretSelection {
621    /// Returns whether the anchor and the caret have the same byte index.
622    ///
623    /// Affinity is ignored, so a caret at a wrap point with two affinities is still empty.
624    pub fn is_empty(&self) -> bool {
625        self.anchor.index == self.caret.index
626    }
627
628    /// Compares the caret's byte index with the anchor's. `Less` means the caret is before the anchor.
629    pub fn endpoint_ordering(self) -> std::cmp::Ordering {
630        self.caret.index.cmp(&self.anchor.index)
631    }
632
633    /// Returns the selected UTF-8 byte range, with the smaller index first.
634    pub fn byte_range(self) -> Range<usize> {
635        self.anchor.index.min(self.caret.index)..self.anchor.index.max(self.caret.index)
636    }
637
638    /// Limits both selection endpoints to `max_idx` while preserving their affinities.
639    pub fn min(mut self, max_idx: usize) -> Self {
640        self.caret.index = self.caret.index.min(max_idx);
641        self.anchor.index = self.anchor.index.min(max_idx);
642
643        self
644    }
645
646    /// Moves the active end while preserving the anchor.
647    pub fn with_caret(self, caret: CaretPosition) -> Self {
648        Self {
649            anchor: self.anchor,
650            caret,
651        }
652    }
653}
654
655/// The destination of a caret movement, and the column to keep across vertical moves.
656#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
657pub struct CaretMovement<T = CaretPosition> {
658    /// The value at the requested destination.
659    pub result: T,
660    /// The horizontal position, from the layout's left edge, to keep across consecutive
661    /// vertical movements. Pass it back in on the next move so the caret keeps its column.
662    pub vertical_navigation_x: Option<Pixels>,
663}
664
665/// The result of moving a selection: the new anchor and caret, and the column to keep.
666pub type CaretSelectionMovement = CaretMovement<CaretSelection>;
667
668impl CaretMovement<CaretSelection> {
669    /// Moves the caret to `caret`, or extends the selection to it when `extend` is true.
670    ///
671    /// Either way, the retained vertical coordinate is cleared.
672    pub fn move_or_select_to(self, caret: CaretPosition, extend: bool) -> Self {
673        let result = if extend {
674            self.result.with_caret(caret)
675        } else {
676            caret.into()
677        };
678
679        Self {
680            result,
681            vertical_navigation_x: None,
682        }
683    }
684}
685
686/// Shaped text with its wrap width, which is how a layout is measured and queried.
687///
688/// Get one from [`WindowTextSystem::shape_text`](crate::WindowTextSystem::shape_text).
689/// [`ShapedText`](crate::ShapedText) derefs to it. Its methods convert between
690/// pixel positions and UTF-8 byte indices, so you can place a caret or find the
691/// character under the mouse. All positions are in logical pixels, relative to
692/// the top-left corner of the text.
693#[derive(Debug)]
694pub struct ShapedTextLayout {
695    /// The laid-out document.
696    pub layout: Arc<LineLayout>,
697
698    /// The width that lines wrap at, or `None` when the text does not wrap.
699    pub wrap_width: Option<Pixels>,
700}
701
702impl ShapedTextLayout {
703    /// Returns the length of the text in UTF-8 bytes.
704    #[allow(clippy::len_without_is_empty)]
705    pub fn len(&self) -> usize {
706        self.layout.platform_layout.len()
707    }
708
709    /// Returns the width of the widest line, capped at the wrap width.
710    pub fn width(&self) -> Pixels {
711        self.wrap_width
712            .unwrap_or(Pixels::MAX)
713            .min(self.layout.width)
714    }
715
716    /// Returns the size of the whole layout, with each line `line_height` tall.
717    pub fn size(&self, line_height: Pixels) -> Size<Pixels> {
718        Size {
719            width: self.width(),
720            height: line_height * self.line_count(),
721        }
722    }
723
724    /// Returns the number of visual lines in this layout.
725    pub fn line_count(&self) -> usize {
726        self.layout.platform_layout.line_count()
727    }
728
729    /// Returns the distance from the baseline up to the tallest glyphs.
730    pub fn ascent(&self) -> Pixels {
731        self.layout.ascent
732    }
733
734    /// Returns the distance from the baseline down to the lowest glyphs.
735    pub fn descent(&self) -> Pixels {
736        self.layout.descent
737    }
738
739    /// Returns the visual lines in paint order.
740    pub fn visual_lines(&self) -> &[VisualLine] {
741        &self.layout.visual_lines
742    }
743
744    /// Returns the fragments belonging to a visual line.
745    pub fn fragments_for_line(&self, line: &VisualLine) -> &[PaintFragment] {
746        &self.layout.paint_fragments[line.paint_fragment_range.clone()]
747    }
748
749    /// Returns the base font size that the text was shaped at, in logical pixels.
750    pub fn font_size(&self) -> Pixels {
751        self.layout.font_size
752    }
753
754    /// The UTF-8 byte index for a point in GPUI layout coordinates.
755    ///
756    /// The backend returns the logical start of the visual cluster under the point.
757    /// Whitespace is hit like any other cluster. Positions outside the line return the boundary at
758    /// that visual edge in `Err`.
759    ///
760    /// See also [`Self::closest_byte_index_for_pixel_point`].
761    pub fn byte_index_for_pixel_point(
762        &self,
763        pixel_point: Point<Pixels>,
764        line_height: Pixels,
765    ) -> Result<usize, usize> {
766        self.layout
767            .platform_layout
768            .byte_index_from_pixel_point(pixel_point, line_height)
769    }
770
771    /// Returns the UTF-8 byte index of the cluster boundary closest to a point.
772    ///
773    /// Use this for a click or drag that should always land on the nearest character,
774    /// even when the point is outside the text. For right-to-left clusters, the visual
775    /// left edge maps to the logical end and the visual right edge maps to the logical
776    /// start. Zero-width clusters share a stop with an adjacent cluster, and visual order
777    /// breaks the tie.
778    ///
779    /// # Errors
780    ///
781    /// Returns `Err` with the index when the point is outside a visual line, and `Ok`
782    /// otherwise. Both cases give an index, so use either value.
783    pub fn closest_byte_index_for_pixel_point(
784        &self,
785        pixel_point: Point<Pixels>,
786        line_height: Pixels,
787    ) -> Result<usize, usize> {
788        self.closest_caret_for_pixel_point(pixel_point, line_height)
789            .map(|caret| caret.index)
790            .map_err(|caret| caret.index)
791    }
792
793    /// Returns the closest backend-native caret for a point in GPUI layout coordinates.
794    ///
795    /// Returns `Err` with the caret at that edge when the point is outside a visual line,
796    /// the same way as [`Self::closest_byte_index_for_pixel_point`].
797    pub fn closest_caret_for_pixel_point(
798        &self,
799        pixel_point: Point<Pixels>,
800        line_height: Pixels,
801    ) -> Result<CaretPosition, CaretPosition> {
802        self.layout
803            .platform_layout
804            .caret_from_pixel_point(pixel_point, line_height)
805    }
806
807    /// Returns the top-left corner of the caret for a UTF-8 byte index, or `None` if the index is past the end.
808    ///
809    /// The position is on the cluster boundary, at the visual edge that matches the text direction.
810    /// An index inside an atomic cluster snaps to the start of that cluster. On a soft-wrap
811    /// boundary, the index places the caret at the start of the following visual line.
812    pub fn visual_position_for_byte_index(
813        &self,
814        byte_index: usize,
815        line_height: Pixels,
816    ) -> Option<Point<Pixels>> {
817        if byte_index > self.len() {
818            return None;
819        }
820
821        self.visual_position_for_caret(
822            CaretPosition::attached_to_next_cluster(byte_index),
823            line_height,
824        )
825    }
826
827    /// Returns the top-left corner of the caret for `caret`, or `None` if the caret has no position.
828    pub fn visual_position_for_caret(
829        &self,
830        caret: CaretPosition,
831        line_height: Pixels,
832    ) -> Option<Point<Pixels>> {
833        self.layout
834            .platform_layout
835            .caret_bounds(caret, line_height)
836            .map(|bounds| bounds.origin)
837    }
838
839    /// Moves a caret onto a valid cluster boundary, keeping its affinity when it can.
840    pub fn normalized_caret(&self, caret: CaretPosition) -> CaretPosition {
841        self.layout.platform_layout.normalized_caret(caret)
842    }
843
844    /// Returns the next caret stop to the left or right of `caret` on screen.
845    ///
846    /// Returns `None` when there is no caret stop in that direction.
847    pub fn adjacent_visual_caret(
848        &self,
849        caret: CaretPosition,
850        direction: VisualDirection,
851    ) -> Option<CaretPosition> {
852        self.layout
853            .platform_layout
854            .adjacent_visual_caret(caret, direction)
855    }
856
857    /// Returns the byte range of the cluster before `caret` in logical order.
858    pub fn logical_cluster_before(&self, caret: CaretPosition) -> Option<Range<usize>> {
859        self.layout.platform_layout.logical_cluster_before(caret)
860    }
861
862    /// Returns the byte range of the cluster after `caret` in logical order.
863    pub fn logical_cluster_after(&self, caret: CaretPosition) -> Option<Range<usize>> {
864        self.layout.platform_layout.logical_cluster_after(caret)
865    }
866
867    /// Returns where the caret lands after `movement`, without changing any state.
868    ///
869    /// Pass the `vertical_navigation_x` from the previous move to keep the caret's column
870    /// across consecutive up or down moves. The result carries the new value.
871    pub fn caret_movement(
872        &self,
873        caret: CaretPosition,
874        movement: TextMovement,
875        vertical_navigation_x: Option<Pixels>,
876    ) -> CaretMovement {
877        self.layout
878            .platform_layout
879            .caret_movement(caret, movement, vertical_navigation_x)
880    }
881
882    /// Returns where a selection lands after `movement`, using visual order.
883    ///
884    /// Without `extend`, a left or right cluster or word move on a non-empty selection collapses
885    /// it to the visual edge in that direction. Other moves start from the caret. With `extend`, the
886    /// anchor stays fixed and the caret moves.
887    ///
888    /// - `selection` is the current anchor and caret.
889    /// - `movement` gives the direction and the unit.
890    /// - `extend` keeps the anchor fixed when `true`, and collapses the selection when `false`.
891    /// - `vertical_navigation_x` carries the column across vertical moves.
892    /// - `line_height` converts caret positions into comparable visual coordinates.
893    pub fn selection_movement(
894        &self,
895        selection: CaretSelection,
896        movement: TextMovement,
897        extend: bool,
898        vertical_navigation_x: Option<Pixels>,
899        line_height: Pixels,
900    ) -> CaretSelectionMovement {
901        let forward = movement.direction == TextDirection::Right;
902        let horizontal = matches!(
903            movement.direction,
904            TextDirection::Left | TextDirection::Right
905        ) && matches!(
906            movement.boundary,
907            TextBoundary::Cluster | TextBoundary::Word
908        );
909
910        if !extend && !selection.is_empty() && horizontal {
911            let caret_visual_position =
912                self.visual_position_for_caret(selection.caret, line_height);
913            let anchor_visual_position =
914                self.visual_position_for_caret(selection.anchor, line_height);
915            let (visual_start, visual_end) = caret_visual_position
916                .zip(anchor_visual_position)
917                .map(|(caret_visual_position, anchor_visual_position)| {
918                    if (caret_visual_position.y, caret_visual_position.x)
919                        <= (anchor_visual_position.y, anchor_visual_position.x)
920                    {
921                        (selection.caret, selection.anchor)
922                    } else {
923                        (selection.anchor, selection.caret)
924                    }
925                })
926                .unwrap_or_else(|| {
927                    if selection.caret.index <= selection.anchor.index {
928                        (selection.caret, selection.anchor)
929                    } else {
930                        (selection.anchor, selection.caret)
931                    }
932                });
933
934            let caret = if forward { visual_end } else { visual_start };
935
936            return CaretSelectionMovement {
937                result: caret.into(),
938                vertical_navigation_x: None,
939            };
940        }
941
942        let CaretMovement {
943            result: caret,
944            vertical_navigation_x,
945        } = self.caret_movement(selection.caret, movement, vertical_navigation_x);
946
947        CaretSelectionMovement {
948            result: if extend {
949                selection.with_caret(caret)
950            } else {
951                caret.into()
952            },
953            vertical_navigation_x,
954        }
955    }
956
957    /// Selects the word or line at a point in GPUI layout coordinates.
958    pub fn selection_from_pixel_point(
959        &self,
960        pixel_point: Point<Pixels>,
961        line_height: Pixels,
962        kind: TextSelectionKind,
963    ) -> Range<usize> {
964        self.layout
965            .platform_layout
966            .selection_from_pixel_point(pixel_point, line_height, kind)
967    }
968
969    /// Returns rectangles covering all selected clusters in visual order.
970    pub fn selection_bounds(
971        &self,
972        byte_range: Range<usize>,
973        line_height: Pixels,
974    ) -> SmallVec<[Bounds<Pixels>; 4]> {
975        let mut result = SmallVec::new();
976
977        if byte_range.is_empty() {
978            return result;
979        }
980
981        result.extend(
982            self.layout
983                .platform_layout
984                .selection_bounds(byte_range, line_height),
985        );
986        result
987    }
988}
989
990impl std::ops::Deref for ShapedTextLayout {
991    type Target = LineLayout;
992
993    fn deref(&self) -> &Self::Target {
994        &self.layout
995    }
996}
997
998pub(crate) struct LineLayoutCache {
999    previous_frame: Mutex<FrameCache>,
1000    current_frame: RwLock<FrameCache>,
1001    platform_text_system: Arc<dyn PlatformTextSystem>,
1002    font_generation: AtomicU64,
1003}
1004
1005#[derive(Default)]
1006struct FrameCache {
1007    lines: FrameLayouts<CacheKey, LineLayout>,
1008    shaped_texts: FrameLayouts<CacheKey, ShapedTextLayout>,
1009    inline_layouts: FrameLayouts<InlineCacheKey, InlineLayout>,
1010}
1011
1012struct FrameLayouts<Key, Value> {
1013    entries: FxHashMap<Arc<Key>, Arc<Value>>,
1014    used: Vec<Arc<Key>>,
1015}
1016
1017impl<Key, Value> Default for FrameLayouts<Key, Value> {
1018    fn default() -> Self {
1019        Self {
1020            entries: FxHashMap::default(),
1021            used: Vec::new(),
1022        }
1023    }
1024}
1025
1026impl<Key, Value> FrameLayouts<Key, Value>
1027where
1028    Key: Eq + Hash,
1029{
1030    fn get<Query>(&self, key: &Query) -> Option<&Arc<Value>>
1031    where
1032        Arc<Key>: Borrow<Query>,
1033        Query: Eq + Hash + ?Sized,
1034    {
1035        self.entries.get(key)
1036    }
1037
1038    fn remove_entry<Query>(&mut self, key: &Query) -> Option<(Arc<Key>, Arc<Value>)>
1039    where
1040        Arc<Key>: Borrow<Query>,
1041        Query: Eq + Hash + ?Sized,
1042    {
1043        self.entries.remove_entry(key)
1044    }
1045
1046    fn insert(&mut self, key: Arc<Key>, value: Arc<Value>) {
1047        self.entries.insert(key.clone(), value);
1048        self.used.push(key);
1049    }
1050
1051    fn reuse(&mut self, previous: &mut Self, range: Range<usize>) {
1052        // A font change clears the previous frame, so a range recorded before it may be stale.
1053        let Some(keys) = previous.used.get(range) else {
1054            return;
1055        };
1056        for key in keys {
1057            if let Some(layout) = previous.entries.remove(key) {
1058                self.entries.insert(key.clone(), layout);
1059            }
1060
1061            self.used.push(key.clone());
1062        }
1063    }
1064
1065    fn clear(&mut self) {
1066        self.entries.clear();
1067        self.used.clear();
1068    }
1069}
1070
1071fn repaint_line_layout(layout: Arc<LineLayout>, runs: &[TextRun]) -> Arc<LineLayout> {
1072    let paint_matches = layout.paint_fragments.iter().all(|fragment| {
1073        runs.get(fragment.source_run)
1074            .is_some_and(|run| fragment.style == PaintStyle::from(run))
1075    });
1076
1077    if paint_matches {
1078        return layout;
1079    }
1080
1081    let mut repainted = (*layout).clone();
1082
1083    for fragment in &mut repainted.paint_fragments {
1084        if let Some(run) = runs.get(fragment.source_run) {
1085            fragment.style = PaintStyle::from(run);
1086        }
1087    }
1088
1089    Arc::new(repainted)
1090}
1091
1092fn repaint_shaped_text_layout(
1093    layout: Arc<ShapedTextLayout>,
1094    runs: &[TextRun],
1095) -> Arc<ShapedTextLayout> {
1096    let repainted = repaint_line_layout(layout.layout.clone(), runs);
1097
1098    if Arc::ptr_eq(&repainted, &layout.layout) {
1099        return layout;
1100    }
1101
1102    Arc::new(ShapedTextLayout {
1103        layout: repainted,
1104        wrap_width: layout.wrap_width,
1105    })
1106}
1107
1108#[derive(Clone, Default)]
1109pub(crate) struct LineLayoutIndex {
1110    lines_index: usize,
1111    shaped_texts_index: usize,
1112    inline_layouts_index: usize,
1113}
1114
1115impl LineLayoutIndex {
1116    /// Moves an index taken inside a range that started at `from` to the same place in a copy of
1117    /// that range starting at `to`. These indices only decide which layouts stay cached, so an
1118    /// index outside the range saturates instead of failing.
1119    pub(crate) fn rebased(&self, from: &Self, to: &Self) -> Self {
1120        let shift = |index: usize, from: usize, to: usize| index.saturating_sub(from) + to;
1121        Self {
1122            lines_index: shift(self.lines_index, from.lines_index, to.lines_index),
1123            shaped_texts_index: shift(
1124                self.shaped_texts_index,
1125                from.shaped_texts_index,
1126                to.shaped_texts_index,
1127            ),
1128            inline_layouts_index: shift(
1129                self.inline_layouts_index,
1130                from.inline_layouts_index,
1131                to.inline_layouts_index,
1132            ),
1133        }
1134    }
1135}
1136
1137impl LineLayoutCache {
1138    pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
1139        let font_generation = platform_text_system.font_generation();
1140        Self {
1141            previous_frame: Mutex::default(),
1142            current_frame: RwLock::default(),
1143            platform_text_system,
1144            font_generation: AtomicU64::new(font_generation),
1145        }
1146    }
1147
1148    pub fn layout_index(&self) -> LineLayoutIndex {
1149        let frame = self.current_frame.read();
1150        LineLayoutIndex {
1151            lines_index: frame.lines.used.len(),
1152            shaped_texts_index: frame.shaped_texts.used.len(),
1153            inline_layouts_index: frame.inline_layouts.used.len(),
1154        }
1155    }
1156
1157    /// Invalidates every cached layout after backend shaping state changes.
1158    pub fn clear(&self) {
1159        *self.previous_frame.lock() = FrameCache::default();
1160        *self.current_frame.write() = FrameCache::default();
1161    }
1162
1163    fn sync_font_generation(&self) {
1164        let generation = self.platform_text_system.font_generation();
1165        if self.font_generation.load(Ordering::Acquire) != generation {
1166            self.clear();
1167            self.font_generation.store(generation, Ordering::Release);
1168        }
1169    }
1170
1171    pub fn reuse_layouts(&self, range: Range<LineLayoutIndex>) {
1172        let mut previous_frame = &mut *self.previous_frame.lock();
1173        let mut current_frame = &mut *self.current_frame.write();
1174
1175        current_frame.lines.reuse(
1176            &mut previous_frame.lines,
1177            range.start.lines_index..range.end.lines_index,
1178        );
1179        current_frame.shaped_texts.reuse(
1180            &mut previous_frame.shaped_texts,
1181            range.start.shaped_texts_index..range.end.shaped_texts_index,
1182        );
1183        current_frame.inline_layouts.reuse(
1184            &mut previous_frame.inline_layouts,
1185            range.start.inline_layouts_index..range.end.inline_layouts_index,
1186        );
1187    }
1188
1189    pub fn truncate_layouts(&self, index: LineLayoutIndex) {
1190        let mut current_frame = &mut *self.current_frame.write();
1191        current_frame.lines.used.truncate(index.lines_index);
1192        current_frame
1193            .shaped_texts
1194            .used
1195            .truncate(index.shaped_texts_index);
1196        current_frame
1197            .inline_layouts
1198            .used
1199            .truncate(index.inline_layouts_index);
1200    }
1201
1202    pub fn finish_frame(&self) {
1203        let mut prev_frame = self.previous_frame.lock();
1204        let mut curr_frame = self.current_frame.write();
1205        std::mem::swap(&mut *prev_frame, &mut *curr_frame);
1206        curr_frame.lines.clear();
1207        curr_frame.shaped_texts.clear();
1208        curr_frame.inline_layouts.clear();
1209    }
1210
1211    pub fn layout_text_with_options<Text>(
1212        &self,
1213        text: Text,
1214        font_size: Pixels,
1215        runs: &[TextRun],
1216        options: TextLayoutOptions,
1217    ) -> Arc<ShapedTextLayout>
1218    where
1219        Text: AsRef<str>,
1220        SharedString: From<Text>,
1221    {
1222        self.sync_font_generation();
1223        let wrap_width = options.wrap_width;
1224        let shaping_runs = runs.iter().map(ShapingRun::from).collect::<SmallVec<_>>();
1225        let key = &CacheKeyRef {
1226            text: text.as_ref(),
1227            font_size,
1228            runs: &shaping_runs,
1229            options,
1230        } as &dyn AsCacheKeyRef;
1231
1232        let current_frame = self.current_frame.upgradable_read();
1233        if let Some(layout) = current_frame.shaped_texts.get(key) {
1234            return repaint_shaped_text_layout(layout.clone(), runs);
1235        }
1236
1237        let previous_frame_entry = self.previous_frame.lock().shaped_texts.remove_entry(key);
1238        if let Some((key, layout)) = previous_frame_entry {
1239            let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
1240            current_frame.shaped_texts.insert(key, layout.clone());
1241            repaint_shaped_text_layout(layout, runs)
1242        } else {
1243            drop(current_frame);
1244            let text = SharedString::from(text);
1245            let document_layout = if options != TextLayoutOptions::default() || text.contains('\n')
1246            {
1247                Arc::new(self.platform_text_system.layout_text(TextLayoutRequest {
1248                    text: &text,
1249                    font_size,
1250                    runs,
1251                    options,
1252                }))
1253            } else {
1254                self.layout_line::<&SharedString>(&text, font_size, runs)
1255            };
1256
1257            let layout = Arc::new(ShapedTextLayout {
1258                layout: document_layout,
1259                wrap_width,
1260            });
1261            let key = Arc::new(CacheKey {
1262                text,
1263                font_size,
1264                runs: shaping_runs,
1265                options,
1266            });
1267
1268            let mut current_frame = self.current_frame.write();
1269            current_frame.shaped_texts.insert(key, layout.clone());
1270
1271            layout
1272        }
1273    }
1274
1275    pub fn layout_line<Text>(
1276        &self,
1277        text: Text,
1278        font_size: Pixels,
1279        runs: &[TextRun],
1280    ) -> Arc<LineLayout>
1281    where
1282        Text: AsRef<str>,
1283        SharedString: From<Text>,
1284    {
1285        self.sync_font_generation();
1286        let shaping_runs = runs.iter().map(ShapingRun::from).collect::<SmallVec<_>>();
1287        let key = &CacheKeyRef {
1288            text: text.as_ref(),
1289            font_size,
1290            runs: &shaping_runs,
1291            options: TextLayoutOptions::default(),
1292        } as &dyn AsCacheKeyRef;
1293
1294        let current_frame = self.current_frame.upgradable_read();
1295        if let Some(layout) = current_frame.lines.get(key) {
1296            return repaint_line_layout(layout.clone(), runs);
1297        }
1298
1299        let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
1300        if let Some((key, layout)) = self.previous_frame.lock().lines.remove_entry(key) {
1301            current_frame.lines.insert(key, layout.clone());
1302            repaint_line_layout(layout, runs)
1303        } else {
1304            let text = SharedString::from(text);
1305            let layout = self.platform_text_system.layout_text(TextLayoutRequest {
1306                text: &text,
1307                font_size,
1308                runs,
1309                options: TextLayoutOptions::default(),
1310            });
1311            let key = Arc::new(CacheKey {
1312                text,
1313                font_size,
1314                runs: shaping_runs,
1315                options: TextLayoutOptions::default(),
1316            });
1317            let layout = Arc::new(layout);
1318            current_frame.lines.insert(key, layout.clone());
1319            layout
1320        }
1321    }
1322
1323    pub fn layout_inline(&self, request: InlineLayoutRequest<'_>) -> Arc<InlineLayout> {
1324        self.sync_font_generation();
1325        let key = &request as &dyn AsInlineCacheKeyRef;
1326        let current_frame = self.current_frame.upgradable_read();
1327
1328        if let Some(layout) = current_frame.inline_layouts.get(key) {
1329            return layout.clone();
1330        }
1331
1332        let previous_frame_entry = self.previous_frame.lock().inline_layouts.remove_entry(key);
1333
1334        if let Some((key, layout)) = previous_frame_entry {
1335            let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
1336            current_frame.inline_layouts.insert(key, layout.clone());
1337
1338            return layout;
1339        }
1340
1341        drop(current_frame);
1342        let layout = Arc::new(self.platform_text_system.layout_inline(request));
1343        let key = Arc::new(InlineCacheKey::from(request));
1344
1345        let mut current_frame = self.current_frame.write();
1346        current_frame.inline_layouts.insert(key, layout.clone());
1347
1348        layout
1349    }
1350}
1351
1352trait AsInlineCacheKeyRef {
1353    fn as_inline_cache_key_ref(&self) -> InlineLayoutRequest<'_>;
1354}
1355
1356#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1357struct InlineCacheKey {
1358    text: SharedString,
1359    runs: SmallVec<[TextRun; 1]>,
1360    text_styles: Vec<InlineTextStyle>,
1361    boxes: Vec<InlineBoxRequest>,
1362    font_size: Pixels,
1363    line_height: Pixels,
1364    text_metrics: InlineTextMetrics,
1365    options: TextLayoutOptions,
1366    bidi_scopes: Vec<InlineBidiScope>,
1367}
1368
1369impl From<InlineLayoutRequest<'_>> for InlineCacheKey {
1370    fn from(request: InlineLayoutRequest<'_>) -> Self {
1371        Self {
1372            text: SharedString::new(request.text),
1373            runs: SmallVec::from(request.runs),
1374            text_styles: request.text_styles.to_vec(),
1375            boxes: request.boxes.to_vec(),
1376            font_size: request.font_size,
1377            line_height: request.line_height,
1378            text_metrics: request.text_metrics,
1379            options: request.options,
1380            bidi_scopes: request.bidi_scopes.to_vec(),
1381        }
1382    }
1383}
1384
1385impl AsInlineCacheKeyRef for InlineCacheKey {
1386    fn as_inline_cache_key_ref(&self) -> InlineLayoutRequest<'_> {
1387        InlineLayoutRequest {
1388            text: &self.text,
1389            runs: &self.runs,
1390            text_styles: &self.text_styles,
1391            boxes: &self.boxes,
1392            font_size: self.font_size,
1393            line_height: self.line_height,
1394            text_metrics: self.text_metrics,
1395            options: self.options,
1396            bidi_scopes: &self.bidi_scopes,
1397        }
1398    }
1399}
1400
1401impl AsInlineCacheKeyRef for InlineLayoutRequest<'_> {
1402    fn as_inline_cache_key_ref(&self) -> InlineLayoutRequest<'_> {
1403        *self
1404    }
1405}
1406
1407impl PartialEq for dyn AsInlineCacheKeyRef + '_ {
1408    fn eq(&self, other: &dyn AsInlineCacheKeyRef) -> bool {
1409        self.as_inline_cache_key_ref() == other.as_inline_cache_key_ref()
1410    }
1411}
1412
1413impl Eq for dyn AsInlineCacheKeyRef + '_ {}
1414
1415impl Hash for dyn AsInlineCacheKeyRef + '_ {
1416    fn hash<H: Hasher>(&self, state: &mut H) {
1417        self.as_inline_cache_key_ref().hash(state);
1418    }
1419}
1420
1421impl<'a> Borrow<dyn AsInlineCacheKeyRef + 'a> for Arc<InlineCacheKey> {
1422    fn borrow(&self) -> &(dyn AsInlineCacheKeyRef + 'a) {
1423        self.as_ref() as &dyn AsInlineCacheKeyRef
1424    }
1425}
1426
1427trait AsCacheKeyRef {
1428    fn as_cache_key_ref(&self) -> CacheKeyRef<'_>;
1429}
1430
1431#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1432struct CacheKey {
1433    text: SharedString,
1434    font_size: Pixels,
1435    runs: SmallVec<[ShapingRun; 1]>,
1436    options: TextLayoutOptions,
1437}
1438
1439#[derive(Copy, Clone, PartialEq, Eq, Hash)]
1440struct CacheKeyRef<'a> {
1441    text: &'a str,
1442    font_size: Pixels,
1443    runs: &'a [ShapingRun],
1444    options: TextLayoutOptions,
1445}
1446
1447#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1448struct ShapingRun {
1449    len: usize,
1450    font: crate::Font,
1451    letter_spacing: Option<Pixels>,
1452}
1453
1454impl From<&TextRun> for ShapingRun {
1455    fn from(run: &TextRun) -> Self {
1456        Self {
1457            len: run.len,
1458            font: run.font.clone(),
1459            letter_spacing: run.letter_spacing,
1460        }
1461    }
1462}
1463
1464impl PartialEq for dyn AsCacheKeyRef + '_ {
1465    fn eq(&self, other: &dyn AsCacheKeyRef) -> bool {
1466        self.as_cache_key_ref() == other.as_cache_key_ref()
1467    }
1468}
1469
1470impl Eq for dyn AsCacheKeyRef + '_ {}
1471
1472impl Hash for dyn AsCacheKeyRef + '_ {
1473    fn hash<H: Hasher>(&self, state: &mut H) {
1474        self.as_cache_key_ref().hash(state)
1475    }
1476}
1477
1478impl AsCacheKeyRef for CacheKey {
1479    fn as_cache_key_ref(&self) -> CacheKeyRef<'_> {
1480        CacheKeyRef {
1481            text: &self.text,
1482            font_size: self.font_size,
1483            runs: self.runs.as_slice(),
1484            options: self.options,
1485        }
1486    }
1487}
1488
1489impl<'a> Borrow<dyn AsCacheKeyRef + 'a> for Arc<CacheKey> {
1490    fn borrow(&self) -> &(dyn AsCacheKeyRef + 'a) {
1491        self.as_ref() as &dyn AsCacheKeyRef
1492    }
1493}
1494
1495impl AsCacheKeyRef for CacheKeyRef<'_> {
1496    fn as_cache_key_ref(&self) -> CacheKeyRef<'_> {
1497        *self
1498    }
1499}
1500
1501#[cfg(test)]
1502mod tests {
1503    use super::*;
1504    use crate::{point, px, size};
1505
1506    #[test]
1507    fn caret_selection_endpoint_ordering_uses_byte_indices() {
1508        for (caret, anchor, expected) in [
1509            (
1510                CaretPosition::attached_to_next_cluster(2),
1511                CaretPosition::attached_to_next_cluster(5),
1512                std::cmp::Ordering::Less,
1513            ),
1514            (
1515                CaretPosition::attached_to_next_cluster(5),
1516                CaretPosition::attached_to_previous_cluster(5),
1517                std::cmp::Ordering::Equal,
1518            ),
1519            (
1520                CaretPosition::attached_to_previous_cluster(8),
1521                CaretPosition::attached_to_next_cluster(5),
1522                std::cmp::Ordering::Greater,
1523            ),
1524        ] {
1525            let selection = CaretSelection { anchor, caret };
1526            assert_eq!(selection.endpoint_ordering(), expected);
1527        }
1528    }
1529
1530    #[test]
1531    fn paragraph_separators_match_unicode_bidi_class_b() {
1532        for character in [
1533            '\n', '\r', '\u{001c}', '\u{001d}', '\u{001e}', '\u{0085}', '\u{2029}',
1534        ] {
1535            assert!(is_paragraph_separator(character));
1536        }
1537
1538        for character in [' ', '\u{2028}'] {
1539            assert!(!is_paragraph_separator(character));
1540        }
1541    }
1542
1543    #[test]
1544    fn inserted_text_affinity_tracks_trailing_paragraph_separators() {
1545        for (text, expected) in [
1546            ("", CaretAffinity::Downstream),
1547            ("ordinary text", CaretAffinity::Upstream),
1548            ("\n", CaretAffinity::Downstream),
1549            ("\r", CaretAffinity::Downstream),
1550            ("\u{001c}", CaretAffinity::Downstream),
1551            ("\u{001d}", CaretAffinity::Downstream),
1552            ("\u{001e}", CaretAffinity::Downstream),
1553            ("\u{0085}", CaretAffinity::Downstream),
1554            ("\u{2028}", CaretAffinity::Downstream),
1555            ("\u{2029}", CaretAffinity::Downstream),
1556            ("\nordinary text", CaretAffinity::Upstream),
1557        ] {
1558            assert_eq!(CaretAffinity::from(text), expected, "From({text:?})");
1559            assert_eq!(
1560                CaretAffinity::for_inserted_text(text),
1561                expected,
1562                "for_inserted_text({text:?})"
1563            );
1564        }
1565    }
1566
1567    #[test]
1568    fn inline_box_alignment_preserves_unsorted_boxes_and_empty_lines() {
1569        let mut lines = [InlineVisualLine::default(); 3];
1570        let mut boxes =
1571            [(20, 2, 14.), (10, 0, 18.), (90, 3, 100.)].map(|(id, line_index, height)| {
1572                PositionedInlineBox {
1573                    id,
1574                    line_index,
1575                    bounds: Bounds::new(point(px(7.), px(99.)), size(px(5.), px(height))),
1576                }
1577            });
1578        let mut layout_size = size(px(100.), px(999.));
1579        let original_lines = lines;
1580        let original_boxes = boxes;
1581
1582        let requests = [InlineBoxRequest {
1583            id: 20,
1584            index: 0,
1585            size: boxes[0].bounds.size,
1586            vertical_align: VerticalAlign::Top,
1587        }];
1588        let metrics = InlineTextMetrics {
1589            ascent: px(8.),
1590            descent: px(2.),
1591            x_height: px(4.),
1592        };
1593
1594        align_inline_boxes(
1595            &mut lines,
1596            &mut boxes,
1597            &mut layout_size,
1598            &requests,
1599            &[metrics],
1600            &[(px(-7.), px(3.)), (px(-11.), px(4.))],
1601            metrics,
1602            px(12.),
1603        );
1604
1605        for ((line, mut expected_line), (origin_y, height, baseline)) in lines
1606            .iter()
1607            .zip(original_lines)
1608            .zip([(0., 21., 18.), (21., 15., 11.), (36., 14., 9.)])
1609        {
1610            expected_line.origin.y = px(origin_y);
1611            expected_line.size.height = px(height);
1612            expected_line.baseline = px(baseline);
1613            assert_eq!(*line, expected_line);
1614        }
1615
1616        for ((inline_box, mut expected_box), origin_y) in
1617            boxes.iter().zip(original_boxes).zip([36., 0., 99.])
1618        {
1619            expected_box.bounds.origin.y = px(origin_y);
1620            assert_eq!(*inline_box, expected_box);
1621        }
1622
1623        assert_eq!(layout_size, size(px(100.), px(50.)));
1624    }
1625}