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