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