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, Hinting, LayoutCursor, LayoutGlyph, LayoutLine, LineEnding, LineIter,
15 Motion, 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 hinting: Hinting,
220}
221
222impl Clone for Buffer {
223 fn clone(&self) -> Self {
224 Self {
225 lines: self.lines.clone(),
226 metrics: self.metrics,
227 width_opt: self.width_opt,
228 height_opt: self.height_opt,
229 scroll: self.scroll,
230 redraw: self.redraw,
231 wrap: self.wrap,
232 monospace_width: self.monospace_width,
233 tab_width: self.tab_width,
234 hinting: self.hinting,
235 }
236 }
237}
238
239impl Buffer {
240 pub fn new_empty(metrics: Metrics) -> Self {
252 assert_ne!(metrics.line_height, 0.0, "line height cannot be 0");
253 Self {
254 lines: Vec::new(),
255 metrics,
256 width_opt: None,
257 height_opt: None,
258 scroll: Scroll::default(),
259 redraw: false,
260 wrap: Wrap::WordOrGlyph,
261 monospace_width: None,
262 tab_width: 8,
263 hinting: Hinting::default(),
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 self.hinting,
304 );
305 }
306 }
307
308 self.redraw = true;
309
310 #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
311 log::debug!("relayout: {:?}", instant.elapsed());
312 }
313
314 #[allow(clippy::missing_panics_doc)]
316 pub fn shape_until_cursor(
317 &mut self,
318 font_system: &mut FontSystem,
319 cursor: Cursor,
320 prune: bool,
321 ) {
322 let metrics = self.metrics;
323 let old_scroll = self.scroll;
324
325 let layout_cursor = self
326 .layout_cursor(font_system, cursor)
327 .expect("shape_until_cursor invalid cursor");
328
329 let mut layout_y = 0.0;
330 let mut total_height = {
331 let layout = self
332 .line_layout(font_system, layout_cursor.line)
333 .expect("shape_until_cursor failed to scroll forwards");
334 (0..layout_cursor.layout).for_each(|layout_i| {
335 layout_y += layout[layout_i]
336 .line_height_opt
337 .unwrap_or(metrics.line_height);
338 });
339 layout_y
340 + layout[layout_cursor.layout]
341 .line_height_opt
342 .unwrap_or(metrics.line_height)
343 };
344
345 if self.scroll.line > layout_cursor.line
346 || (self.scroll.line == layout_cursor.line && self.scroll.vertical > layout_y)
347 {
348 self.scroll.line = layout_cursor.line;
350 self.scroll.vertical = layout_y;
351 } else if let Some(height) = self.height_opt {
352 let mut line_i = layout_cursor.line;
354 if line_i <= self.scroll.line {
355 if total_height > height + self.scroll.vertical {
357 self.scroll.vertical = total_height - height;
358 }
359 } else {
360 while line_i > self.scroll.line {
361 line_i -= 1;
362 let layout = self
363 .line_layout(font_system, line_i)
364 .expect("shape_until_cursor failed to scroll forwards");
365 for layout_line in layout {
366 total_height += layout_line.line_height_opt.unwrap_or(metrics.line_height);
367 }
368 if total_height > height + self.scroll.vertical {
369 self.scroll.line = line_i;
370 self.scroll.vertical = total_height - height;
371 }
372 }
373 }
374 }
375
376 if old_scroll != self.scroll {
377 self.redraw = true;
378 }
379
380 self.shape_until_scroll(font_system, prune);
381
382 if let Some(layout_cursor) = self.layout_cursor(font_system, cursor) {
384 if let Some(layout_lines) = self.line_layout(font_system, layout_cursor.line) {
385 if let Some(layout_line) = layout_lines.get(layout_cursor.layout) {
386 let (x_min, x_max) = layout_line
387 .glyphs
388 .get(layout_cursor.glyph)
389 .or_else(|| layout_line.glyphs.last())
390 .map_or((0.0, 0.0), |glyph| {
391 let x_a = glyph.x;
393 let x_b = glyph.x + glyph.w;
394 (x_a.min(x_b), x_a.max(x_b))
395 });
396 if x_min < self.scroll.horizontal {
397 self.scroll.horizontal = x_min;
398 self.redraw = true;
399 }
400 if let Some(width) = self.width_opt {
401 if x_max > self.scroll.horizontal + width {
402 self.scroll.horizontal = x_max - width;
403 self.redraw = true;
404 }
405 }
406 }
407 }
408 }
409 }
410
411 #[allow(clippy::missing_panics_doc)]
413 pub fn shape_until_scroll(&mut self, font_system: &mut FontSystem, prune: bool) {
414 let metrics = self.metrics;
415 let old_scroll = self.scroll;
416
417 loop {
418 while self.scroll.vertical < 0.0 {
420 if self.scroll.line > 0 {
421 let line_i = self.scroll.line - 1;
422 if let Some(layout) = self.line_layout(font_system, line_i) {
423 let mut layout_height = 0.0;
424 for layout_line in layout {
425 layout_height +=
426 layout_line.line_height_opt.unwrap_or(metrics.line_height);
427 }
428 self.scroll.line = line_i;
429 self.scroll.vertical += layout_height;
430 } else {
431 self.scroll.line = line_i;
433 self.scroll.vertical += metrics.line_height;
434 }
435 } else {
436 self.scroll.vertical = 0.0;
437 break;
438 }
439 }
440
441 let scroll_start = self.scroll.vertical;
442 let scroll_end = scroll_start + self.height_opt.unwrap_or(f32::INFINITY);
443
444 let mut total_height = 0.0;
445 for line_i in 0..self.lines.len() {
446 if line_i < self.scroll.line {
447 if prune {
448 self.lines[line_i].reset_shaping();
449 }
450 continue;
451 }
452 if total_height > scroll_end {
453 if prune {
454 self.lines[line_i].reset_shaping();
455 continue;
456 }
457 break;
458 }
459
460 let mut layout_height = 0.0;
461 let layout = self
462 .line_layout(font_system, line_i)
463 .expect("shape_until_scroll invalid line");
464 for layout_line in layout {
465 let line_height = layout_line.line_height_opt.unwrap_or(metrics.line_height);
466 layout_height += line_height;
467 total_height += line_height;
468 }
469
470 if line_i == self.scroll.line && layout_height <= self.scroll.vertical {
472 self.scroll.line += 1;
473 self.scroll.vertical -= layout_height;
474 }
475 }
476
477 if total_height < scroll_end && self.scroll.line > 0 {
478 self.scroll.vertical -= scroll_end - total_height;
480 } else {
481 break;
483 }
484 }
485
486 if old_scroll != self.scroll {
487 self.redraw = true;
488 }
489 }
490
491 pub fn layout_cursor(
493 &mut self,
494 font_system: &mut FontSystem,
495 cursor: Cursor,
496 ) -> Option<LayoutCursor> {
497 let layout = self.line_layout(font_system, cursor.line)?;
498 for (layout_i, layout_line) in layout.iter().enumerate() {
499 for (glyph_i, glyph) in layout_line.glyphs.iter().enumerate() {
500 let cursor_end =
501 Cursor::new_with_affinity(cursor.line, glyph.end, Affinity::Before);
502 let cursor_start =
503 Cursor::new_with_affinity(cursor.line, glyph.start, Affinity::After);
504 let (cursor_left, cursor_right) = if glyph.level.is_ltr() {
505 (cursor_start, cursor_end)
506 } else {
507 (cursor_end, cursor_start)
508 };
509 if cursor == cursor_left {
510 return Some(LayoutCursor::new(cursor.line, layout_i, glyph_i));
511 }
512 if cursor == cursor_right {
513 return Some(LayoutCursor::new(cursor.line, layout_i, glyph_i + 1));
514 }
515 }
516 }
517
518 Some(LayoutCursor::new(cursor.line, 0, 0))
521 }
522
523 pub fn line_shape(
525 &mut self,
526 font_system: &mut FontSystem,
527 line_i: usize,
528 ) -> Option<&ShapeLine> {
529 let line = self.lines.get_mut(line_i)?;
530 Some(line.shape(font_system, self.tab_width))
531 }
532
533 pub fn line_layout(
535 &mut self,
536 font_system: &mut FontSystem,
537 line_i: usize,
538 ) -> Option<&[LayoutLine]> {
539 let line = self.lines.get_mut(line_i)?;
540 Some(line.layout(
541 font_system,
542 self.metrics.font_size,
543 self.width_opt,
544 self.wrap,
545 self.monospace_width,
546 self.tab_width,
547 self.hinting,
548 ))
549 }
550
551 pub const fn metrics(&self) -> Metrics {
553 self.metrics
554 }
555
556 pub fn set_metrics(&mut self, font_system: &mut FontSystem, metrics: Metrics) {
562 self.set_metrics_and_size(font_system, metrics, self.width_opt, self.height_opt);
563 }
564
565 pub const fn hinting(&self) -> Hinting {
567 self.hinting
568 }
569
570 pub fn set_hinting(&mut self, font_system: &mut FontSystem, hinting: Hinting) {
572 if hinting != self.hinting {
573 self.hinting = hinting;
574 self.relayout(font_system);
575 self.shape_until_scroll(font_system, false);
576 }
577 }
578
579 pub const fn wrap(&self) -> Wrap {
581 self.wrap
582 }
583
584 pub fn set_wrap(&mut self, font_system: &mut FontSystem, wrap: Wrap) {
586 if wrap != self.wrap {
587 self.wrap = wrap;
588 self.relayout(font_system);
589 self.shape_until_scroll(font_system, false);
590 }
591 }
592
593 pub const fn monospace_width(&self) -> Option<f32> {
595 self.monospace_width
596 }
597
598 pub fn set_monospace_width(
600 &mut self,
601 font_system: &mut FontSystem,
602 monospace_width: Option<f32>,
603 ) {
604 if monospace_width != self.monospace_width {
605 self.monospace_width = monospace_width;
606 self.relayout(font_system);
607 self.shape_until_scroll(font_system, false);
608 }
609 }
610
611 pub const fn tab_width(&self) -> u16 {
613 self.tab_width
614 }
615
616 pub fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16) {
618 if tab_width == 0 {
620 return;
621 }
622 if tab_width != self.tab_width {
623 self.tab_width = tab_width;
624 for line in &mut self.lines {
626 if line.shape_opt().is_some() && line.text().contains('\t') {
627 line.reset_shaping();
628 }
629 }
630 self.redraw = true;
631 self.shape_until_scroll(font_system, false);
632 }
633 }
634
635 pub const fn size(&self) -> (Option<f32>, Option<f32>) {
637 (self.width_opt, self.height_opt)
638 }
639
640 pub fn set_size(
642 &mut self,
643 font_system: &mut FontSystem,
644 width_opt: Option<f32>,
645 height_opt: Option<f32>,
646 ) {
647 self.set_metrics_and_size(font_system, self.metrics, width_opt, height_opt);
648 }
649
650 pub fn set_metrics_and_size(
656 &mut self,
657 font_system: &mut FontSystem,
658 metrics: Metrics,
659 width_opt: Option<f32>,
660 height_opt: Option<f32>,
661 ) {
662 let clamped_width_opt = width_opt.map(|width| width.max(0.0));
663 let clamped_height_opt = height_opt.map(|height| height.max(0.0));
664
665 if metrics != self.metrics
666 || clamped_width_opt != self.width_opt
667 || clamped_height_opt != self.height_opt
668 {
669 assert_ne!(metrics.font_size, 0.0, "font size cannot be 0");
670 self.metrics = metrics;
671 self.width_opt = clamped_width_opt;
672 self.height_opt = clamped_height_opt;
673 self.relayout(font_system);
674 self.shape_until_scroll(font_system, false);
675 }
676 }
677
678 pub const fn scroll(&self) -> Scroll {
680 self.scroll
681 }
682
683 pub fn set_scroll(&mut self, scroll: Scroll) {
685 if scroll != self.scroll {
686 self.scroll = scroll;
687 self.redraw = true;
688 }
689 }
690
691 pub fn set_text(
693 &mut self,
694 font_system: &mut FontSystem,
695 text: &str,
696 attrs: &Attrs,
697 shaping: Shaping,
698 alignment: Option<Align>,
699 ) {
700 self.lines.clear();
701 for (range, ending) in LineIter::new(text) {
702 self.lines.push(BufferLine::new(
703 &text[range],
704 ending,
705 AttrsList::new(attrs),
706 shaping,
707 ));
708 }
709
710 if self
712 .lines
713 .last()
714 .map(|line| line.ending())
715 .unwrap_or_default()
716 != LineEnding::None
717 {
718 self.lines.push(BufferLine::new(
719 "",
720 LineEnding::None,
721 AttrsList::new(attrs),
722 shaping,
723 ));
724 }
725
726 if alignment.is_some() {
727 self.lines.iter_mut().for_each(|line| {
728 line.set_align(alignment);
729 });
730 }
731
732 self.scroll = Scroll::default();
733 self.shape_until_scroll(font_system, false);
734 }
735
736 pub fn set_rich_text<'r, 's, I>(
755 &mut self,
756 font_system: &mut FontSystem,
757 spans: I,
758 default_attrs: &Attrs,
759 shaping: Shaping,
760 alignment: Option<Align>,
761 ) where
762 I: IntoIterator<Item = (&'s str, Attrs<'r>)>,
763 {
764 let mut end = 0;
765 let (string, spans_data): (String, Vec<_>) = spans
767 .into_iter()
768 .map(|(s, attrs)| {
769 let start = end;
770 end += s.len();
771 (s, (attrs, start..end))
772 })
773 .unzip();
774
775 let mut spans_iter = spans_data.into_iter();
776 let mut maybe_span = spans_iter.next();
777
778 let string_start = string.as_ptr() as usize;
780 let mut lines_iter = BidiParagraphs::new(&string).map(|line: &str| {
781 let start = line.as_ptr() as usize - string_start;
782 let end = start + line.len();
783 start..end
784 });
785 let mut maybe_line = lines_iter.next();
786 let line_ending = LineEnding::default();
788
789 let mut line_count = 0;
790 let mut attrs_list = self
791 .lines
792 .get_mut(line_count)
793 .map_or_else(|| AttrsList::new(&Attrs::new()), BufferLine::reclaim_attrs)
794 .reset(default_attrs);
795 let mut line_string = self
796 .lines
797 .get_mut(line_count)
798 .map(BufferLine::reclaim_text)
799 .unwrap_or_default();
800
801 loop {
802 let (Some(line_range), Some((attrs, span_range))) = (&maybe_line, &maybe_span) else {
803 if self.lines.len() == line_count {
805 self.lines.push(BufferLine::empty());
806 }
807 self.lines[line_count].reset_new(
808 String::new(),
809 line_ending,
810 AttrsList::new(default_attrs),
811 shaping,
812 );
813 line_count += 1;
814 break;
815 };
816
817 let start = line_range.start.max(span_range.start);
819 let end = line_range.end.min(span_range.end);
820 if start < end {
821 let text = &string[start..end];
822 let text_start = line_string.len();
823 line_string.push_str(text);
824 let text_end = line_string.len();
825 if *attrs != attrs_list.defaults() {
827 attrs_list.add_span(text_start..text_end, attrs);
828 }
829 }
830
831 if span_range.end < line_range.end {
837 maybe_span = spans_iter.next();
838 } else {
839 maybe_line = lines_iter.next();
840 if maybe_line.is_some() {
841 let next_attrs_list = self
843 .lines
844 .get_mut(line_count + 1)
845 .map_or_else(|| AttrsList::new(&Attrs::new()), BufferLine::reclaim_attrs)
846 .reset(default_attrs);
847 let next_line_string = self
848 .lines
849 .get_mut(line_count + 1)
850 .map(BufferLine::reclaim_text)
851 .unwrap_or_default();
852 let prev_attrs_list = core::mem::replace(&mut attrs_list, next_attrs_list);
853 let prev_line_string = core::mem::replace(&mut line_string, next_line_string);
854 if self.lines.len() == line_count {
855 self.lines.push(BufferLine::empty());
856 }
857 self.lines[line_count].reset_new(
858 prev_line_string,
859 line_ending,
860 prev_attrs_list,
861 shaping,
862 );
863 line_count += 1;
864 } else {
865 if self.lines.len() == line_count {
867 self.lines.push(BufferLine::empty());
868 }
869 self.lines[line_count].reset_new(line_string, line_ending, attrs_list, shaping);
870 line_count += 1;
871 break;
872 }
873 }
874 }
875
876 self.lines.truncate(line_count);
878
879 self.lines.iter_mut().for_each(|line| {
880 line.set_align(alignment);
881 });
882
883 self.scroll = Scroll::default();
884
885 self.shape_until_scroll(font_system, false);
886 }
887
888 pub const fn redraw(&self) -> bool {
890 self.redraw
891 }
892
893 pub fn set_redraw(&mut self, redraw: bool) {
895 self.redraw = redraw;
896 }
897
898 pub fn layout_runs(&self) -> LayoutRunIter<'_> {
900 LayoutRunIter::new(self)
901 }
902
903 pub fn hit(&self, x: f32, y: f32) -> Option<Cursor> {
905 #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
906 let instant = std::time::Instant::now();
907
908 let mut new_cursor_opt = None;
909
910 let mut runs = self.layout_runs().peekable();
911 let mut first_run = true;
912 while let Some(run) = runs.next() {
913 let line_top = run.line_top;
914 let line_height = run.line_height;
915
916 if first_run && y < line_top {
917 first_run = false;
918 let new_cursor = Cursor::new(run.line_i, 0);
919 new_cursor_opt = Some(new_cursor);
920 } else if y >= line_top && y < line_top + line_height {
921 let mut new_cursor_glyph = run.glyphs.len();
922 let mut new_cursor_char = 0;
923 let mut new_cursor_affinity = Affinity::After;
924
925 let mut first_glyph = true;
926
927 'hit: for (glyph_i, glyph) in run.glyphs.iter().enumerate() {
928 if first_glyph {
929 first_glyph = false;
930 if (run.rtl && x > glyph.x) || (!run.rtl && x < 0.0) {
931 new_cursor_glyph = 0;
932 new_cursor_char = 0;
933 }
934 }
935 if x >= glyph.x && x <= glyph.x + glyph.w {
936 new_cursor_glyph = glyph_i;
937
938 let cluster = &run.text[glyph.start..glyph.end];
939 let total = cluster.grapheme_indices(true).count();
940 let mut egc_x = glyph.x;
941 let egc_w = glyph.w / (total as f32);
942 for (egc_i, egc) in cluster.grapheme_indices(true) {
943 if x >= egc_x && x <= egc_x + egc_w {
944 new_cursor_char = egc_i;
945
946 let right_half = x >= egc_x + egc_w / 2.0;
947 if right_half != glyph.level.is_rtl() {
948 new_cursor_char += egc.len();
950 new_cursor_affinity = Affinity::Before;
951 }
952 break 'hit;
953 }
954 egc_x += egc_w;
955 }
956
957 let right_half = x >= glyph.x + glyph.w / 2.0;
958 if right_half != glyph.level.is_rtl() {
959 new_cursor_char = cluster.len();
961 new_cursor_affinity = Affinity::Before;
962 }
963 break 'hit;
964 }
965 }
966
967 let mut new_cursor = Cursor::new(run.line_i, 0);
968
969 match run.glyphs.get(new_cursor_glyph) {
970 Some(glyph) => {
971 new_cursor.index = glyph.start + new_cursor_char;
973 new_cursor.affinity = new_cursor_affinity;
974 }
975 None => {
976 if let Some(glyph) = run.glyphs.last() {
977 new_cursor.index = glyph.end;
979 new_cursor.affinity = Affinity::Before;
980 }
981 }
982 }
983
984 new_cursor_opt = Some(new_cursor);
985
986 break;
987 } else if runs.peek().is_none() && y > run.line_y {
988 let mut new_cursor = Cursor::new(run.line_i, 0);
989 if let Some(glyph) = run.glyphs.last() {
990 new_cursor = run.cursor_from_glyph_right(glyph);
991 }
992 new_cursor_opt = Some(new_cursor);
993 }
994 }
995
996 #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
997 log::trace!("click({}, {}): {:?}", x, y, instant.elapsed());
998
999 new_cursor_opt
1000 }
1001
1002 pub fn cursor_motion(
1004 &mut self,
1005 font_system: &mut FontSystem,
1006 mut cursor: Cursor,
1007 mut cursor_x_opt: Option<i32>,
1008 motion: Motion,
1009 ) -> Option<(Cursor, Option<i32>)> {
1010 match motion {
1011 Motion::LayoutCursor(layout_cursor) => {
1012 let layout = self.line_layout(font_system, layout_cursor.line)?;
1013
1014 let layout_line = match layout.get(layout_cursor.layout) {
1015 Some(some) => some,
1016 None => match layout.last() {
1017 Some(some) => some,
1018 None => {
1019 return None;
1020 }
1021 },
1022 };
1023
1024 let (new_index, new_affinity) =
1025 layout_line.glyphs.get(layout_cursor.glyph).map_or_else(
1026 || {
1027 layout_line
1028 .glyphs
1029 .last()
1030 .map_or((0, Affinity::After), |glyph| (glyph.end, Affinity::Before))
1031 },
1032 |glyph| (glyph.start, Affinity::After),
1033 );
1034
1035 if cursor.line != layout_cursor.line
1036 || cursor.index != new_index
1037 || cursor.affinity != new_affinity
1038 {
1039 cursor.line = layout_cursor.line;
1040 cursor.index = new_index;
1041 cursor.affinity = new_affinity;
1042 }
1043 }
1044 Motion::Previous => {
1045 let line = self.lines.get(cursor.line)?;
1046 if cursor.index > 0 {
1047 let mut prev_index = 0;
1049 for (i, _) in line.text().grapheme_indices(true) {
1050 if i < cursor.index {
1051 prev_index = i;
1052 } else {
1053 break;
1054 }
1055 }
1056
1057 cursor.index = prev_index;
1058 cursor.affinity = Affinity::After;
1059 } else if cursor.line > 0 {
1060 cursor.line -= 1;
1061 cursor.index = self.lines.get(cursor.line)?.text().len();
1062 cursor.affinity = Affinity::After;
1063 }
1064 cursor_x_opt = None;
1065 }
1066 Motion::Next => {
1067 let line = self.lines.get(cursor.line)?;
1068 if cursor.index < line.text().len() {
1069 for (i, c) in line.text().grapheme_indices(true) {
1070 if i == cursor.index {
1071 cursor.index += c.len();
1072 cursor.affinity = Affinity::Before;
1073 break;
1074 }
1075 }
1076 } else if cursor.line + 1 < self.lines.len() {
1077 cursor.line += 1;
1078 cursor.index = 0;
1079 cursor.affinity = Affinity::Before;
1080 }
1081 cursor_x_opt = None;
1082 }
1083 Motion::Left => {
1084 let rtl_opt = self
1085 .line_shape(font_system, cursor.line)
1086 .map(|shape| shape.rtl);
1087 if let Some(rtl) = rtl_opt {
1088 if rtl {
1089 (cursor, cursor_x_opt) =
1090 self.cursor_motion(font_system, cursor, cursor_x_opt, Motion::Next)?;
1091 } else {
1092 (cursor, cursor_x_opt) = self.cursor_motion(
1093 font_system,
1094 cursor,
1095 cursor_x_opt,
1096 Motion::Previous,
1097 )?;
1098 }
1099 }
1100 }
1101 Motion::Right => {
1102 let rtl_opt = self
1103 .line_shape(font_system, cursor.line)
1104 .map(|shape| shape.rtl);
1105 if let Some(rtl) = rtl_opt {
1106 if rtl {
1107 (cursor, cursor_x_opt) = self.cursor_motion(
1108 font_system,
1109 cursor,
1110 cursor_x_opt,
1111 Motion::Previous,
1112 )?;
1113 } else {
1114 (cursor, cursor_x_opt) =
1115 self.cursor_motion(font_system, cursor, cursor_x_opt, Motion::Next)?;
1116 }
1117 }
1118 }
1119 Motion::Up => {
1120 let mut layout_cursor = self.layout_cursor(font_system, cursor)?;
1121
1122 if cursor_x_opt.is_none() {
1123 cursor_x_opt = Some(
1124 layout_cursor.glyph as i32, );
1126 }
1127
1128 if layout_cursor.layout > 0 {
1129 layout_cursor.layout -= 1;
1130 } else if layout_cursor.line > 0 {
1131 layout_cursor.line -= 1;
1132 layout_cursor.layout = usize::MAX;
1133 }
1134
1135 if let Some(cursor_x) = cursor_x_opt {
1136 layout_cursor.glyph = cursor_x as usize; }
1138
1139 (cursor, cursor_x_opt) = self.cursor_motion(
1140 font_system,
1141 cursor,
1142 cursor_x_opt,
1143 Motion::LayoutCursor(layout_cursor),
1144 )?;
1145 }
1146 Motion::Down => {
1147 let mut layout_cursor = self.layout_cursor(font_system, cursor)?;
1148
1149 let layout_len = self.line_layout(font_system, layout_cursor.line)?.len();
1150
1151 if cursor_x_opt.is_none() {
1152 cursor_x_opt = Some(
1153 layout_cursor.glyph as i32, );
1155 }
1156
1157 if layout_cursor.layout + 1 < layout_len {
1158 layout_cursor.layout += 1;
1159 } else if layout_cursor.line + 1 < self.lines.len() {
1160 layout_cursor.line += 1;
1161 layout_cursor.layout = 0;
1162 }
1163
1164 if let Some(cursor_x) = cursor_x_opt {
1165 layout_cursor.glyph = cursor_x as usize; }
1167
1168 (cursor, cursor_x_opt) = self.cursor_motion(
1169 font_system,
1170 cursor,
1171 cursor_x_opt,
1172 Motion::LayoutCursor(layout_cursor),
1173 )?;
1174 }
1175 Motion::Home => {
1176 let mut layout_cursor = self.layout_cursor(font_system, cursor)?;
1177 layout_cursor.glyph = 0;
1178 #[allow(unused_assignments)]
1179 {
1180 (cursor, cursor_x_opt) = self.cursor_motion(
1181 font_system,
1182 cursor,
1183 cursor_x_opt,
1184 Motion::LayoutCursor(layout_cursor),
1185 )?;
1186 }
1187 cursor_x_opt = None;
1188 }
1189 Motion::SoftHome => {
1190 let line = self.lines.get(cursor.line)?;
1191 cursor.index = line
1192 .text()
1193 .char_indices()
1194 .find_map(|(i, c)| if c.is_whitespace() { None } else { Some(i) })
1195 .unwrap_or(0);
1196 cursor_x_opt = None;
1197 }
1198 Motion::End => {
1199 let mut layout_cursor = self.layout_cursor(font_system, cursor)?;
1200 layout_cursor.glyph = usize::MAX;
1201 #[allow(unused_assignments)]
1202 {
1203 (cursor, cursor_x_opt) = self.cursor_motion(
1204 font_system,
1205 cursor,
1206 cursor_x_opt,
1207 Motion::LayoutCursor(layout_cursor),
1208 )?;
1209 }
1210 cursor_x_opt = None;
1211 }
1212 Motion::ParagraphStart => {
1213 cursor.index = 0;
1214 cursor_x_opt = None;
1215 }
1216 Motion::ParagraphEnd => {
1217 cursor.index = self.lines.get(cursor.line)?.text().len();
1218 cursor_x_opt = None;
1219 }
1220 Motion::PageUp => {
1221 if let Some(height) = self.height_opt {
1222 (cursor, cursor_x_opt) = self.cursor_motion(
1223 font_system,
1224 cursor,
1225 cursor_x_opt,
1226 Motion::Vertical(-height as i32),
1227 )?;
1228 }
1229 }
1230 Motion::PageDown => {
1231 if let Some(height) = self.height_opt {
1232 (cursor, cursor_x_opt) = self.cursor_motion(
1233 font_system,
1234 cursor,
1235 cursor_x_opt,
1236 Motion::Vertical(height as i32),
1237 )?;
1238 }
1239 }
1240 Motion::Vertical(px) => {
1241 let lines = px / self.metrics().line_height as i32;
1243 match lines.cmp(&0) {
1244 cmp::Ordering::Less => {
1245 for _ in 0..-lines {
1246 (cursor, cursor_x_opt) =
1247 self.cursor_motion(font_system, cursor, cursor_x_opt, Motion::Up)?;
1248 }
1249 }
1250 cmp::Ordering::Greater => {
1251 for _ in 0..lines {
1252 (cursor, cursor_x_opt) = self.cursor_motion(
1253 font_system,
1254 cursor,
1255 cursor_x_opt,
1256 Motion::Down,
1257 )?;
1258 }
1259 }
1260 cmp::Ordering::Equal => {}
1261 }
1262 }
1263 Motion::PreviousWord => {
1264 let line = self.lines.get(cursor.line)?;
1265 if cursor.index > 0 {
1266 cursor.index = line
1267 .text()
1268 .unicode_word_indices()
1269 .rev()
1270 .map(|(i, _)| i)
1271 .find(|&i| i < cursor.index)
1272 .unwrap_or(0);
1273 } else if cursor.line > 0 {
1274 cursor.line -= 1;
1275 cursor.index = self.lines.get(cursor.line)?.text().len();
1276 }
1277 cursor_x_opt = None;
1278 }
1279 Motion::NextWord => {
1280 let line = self.lines.get(cursor.line)?;
1281 if cursor.index < line.text().len() {
1282 cursor.index = line
1283 .text()
1284 .unicode_word_indices()
1285 .map(|(i, word)| i + word.len())
1286 .find(|&i| i > cursor.index)
1287 .unwrap_or_else(|| line.text().len());
1288 } else if cursor.line + 1 < self.lines.len() {
1289 cursor.line += 1;
1290 cursor.index = 0;
1291 }
1292 cursor_x_opt = None;
1293 }
1294 Motion::LeftWord => {
1295 let rtl_opt = self
1296 .line_shape(font_system, cursor.line)
1297 .map(|shape| shape.rtl);
1298 if let Some(rtl) = rtl_opt {
1299 if rtl {
1300 (cursor, cursor_x_opt) = self.cursor_motion(
1301 font_system,
1302 cursor,
1303 cursor_x_opt,
1304 Motion::NextWord,
1305 )?;
1306 } else {
1307 (cursor, cursor_x_opt) = self.cursor_motion(
1308 font_system,
1309 cursor,
1310 cursor_x_opt,
1311 Motion::PreviousWord,
1312 )?;
1313 }
1314 }
1315 }
1316 Motion::RightWord => {
1317 let rtl_opt = self
1318 .line_shape(font_system, cursor.line)
1319 .map(|shape| shape.rtl);
1320 if let Some(rtl) = rtl_opt {
1321 if rtl {
1322 (cursor, cursor_x_opt) = self.cursor_motion(
1323 font_system,
1324 cursor,
1325 cursor_x_opt,
1326 Motion::PreviousWord,
1327 )?;
1328 } else {
1329 (cursor, cursor_x_opt) = self.cursor_motion(
1330 font_system,
1331 cursor,
1332 cursor_x_opt,
1333 Motion::NextWord,
1334 )?;
1335 }
1336 }
1337 }
1338 Motion::BufferStart => {
1339 cursor.line = 0;
1340 cursor.index = 0;
1341 cursor_x_opt = None;
1342 }
1343 Motion::BufferEnd => {
1344 cursor.line = self.lines.len().saturating_sub(1);
1345 cursor.index = self.lines.get(cursor.line)?.text().len();
1346 cursor_x_opt = None;
1347 }
1348 Motion::GotoLine(line) => {
1349 let mut layout_cursor = self.layout_cursor(font_system, cursor)?;
1350 layout_cursor.line = line;
1351 (cursor, cursor_x_opt) = self.cursor_motion(
1352 font_system,
1353 cursor,
1354 cursor_x_opt,
1355 Motion::LayoutCursor(layout_cursor),
1356 )?;
1357 }
1358 }
1359 Some((cursor, cursor_x_opt))
1360 }
1361
1362 #[cfg(feature = "swash")]
1364 pub fn draw<F>(
1365 &self,
1366 font_system: &mut FontSystem,
1367 cache: &mut crate::SwashCache,
1368 color: Color,
1369 callback: F,
1370 ) where
1371 F: FnMut(i32, i32, u32, u32, Color),
1372 {
1373 let mut renderer = crate::LegacyRenderer {
1374 font_system,
1375 cache,
1376 callback,
1377 };
1378 self.render(&mut renderer, color);
1379 }
1380
1381 pub fn render<R: Renderer>(&self, renderer: &mut R, color: Color) {
1382 for run in self.layout_runs() {
1383 for glyph in run.glyphs {
1384 let physical_glyph = glyph.physical((0., run.line_y), 1.0);
1385 let glyph_color = glyph.color_opt.map_or(color, |some| some);
1386 renderer.glyph(physical_glyph, glyph_color);
1387 }
1388 }
1389 }
1390}
1391
1392impl BorrowedWithFontSystem<'_, Buffer> {
1393 pub fn shape_until_cursor(&mut self, cursor: Cursor, prune: bool) {
1395 self.inner
1396 .shape_until_cursor(self.font_system, cursor, prune);
1397 }
1398
1399 pub fn shape_until_scroll(&mut self, prune: bool) {
1401 self.inner.shape_until_scroll(self.font_system, prune);
1402 }
1403
1404 pub fn line_shape(&mut self, line_i: usize) -> Option<&ShapeLine> {
1406 self.inner.line_shape(self.font_system, line_i)
1407 }
1408
1409 pub fn line_layout(&mut self, line_i: usize) -> Option<&[LayoutLine]> {
1411 self.inner.line_layout(self.font_system, line_i)
1412 }
1413
1414 pub fn set_metrics(&mut self, metrics: Metrics) {
1420 self.inner.set_metrics(self.font_system, metrics);
1421 }
1422
1423 pub fn set_wrap(&mut self, wrap: Wrap) {
1425 self.inner.set_wrap(self.font_system, wrap);
1426 }
1427
1428 pub fn set_size(&mut self, width_opt: Option<f32>, height_opt: Option<f32>) {
1430 self.inner.set_size(self.font_system, width_opt, height_opt);
1431 }
1432
1433 pub fn set_metrics_and_size(
1439 &mut self,
1440 metrics: Metrics,
1441 width_opt: Option<f32>,
1442 height_opt: Option<f32>,
1443 ) {
1444 self.inner
1445 .set_metrics_and_size(self.font_system, metrics, width_opt, height_opt);
1446 }
1447
1448 pub fn set_tab_width(&mut self, tab_width: u16) {
1450 self.inner.set_tab_width(self.font_system, tab_width);
1451 }
1452
1453 pub fn set_text(
1455 &mut self,
1456 text: &str,
1457 attrs: &Attrs,
1458 shaping: Shaping,
1459 alignment: Option<Align>,
1460 ) {
1461 self.inner
1462 .set_text(self.font_system, text, attrs, shaping, alignment);
1463 }
1464
1465 pub fn set_rich_text<'r, 's, I>(
1484 &mut self,
1485 spans: I,
1486 default_attrs: &Attrs,
1487 shaping: Shaping,
1488 alignment: Option<Align>,
1489 ) where
1490 I: IntoIterator<Item = (&'s str, Attrs<'r>)>,
1491 {
1492 self.inner
1493 .set_rich_text(self.font_system, spans, default_attrs, shaping, alignment);
1494 }
1495
1496 pub fn cursor_motion(
1498 &mut self,
1499 cursor: Cursor,
1500 cursor_x_opt: Option<i32>,
1501 motion: Motion,
1502 ) -> Option<(Cursor, Option<i32>)> {
1503 self.inner
1504 .cursor_motion(self.font_system, cursor, cursor_x_opt, motion)
1505 }
1506
1507 #[cfg(feature = "swash")]
1509 pub fn draw<F>(&mut self, cache: &mut crate::SwashCache, color: Color, f: F)
1510 where
1511 F: FnMut(i32, i32, u32, u32, Color),
1512 {
1513 self.inner.draw(self.font_system, cache, color, f);
1514 }
1515}