Skip to main content

gpui_core/text_system/
line.rs

1use crate::{
2    App, Bounds, Half, Hsla, LineLayout, Pixels, Point, Result, SharedString, StrikethroughStyle,
3    TextAlign, TextShadow, UnderlineStyle, Window, WrapBoundary, WrappedLineLayout, black, fill,
4    point, px, size,
5};
6use derive_more::{Deref, DerefMut};
7use smallvec::SmallVec;
8use std::sync::Arc;
9
10/// Set the text decoration for a run of text.
11#[derive(Debug, Clone)]
12pub struct DecorationRun {
13    /// The length of the run in utf-8 bytes.
14    pub len: u32,
15
16    /// The color for this run
17    pub color: Hsla,
18
19    /// The background color for this run
20    pub background_color: Option<Hsla>,
21
22    /// The underline style for this run
23    pub underline: Option<UnderlineStyle>,
24
25    /// The strikethrough style for this run
26    pub strikethrough: Option<StrikethroughStyle>,
27}
28
29/// A line of text that has been shaped and decorated.
30#[derive(Clone, Default, Debug, Deref, DerefMut)]
31pub struct ShapedLine {
32    #[deref]
33    #[deref_mut]
34    pub(crate) layout: Arc<LineLayout>,
35    /// The text that was shaped for this line.
36    pub text: SharedString,
37    pub(crate) decoration_runs: SmallVec<[DecorationRun; 32]>,
38}
39
40impl ShapedLine {
41    /// Returns a forward-only cursor for this shaped line.
42    ///
43    /// Returns `None` for a line whose length was adjusted with
44    /// [`ShapedLine::with_len`] (e.g. when rendering invisibles): its glyph
45    /// indices no longer correspond to its text, so it cannot be split.
46    pub fn cursor(&self) -> Option<ShapedLineCursor<'_>> {
47        if self.len() != self.text.len() {
48            return None;
49        }
50        let byte_ordered = self
51            .layout
52            .runs
53            .iter()
54            .flat_map(|run| run.glyphs.iter().map(|glyph| glyph.index))
55            .is_sorted();
56        Some(ShapedLineCursor {
57            line: self,
58            unordered_remainder: (!byte_ordered).then(|| self.clone()),
59            byte_index: 0,
60            run_index: 0,
61            glyph_index: 0,
62            decoration_index: 0,
63            decoration_offset: 0,
64            x_offset: px(0.),
65        })
66    }
67
68    /// The length of the line in utf-8 bytes.
69    #[allow(clippy::len_without_is_empty)]
70    pub fn len(&self) -> usize {
71        self.layout.len
72    }
73
74    /// Override the len, useful if you're rendering text a
75    /// as text b (e.g. rendering invisibles).
76    pub fn with_len(mut self, len: usize) -> Self {
77        let layout = self.layout.as_ref();
78        self.layout = Arc::new(LineLayout {
79            font_size: layout.font_size,
80            width: layout.width,
81            ascent: layout.ascent,
82            descent: layout.descent,
83            runs: layout.runs.clone(),
84            len,
85        });
86        self
87    }
88
89    /// Paint the line of text to the window.
90    pub fn paint(
91        &self,
92        origin: Point<Pixels>,
93        line_height: Pixels,
94        window: &mut Window,
95        cx: &mut App,
96    ) -> Result<()> {
97        paint_line(
98            origin,
99            &self.layout,
100            line_height,
101            TextAlign::default(),
102            None,
103            &self.decoration_runs,
104            &[],
105            window,
106            cx,
107        )?;
108
109        Ok(())
110    }
111
112    /// Paint the background of the line to the window.
113    pub fn paint_background(
114        &self,
115        origin: Point<Pixels>,
116        line_height: Pixels,
117        window: &mut Window,
118        cx: &mut App,
119    ) -> Result<()> {
120        paint_line_background(
121            origin,
122            &self.layout,
123            line_height,
124            TextAlign::default(),
125            None,
126            &self.decoration_runs,
127            &[],
128            window,
129            cx,
130        )?;
131
132        Ok(())
133    }
134
135    /// Paint a shadow behind the text by rendering glyphs with offset and shadow color.
136    pub fn paint_shadow(
137        &self,
138        origin: Point<Pixels>,
139        line_height: Pixels,
140        shadow: &TextShadow,
141        window: &mut Window,
142        cx: &mut App,
143    ) -> Result<()> {
144        let shadow_runs: SmallVec<[DecorationRun; 32]> = self
145            .decoration_runs
146            .iter()
147            .map(|run| DecorationRun {
148                len: run.len,
149                color: shadow.color,
150                background_color: None,
151                underline: None,
152                strikethrough: None,
153            })
154            .collect();
155        let shadow_origin = point(origin.x + shadow.offset.x, origin.y + shadow.offset.y);
156        paint_line(
157            shadow_origin,
158            &self.layout,
159            line_height,
160            TextAlign::default(),
161            None,
162            &shadow_runs,
163            &[],
164            window,
165            cx,
166        )
167    }
168
169    /// Split this shaped line at a byte index, returning `(prefix, suffix)`.
170    ///
171    /// - `prefix` contains glyphs for bytes `[0, byte_index)` with original positions.
172    ///   Its width equals the x-advance up to the split point.
173    /// - `suffix` contains glyphs for bytes `[byte_index, len)` with positions
174    ///   shifted left so the first glyph starts at x=0, and byte indices rebased to 0.
175    /// - Decoration runs are partitioned at the boundary; a run that straddles it is
176    ///   split into two with adjusted lengths.
177    /// - `font_size`, `ascent`, and `descent` are copied to both halves.
178    pub fn split_at(&self, byte_index: usize) -> (ShapedLine, ShapedLine) {
179        let (left_layout, right_layout) = self.layout.split_at(byte_index);
180
181        // Partition decoration runs. A run straddling the boundary is split into two.
182        let mut left_decorations = SmallVec::new();
183        let mut right_decorations = SmallVec::new();
184        let mut decoration_offset = 0u32;
185        let split_point = byte_index as u32;
186
187        for decoration in &self.decoration_runs {
188            let run_end = decoration_offset + decoration.len;
189
190            if run_end <= split_point {
191                left_decorations.push(decoration.clone());
192            } else if decoration_offset >= split_point {
193                right_decorations.push(decoration.clone());
194            } else {
195                let left_len = split_point - decoration_offset;
196                let right_len = run_end - split_point;
197                left_decorations.push(DecorationRun {
198                    len: left_len,
199                    color: decoration.color,
200                    background_color: decoration.background_color,
201                    underline: decoration.underline,
202                    strikethrough: decoration.strikethrough,
203                });
204                right_decorations.push(DecorationRun {
205                    len: right_len,
206                    color: decoration.color,
207                    background_color: decoration.background_color,
208                    underline: decoration.underline,
209                    strikethrough: decoration.strikethrough,
210                });
211            }
212
213            decoration_offset = run_end;
214        }
215
216        // Split text
217        let left_text = if byte_index == self.text.len() {
218            self.text.clone()
219        } else {
220            SharedString::new(&self.text[..byte_index])
221        };
222        let right_text = if byte_index == 0 {
223            self.text.clone()
224        } else {
225            SharedString::new(&self.text[byte_index..])
226        };
227
228        let left = ShapedLine {
229            layout: Arc::new(left_layout),
230            text: left_text,
231            decoration_runs: left_decorations,
232        };
233
234        let right = ShapedLine {
235            layout: Arc::new(right_layout),
236            text: right_text,
237            decoration_runs: right_decorations,
238        };
239
240        (left, right)
241    }
242}
243
244/// Incrementally splits a [`ShapedLine`] at increasing UTF-8 byte boundaries.
245///
246/// Each piece preserves the original glyphs and decorations, rebased as in
247/// [`ShapedLine::split_at`]. Byte-ordered glyphs are advanced in linear time,
248/// copying each glyph and byte at most once; visually reordered glyphs fall
249/// back to the existing split operation.
250pub struct ShapedLineCursor<'a> {
251    line: &'a ShapedLine,
252    /// Bidirectional shaping can put glyphs out of byte order.
253    unordered_remainder: Option<ShapedLine>,
254    byte_index: usize,
255    run_index: usize,
256    glyph_index: usize,
257    decoration_index: usize,
258    decoration_offset: u32,
259    x_offset: Pixels,
260}
261
262impl<'a> ShapedLineCursor<'a> {
263    /// Takes the bytes since the previous boundary.
264    ///
265    /// Panics if the boundary precedes the previous one, exceeds the line's
266    /// length, or falls inside a UTF-8 character.
267    pub fn take_until(&mut self, byte_index: usize) -> ShapedLine {
268        assert!(
269            byte_index >= self.byte_index,
270            "split boundary moved backwards"
271        );
272        assert!(
273            byte_index <= self.line.len(),
274            "split boundary exceeds line length"
275        );
276        assert!(
277            self.line.text.is_char_boundary(byte_index),
278            "split boundary is not a UTF-8 character boundary"
279        );
280        let previous_index = self.byte_index;
281        let previous_x = self.x_offset;
282        if let Some(remainder) = &mut self.unordered_remainder {
283            let (piece, rest) = remainder.split_at(byte_index - previous_index);
284            *remainder = rest;
285            self.byte_index = byte_index;
286            self.x_offset = self.line.layout.x_for_index(byte_index);
287            return piece;
288        }
289        let mut runs = Vec::new();
290        let mut next_x = self.line.layout.width;
291        while let Some(run) = self.line.layout.runs.get(self.run_index) {
292            let start = self.glyph_index;
293            while let Some(glyph) = run.glyphs.get(self.glyph_index) {
294                if glyph.index >= byte_index {
295                    break;
296                }
297                self.glyph_index += 1;
298            }
299            let end = self.glyph_index;
300            if start < end {
301                runs.push(crate::ShapedRun {
302                    font_id: run.font_id,
303                    glyphs: run.glyphs[start..end]
304                        .iter()
305                        .map(|glyph| crate::ShapedGlyph {
306                            id: glyph.id,
307                            position: point(glyph.position.x - previous_x, glyph.position.y),
308                            index: glyph.index - previous_index,
309                            is_emoji: glyph.is_emoji,
310                        })
311                        .collect(),
312                });
313            }
314            if let Some(glyph) = run.glyphs.get(self.glyph_index) {
315                next_x = glyph.position.x;
316                break;
317            }
318            self.run_index += 1;
319            self.glyph_index = 0;
320        }
321        let mut decorations = SmallVec::new();
322        while let Some(decoration) = self.line.decoration_runs.get(self.decoration_index)
323            && (self.decoration_offset < byte_index as u32
324                || (decoration.len == 0 && self.decoration_offset == byte_index as u32))
325        {
326            let end = self.decoration_offset + decoration.len;
327            let start = self.decoration_offset.max(previous_index as u32);
328            let len = end.min(byte_index as u32) - start;
329            if len > 0 || decoration.len == 0 {
330                decorations.push(DecorationRun {
331                    len,
332                    color: decoration.color,
333                    background_color: decoration.background_color,
334                    underline: decoration.underline,
335                    strikethrough: decoration.strikethrough,
336                });
337            }
338            if end <= byte_index as u32 {
339                self.decoration_index += 1;
340                self.decoration_offset = end;
341            } else {
342                break;
343            }
344        }
345        self.byte_index = byte_index;
346        self.x_offset = next_x;
347        ShapedLine {
348            layout: Arc::new(LineLayout {
349                font_size: self.line.layout.font_size,
350                width: next_x - previous_x,
351                ascent: self.line.layout.ascent,
352                descent: self.line.layout.descent,
353                runs,
354                len: byte_index - previous_index,
355            }),
356            text: SharedString::new(&self.line.text[previous_index..byte_index]),
357            decoration_runs: decorations,
358        }
359    }
360
361    /// Returns the original line's x position at the current boundary.
362    pub fn x_offset(&self) -> Pixels {
363        self.x_offset
364    }
365}
366
367impl LineLayout {
368    /// Paint this layout to the window, using the given decoration runs to color
369    /// glyphs and draw underlines and strikethroughs.
370    ///
371    /// This is a lower-level alternative to [`ShapedLine::paint`] for callers that
372    /// hold a bare layout and track decorations themselves.
373    pub fn paint(
374        &self,
375        origin: Point<Pixels>,
376        line_height: Pixels,
377        align: TextAlign,
378        align_width: Option<Pixels>,
379        decoration_runs: &[DecorationRun],
380        window: &mut Window,
381        cx: &mut App,
382    ) -> Result<()> {
383        paint_line(
384            origin,
385            self,
386            line_height,
387            align,
388            align_width,
389            decoration_runs,
390            &[],
391            window,
392            cx,
393        )
394    }
395
396    /// Paint the background of this layout to the window, using the given
397    /// decoration runs to determine background colors.
398    ///
399    /// This is a lower-level alternative to [`ShapedLine::paint_background`] for
400    /// callers that hold a bare layout and track decorations themselves.
401    pub fn paint_background(
402        &self,
403        origin: Point<Pixels>,
404        line_height: Pixels,
405        align: TextAlign,
406        align_width: Option<Pixels>,
407        decoration_runs: &[DecorationRun],
408        window: &mut Window,
409        cx: &mut App,
410    ) -> Result<()> {
411        paint_line_background(
412            origin,
413            self,
414            line_height,
415            align,
416            align_width,
417            decoration_runs,
418            &[],
419            window,
420            cx,
421        )
422    }
423}
424
425/// A line of text that has been shaped, decorated, and wrapped by the text layout system.
426#[derive(Clone, Default, Debug, Deref, DerefMut)]
427pub struct WrappedLine {
428    #[deref]
429    #[deref_mut]
430    pub(crate) layout: Arc<WrappedLineLayout>,
431    /// The text that was shaped for this line.
432    pub text: SharedString,
433    pub(crate) decoration_runs: SmallVec<[DecorationRun; 32]>,
434}
435
436impl WrappedLine {
437    /// The length of the underlying, unwrapped layout, in utf-8 bytes.
438    #[allow(clippy::len_without_is_empty)]
439    pub fn len(&self) -> usize {
440        self.layout.len()
441    }
442
443    /// Paint this line of text to the window.
444    pub fn paint(
445        &self,
446        origin: Point<Pixels>,
447        line_height: Pixels,
448        align: TextAlign,
449        bounds: Option<Bounds<Pixels>>,
450        window: &mut Window,
451        cx: &mut App,
452    ) -> Result<()> {
453        let align_width = match bounds {
454            Some(bounds) => Some(bounds.size.width),
455            None => self.layout.wrap_width,
456        };
457
458        paint_line(
459            origin,
460            &self.layout.unwrapped_layout,
461            line_height,
462            align,
463            align_width,
464            &self.decoration_runs,
465            &self.wrap_boundaries,
466            window,
467            cx,
468        )?;
469
470        Ok(())
471    }
472
473    /// Paint the background of line of text to the window.
474    pub fn paint_background(
475        &self,
476        origin: Point<Pixels>,
477        line_height: Pixels,
478        align: TextAlign,
479        bounds: Option<Bounds<Pixels>>,
480        window: &mut Window,
481        cx: &mut App,
482    ) -> Result<()> {
483        let align_width = match bounds {
484            Some(bounds) => Some(bounds.size.width),
485            None => self.layout.wrap_width,
486        };
487
488        paint_line_background(
489            origin,
490            &self.layout.unwrapped_layout,
491            line_height,
492            align,
493            align_width,
494            &self.decoration_runs,
495            &self.wrap_boundaries,
496            window,
497            cx,
498        )?;
499
500        Ok(())
501    }
502
503    /// Paint a shadow behind the text by rendering glyphs with offset and shadow color.
504    pub fn paint_shadow(
505        &self,
506        origin: Point<Pixels>,
507        line_height: Pixels,
508        align: TextAlign,
509        bounds: Option<Bounds<Pixels>>,
510        shadow: &TextShadow,
511        window: &mut Window,
512        cx: &mut App,
513    ) -> Result<()> {
514        let align_width = match bounds {
515            Some(bounds) => Some(bounds.size.width),
516            None => self.layout.wrap_width,
517        };
518        let shadow_runs: SmallVec<[DecorationRun; 32]> = self
519            .decoration_runs
520            .iter()
521            .map(|run| DecorationRun {
522                len: run.len,
523                color: shadow.color,
524                background_color: None,
525                underline: None,
526                strikethrough: None,
527            })
528            .collect();
529        let shadow_origin = point(origin.x + shadow.offset.x, origin.y + shadow.offset.y);
530        paint_line(
531            shadow_origin,
532            &self.layout.unwrapped_layout,
533            line_height,
534            align,
535            align_width,
536            &shadow_runs,
537            &self.wrap_boundaries,
538            window,
539            cx,
540        )
541    }
542}
543
544fn paint_line(
545    origin: Point<Pixels>,
546    layout: &LineLayout,
547    line_height: Pixels,
548    align: TextAlign,
549    align_width: Option<Pixels>,
550    decoration_runs: &[DecorationRun],
551    wrap_boundaries: &[WrapBoundary],
552    window: &mut Window,
553    cx: &mut App,
554) -> Result<()> {
555    let line_bounds = line_paint_bounds(
556        origin,
557        layout,
558        line_height,
559        align,
560        align_width,
561        wrap_boundaries,
562    );
563    window.paint_layer(line_bounds, |window| {
564        let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
565        let baseline_offset = point(px(0.), padding_top + layout.ascent);
566        let mut decoration_runs = decoration_runs.iter();
567        let mut wraps = wrap_boundaries.iter().peekable();
568        let mut run_end = 0;
569        let mut color = black();
570        let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
571        let mut current_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
572        let text_system = cx.text_system().clone();
573        let mut glyph_origin = point(
574            aligned_origin_x(
575                origin,
576                align_width.unwrap_or(layout.width),
577                px(0.0),
578                &align,
579                layout,
580                wraps.peek(),
581            ),
582            origin.y,
583        );
584        let mut prev_glyph_position = Point::default();
585        let mut max_glyph_size = size(px(0.), px(0.));
586        let mut first_glyph_x = origin.x;
587        for (run_ix, run) in layout.runs.iter().enumerate() {
588            max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
589
590            for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
591                glyph_origin.x += glyph.position.x - prev_glyph_position.x;
592                if glyph_ix == 0 && run_ix == 0 {
593                    first_glyph_x = glyph_origin.x;
594                }
595
596                if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
597                    wraps.next();
598                    if let Some((underline_origin, underline_style)) = current_underline.as_mut() {
599                        if glyph_origin.x == underline_origin.x {
600                            underline_origin.x -= max_glyph_size.width.half();
601                        };
602                        window.paint_underline(
603                            *underline_origin,
604                            glyph_origin.x - underline_origin.x,
605                            underline_style,
606                        );
607                        if glyph.index < run_end {
608                            underline_origin.x = origin.x;
609                            underline_origin.y += line_height;
610                        } else {
611                            current_underline = None;
612                        }
613                    }
614                    if let Some((strikethrough_origin, strikethrough_style)) =
615                        current_strikethrough.as_mut()
616                    {
617                        if glyph_origin.x == strikethrough_origin.x {
618                            strikethrough_origin.x -= max_glyph_size.width.half();
619                        };
620                        window.paint_strikethrough(
621                            *strikethrough_origin,
622                            glyph_origin.x - strikethrough_origin.x,
623                            strikethrough_style,
624                        );
625                        if glyph.index < run_end {
626                            strikethrough_origin.x = origin.x;
627                            strikethrough_origin.y += line_height;
628                        } else {
629                            current_strikethrough = None;
630                        }
631                    }
632
633                    glyph_origin.x = aligned_origin_x(
634                        origin,
635                        align_width.unwrap_or(layout.width),
636                        glyph.position.x,
637                        &align,
638                        layout,
639                        wraps.peek(),
640                    );
641                    glyph_origin.y += line_height;
642                }
643                prev_glyph_position = glyph.position;
644
645                let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
646                let mut finished_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
647                if glyph.index >= run_end {
648                    let mut style_run = decoration_runs.next();
649
650                    // ignore style runs that apply to a partial glyph
651                    while let Some(run) = style_run {
652                        if glyph.index < run_end + (run.len as usize) {
653                            break;
654                        }
655                        run_end += run.len as usize;
656                        style_run = decoration_runs.next();
657                    }
658
659                    if let Some(style_run) = style_run {
660                        if let Some((_, underline_style)) = &mut current_underline
661                            && style_run.underline.as_ref() != Some(underline_style)
662                        {
663                            finished_underline = current_underline.take();
664                        }
665                        if let Some(run_underline) = style_run.underline.as_ref() {
666                            current_underline.get_or_insert((
667                                point(
668                                    glyph_origin.x,
669                                    glyph_origin.y + baseline_offset.y + (layout.descent * 0.618),
670                                ),
671                                UnderlineStyle {
672                                    color: Some(run_underline.color.unwrap_or(style_run.color)),
673                                    thickness: run_underline.thickness,
674                                    wavy: run_underline.wavy,
675                                },
676                            ));
677                        }
678                        if let Some((_, strikethrough_style)) = &mut current_strikethrough
679                            && style_run.strikethrough.as_ref() != Some(strikethrough_style)
680                        {
681                            finished_strikethrough = current_strikethrough.take();
682                        }
683                        if let Some(run_strikethrough) = style_run.strikethrough.as_ref() {
684                            current_strikethrough.get_or_insert((
685                                point(
686                                    glyph_origin.x,
687                                    glyph_origin.y
688                                        + (((layout.ascent * 0.5) + baseline_offset.y) * 0.5),
689                                ),
690                                StrikethroughStyle {
691                                    color: Some(run_strikethrough.color.unwrap_or(style_run.color)),
692                                    thickness: run_strikethrough.thickness,
693                                },
694                            ));
695                        }
696
697                        run_end += style_run.len as usize;
698                        color = style_run.color;
699                    } else {
700                        run_end = layout.len;
701                        finished_underline = current_underline.take();
702                        finished_strikethrough = current_strikethrough.take();
703                    }
704                }
705
706                if let Some((mut underline_origin, underline_style)) = finished_underline {
707                    if underline_origin.x == glyph_origin.x {
708                        underline_origin.x -= max_glyph_size.width.half();
709                    };
710                    window.paint_underline(
711                        underline_origin,
712                        glyph_origin.x - underline_origin.x,
713                        &underline_style,
714                    );
715                }
716
717                if let Some((mut strikethrough_origin, strikethrough_style)) =
718                    finished_strikethrough
719                {
720                    if strikethrough_origin.x == glyph_origin.x {
721                        strikethrough_origin.x -= max_glyph_size.width.half();
722                    };
723                    window.paint_strikethrough(
724                        strikethrough_origin,
725                        glyph_origin.x - strikethrough_origin.x,
726                        &strikethrough_style,
727                    );
728                }
729
730                let max_glyph_bounds = Bounds {
731                    origin: glyph_origin,
732                    size: max_glyph_size,
733                };
734
735                let content_mask = window.content_mask();
736                if max_glyph_bounds.intersects(&content_mask.bounds) {
737                    if glyph.is_emoji {
738                        window.paint_emoji(
739                            glyph_origin + baseline_offset,
740                            run.font_id,
741                            glyph.id,
742                            layout.font_size,
743                        )?;
744                    } else {
745                        window.paint_glyph(
746                            glyph_origin + baseline_offset,
747                            run.font_id,
748                            glyph.id,
749                            layout.font_size,
750                            color,
751                        )?;
752                    }
753                }
754            }
755        }
756
757        let mut last_line_end_x = first_glyph_x + layout.width;
758        if let Some(boundary_x) = wrap_boundaries
759            .last()
760            .and_then(|boundary| wrap_boundary_x(layout, boundary))
761        {
762            last_line_end_x -= boundary_x;
763        }
764
765        if let Some((mut underline_start, underline_style)) = current_underline.take() {
766            if last_line_end_x == underline_start.x {
767                underline_start.x -= max_glyph_size.width.half()
768            };
769            window.paint_underline(
770                underline_start,
771                last_line_end_x - underline_start.x,
772                &underline_style,
773            );
774        }
775
776        if let Some((mut strikethrough_start, strikethrough_style)) = current_strikethrough.take() {
777            if last_line_end_x == strikethrough_start.x {
778                strikethrough_start.x -= max_glyph_size.width.half()
779            };
780            window.paint_strikethrough(
781                strikethrough_start,
782                last_line_end_x - strikethrough_start.x,
783                &strikethrough_style,
784            );
785        }
786
787        Ok(())
788    })
789}
790
791fn paint_line_background(
792    origin: Point<Pixels>,
793    layout: &LineLayout,
794    line_height: Pixels,
795    align: TextAlign,
796    align_width: Option<Pixels>,
797    decoration_runs: &[DecorationRun],
798    wrap_boundaries: &[WrapBoundary],
799    window: &mut Window,
800    cx: &mut App,
801) -> Result<()> {
802    let line_bounds = line_paint_bounds(
803        origin,
804        layout,
805        line_height,
806        align,
807        align_width,
808        wrap_boundaries,
809    );
810    window.paint_layer(line_bounds, |window| {
811        let mut decoration_runs = decoration_runs.iter();
812        let mut wraps = wrap_boundaries.iter().peekable();
813        let mut run_end = 0;
814        let mut current_background: Option<(Point<Pixels>, Hsla)> = None;
815        let text_system = cx.text_system().clone();
816        let mut glyph_origin = point(
817            aligned_origin_x(
818                origin,
819                align_width.unwrap_or(layout.width),
820                px(0.0),
821                &align,
822                layout,
823                wraps.peek(),
824            ),
825            origin.y,
826        );
827        let mut prev_glyph_position = Point::default();
828        let mut max_glyph_size = size(px(0.), px(0.));
829        for (run_ix, run) in layout.runs.iter().enumerate() {
830            max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
831
832            for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
833                glyph_origin.x += glyph.position.x - prev_glyph_position.x;
834
835                if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
836                    wraps.next();
837                    if let Some((background_origin, background_color)) = current_background.as_mut()
838                    {
839                        if glyph_origin.x == background_origin.x {
840                            background_origin.x -= max_glyph_size.width.half()
841                        }
842                        window.paint_quad(fill(
843                            Bounds {
844                                origin: *background_origin,
845                                size: size(glyph_origin.x - background_origin.x, line_height),
846                            },
847                            *background_color,
848                        ));
849                        if glyph.index < run_end {
850                            background_origin.x = origin.x;
851                            background_origin.y += line_height;
852                        } else {
853                            current_background = None;
854                        }
855                    }
856
857                    glyph_origin.x = aligned_origin_x(
858                        origin,
859                        align_width.unwrap_or(layout.width),
860                        glyph.position.x,
861                        &align,
862                        layout,
863                        wraps.peek(),
864                    );
865                    glyph_origin.y += line_height;
866                }
867                prev_glyph_position = glyph.position;
868
869                let mut finished_background: Option<(Point<Pixels>, Hsla)> = None;
870                if glyph.index >= run_end {
871                    let mut style_run = decoration_runs.next();
872
873                    // ignore style runs that apply to a partial glyph
874                    while let Some(run) = style_run {
875                        if glyph.index < run_end + (run.len as usize) {
876                            break;
877                        }
878                        run_end += run.len as usize;
879                        style_run = decoration_runs.next();
880                    }
881
882                    if let Some(style_run) = style_run {
883                        if let Some((_, background_color)) = &mut current_background
884                            && style_run.background_color.as_ref() != Some(background_color)
885                        {
886                            finished_background = current_background.take();
887                        }
888                        if let Some(run_background) = style_run.background_color {
889                            current_background.get_or_insert((
890                                point(glyph_origin.x, glyph_origin.y),
891                                run_background,
892                            ));
893                        }
894                        run_end += style_run.len as usize;
895                    } else {
896                        run_end = layout.len;
897                        finished_background = current_background.take();
898                    }
899                }
900
901                if let Some((mut background_origin, background_color)) = finished_background {
902                    let mut width = glyph_origin.x - background_origin.x;
903                    if background_origin.x == glyph_origin.x {
904                        background_origin.x -= max_glyph_size.width.half();
905                    };
906                    window.paint_quad(fill(
907                        Bounds {
908                            origin: background_origin,
909                            size: size(width, line_height),
910                        },
911                        background_color,
912                    ));
913                }
914            }
915        }
916
917        let mut last_line_end_x = origin.x + layout.width;
918        if let Some(boundary_x) = wrap_boundaries
919            .last()
920            .and_then(|boundary| wrap_boundary_x(layout, boundary))
921        {
922            last_line_end_x -= boundary_x;
923        }
924
925        if let Some((mut background_origin, background_color)) = current_background.take() {
926            if last_line_end_x == background_origin.x {
927                background_origin.x -= max_glyph_size.width.half()
928            };
929            window.paint_quad(fill(
930                Bounds {
931                    origin: background_origin,
932                    size: size(last_line_end_x - background_origin.x, line_height),
933                },
934                background_color,
935            ));
936        }
937
938        Ok(())
939    })
940}
941
942fn line_paint_bounds(
943    origin: Point<Pixels>,
944    layout: &LineLayout,
945    line_height: Pixels,
946    align: TextAlign,
947    align_width: Option<Pixels>,
948    wrap_boundaries: &[WrapBoundary],
949) -> Bounds<Pixels> {
950    let mut bounds = Bounds::new(
951        origin,
952        size(
953            layout.width,
954            line_height * (wrap_boundaries.len() as f32 + 1.),
955        ),
956    );
957    if align == TextAlign::Left || layout.len == 0 {
958        return bounds;
959    }
960
961    let align_width = align_width.unwrap_or(layout.width);
962    let mut row_start = Pixels::ZERO;
963    let row_ends = wrap_boundaries
964        .iter()
965        .filter_map(|boundary| wrap_boundary_x(layout, boundary))
966        .chain([layout.width]);
967    for (row, row_end) in row_ends.enumerate() {
968        let width = row_end - row_start;
969        let offset = match align {
970            TextAlign::Left => Pixels::ZERO,
971            TextAlign::Center => (align_width - width) / 2.,
972            TextAlign::Right => align_width - width,
973        };
974        bounds = bounds.union(&Bounds::new(
975            point(origin.x + offset, origin.y + line_height * row as f32),
976            size(width, line_height),
977        ));
978        row_start = row_end;
979    }
980    bounds
981}
982
983/// The x position of a wrap boundary's glyph, or `None` for a boundary that
984/// indexes outside the layout (e.g. a stale boundary); paint must not panic.
985fn wrap_boundary_x(layout: &LineLayout, boundary: &WrapBoundary) -> Option<Pixels> {
986    layout
987        .runs
988        .get(boundary.run_ix)
989        .and_then(|run| run.glyphs.get(boundary.glyph_ix))
990        .map(|glyph| glyph.position.x)
991}
992
993fn aligned_origin_x(
994    origin: Point<Pixels>,
995    align_width: Pixels,
996    last_glyph_x: Pixels,
997    align: &TextAlign,
998    layout: &LineLayout,
999    wrap_boundary: Option<&&WrapBoundary>,
1000) -> Pixels {
1001    let end_of_line = wrap_boundary
1002        .and_then(|boundary| wrap_boundary_x(layout, boundary))
1003        .unwrap_or(layout.width);
1004
1005    let line_width = end_of_line - last_glyph_x;
1006
1007    match align {
1008        TextAlign::Left => origin.x,
1009        TextAlign::Center => (origin.x * 2.0 + align_width - line_width) / 2.0,
1010        TextAlign::Right => origin.x + align_width - line_width,
1011    }
1012}
1013
1014#[cfg(test)]
1015mod tests {
1016    use super::*;
1017    use crate::{FontId, GlyphId, ShapedGlyph, ShapedRun};
1018
1019    /// Build a [`ShapedLine`] from glyph descriptors without the platform text system.
1020    /// Each glyph is `(byte_index, x_position)`.
1021    fn make_shaped_line(
1022        text: &str,
1023        glyphs: &[(usize, f32)],
1024        width: f32,
1025        decorations: &[DecorationRun],
1026    ) -> ShapedLine {
1027        let shaped_glyphs: Vec<ShapedGlyph> = glyphs
1028            .iter()
1029            .map(|&(index, x)| ShapedGlyph {
1030                id: GlyphId(0),
1031                position: point(px(x), px(0.0)),
1032                index,
1033                is_emoji: false,
1034            })
1035            .collect();
1036
1037        ShapedLine {
1038            layout: Arc::new(LineLayout {
1039                font_size: px(16.0),
1040                width: px(width),
1041                ascent: px(12.0),
1042                descent: px(4.0),
1043                runs: vec![ShapedRun {
1044                    font_id: FontId(0),
1045                    glyphs: shaped_glyphs,
1046                }],
1047                len: text.len(),
1048            }),
1049            text: SharedString::new(text),
1050            decoration_runs: SmallVec::from(decorations.to_vec()),
1051        }
1052    }
1053
1054    #[test]
1055    fn test_aligned_line_paint_bounds() {
1056        let line = make_shaped_line("abcd", &[(0, 0.), (1, 10.), (2, 20.), (3, 30.)], 40., &[]);
1057        let origin = point(px(10.), px(20.));
1058        let line_height = px(16.);
1059        for (wrapped, align_width, expected_edges) in [
1060            (false, None, [(10., 50.), (10., 50.), (10., 50.)]),
1061            (true, None, [(10., 50.), (10., 50.), (10., 50.)]),
1062            (false, Some(40.), [(10., 50.), (10., 50.), (10., 50.)]),
1063            (true, Some(40.), [(10., 50.), (10., 50.), (10., 50.)]),
1064            (false, Some(100.), [(10., 50.), (10., 80.), (10., 110.)]),
1065            (true, Some(100.), [(10., 50.), (10., 70.), (10., 110.)]),
1066            (false, Some(10.), [(10., 50.), (-5., 50.), (-20., 50.)]),
1067            (true, Some(10.), [(10., 50.), (5., 50.), (0., 50.)]),
1068            (false, Some(0.), [(10., 50.), (-10., 50.), (-30., 50.)]),
1069            (true, Some(0.), [(10., 50.), (0., 50.), (-10., 50.)]),
1070        ] {
1071            let boundaries = if wrapped {
1072                vec![WrapBoundary {
1073                    run_ix: 0,
1074                    glyph_ix: 2,
1075                }]
1076            } else {
1077                Vec::new()
1078            };
1079            for (align, (left, right)) in [TextAlign::Left, TextAlign::Center, TextAlign::Right]
1080                .into_iter()
1081                .zip(expected_edges)
1082            {
1083                assert_eq!(
1084                    line_paint_bounds(
1085                        origin,
1086                        &line.layout,
1087                        line_height,
1088                        align,
1089                        align_width.map(px),
1090                        &boundaries,
1091                    ),
1092                    Bounds::new(
1093                        point(px(left), origin.y),
1094                        size(px(right - left), px(if wrapped { 32. } else { 16. })),
1095                    ),
1096                    "wrapped={wrapped}, align={align:?}, align_width={align_width:?}",
1097                );
1098            }
1099        }
1100        let empty = make_shaped_line("", &[], 0., &[]);
1101        for align in [TextAlign::Left, TextAlign::Center, TextAlign::Right] {
1102            assert_eq!(
1103                line_paint_bounds(
1104                    origin,
1105                    &empty.layout,
1106                    line_height,
1107                    align,
1108                    Some(px(100.)),
1109                    &[]
1110                ),
1111                Bounds::new(origin, size(Pixels::ZERO, line_height)),
1112            );
1113        }
1114    }
1115
1116    #[test]
1117    fn test_cursor_preserves_shaping_metadata_across_runs() {
1118        let line = ShapedLine {
1119            layout: Arc::new(LineLayout {
1120                font_size: px(16.0),
1121                width: px(50.0),
1122                ascent: px(12.0),
1123                descent: px(4.0),
1124                runs: vec![
1125                    ShapedRun {
1126                        font_id: FontId(3),
1127                        glyphs: vec![
1128                            ShapedGlyph {
1129                                id: GlyphId(11),
1130                                position: point(px(0.0), px(1.0)),
1131                                index: 0,
1132                                is_emoji: true,
1133                            },
1134                            ShapedGlyph {
1135                                id: GlyphId(12),
1136                                position: point(px(17.0), px(1.0)),
1137                                index: 1,
1138                                is_emoji: false,
1139                            },
1140                            ShapedGlyph {
1141                                id: GlyphId(13),
1142                                position: point(px(19.0), px(-1.0)),
1143                                index: 1,
1144                                is_emoji: false,
1145                            },
1146                        ],
1147                    },
1148                    ShapedRun {
1149                        font_id: FontId(8),
1150                        glyphs: vec![
1151                            ShapedGlyph {
1152                                id: GlyphId(21),
1153                                position: point(px(25.0), px(1.0)),
1154                                index: 5,
1155                                is_emoji: true,
1156                            },
1157                            ShapedGlyph {
1158                                id: GlyphId(22),
1159                                position: point(px(41.0), px(1.0)),
1160                                index: 7,
1161                                is_emoji: false,
1162                            },
1163                        ],
1164                    },
1165                ],
1166                len: 10,
1167            }),
1168            text: "a😀bcdef".into(),
1169            decoration_runs: SmallVec::new(),
1170        };
1171        let mut cursor = line.cursor().unwrap();
1172        let first = cursor.take_until(5);
1173        assert_eq!(first.text.as_ref(), "a😀");
1174        assert_eq!(first.runs[0].font_id, FontId(3));
1175        assert_eq!(first.runs[0].glyphs[0].id, GlyphId(11));
1176        assert!(first.runs[0].glyphs[0].is_emoji);
1177        assert_eq!(first.runs[0].glyphs[1].index, 1);
1178        assert_eq!(first.runs[0].glyphs[1].position, point(px(17.0), px(1.0)));
1179        assert_eq!(first.runs[0].glyphs.len(), 3);
1180        assert_eq!(first.runs[0].glyphs[2].index, 1);
1181        assert_eq!(first.runs[0].glyphs[2].position, point(px(19.0), px(-1.0)));
1182        assert_eq!(cursor.x_offset(), px(25.0));
1183
1184        let second = cursor.take_until(7);
1185        assert_eq!(second.text.as_ref(), "bc");
1186        assert_eq!(second.runs[0].font_id, FontId(8));
1187        assert_eq!(second.runs[0].glyphs[0].id, GlyphId(21));
1188        assert_eq!(second.runs[0].glyphs[0].index, 0);
1189        assert_eq!(second.runs[0].glyphs[0].position, point(px(0.0), px(1.0)));
1190        assert_eq!(cursor.x_offset(), px(41.0));
1191
1192        let final_part = cursor.take_until(10);
1193        assert_eq!(final_part.text.as_ref(), "def");
1194        assert_eq!(final_part.runs[0].font_id, FontId(8));
1195        assert_eq!(final_part.runs[0].glyphs[0].id, GlyphId(22));
1196        assert_eq!(final_part.runs[0].glyphs[0].index, 0);
1197        assert_eq!(
1198            final_part.runs[0].glyphs[0].position,
1199            point(px(0.0), px(1.0))
1200        );
1201    }
1202
1203    #[test]
1204    fn test_cursor_preserves_existing_visual_order_splitting() {
1205        let line = make_shaped_line("abc", &[(0, 0.0), (2, 10.0), (1, 20.0)], 30.0, &[]);
1206        let mut cursor = line.cursor().unwrap();
1207        let mut remainder = line.clone();
1208        let mut previous_boundary = 0;
1209        for boundary in [0, 1, 2, 3] {
1210            let (expected, rest) = remainder.split_at(boundary - previous_boundary);
1211            let actual = cursor.take_until(boundary);
1212            assert_eq!(actual.text, expected.text);
1213            assert_eq!(actual.width, expected.width);
1214            assert_eq!(actual.runs.len(), expected.runs.len());
1215            for (actual, expected) in actual.runs.iter().zip(&expected.runs) {
1216                assert_eq!(actual.font_id, expected.font_id);
1217                assert_eq!(actual.glyphs.len(), expected.glyphs.len());
1218                for (actual, expected) in actual.glyphs.iter().zip(&expected.glyphs) {
1219                    assert_eq!(actual.id, expected.id);
1220                    assert_eq!(actual.index, expected.index);
1221                    assert_eq!(actual.position, expected.position);
1222                }
1223            }
1224            assert_eq!(cursor.x_offset(), line.x_for_index(boundary));
1225            remainder = rest;
1226            previous_boundary = boundary;
1227        }
1228    }
1229
1230    #[test]
1231    fn test_cursor_partitions_one_decoration_across_three_chunks() {
1232        let line = make_shaped_line(
1233            "abcdef",
1234            &[
1235                (0, 0.0),
1236                (1, 10.0),
1237                (2, 20.0),
1238                (3, 30.0),
1239                (4, 40.0),
1240                (5, 50.0),
1241            ],
1242            60.0,
1243            &[DecorationRun {
1244                len: 6,
1245                color: Hsla {
1246                    h: 0.2,
1247                    s: 0.4,
1248                    l: 0.6,
1249                    a: 1.0,
1250                },
1251                background_color: None,
1252                underline: None,
1253                strikethrough: None,
1254            }],
1255        );
1256        let mut cursor = line.cursor().unwrap();
1257        assert_eq!(cursor.take_until(2).decoration_runs[0].len, 2);
1258        assert_eq!(cursor.take_until(4).decoration_runs[0].len, 2);
1259        assert_eq!(cursor.take_until(6).decoration_runs[0].len, 2);
1260    }
1261
1262    #[test]
1263    fn test_cursor_matches_successive_splits_at_ordered_boundaries() {
1264        let decorations: Vec<_> = [2, 0, 3, 1]
1265            .into_iter()
1266            .map(|len| DecorationRun {
1267                len,
1268                color: Hsla {
1269                    h: len as f32 / 10.0,
1270                    s: 0.5,
1271                    l: 0.5,
1272                    a: 1.0,
1273                },
1274                background_color: Some(black()),
1275                underline: None,
1276                strikethrough: None,
1277            })
1278            .collect();
1279        let line = make_shaped_line(
1280            "abcdef",
1281            &[(0, 5.0), (0, 5.0), (2, 15.0), (4, 25.0), (5, 35.0)],
1282            45.0,
1283            &decorations,
1284        );
1285        for first in 0..=line.len() {
1286            for second in first..=line.len() {
1287                let mut cursor = line.cursor().unwrap();
1288                let mut remainder = line.clone();
1289                let mut previous_boundary = 0;
1290                let mut total_width = px(0.0);
1291                let mut text = String::new();
1292                for boundary in [first, second, line.len(), line.len()] {
1293                    let (expected, rest) = remainder.split_at(boundary - previous_boundary);
1294                    let actual = cursor.take_until(boundary);
1295                    assert_eq!(actual.text, expected.text);
1296                    assert_eq!(actual.len(), expected.len());
1297                    assert_eq!(actual.width, expected.width);
1298                    assert_eq!(actual.runs.len(), expected.runs.len());
1299                    for (actual, expected) in actual.runs.iter().zip(&expected.runs) {
1300                        assert_eq!(actual.font_id, expected.font_id);
1301                        assert_eq!(actual.glyphs.len(), expected.glyphs.len());
1302                        for (actual, expected) in actual.glyphs.iter().zip(&expected.glyphs) {
1303                            assert_eq!(actual.id, expected.id);
1304                            assert_eq!(actual.index, expected.index);
1305                            assert_eq!(actual.position, expected.position);
1306                        }
1307                    }
1308                    assert_eq!(actual.decoration_runs.len(), expected.decoration_runs.len());
1309                    for (actual, expected) in
1310                        actual.decoration_runs.iter().zip(&expected.decoration_runs)
1311                    {
1312                        assert_eq!(actual.len, expected.len);
1313                        assert_eq!(actual.color, expected.color);
1314                        assert_eq!(actual.background_color, expected.background_color);
1315                    }
1316                    total_width += actual.width;
1317                    text.push_str(&actual.text);
1318                    remainder = rest;
1319                    previous_boundary = boundary;
1320                }
1321                assert_eq!(total_width, line.width);
1322                assert_eq!(text, line.text.as_ref());
1323            }
1324        }
1325    }
1326
1327    #[test]
1328    fn test_cursor_empty_chunks_and_repeated_boundaries() {
1329        let line = make_shaped_line("ab", &[(0, 5.0), (1, 15.0)], 20.0, &[]);
1330        let mut cursor = line.cursor().unwrap();
1331        assert_eq!(cursor.take_until(0).text.as_ref(), "");
1332        assert_eq!(cursor.take_until(0).text.as_ref(), "");
1333        assert_eq!(cursor.take_until(1).text.as_ref(), "a");
1334        assert_eq!(cursor.take_until(2).text.as_ref(), "b");
1335        assert_eq!(cursor.take_until(2).text.as_ref(), "");
1336        let empty = make_shaped_line("", &[], 0.0, &[]);
1337        let piece = empty.cursor().unwrap().take_until(0);
1338        assert!(piece.text.is_empty());
1339        assert!(piece.runs.is_empty());
1340        assert_eq!(piece.width, px(0.0));
1341    }
1342
1343    #[test]
1344    fn cursor_rejects_lines_with_adjusted_length() {
1345        let line = make_shaped_line("ab", &[(0, 5.0), (1, 15.0)], 20.0, &[]);
1346        assert!(line.clone().cursor().is_some());
1347        // `with_len` desynchronizes glyph indices from the text, so the line
1348        // cannot be split and `cursor` declines instead of panicking.
1349        assert!(line.with_len(4).cursor().is_none());
1350    }
1351
1352    #[test]
1353    fn test_cursor_rejects_invalid_boundaries() {
1354        let line = make_shaped_line("é", &[(0, 0.0)], 10.0, &[]);
1355        let mut cursor = line.cursor().unwrap();
1356        assert!(
1357            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1358                cursor.take_until(1);
1359            }))
1360            .is_err()
1361        );
1362        let mut cursor = line.cursor().unwrap();
1363        cursor.take_until(2);
1364        assert!(
1365            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1366                cursor.take_until(0);
1367            }))
1368            .is_err()
1369        );
1370        let mut cursor = line.cursor().unwrap();
1371        assert!(
1372            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1373                cursor.take_until(3);
1374            }))
1375            .is_err()
1376        );
1377    }
1378}