@@ -9,141 +9,152 @@ use crate::{flow::{Char, Rect, Word}, util::avg};
9
9
10
10
pub fn concat_text < ' a , E : Encoder + ' a > ( out : & mut String , items : impl Iterator < Item =& ' a TextSpan < E > > + Clone ) -> Vec < Word > {
11
11
let word_gap = analyze_word_gap ( items. clone ( ) ) ;
12
- let mut words: Vec < Word > = vec ! [ ] ;
13
-
14
- let mut end = 0. ; // trailing edge of the last char
15
-
12
+ let mut words = Vec :: new ( ) ;
13
+ let mut current_word = WordBuilder :: new ( out. len ( ) ) ;
14
+
16
15
// Whether the last processed TextChar is a whitespace
17
16
// ' ' Space
18
17
// '\t' Tab
19
18
// '\n' Line feed
20
19
// '\r' Carriage return
21
20
// '\u{00A0}' Non-breaking space
22
- let mut trailing_space = out. chars ( ) . last ( ) . map ( |c| c. is_whitespace ( ) ) . unwrap_or ( true ) ;
23
-
24
- let mut word_start_idx = out. len ( ) ;
25
-
26
- // For calculating the layout(position, width , height) of a word
27
- let mut word_start_pos = 0.0 ;
28
- let mut word_end_pos = 0.0 ;
29
- let mut y_min = f32:: INFINITY ;
30
- let mut y_max = -f32:: INFINITY ;
31
-
32
- let mut word_start = true ;
33
- let mut word_chars = vec ! [ ] ;
34
- let mut word_char_idx = 0 ;
21
+ let mut trailing_space = out. chars ( ) . last ( ) . map_or ( true , |c| c. is_whitespace ( ) ) ;
35
22
36
23
for span in items {
37
- let mut offset = 0 ; // byte index of last char into span.text
24
+ let mut offset = 0 ;
38
25
let tr_inv = span. transform . matrix . inverse ( ) ;
39
26
let x_off = ( tr_inv * span. transform . vector ) . x ( ) ;
40
-
27
+
41
28
let mut chars = span. chars . iter ( ) . peekable ( ) ;
42
29
while let Some ( current) = chars. next ( ) {
43
- let s ;
44
- if let Some ( next) = chars. peek ( ) {
45
- s = & span. text [ offset..next. offset ] ;
30
+ // Get text for current char
31
+ let text = if let Some ( next) = chars. peek ( ) {
32
+ let s = & span. text [ offset..next. offset ] ;
46
33
offset = next. offset ;
34
+ s
47
35
} else {
48
- s = & span. text [ offset..] ;
36
+ & span. text [ offset..]
37
+ } ;
38
+
39
+ // Calculate char positions
40
+ let char_start = ( span. transform . matrix * Vector2F :: new ( current. pos + x_off, 0.0 ) ) . x ( ) ;
41
+ let char_end = ( span. transform . matrix * Vector2F :: new ( current. pos + x_off + current. width , 0.0 ) ) . x ( ) ;
42
+
43
+ let is_whitespace = text. chars ( ) . all ( |c| c. is_whitespace ( ) ) ;
44
+
45
+ // Handle word boundaries
46
+ if trailing_space && !is_whitespace {
47
+ // Start new word after space
48
+ current_word. start_new ( out. len ( ) , char_start) ;
49
+ current_word. add_char ( 0 , char_start, char_end) ;
50
+ out. extend ( text. nfkc ( ) ) ;
51
+ } else if !trailing_space {
52
+ if is_whitespace {
53
+ // End word at space
54
+ words. push ( current_word. build ( out, char_end) ) ;
55
+ current_word = WordBuilder :: new ( out. len ( ) ) ;
56
+ out. push ( ' ' ) ;
57
+ } else if current. pos + x_off > current_word. end_pos + word_gap {
58
+ // End word at large gap
59
+ words. push ( current_word. build ( out, char_end) ) ;
60
+
61
+ current_word = WordBuilder :: new ( out. len ( ) ) ;
62
+ current_word. start_new ( out. len ( ) , char_start) ;
63
+ current_word. add_char ( 0 , char_start, char_end) ;
64
+ out. extend ( text. nfkc ( ) ) ;
65
+ } else {
66
+ // Continue current word
67
+ current_word. add_char ( current_word. char_count , char_start, char_end) ;
68
+ out. extend ( text. nfkc ( ) ) ;
69
+ }
49
70
}
50
- end = current. pos + x_off + current. width ;
51
71
52
- let char_start_pos = ( span. transform . matrix * Vector2F :: new ( current. pos + x_off, 0.0 ) ) . x ( ) ;
53
- let char_end_pos = ( span. transform . matrix * Vector2F :: new ( end, 0.0 ) ) . x ( ) ;
72
+ trailing_space = is_whitespace;
73
+ current_word. update_bounds ( span. rect . min_y ( ) , span. rect . max_y ( ) ) ;
74
+ }
75
+ }
54
76
55
- let is_whitespace = s. chars ( ) . all ( |c| c. is_whitespace ( ) ) ;
77
+ // Add final word if any
78
+ if !current_word. is_empty ( ) {
79
+ let end_pos = current_word. end_pos ;
80
+ words. push ( current_word. build ( out, end_pos) ) ;
81
+ }
56
82
57
- if trailing_space {
58
- if !is_whitespace {
59
- word_start = true ;
60
- word_start_idx = out. len ( ) ;
83
+ words
84
+ }
61
85
62
- word_chars. push ( Char {
63
- offset : 0 ,
64
- pos : char_start_pos,
65
- width : char_end_pos - char_start_pos,
66
- } ) ;
67
- out. extend ( s. nfkc ( ) ) ;
86
+ // Helper struct to build up words
87
+ struct WordBuilder {
88
+ word_start_idx : usize ,
68
89
69
- word_char_idx += 1 ;
70
- }
71
- } else {
72
- if is_whitespace {
73
- words. push ( Word {
74
- text : out[ word_start_idx..] . into ( ) ,
75
- rect : Rect {
76
- x : word_start_pos,
77
- y : y_min,
78
- h : y_max - y_min,
79
- w : word_end_pos - word_start_pos
80
- } ,
81
- chars : take ( & mut word_chars)
82
- } ) ;
83
- out. push_str ( " " ) ;
84
- word_start_idx = out. len ( ) ;
85
- word_char_idx = 0 ;
86
- } else if current. pos + x_off > end + word_gap {
87
- words. push ( Word {
88
- text : out[ word_start_idx..] . into ( ) ,
89
- rect : Rect {
90
- x : word_start_pos,
91
- y : y_min,
92
- h : y_max - y_min,
93
- w : word_end_pos - word_start_pos
94
- } ,
95
- chars : take ( & mut word_chars)
96
- } ) ;
97
-
98
- word_start = true ;
99
- word_start_idx = out. len ( ) ;
100
- word_chars. push ( Char {
101
- offset : 0 ,
102
- pos : char_start_pos,
103
- width : char_end_pos - char_start_pos,
104
- } ) ;
105
- word_char_idx += 1 ;
106
-
107
- out. extend ( s. nfkc ( ) ) ;
108
- } else {
109
- word_chars. push ( Char {
110
- offset : word_char_idx,
111
- pos : char_start_pos,
112
- width : char_end_pos - char_start_pos,
113
- } ) ;
114
-
115
- word_char_idx += 1 ;
116
- out. extend ( s. nfkc ( ) ) ;
117
- }
118
- }
119
- trailing_space = is_whitespace;
90
+ // For calculating the layout(position, width , height) of a word
91
+ start_pos : f32 ,
92
+ end_pos : f32 , // trailing edge of the last char
93
+ y_min : f32 ,
94
+ y_max : f32 ,
95
+
96
+ chars : Vec < Char > ,
97
+ char_count : usize ,
98
+ started : bool ,
99
+ }
120
100
121
- word_end_pos = char_end_pos;
101
+ impl WordBuilder {
102
+ fn new ( word_start_idx : usize ) -> Self {
103
+ Self {
104
+ word_start_idx,
105
+ start_pos : 0.0 ,
106
+ end_pos : 0.0 ,
107
+ y_min : f32:: INFINITY ,
108
+ y_max : -f32:: INFINITY ,
109
+ chars : Vec :: new ( ) ,
110
+ char_count : 0 ,
111
+ started : false ,
112
+ }
113
+ }
122
114
123
- if word_start {
124
- y_min = span. rect . min_y ( ) ;
125
- y_max = span. rect . max_y ( ) ;
126
- word_start_pos = char_start_pos;
127
- word_start = false ;
128
- } else {
129
- y_min = y_min. min ( span. rect . min_y ( ) ) ;
130
- y_max = y_max. max ( span. rect . max_y ( ) ) ;
131
- }
115
+ fn start_new ( & mut self , word_start_idx : usize , start_pos : f32 ) {
116
+ self . word_start_idx = word_start_idx;
117
+ self . start_pos = start_pos;
118
+ self . started = true ;
119
+ }
120
+
121
+ fn add_char ( & mut self , offset : usize , start : f32 , end : f32 ) {
122
+ self . chars . push ( Char {
123
+ offset,
124
+ pos : start,
125
+ width : end - start,
126
+ } ) ;
127
+ self . end_pos = end;
128
+ self . char_count += 1 ;
129
+ }
130
+
131
+ fn update_bounds ( & mut self , min_y : f32 , max_y : f32 ) {
132
+ if !self . started {
133
+ self . y_min = min_y;
134
+ self . y_max = max_y;
135
+ self . started = true ;
136
+ } else {
137
+ self . y_min = self . y_min . min ( min_y) ;
138
+ self . y_max = self . y_max . max ( max_y) ;
132
139
}
133
140
}
134
141
135
- words. push ( Word {
136
- text : out[ word_start_idx..] . into ( ) ,
137
- rect : Rect {
138
- x : word_start_pos,
139
- y : y_min,
140
- h : y_max - y_min,
141
- w : word_end_pos - word_start_pos
142
- } ,
143
- chars : take ( & mut word_chars)
144
- } ) ;
145
-
146
- words
142
+ fn is_empty ( & self ) -> bool {
143
+ self . chars . is_empty ( )
144
+ }
145
+
146
+ fn build ( mut self , out : & str , end_pos : f32 ) -> Word {
147
+ Word {
148
+ text : out[ self . word_start_idx ..] . into ( ) ,
149
+ rect : Rect {
150
+ x : self . start_pos ,
151
+ y : self . y_min ,
152
+ h : self . y_max - self . y_min ,
153
+ w : end_pos - self . start_pos
154
+ } ,
155
+ chars : take ( & mut self . chars )
156
+ }
157
+ }
147
158
}
148
159
149
160
/// Calculate gaps between each char,
0 commit comments