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