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