Skip to main content

fastui_cosmic/
buffer.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3#[cfg(not(feature = "std"))]
4use alloc::{string::String, vec::Vec};
5use core::{cmp, fmt};
6use unicode_segmentation::UnicodeSegmentation;
7
8use crate::{
9    Affinity, Align, Attrs, AttrsList, BidiParagraphs, BorrowedWithFontSystem, BufferLine, Color,
10    Cursor, FontSystem, LayoutCursor, LayoutGlyph, LayoutLine, LineEnding, LineIter, Motion,
11    Scroll, ShapeLine, Shaping, Wrap,
12};
13
14/// A line of visible text for rendering
15#[derive(Debug)]
16pub struct LayoutRun<'a> {
17    /// The index of the original text line
18    pub line_i: usize,
19    /// The original text line
20    pub text: &'a str,
21    /// True if the original paragraph direction is RTL
22    pub rtl: bool,
23    /// The array of layout glyphs to draw
24    pub glyphs: &'a [LayoutGlyph],
25    /// Y offset to baseline of line
26    pub line_y: f32,
27    /// Y offset to top of line
28    pub line_top: f32,
29    /// Y offset to next line
30    pub line_height: f32,
31    /// Width of line
32    pub line_w: f32,
33}
34
35impl LayoutRun<'_> {
36    /// Return the pixel span `Some((x_left, x_width))` of the highlighted area between `cursor_start`
37    /// and `cursor_end` within this run, or None if the cursor range does not intersect this run.
38    /// This may return widths of zero if `cursor_start == cursor_end`, if the run is empty, or if the
39    /// region's left start boundary is the same as the cursor's end boundary or vice versa.
40    #[allow(clippy::missing_panics_doc)]
41    pub fn highlight(&self, cursor_start: Cursor, cursor_end: Cursor) -> Option<(f32, f32)> {
42        let mut x_start = None;
43        let mut x_end = None;
44        let rtl_factor = if self.rtl { 1. } else { 0. };
45        let ltr_factor = 1. - rtl_factor;
46        for glyph in self.glyphs.iter() {
47            let cursor = self.cursor_from_glyph_left(glyph);
48            if cursor >= cursor_start && cursor <= cursor_end {
49                if x_start.is_none() {
50                    x_start = Some(glyph.x + glyph.w * rtl_factor);
51                }
52                x_end = Some(glyph.x + glyph.w * rtl_factor);
53            }
54            let cursor = self.cursor_from_glyph_right(glyph);
55            if cursor >= cursor_start && cursor <= cursor_end {
56                if x_start.is_none() {
57                    x_start = Some(glyph.x + glyph.w * ltr_factor);
58                }
59                x_end = Some(glyph.x + glyph.w * ltr_factor);
60            }
61        }
62        if let Some(x_start) = x_start {
63            let x_end = x_end.expect("end of cursor not found");
64            let (x_start, x_end) = if x_start < x_end {
65                (x_start, x_end)
66            } else {
67                (x_end, x_start)
68            };
69            Some((x_start, x_end - x_start))
70        } else {
71            None
72        }
73    }
74
75    fn cursor_from_glyph_left(&self, glyph: &LayoutGlyph) -> Cursor {
76        if self.rtl {
77            Cursor::new_with_affinity(self.line_i, glyph.end, Affinity::Before)
78        } else {
79            Cursor::new_with_affinity(self.line_i, glyph.start, Affinity::After)
80        }
81    }
82
83    fn cursor_from_glyph_right(&self, glyph: &LayoutGlyph) -> Cursor {
84        if self.rtl {
85            Cursor::new_with_affinity(self.line_i, glyph.start, Affinity::After)
86        } else {
87            Cursor::new_with_affinity(self.line_i, glyph.end, Affinity::Before)
88        }
89    }
90}
91
92/// An iterator of visible text lines, see [`LayoutRun`]
93#[derive(Debug)]
94pub struct LayoutRunIter<'b> {
95    buffer: &'b Buffer,
96    line_i: usize,
97    layout_i: usize,
98    total_height: f32,
99    line_top: f32,
100}
101
102impl<'b> LayoutRunIter<'b> {
103    pub fn new(buffer: &'b Buffer) -> Self {
104        Self {
105            buffer,
106            line_i: buffer.scroll.line,
107            layout_i: 0,
108            total_height: 0.0,
109            line_top: 0.0,
110        }
111    }
112}
113
114impl<'b> Iterator for LayoutRunIter<'b> {
115    type Item = LayoutRun<'b>;
116
117    fn next(&mut self) -> Option<Self::Item> {
118        while let Some(line) = self.buffer.lines.get(self.line_i) {
119            let shape = line.shape_opt()?;
120            let layout = line.layout_opt()?;
121            while let Some(layout_line) = layout.get(self.layout_i) {
122                self.layout_i += 1;
123
124                let line_height = layout_line
125                    .line_height_opt
126                    .unwrap_or(self.buffer.metrics.line_height);
127                self.total_height += line_height;
128
129                let line_top = self.line_top - self.buffer.scroll.vertical;
130                let glyph_height = layout_line.max_ascent + layout_line.max_descent;
131                let centering_offset = (line_height - glyph_height) / 2.0;
132                let line_y = line_top + centering_offset + layout_line.max_ascent;
133                if let Some(height) = self.buffer.height_opt {
134                    if line_y > height {
135                        return None;
136                    }
137                }
138                self.line_top += line_height;
139                if line_y < 0.0 {
140                    continue;
141                }
142
143                return Some(LayoutRun {
144                    line_i: self.line_i,
145                    text: line.text(),
146                    rtl: shape.rtl,
147                    glyphs: &layout_line.glyphs,
148                    line_y,
149                    line_top,
150                    line_height,
151                    line_w: layout_line.w,
152                });
153            }
154            self.line_i += 1;
155            self.layout_i = 0;
156        }
157
158        None
159    }
160}
161
162/// Metrics of text
163#[derive(Clone, Copy, Debug, Default, PartialEq)]
164pub struct Metrics {
165    /// Font size in pixels
166    pub font_size: f32,
167    /// Line height in pixels
168    pub line_height: f32,
169}
170
171impl Metrics {
172    /// Create metrics with given font size and line height
173    pub const fn new(font_size: f32, line_height: f32) -> Self {
174        Self {
175            font_size,
176            line_height,
177        }
178    }
179
180    /// Create metrics with given font size and calculate line height using relative scale
181    pub fn relative(font_size: f32, line_height_scale: f32) -> Self {
182        Self {
183            font_size,
184            line_height: font_size * line_height_scale,
185        }
186    }
187
188    /// Scale font size and line height
189    pub fn scale(self, scale: f32) -> Self {
190        Self {
191            font_size: self.font_size * scale,
192            line_height: self.line_height * scale,
193        }
194    }
195}
196
197impl fmt::Display for Metrics {
198    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
199        write!(f, "{}px / {}px", self.font_size, self.line_height)
200    }
201}
202
203/// A buffer of text that is shaped and laid out
204#[derive(Debug)]
205pub struct Buffer {
206    /// [`BufferLine`]s (or paragraphs) of text in the buffer
207    pub lines: Vec<BufferLine>,
208    metrics: Metrics,
209    width_opt: Option<f32>,
210    height_opt: Option<f32>,
211    first_line_indent: Option<f32>,
212    scroll: Scroll,
213    /// True if a redraw is requires. Set to false after processing
214    redraw: bool,
215    wrap: Wrap,
216    monospace_width: Option<f32>,
217    tab_width: u16,
218}
219
220impl Clone for Buffer {
221    fn clone(&self) -> Self {
222        Self {
223            lines: self.lines.clone(),
224            metrics: self.metrics,
225            width_opt: self.width_opt,
226            height_opt: self.height_opt,
227            first_line_indent: self.first_line_indent,
228            scroll: self.scroll,
229            redraw: self.redraw,
230            wrap: self.wrap,
231            monospace_width: self.monospace_width,
232            tab_width: self.tab_width,
233        }
234    }
235}
236
237impl Buffer {
238    /// Create an empty [`Buffer`] with the provided [`Metrics`].
239    /// This is useful for initializing a [`Buffer`] without a [`FontSystem`].
240    ///
241    /// You must populate the [`Buffer`] with at least one [`BufferLine`] before shaping and layout,
242    /// for example by calling [`Buffer::set_text`].
243    ///
244    /// If you have a [`FontSystem`] in scope, you should use [`Buffer::new`] instead.
245    ///
246    /// # Panics
247    ///
248    /// Will panic if `metrics.line_height` is zero.
249    pub fn new_empty(metrics: Metrics) -> Self {
250        assert_ne!(metrics.line_height, 0.0, "line height cannot be 0");
251        Self {
252            lines: Vec::new(),
253            metrics,
254            width_opt: None,
255            height_opt: None,
256            first_line_indent: None,
257            scroll: Scroll::default(),
258            redraw: false,
259            wrap: Wrap::WordOrGlyph,
260            monospace_width: None,
261            tab_width: 8,
262        }
263    }
264
265    /// Create a new [`Buffer`] with the provided [`FontSystem`] and [`Metrics`]
266    ///
267    /// # Panics
268    ///
269    /// Will panic if `metrics.line_height` is zero.
270    pub fn new(font_system: &mut FontSystem, metrics: Metrics) -> Self {
271        let mut buffer = Self::new_empty(metrics);
272        buffer.set_text(font_system, "", &Attrs::new(), Shaping::Advanced);
273        buffer
274    }
275
276    /// Mutably borrows the buffer together with an [`FontSystem`] for more convenient methods
277    pub fn borrow_with<'a>(
278        &'a mut self,
279        font_system: &'a mut FontSystem,
280    ) -> BorrowedWithFontSystem<'a, Buffer> {
281        BorrowedWithFontSystem {
282            inner: self,
283            font_system,
284        }
285    }
286
287    fn relayout(&mut self, font_system: &mut FontSystem) {
288        #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
289        let instant = std::time::Instant::now();
290
291        for line in &mut self.lines {
292            if line.shape_opt().is_some() {
293                line.reset_layout();
294                line.layout(
295                    font_system,
296                    self.metrics.font_size,
297                    self.width_opt,
298                    self.first_line_indent,
299                    self.wrap,
300                    self.monospace_width,
301                    self.tab_width,
302                );
303            }
304        }
305
306        self.redraw = true;
307
308        #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
309        log::debug!("relayout: {:?}", instant.elapsed());
310    }
311
312    /// Shape lines until cursor, also scrolling to include cursor in view
313    #[allow(clippy::missing_panics_doc)]
314    pub fn shape_until_cursor(
315        &mut self,
316        font_system: &mut FontSystem,
317        cursor: Cursor,
318        prune: bool,
319    ) {
320        let metrics = self.metrics;
321        let old_scroll = self.scroll;
322
323        let layout_cursor = self
324            .layout_cursor(font_system, cursor)
325            .expect("shape_until_cursor invalid cursor");
326
327        let mut layout_y = 0.0;
328        let mut total_height = {
329            let layout = self
330                .line_layout(font_system, layout_cursor.line)
331                .expect("shape_until_cursor failed to scroll forwards");
332            (0..layout_cursor.layout).for_each(|layout_i| {
333                layout_y += layout[layout_i]
334                    .line_height_opt
335                    .unwrap_or(metrics.line_height);
336            });
337            layout_y
338                + layout[layout_cursor.layout]
339                    .line_height_opt
340                    .unwrap_or(metrics.line_height)
341        };
342
343        if self.scroll.line > layout_cursor.line
344            || (self.scroll.line == layout_cursor.line && self.scroll.vertical > layout_y)
345        {
346            // Adjust scroll backwards if cursor is before it
347            self.scroll.line = layout_cursor.line;
348            self.scroll.vertical = layout_y;
349        } else if let Some(height) = self.height_opt {
350            // Adjust scroll forwards if cursor is after it
351            let mut line_i = layout_cursor.line;
352            if line_i <= self.scroll.line {
353                // This is a single line that may wrap
354                if total_height > height + self.scroll.vertical {
355                    self.scroll.vertical = total_height - height;
356                }
357            } else {
358                while line_i > self.scroll.line {
359                    line_i -= 1;
360                    let layout = self
361                        .line_layout(font_system, line_i)
362                        .expect("shape_until_cursor failed to scroll forwards");
363                    for layout_line in layout.iter() {
364                        total_height += layout_line.line_height_opt.unwrap_or(metrics.line_height);
365                    }
366                    if total_height > height + self.scroll.vertical {
367                        self.scroll.line = line_i;
368                        self.scroll.vertical = total_height - height;
369                    }
370                }
371            }
372        }
373
374        if old_scroll != self.scroll {
375            self.redraw = true;
376        }
377
378        self.shape_until_scroll(font_system, prune);
379
380        // Adjust horizontal scroll to include cursor
381        if let Some(layout_cursor) = self.layout_cursor(font_system, cursor) {
382            if let Some(layout_lines) = self.line_layout(font_system, layout_cursor.line) {
383                if let Some(layout_line) = layout_lines.get(layout_cursor.layout) {
384                    let (x_min, x_max) = if let Some(glyph) = layout_line
385                        .glyphs
386                        .get(layout_cursor.glyph)
387                        .or_else(|| layout_line.glyphs.last())
388                    {
389                        //TODO: use code from cursor_glyph_opt?
390                        let x_a = glyph.x;
391                        let x_b = glyph.x + glyph.w;
392                        (x_a.min(x_b), x_a.max(x_b))
393                    } else {
394                        (0.0, 0.0)
395                    };
396                    if x_min < self.scroll.horizontal {
397                        self.scroll.horizontal = x_min;
398                        self.redraw = true;
399                    }
400                    if let Some(width) = self.width_opt {
401                        if x_max > self.scroll.horizontal + width {
402                            self.scroll.horizontal = x_max - width;
403                            self.redraw = true;
404                        }
405                    }
406                }
407            }
408        }
409    }
410
411    /// Shape lines until scroll
412    #[allow(clippy::missing_panics_doc)]
413    pub fn shape_until_scroll(&mut self, font_system: &mut FontSystem, prune: bool) {
414        let metrics = self.metrics;
415        let old_scroll = self.scroll;
416
417        loop {
418            // Adjust scroll.layout to be positive by moving scroll.line backwards
419            while self.scroll.vertical < 0.0 {
420                if self.scroll.line > 0 {
421                    let line_i = self.scroll.line - 1;
422                    if let Some(layout) = self.line_layout(font_system, line_i) {
423                        let mut layout_height = 0.0;
424                        for layout_line in layout.iter() {
425                            layout_height +=
426                                layout_line.line_height_opt.unwrap_or(metrics.line_height);
427                        }
428                        self.scroll.line = line_i;
429                        self.scroll.vertical += layout_height;
430                    } else {
431                        // If layout is missing, just assume line height
432                        self.scroll.line = line_i;
433                        self.scroll.vertical += metrics.line_height;
434                    }
435                } else {
436                    self.scroll.vertical = 0.0;
437                    break;
438                }
439            }
440
441            let scroll_start = self.scroll.vertical;
442            let scroll_end = scroll_start + self.height_opt.unwrap_or(f32::INFINITY);
443
444            let mut total_height = 0.0;
445            for line_i in 0..self.lines.len() {
446                if line_i < self.scroll.line {
447                    if prune {
448                        self.lines[line_i].reset_shaping();
449                    }
450                    continue;
451                }
452                if total_height > scroll_end {
453                    if prune {
454                        self.lines[line_i].reset_shaping();
455                        continue;
456                    } else {
457                        break;
458                    }
459                }
460
461                let mut layout_height = 0.0;
462                let layout = self
463                    .line_layout(font_system, line_i)
464                    .expect("shape_until_scroll invalid line");
465                for layout_line in layout.iter() {
466                    let line_height = layout_line.line_height_opt.unwrap_or(metrics.line_height);
467                    layout_height += line_height;
468                    total_height += line_height;
469                }
470
471                // Adjust scroll.vertical to be smaller by moving scroll.line forwards
472                //TODO: do we want to adjust it exactly to a layout line?
473                if line_i == self.scroll.line && layout_height < self.scroll.vertical {
474                    self.scroll.line += 1;
475                    self.scroll.vertical -= layout_height;
476                }
477            }
478
479            if total_height < scroll_end && self.scroll.line > 0 {
480                // Need to scroll up to stay inside of buffer
481                self.scroll.vertical -= scroll_end - total_height;
482            } else {
483                // Done adjusting scroll
484                break;
485            }
486        }
487
488        if old_scroll != self.scroll {
489            self.redraw = true;
490        }
491    }
492
493    /// Convert a [`Cursor`] to a [`LayoutCursor`]
494    pub fn layout_cursor(
495        &mut self,
496        font_system: &mut FontSystem,
497        cursor: Cursor,
498    ) -> Option<LayoutCursor> {
499        let layout = self.line_layout(font_system, cursor.line)?;
500        for (layout_i, layout_line) in layout.iter().enumerate() {
501            for (glyph_i, glyph) in layout_line.glyphs.iter().enumerate() {
502                let cursor_end =
503                    Cursor::new_with_affinity(cursor.line, glyph.end, Affinity::Before);
504                let cursor_start =
505                    Cursor::new_with_affinity(cursor.line, glyph.start, Affinity::After);
506                let (cursor_left, cursor_right) = if glyph.level.is_ltr() {
507                    (cursor_start, cursor_end)
508                } else {
509                    (cursor_end, cursor_start)
510                };
511                if cursor == cursor_left {
512                    return Some(LayoutCursor::new(cursor.line, layout_i, glyph_i));
513                }
514                if cursor == cursor_right {
515                    return Some(LayoutCursor::new(cursor.line, layout_i, glyph_i + 1));
516                }
517            }
518        }
519
520        // Fall back to start of line
521        //TODO: should this be the end of the line?
522        Some(LayoutCursor::new(cursor.line, 0, 0))
523    }
524
525    /// Shape the provided line index and return the result
526    pub fn line_shape(
527        &mut self,
528        font_system: &mut FontSystem,
529        line_i: usize,
530    ) -> Option<&ShapeLine> {
531        let line = self.lines.get_mut(line_i)?;
532        Some(line.shape(font_system, self.tab_width))
533    }
534
535    /// Lay out the provided line index and return the result
536    pub fn line_layout(
537        &mut self,
538        font_system: &mut FontSystem,
539        line_i: usize,
540    ) -> Option<&[LayoutLine]> {
541        let line = self.lines.get_mut(line_i)?;
542        Some(line.layout(
543            font_system,
544            self.metrics.font_size,
545            self.width_opt,
546            self.first_line_indent,
547            self.wrap,
548            self.monospace_width,
549            self.tab_width,
550        ))
551    }
552
553    /// Get the current [`Metrics`]
554    pub fn metrics(&self) -> Metrics {
555        self.metrics
556    }
557
558    /// Set the current [`Metrics`]
559    ///
560    /// # Panics
561    ///
562    /// Will panic if `metrics.font_size` is zero.
563    pub fn set_metrics(&mut self, font_system: &mut FontSystem, metrics: Metrics) {
564        self.set_metrics_and_size(font_system, metrics, self.width_opt, self.height_opt);
565    }
566
567    /// Get the current [`Wrap`]
568    pub fn wrap(&self) -> Wrap {
569        self.wrap
570    }
571
572    /// Set the current [`Wrap`]
573    pub fn set_wrap(&mut self, font_system: &mut FontSystem, wrap: Wrap) {
574        if wrap != self.wrap {
575            self.wrap = wrap;
576            self.relayout(font_system);
577            self.shape_until_scroll(font_system, false);
578        }
579    }
580
581    /// Get the current `monospace_width`
582    pub fn monospace_width(&self) -> Option<f32> {
583        self.monospace_width
584    }
585
586    /// Set monospace width monospace glyphs should be resized to match. `None` means don't resize
587    pub fn set_monospace_width(
588        &mut self,
589        font_system: &mut FontSystem,
590        monospace_width: Option<f32>,
591    ) {
592        if monospace_width != self.monospace_width {
593            self.monospace_width = monospace_width;
594            self.relayout(font_system);
595            self.shape_until_scroll(font_system, false);
596        }
597    }
598
599    /// Get the current `tab_width`
600    pub fn tab_width(&self) -> u16 {
601        self.tab_width
602    }
603
604    /// Set tab width (number of spaces between tab stops)
605    pub fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16) {
606        // A tab width of 0 is not allowed
607        if tab_width == 0 {
608            return;
609        }
610        if tab_width != self.tab_width {
611            self.tab_width = tab_width;
612            // Shaping must be reset when tab width is changed
613            for line in self.lines.iter_mut() {
614                if line.shape_opt().is_some() && line.text().contains('\t') {
615                    line.reset_shaping();
616                }
617            }
618            self.redraw = true;
619            self.shape_until_scroll(font_system, false);
620        }
621    }
622
623    /// Get the current buffer dimensions (width, height)
624    pub fn size(&self) -> (Option<f32>, Option<f32>) {
625        (self.width_opt, self.height_opt)
626    }
627
628    /// Set the current buffer dimensions
629    pub fn set_size(
630        &mut self,
631        font_system: &mut FontSystem,
632        width_opt: Option<f32>,
633        height_opt: Option<f32>,
634    ) {
635        self.set_metrics_and_size(font_system, self.metrics, width_opt, height_opt);
636    }
637
638    /// Set the current [`Metrics`] and buffer dimensions at the same time
639    ///
640    /// # Panics
641    ///
642    /// Will panic if `metrics.font_size` is zero.
643    pub fn set_metrics_and_size(
644        &mut self,
645        font_system: &mut FontSystem,
646        metrics: Metrics,
647        width_opt: Option<f32>,
648        height_opt: Option<f32>,
649    ) {
650        let clamped_width_opt = width_opt.map(|width| width.max(0.0));
651        let clamped_height_opt = height_opt.map(|height| height.max(0.0));
652
653        if metrics != self.metrics
654            || clamped_width_opt != self.width_opt
655            || clamped_height_opt != self.height_opt
656        {
657            assert_ne!(metrics.font_size, 0.0, "font size cannot be 0");
658            self.metrics = metrics;
659            self.width_opt = clamped_width_opt;
660            self.height_opt = clamped_height_opt;
661            self.relayout(font_system);
662            self.shape_until_scroll(font_system, false);
663        }
664    }
665
666    /// Get the current scroll location
667    pub fn scroll(&self) -> Scroll {
668        self.scroll
669    }
670
671    /// Set the current scroll location
672    pub fn set_scroll(&mut self, scroll: Scroll) {
673        if scroll != self.scroll {
674            self.scroll = scroll;
675            self.redraw = true;
676        }
677    }
678
679    /// Set text of buffer, using provided attributes for each line by default
680    pub fn set_text(
681        &mut self,
682        font_system: &mut FontSystem,
683        text: &str,
684        attrs: &Attrs,
685        shaping: Shaping,
686    ) {
687        self.lines.clear();
688        for (range, ending) in LineIter::new(text) {
689            self.lines.push(BufferLine::new(
690                &text[range],
691                ending,
692                AttrsList::new(attrs),
693                shaping,
694            ));
695        }
696        if self.lines.is_empty() {
697            self.lines.push(BufferLine::new(
698                "",
699                LineEnding::default(),
700                AttrsList::new(attrs),
701                shaping,
702            ));
703        }
704        self.scroll = Scroll::default();
705        self.shape_until_scroll(font_system, false);
706    }
707
708    /// Set text of buffer, using an iterator of styled spans (pairs of text and attributes)
709    ///
710    /// ```
711    /// # use fastui_cosmic::{Attrs, Buffer, Family, FontSystem, Metrics, Shaping};
712    /// # let mut font_system = FontSystem::new();
713    /// let mut buffer = Buffer::new_empty(Metrics::new(32.0, 44.0));
714    /// let attrs = Attrs::new().family(Family::Serif);
715    /// buffer.set_rich_text(
716    ///     &mut font_system,
717    ///     [
718    ///         ("hello, ", attrs.clone()),
719    ///         ("cosmic\ntext", attrs.clone().family(Family::Monospace)),
720    ///     ],
721    ///     &attrs,
722    ///     Shaping::Advanced,
723    ///     None,
724    /// );
725    /// ```
726    pub fn set_rich_text<'r, 's, I>(
727        &mut self,
728        font_system: &mut FontSystem,
729        spans: I,
730        default_attrs: &Attrs,
731        shaping: Shaping,
732        alignment: Option<Align>,
733    ) where
734        I: IntoIterator<Item = (&'s str, Attrs<'r>)>,
735    {
736        let mut end = 0;
737        // TODO: find a way to cache this string and vec for reuse
738        let (string, spans_data): (String, Vec<_>) = spans
739            .into_iter()
740            .map(|(s, attrs)| {
741                let start = end;
742                end += s.len();
743                (s, (attrs, start..end))
744            })
745            .unzip();
746
747        let mut spans_iter = spans_data.into_iter();
748        let mut maybe_span = spans_iter.next();
749
750        // split the string into lines, as ranges
751        let string_start = string.as_ptr() as usize;
752        let mut lines_iter = BidiParagraphs::new(&string).map(|line: &str| {
753            let start = line.as_ptr() as usize - string_start;
754            let end = start + line.len();
755            start..end
756        });
757        let mut maybe_line = lines_iter.next();
758        //TODO: set this based on information from spans
759        let line_ending = LineEnding::default();
760
761        let mut line_count = 0;
762        let mut attrs_list = self
763            .lines
764            .get_mut(line_count)
765            .map(BufferLine::reclaim_attrs)
766            .unwrap_or_else(|| AttrsList::new(&Attrs::new()))
767            .reset(default_attrs);
768        let mut line_string = self
769            .lines
770            .get_mut(line_count)
771            .map(BufferLine::reclaim_text)
772            .unwrap_or_default();
773
774        loop {
775            let (Some(line_range), Some((attrs, span_range))) = (&maybe_line, &maybe_span) else {
776                // this is reached only if this text is empty
777                if self.lines.len() == line_count {
778                    self.lines.push(BufferLine::empty());
779                }
780                self.lines[line_count].reset_new(
781                    String::new(),
782                    line_ending,
783                    AttrsList::new(default_attrs),
784                    shaping,
785                );
786                line_count += 1;
787                break;
788            };
789
790            // start..end is the intersection of this line and this span
791            let start = line_range.start.max(span_range.start);
792            let end = line_range.end.min(span_range.end);
793            if start < end {
794                let text = &string[start..end];
795                let text_start = line_string.len();
796                line_string.push_str(text);
797                let text_end = line_string.len();
798                // Only add attrs if they don't match the defaults
799                if *attrs != attrs_list.defaults() {
800                    attrs_list.add_span(text_start..text_end, attrs);
801                }
802            }
803
804            // we know that at the end of a line,
805            // span text's end index is always >= line text's end index
806            // so if this span ends before this line ends,
807            // there is another span in this line.
808            // otherwise, we move on to the next line.
809            if span_range.end < line_range.end {
810                maybe_span = spans_iter.next();
811            } else {
812                maybe_line = lines_iter.next();
813                if maybe_line.is_some() {
814                    // finalize this line and start a new line
815                    let next_attrs_list = self
816                        .lines
817                        .get_mut(line_count + 1)
818                        .map(BufferLine::reclaim_attrs)
819                        .unwrap_or_else(|| AttrsList::new(&Attrs::new()))
820                        .reset(default_attrs);
821                    let next_line_string = self
822                        .lines
823                        .get_mut(line_count + 1)
824                        .map(BufferLine::reclaim_text)
825                        .unwrap_or_default();
826                    let prev_attrs_list = core::mem::replace(&mut attrs_list, next_attrs_list);
827                    let prev_line_string = core::mem::replace(&mut line_string, next_line_string);
828                    if self.lines.len() == line_count {
829                        self.lines.push(BufferLine::empty());
830                    }
831                    self.lines[line_count].reset_new(
832                        prev_line_string,
833                        line_ending,
834                        prev_attrs_list,
835                        shaping,
836                    );
837                    line_count += 1;
838                } else {
839                    // finalize the final line
840                    if self.lines.len() == line_count {
841                        self.lines.push(BufferLine::empty());
842                    }
843                    self.lines[line_count].reset_new(line_string, line_ending, attrs_list, shaping);
844                    line_count += 1;
845                    break;
846                }
847            }
848        }
849
850        // Discard excess lines now that we have reused as much of the existing allocations as possible.
851        self.lines.truncate(line_count);
852
853        self.lines.iter_mut().for_each(|line| {
854            line.set_align(alignment);
855        });
856
857        self.scroll = Scroll::default();
858
859        self.shape_until_scroll(font_system, false);
860    }
861
862    /// True if a redraw is needed
863    pub fn redraw(&self) -> bool {
864        self.redraw
865    }
866
867    /// Set redraw needed flag
868    pub fn set_redraw(&mut self, redraw: bool) {
869        self.redraw = redraw;
870    }
871
872    /// Get the visible layout runs for rendering and other tasks
873    pub fn layout_runs(&self) -> LayoutRunIter {
874        LayoutRunIter::new(self)
875    }
876
877    /// Convert x, y position to Cursor (hit detection)
878    pub fn hit(&self, x: f32, y: f32) -> Option<Cursor> {
879        #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
880        let instant = std::time::Instant::now();
881
882        let mut new_cursor_opt = None;
883
884        let mut runs = self.layout_runs().peekable();
885        let mut first_run = true;
886        while let Some(run) = runs.next() {
887            let line_top = run.line_top;
888            let line_height = run.line_height;
889
890            if first_run && y < line_top {
891                first_run = false;
892                let new_cursor = Cursor::new(run.line_i, 0);
893                new_cursor_opt = Some(new_cursor);
894            } else if y >= line_top && y < line_top + line_height {
895                let mut new_cursor_glyph = run.glyphs.len();
896                let mut new_cursor_char = 0;
897                let mut new_cursor_affinity = Affinity::After;
898
899                let mut first_glyph = true;
900
901                'hit: for (glyph_i, glyph) in run.glyphs.iter().enumerate() {
902                    if first_glyph {
903                        first_glyph = false;
904                        if (run.rtl && x > glyph.x) || (!run.rtl && x < 0.0) {
905                            new_cursor_glyph = 0;
906                            new_cursor_char = 0;
907                        }
908                    }
909                    if x >= glyph.x && x <= glyph.x + glyph.w {
910                        new_cursor_glyph = glyph_i;
911
912                        let cluster = &run.text[glyph.start..glyph.end];
913                        let total = cluster.grapheme_indices(true).count();
914                        let mut egc_x = glyph.x;
915                        let egc_w = glyph.w / (total as f32);
916                        for (egc_i, egc) in cluster.grapheme_indices(true) {
917                            if x >= egc_x && x <= egc_x + egc_w {
918                                new_cursor_char = egc_i;
919
920                                let right_half = x >= egc_x + egc_w / 2.0;
921                                if right_half != glyph.level.is_rtl() {
922                                    // If clicking on last half of glyph, move cursor past glyph
923                                    new_cursor_char += egc.len();
924                                    new_cursor_affinity = Affinity::Before;
925                                }
926                                break 'hit;
927                            }
928                            egc_x += egc_w;
929                        }
930
931                        let right_half = x >= glyph.x + glyph.w / 2.0;
932                        if right_half != glyph.level.is_rtl() {
933                            // If clicking on last half of glyph, move cursor past glyph
934                            new_cursor_char = cluster.len();
935                            new_cursor_affinity = Affinity::Before;
936                        }
937                        break 'hit;
938                    }
939                }
940
941                let mut new_cursor = Cursor::new(run.line_i, 0);
942
943                match run.glyphs.get(new_cursor_glyph) {
944                    Some(glyph) => {
945                        // Position at glyph
946                        new_cursor.index = glyph.start + new_cursor_char;
947                        new_cursor.affinity = new_cursor_affinity;
948                    }
949                    None => {
950                        if let Some(glyph) = run.glyphs.last() {
951                            // Position at end of line
952                            new_cursor.index = glyph.end;
953                            new_cursor.affinity = Affinity::Before;
954                        }
955                    }
956                }
957
958                new_cursor_opt = Some(new_cursor);
959
960                break;
961            } else if runs.peek().is_none() && y > run.line_y {
962                let mut new_cursor = Cursor::new(run.line_i, 0);
963                if let Some(glyph) = run.glyphs.last() {
964                    new_cursor = run.cursor_from_glyph_right(glyph);
965                }
966                new_cursor_opt = Some(new_cursor);
967            }
968        }
969
970        #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
971        log::trace!("click({}, {}): {:?}", x, y, instant.elapsed());
972
973        new_cursor_opt
974    }
975
976    /// Apply a [`Motion`] to a [`Cursor`]
977    pub fn cursor_motion(
978        &mut self,
979        font_system: &mut FontSystem,
980        mut cursor: Cursor,
981        mut cursor_x_opt: Option<i32>,
982        motion: Motion,
983    ) -> Option<(Cursor, Option<i32>)> {
984        match motion {
985            Motion::LayoutCursor(layout_cursor) => {
986                let layout = self.line_layout(font_system, layout_cursor.line)?;
987
988                let layout_line = match layout.get(layout_cursor.layout) {
989                    Some(some) => some,
990                    None => match layout.last() {
991                        Some(some) => some,
992                        None => {
993                            return None;
994                        }
995                    },
996                };
997
998                let (new_index, new_affinity) = match layout_line.glyphs.get(layout_cursor.glyph) {
999                    Some(glyph) => (glyph.start, Affinity::After),
1000                    None => match layout_line.glyphs.last() {
1001                        Some(glyph) => (glyph.end, Affinity::Before),
1002                        //TODO: is this correct?
1003                        None => (0, Affinity::After),
1004                    },
1005                };
1006
1007                if cursor.line != layout_cursor.line
1008                    || cursor.index != new_index
1009                    || cursor.affinity != new_affinity
1010                {
1011                    cursor.line = layout_cursor.line;
1012                    cursor.index = new_index;
1013                    cursor.affinity = new_affinity;
1014                }
1015            }
1016            Motion::Previous => {
1017                let line = self.lines.get(cursor.line)?;
1018                if cursor.index > 0 {
1019                    // Find previous character index
1020                    let mut prev_index = 0;
1021                    for (i, _) in line.text().grapheme_indices(true) {
1022                        if i < cursor.index {
1023                            prev_index = i;
1024                        } else {
1025                            break;
1026                        }
1027                    }
1028
1029                    cursor.index = prev_index;
1030                    cursor.affinity = Affinity::After;
1031                } else if cursor.line > 0 {
1032                    cursor.line -= 1;
1033                    cursor.index = self.lines.get(cursor.line)?.text().len();
1034                    cursor.affinity = Affinity::After;
1035                }
1036                cursor_x_opt = None;
1037            }
1038            Motion::Next => {
1039                let line = self.lines.get(cursor.line)?;
1040                if cursor.index < line.text().len() {
1041                    for (i, c) in line.text().grapheme_indices(true) {
1042                        if i == cursor.index {
1043                            cursor.index += c.len();
1044                            cursor.affinity = Affinity::Before;
1045                            break;
1046                        }
1047                    }
1048                } else if cursor.line + 1 < self.lines.len() {
1049                    cursor.line += 1;
1050                    cursor.index = 0;
1051                    cursor.affinity = Affinity::Before;
1052                }
1053                cursor_x_opt = None;
1054            }
1055            Motion::Left => {
1056                let rtl_opt = self
1057                    .line_shape(font_system, cursor.line)
1058                    .map(|shape| shape.rtl);
1059                if let Some(rtl) = rtl_opt {
1060                    if rtl {
1061                        (cursor, cursor_x_opt) =
1062                            self.cursor_motion(font_system, cursor, cursor_x_opt, Motion::Next)?;
1063                    } else {
1064                        (cursor, cursor_x_opt) = self.cursor_motion(
1065                            font_system,
1066                            cursor,
1067                            cursor_x_opt,
1068                            Motion::Previous,
1069                        )?;
1070                    }
1071                }
1072            }
1073            Motion::Right => {
1074                let rtl_opt = self
1075                    .line_shape(font_system, cursor.line)
1076                    .map(|shape| shape.rtl);
1077                if let Some(rtl) = rtl_opt {
1078                    if rtl {
1079                        (cursor, cursor_x_opt) = self.cursor_motion(
1080                            font_system,
1081                            cursor,
1082                            cursor_x_opt,
1083                            Motion::Previous,
1084                        )?;
1085                    } else {
1086                        (cursor, cursor_x_opt) =
1087                            self.cursor_motion(font_system, cursor, cursor_x_opt, Motion::Next)?;
1088                    }
1089                }
1090            }
1091            Motion::Up => {
1092                let mut layout_cursor = self.layout_cursor(font_system, cursor)?;
1093
1094                if cursor_x_opt.is_none() {
1095                    cursor_x_opt = Some(
1096                        layout_cursor.glyph as i32, //TODO: glyph x position
1097                    );
1098                }
1099
1100                if layout_cursor.layout > 0 {
1101                    layout_cursor.layout -= 1;
1102                } else if layout_cursor.line > 0 {
1103                    layout_cursor.line -= 1;
1104                    layout_cursor.layout = usize::MAX;
1105                }
1106
1107                if let Some(cursor_x) = cursor_x_opt {
1108                    layout_cursor.glyph = cursor_x as usize; //TODO: glyph x position
1109                }
1110
1111                (cursor, cursor_x_opt) = self.cursor_motion(
1112                    font_system,
1113                    cursor,
1114                    cursor_x_opt,
1115                    Motion::LayoutCursor(layout_cursor),
1116                )?;
1117            }
1118            Motion::Down => {
1119                let mut layout_cursor = self.layout_cursor(font_system, cursor)?;
1120
1121                let layout_len = self.line_layout(font_system, layout_cursor.line)?.len();
1122
1123                if cursor_x_opt.is_none() {
1124                    cursor_x_opt = Some(
1125                        layout_cursor.glyph as i32, //TODO: glyph x position
1126                    );
1127                }
1128
1129                if layout_cursor.layout + 1 < layout_len {
1130                    layout_cursor.layout += 1;
1131                } else if layout_cursor.line + 1 < self.lines.len() {
1132                    layout_cursor.line += 1;
1133                    layout_cursor.layout = 0;
1134                }
1135
1136                if let Some(cursor_x) = cursor_x_opt {
1137                    layout_cursor.glyph = cursor_x as usize; //TODO: glyph x position
1138                }
1139
1140                (cursor, cursor_x_opt) = self.cursor_motion(
1141                    font_system,
1142                    cursor,
1143                    cursor_x_opt,
1144                    Motion::LayoutCursor(layout_cursor),
1145                )?;
1146            }
1147            Motion::Home => {
1148                let mut layout_cursor = self.layout_cursor(font_system, cursor)?;
1149                layout_cursor.glyph = 0;
1150                #[allow(unused_assignments)]
1151                {
1152                    (cursor, cursor_x_opt) = self.cursor_motion(
1153                        font_system,
1154                        cursor,
1155                        cursor_x_opt,
1156                        Motion::LayoutCursor(layout_cursor),
1157                    )?;
1158                }
1159                cursor_x_opt = None;
1160            }
1161            Motion::SoftHome => {
1162                let line = self.lines.get(cursor.line)?;
1163                cursor.index = line
1164                    .text()
1165                    .char_indices()
1166                    .filter_map(|(i, c)| if c.is_whitespace() { None } else { Some(i) })
1167                    .next()
1168                    .unwrap_or(0);
1169                cursor_x_opt = None;
1170            }
1171            Motion::End => {
1172                let mut layout_cursor = self.layout_cursor(font_system, cursor)?;
1173                layout_cursor.glyph = usize::MAX;
1174                #[allow(unused_assignments)]
1175                {
1176                    (cursor, cursor_x_opt) = self.cursor_motion(
1177                        font_system,
1178                        cursor,
1179                        cursor_x_opt,
1180                        Motion::LayoutCursor(layout_cursor),
1181                    )?;
1182                }
1183                cursor_x_opt = None;
1184            }
1185            Motion::ParagraphStart => {
1186                cursor.index = 0;
1187                cursor_x_opt = None;
1188            }
1189            Motion::ParagraphEnd => {
1190                cursor.index = self.lines.get(cursor.line)?.text().len();
1191                cursor_x_opt = None;
1192            }
1193            Motion::PageUp => {
1194                if let Some(height) = self.height_opt {
1195                    (cursor, cursor_x_opt) = self.cursor_motion(
1196                        font_system,
1197                        cursor,
1198                        cursor_x_opt,
1199                        Motion::Vertical(-height as i32),
1200                    )?;
1201                }
1202            }
1203            Motion::PageDown => {
1204                if let Some(height) = self.height_opt {
1205                    (cursor, cursor_x_opt) = self.cursor_motion(
1206                        font_system,
1207                        cursor,
1208                        cursor_x_opt,
1209                        Motion::Vertical(height as i32),
1210                    )?;
1211                }
1212            }
1213            Motion::Vertical(px) => {
1214                // TODO more efficient, use layout run line height
1215                let lines = px / self.metrics().line_height as i32;
1216                match lines.cmp(&0) {
1217                    cmp::Ordering::Less => {
1218                        for _ in 0..-lines {
1219                            (cursor, cursor_x_opt) =
1220                                self.cursor_motion(font_system, cursor, cursor_x_opt, Motion::Up)?;
1221                        }
1222                    }
1223                    cmp::Ordering::Greater => {
1224                        for _ in 0..lines {
1225                            (cursor, cursor_x_opt) = self.cursor_motion(
1226                                font_system,
1227                                cursor,
1228                                cursor_x_opt,
1229                                Motion::Down,
1230                            )?;
1231                        }
1232                    }
1233                    cmp::Ordering::Equal => {}
1234                }
1235            }
1236            Motion::PreviousWord => {
1237                let line = self.lines.get(cursor.line)?;
1238                if cursor.index > 0 {
1239                    cursor.index = line
1240                        .text()
1241                        .unicode_word_indices()
1242                        .rev()
1243                        .map(|(i, _)| i)
1244                        .find(|&i| i < cursor.index)
1245                        .unwrap_or(0);
1246                } else if cursor.line > 0 {
1247                    cursor.line -= 1;
1248                    cursor.index = self.lines.get(cursor.line)?.text().len();
1249                }
1250                cursor_x_opt = None;
1251            }
1252            Motion::NextWord => {
1253                let line = self.lines.get(cursor.line)?;
1254                if cursor.index < line.text().len() {
1255                    cursor.index = line
1256                        .text()
1257                        .unicode_word_indices()
1258                        .map(|(i, word)| i + word.len())
1259                        .find(|&i| i > cursor.index)
1260                        .unwrap_or(line.text().len());
1261                } else if cursor.line + 1 < self.lines.len() {
1262                    cursor.line += 1;
1263                    cursor.index = 0;
1264                }
1265                cursor_x_opt = None;
1266            }
1267            Motion::LeftWord => {
1268                let rtl_opt = self
1269                    .line_shape(font_system, cursor.line)
1270                    .map(|shape| shape.rtl);
1271                if let Some(rtl) = rtl_opt {
1272                    if rtl {
1273                        (cursor, cursor_x_opt) = self.cursor_motion(
1274                            font_system,
1275                            cursor,
1276                            cursor_x_opt,
1277                            Motion::NextWord,
1278                        )?;
1279                    } else {
1280                        (cursor, cursor_x_opt) = self.cursor_motion(
1281                            font_system,
1282                            cursor,
1283                            cursor_x_opt,
1284                            Motion::PreviousWord,
1285                        )?;
1286                    }
1287                }
1288            }
1289            Motion::RightWord => {
1290                let rtl_opt = self
1291                    .line_shape(font_system, cursor.line)
1292                    .map(|shape| shape.rtl);
1293                if let Some(rtl) = rtl_opt {
1294                    if rtl {
1295                        (cursor, cursor_x_opt) = self.cursor_motion(
1296                            font_system,
1297                            cursor,
1298                            cursor_x_opt,
1299                            Motion::PreviousWord,
1300                        )?;
1301                    } else {
1302                        (cursor, cursor_x_opt) = self.cursor_motion(
1303                            font_system,
1304                            cursor,
1305                            cursor_x_opt,
1306                            Motion::NextWord,
1307                        )?;
1308                    }
1309                }
1310            }
1311            Motion::BufferStart => {
1312                cursor.line = 0;
1313                cursor.index = 0;
1314                cursor_x_opt = None;
1315            }
1316            Motion::BufferEnd => {
1317                cursor.line = self.lines.len().saturating_sub(1);
1318                cursor.index = self.lines.get(cursor.line)?.text().len();
1319                cursor_x_opt = None;
1320            }
1321            Motion::GotoLine(line) => {
1322                let mut layout_cursor = self.layout_cursor(font_system, cursor)?;
1323                layout_cursor.line = line;
1324                (cursor, cursor_x_opt) = self.cursor_motion(
1325                    font_system,
1326                    cursor,
1327                    cursor_x_opt,
1328                    Motion::LayoutCursor(layout_cursor),
1329                )?;
1330            }
1331        }
1332        Some((cursor, cursor_x_opt))
1333    }
1334
1335    /// Draw the buffer
1336    #[cfg(feature = "swash")]
1337    pub fn draw<F>(
1338        &self,
1339        font_system: &mut FontSystem,
1340        cache: &mut crate::SwashCache,
1341        color: Color,
1342        mut f: F,
1343    ) where
1344        F: FnMut(i32, i32, u32, u32, Color),
1345    {
1346        for run in self.layout_runs() {
1347            for glyph in run.glyphs.iter() {
1348                let physical_glyph = glyph.physical((0., 0.), 1.0);
1349
1350                let glyph_color = match glyph.color_opt {
1351                    Some(some) => some,
1352                    None => color,
1353                };
1354
1355                cache.with_pixels(
1356                    font_system,
1357                    physical_glyph.cache_key,
1358                    glyph_color,
1359                    |x, y, color| {
1360                        f(
1361                            physical_glyph.x + x,
1362                            run.line_y as i32 + physical_glyph.y + y,
1363                            1,
1364                            1,
1365                            color,
1366                        );
1367                    },
1368                );
1369            }
1370        }
1371    }
1372}
1373
1374impl BorrowedWithFontSystem<'_, Buffer> {
1375    /// Shape lines until cursor, also scrolling to include cursor in view
1376    pub fn shape_until_cursor(&mut self, cursor: Cursor, prune: bool) {
1377        self.inner
1378            .shape_until_cursor(self.font_system, cursor, prune);
1379    }
1380
1381    /// Shape lines until scroll
1382    pub fn shape_until_scroll(&mut self, prune: bool) {
1383        self.inner.shape_until_scroll(self.font_system, prune);
1384    }
1385
1386    /// Shape the provided line index and return the result
1387    pub fn line_shape(&mut self, line_i: usize) -> Option<&ShapeLine> {
1388        self.inner.line_shape(self.font_system, line_i)
1389    }
1390
1391    /// Lay out the provided line index and return the result
1392    pub fn line_layout(&mut self, line_i: usize) -> Option<&[LayoutLine]> {
1393        self.inner.line_layout(self.font_system, line_i)
1394    }
1395
1396    /// Set the current [`Metrics`]
1397    ///
1398    /// # Panics
1399    ///
1400    /// Will panic if `metrics.font_size` is zero.
1401    pub fn set_metrics(&mut self, metrics: Metrics) {
1402        self.inner.set_metrics(self.font_system, metrics);
1403    }
1404
1405    /// Set the current [`Wrap`]
1406    pub fn set_wrap(&mut self, wrap: Wrap) {
1407        self.inner.set_wrap(self.font_system, wrap);
1408    }
1409
1410    /// Set the current buffer dimensions
1411    pub fn set_size(&mut self, width_opt: Option<f32>, height_opt: Option<f32>) {
1412        self.inner.set_size(self.font_system, width_opt, height_opt);
1413    }
1414
1415    /// Set the current [`Metrics`] and buffer dimensions at the same time
1416    ///
1417    /// # Panics
1418    ///
1419    /// Will panic if `metrics.font_size` is zero.
1420    pub fn set_metrics_and_size(
1421        &mut self,
1422        metrics: Metrics,
1423        width_opt: Option<f32>,
1424        height_opt: Option<f32>,
1425    ) {
1426        self.inner
1427            .set_metrics_and_size(self.font_system, metrics, width_opt, height_opt);
1428    }
1429
1430    /// Set tab width (number of spaces between tab stops)
1431    pub fn set_tab_width(&mut self, tab_width: u16) {
1432        self.inner.set_tab_width(self.font_system, tab_width);
1433    }
1434
1435    /// Set text of buffer, using provided attributes for each line by default
1436    pub fn set_text(&mut self, text: &str, attrs: &Attrs, shaping: Shaping) {
1437        self.inner.set_text(self.font_system, text, attrs, shaping);
1438    }
1439
1440    /// Set text of buffer, using an iterator of styled spans (pairs of text and attributes)
1441    ///
1442    /// ```
1443    /// # use fastui_cosmic::{Attrs, Buffer, Family, FontSystem, Metrics, Shaping};
1444    /// # let mut font_system = FontSystem::new();
1445    /// let mut buffer = Buffer::new_empty(Metrics::new(32.0, 44.0));
1446    /// let attrs = Attrs::new().family(Family::Serif);
1447    /// buffer.set_rich_text(
1448    ///     &mut font_system,
1449    ///     [
1450    ///         ("hello, ", attrs.clone()),
1451    ///         ("cosmic\ntext", attrs.clone().family(Family::Monospace)),
1452    ///     ],
1453    ///     &attrs,
1454    ///     Shaping::Advanced,
1455    ///     None,
1456    /// );
1457    /// ```
1458    pub fn set_rich_text<'r, 's, I>(
1459        &mut self,
1460        spans: I,
1461        default_attrs: &Attrs,
1462        shaping: Shaping,
1463        alignment: Option<Align>,
1464    ) where
1465        I: IntoIterator<Item = (&'s str, Attrs<'r>)>,
1466    {
1467        self.inner
1468            .set_rich_text(self.font_system, spans, default_attrs, shaping, alignment);
1469    }
1470
1471    /// Apply a [`Motion`] to a [`Cursor`]
1472    pub fn cursor_motion(
1473        &mut self,
1474        cursor: Cursor,
1475        cursor_x_opt: Option<i32>,
1476        motion: Motion,
1477    ) -> Option<(Cursor, Option<i32>)> {
1478        self.inner
1479            .cursor_motion(self.font_system, cursor, cursor_x_opt, motion)
1480    }
1481
1482    /// Draw the buffer
1483    #[cfg(feature = "swash")]
1484    pub fn draw<F>(&mut self, cache: &mut crate::SwashCache, color: Color, f: F)
1485    where
1486        F: FnMut(i32, i32, u32, u32, Color),
1487    {
1488        self.inner.draw(self.font_system, cache, color, f);
1489    }
1490}