Skip to main content

gpui_authoring/text_system/
line.rs

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