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