@@ -11,10 +11,170 @@ use unicode_segmentation::UnicodeSegmentation;
1111
1212use crate :: {
1313 render_decoration, Affinity , Align , Attrs , AttrsList , BidiParagraphs , BorrowedWithFontSystem ,
14- BufferLine , Color , Cursor , Ellipsize , FontSystem , Hinting , LayoutCursor , LayoutLine ,
15- LayoutRunIter , LineEnding , LineIter , Motion , Renderer , Scroll , ShapeLine , Shaping , Wrap ,
14+ BufferLine , Color , Cursor , DecorationSpan , Ellipsize , FontSystem , Hinting , LayoutCursor ,
15+ LayoutGlyph , LayoutLine , LineEnding , LineIter , Motion , Renderer , Scroll , ShapeLine , Shaping ,
16+ Wrap ,
1617} ;
1718
19+ /// A line of visible text for rendering
20+ #[ derive( Debug ) ]
21+ pub struct LayoutRun < ' a > {
22+ /// The index of the original text line
23+ pub line_i : usize ,
24+ /// The original text line
25+ pub text : & ' a str ,
26+ /// True if the original paragraph direction is RTL
27+ pub rtl : bool ,
28+ /// The array of layout glyphs to draw
29+ pub glyphs : & ' a [ LayoutGlyph ] ,
30+ /// Text decoration spans covering ranges of glyphs
31+ pub decorations : & ' a [ DecorationSpan ] ,
32+ /// Y offset to baseline of line
33+ pub line_y : f32 ,
34+ /// Y offset to top of line
35+ pub line_top : f32 ,
36+ /// Y offset to next line
37+ pub line_height : f32 ,
38+ /// Width of line
39+ pub line_w : f32 ,
40+ }
41+
42+ impl LayoutRun < ' _ > {
43+ /// Return the pixel span `Some((x_left, x_width))` of the highlighted area between `cursor_start`
44+ /// and `cursor_end` within this run, or None if the cursor range does not intersect this run.
45+ /// This may return widths of zero if `cursor_start == cursor_end`, if the run is empty, or if the
46+ /// region's left start boundary is the same as the cursor's end boundary or vice versa.
47+ #[ 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+ /// An iterator of visible text lines, see [`LayoutRun`]
98+ #[ derive( Debug ) ]
99+ pub struct LayoutRunIter < ' b > {
100+ lines : & ' b [ BufferLine ] ,
101+ height_opt : Option < f32 > ,
102+ line_height : f32 ,
103+ scroll : f32 ,
104+ line_i : usize ,
105+ layout_i : usize ,
106+ total_height : f32 ,
107+ line_top : f32 ,
108+ }
109+
110+ impl < ' b > LayoutRunIter < ' b > {
111+ pub const fn new (
112+ lines : & ' b [ BufferLine ] ,
113+ height_opt : Option < f32 > ,
114+ line_height : f32 ,
115+ scroll : f32 ,
116+ start : usize ,
117+ ) -> Self {
118+ Self {
119+ lines,
120+ height_opt,
121+ line_height,
122+ scroll,
123+ line_i : start,
124+ layout_i : 0 ,
125+ total_height : 0.0 ,
126+ line_top : 0.0 ,
127+ }
128+ }
129+ }
130+
131+ impl < ' b > Iterator for LayoutRunIter < ' b > {
132+ type Item = LayoutRun < ' b > ;
133+
134+ fn next ( & mut self ) -> Option < Self :: Item > {
135+ while let Some ( line) = self . lines . get ( self . line_i ) {
136+ let shape = line. shape_opt ( ) ?;
137+ let layout = line. layout_opt ( ) ?;
138+ while let Some ( layout_line) = layout. get ( self . layout_i ) {
139+ self . layout_i += 1 ;
140+
141+ let line_height = layout_line. line_height_opt . unwrap_or ( self . line_height ) ;
142+ self . total_height += line_height;
143+
144+ let line_top = self . line_top - self . scroll ;
145+ let glyph_height = layout_line. max_ascent + layout_line. max_descent ;
146+ let centering_offset = ( line_height - glyph_height) / 2.0 ;
147+ let line_y = line_top + centering_offset + layout_line. max_ascent ;
148+ if let Some ( height) = self . height_opt {
149+ if line_y - layout_line. max_ascent > height {
150+ return None ;
151+ }
152+ }
153+ self . line_top += line_height;
154+ if line_y + layout_line. max_descent < 0.0 {
155+ continue ;
156+ }
157+
158+ return Some ( LayoutRun {
159+ line_i : self . line_i ,
160+ text : line. text ( ) ,
161+ rtl : shape. rtl ,
162+ glyphs : & layout_line. glyphs ,
163+ decorations : & layout_line. decorations ,
164+ line_y,
165+ line_top,
166+ line_height,
167+ line_w : layout_line. w ,
168+ } ) ;
169+ }
170+ self . line_i += 1 ;
171+ self . layout_i = 0 ;
172+ }
173+
174+ None
175+ }
176+ }
177+
18178/// Metrics of text
19179#[ derive( Clone , Copy , Debug , Default , PartialEq ) ]
20180pub struct Metrics {
0 commit comments