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