Skip to main content

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