1#[derive(Debug, Clone, Copy, PartialEq)]
6pub struct Rect {
7 pub x0: f32, pub y0: f32, pub x1: f32, pub y1: f32, }
12
13impl Rect {
14 pub fn new(x0: f32, y0: f32, x1: f32, y1: f32) -> Self {
16 Self {
17 x0: x0.min(x1),
18 y0: y0.min(y1),
19 x1: x0.max(x1),
20 y1: y0.max(y1),
21 }
22 }
23
24 pub fn width(&self) -> f32 {
26 (self.x1 - self.x0).max(0.0)
27 }
28
29 pub fn height(&self) -> f32 {
31 (self.y1 - self.y0).max(0.0)
32 }
33
34 pub fn union(&self, other: &Rect) -> Rect {
36 Rect {
37 x0: self.x0.min(other.x0),
38 y0: self.y0.min(other.y0),
39 x1: self.x1.max(other.x1),
40 y1: self.y1.max(other.y1),
41 }
42 }
43
44 pub fn expand(&self, pad_x: f32, pad_y: f32) -> Rect {
46 Rect {
47 x0: self.x0 - pad_x,
48 y0: self.y0 - pad_y,
49 x1: self.x1 + pad_x,
50 y1: self.y1 + pad_y,
51 }
52 }
53
54 pub fn contains_point(&self, x: f32, y: f32) -> bool {
56 x >= self.x0 && x <= self.x1 && y >= self.y0 && y <= self.y1
57 }
58
59 pub fn overlaps(&self, other: &Rect) -> bool {
61 self.x0 < other.x1 && self.x1 > other.x0 && self.y0 < other.y1 && self.y1 > other.y0
62 }
63}
64
65#[derive(Debug, Clone, PartialEq)]
67pub struct TextToken {
68 pub text: String,
70 pub normalized_text: String,
72 pub bounds: Rect,
74 pub page_index: usize,
76 pub column_index: usize,
78 pub line_index: usize,
80 pub trailing_space: bool,
82}
83
84impl TextToken {
85 pub fn new(
87 text: String,
88 normalized_text: String,
89 bounds: Rect,
90 page_index: usize,
91 column_index: usize,
92 line_index: usize,
93 trailing_space: bool,
94 ) -> Self {
95 Self {
96 text,
97 normalized_text,
98 bounds,
99 page_index,
100 column_index,
101 line_index,
102 trailing_space,
103 }
104 }
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
109pub enum DiffOpKind {
110 Equal,
112 Delete,
114 Insert,
116 Replace,
118}
119
120#[derive(Debug, Clone, PartialEq)]
122pub struct HighlightSpan {
123 pub bounds: Rect,
125 pub op: DiffOpKind,
127 pub is_modified_token: bool,
129}
130
131impl HighlightSpan {
132 pub fn new(bounds: Rect, op: DiffOpKind, is_modified_token: bool) -> Self {
134 Self {
135 bounds,
136 op,
137 is_modified_token,
138 }
139 }
140}
141
142#[derive(Debug, Clone, PartialEq)]
144pub struct PageText {
145 pub tokens: Vec<TextToken>,
147 pub width: f32,
149 pub height: f32,
151 pub page_index: usize,
153}
154
155impl PageText {
156 pub fn new(tokens: Vec<TextToken>, width: f32, height: f32, page_index: usize) -> Self {
158 Self {
159 tokens,
160 width,
161 height,
162 page_index,
163 }
164 }
165
166 pub fn is_empty(&self) -> bool {
168 self.tokens.is_empty()
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_rect_math() {
178 let r1 = Rect::new(10.0, 20.0, 50.0, 60.0);
179 assert_eq!(r1.width(), 40.0);
180 assert_eq!(r1.height(), 40.0);
181 assert!(r1.contains_point(30.0, 40.0));
182 assert!(!r1.contains_point(5.0, 40.0));
183
184 let r2 = Rect::new(40.0, 50.0, 80.0, 90.0);
185 assert!(r1.overlaps(&r2));
186
187 let u = r1.union(&r2);
188 assert_eq!(u, Rect::new(10.0, 20.0, 80.0, 90.0));
189
190 let exp = r1.expand(0.5, 1.0);
191 assert_eq!(exp, Rect::new(9.5, 19.0, 50.5, 61.0));
192 }
193
194 #[test]
195 fn test_rect_new_inverted_coordinates() {
196 let r = Rect::new(50.0, 60.0, 10.0, 20.0);
197 assert_eq!(
198 r,
199 Rect {
200 x0: 10.0,
201 y0: 20.0,
202 x1: 50.0,
203 y1: 60.0,
204 }
205 );
206 assert_eq!(r.width(), 40.0);
207 assert_eq!(r.height(), 40.0);
208 }
209
210 #[test]
211 fn test_token_and_page_text() {
212 let rect = Rect::new(0.0, 0.0, 100.0, 20.0);
213 let token = TextToken::new(
214 "Hello".to_string(),
215 "Hello".to_string(),
216 rect,
217 0,
218 0,
219 0,
220 true,
221 );
222 assert_eq!(token.text, "Hello");
223 assert!(token.trailing_space);
224
225 let page = PageText::new(vec![token], 612.0, 792.0, 0);
226 assert!(!page.is_empty());
227 assert_eq!(page.tokens.len(), 1);
228 }
229}