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