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