1pub mod assert_passes;
2pub mod binary_snapshots;
3pub mod build_element;
4pub mod element_proxy;
5pub mod fake_image;
6pub mod fake_text;
7pub mod frantic_jumper;
8pub mod old;
9pub mod record_passes;
10
11pub use build_element::BuildElement;
12pub use element_proxy::ElementProxy;
13pub use fake_image::FakeImage;
14pub use fake_text::FakeText;
15pub use frantic_jumper::FranticJumper;
16pub use old::*;
17
18use crate::{utils::max_optional_size, *};
19
20use self::build_element::{BuildElementCallback, BuildElementReturnToken};
21
22pub struct TestElementParams {
23 pub width: WidthConstraint,
24 pub first_height: f32,
25 pub preferred_height: Option<f32>,
26 pub breakable: Option<TestElementParamsBreakable>,
27 pub pos: (f32, f32),
28 pub page_size: (f32, f32),
29}
30
31pub struct TestElementParamsBreakable {
32 pub preferred_height_break_count: u32,
33 pub full_height: f32,
34}
35
36impl Default for TestElementParams {
37 fn default() -> Self {
38 TestElementParams {
39 width: WidthConstraint {
40 max: 10.,
41 expand: true,
42 },
43 first_height: 10.,
44 preferred_height: None,
45 breakable: None,
46 pos: (0., 10.),
47 page_size: (10., 10.),
48 }
49 }
50}
51
52impl Default for TestElementParamsBreakable {
53 fn default() -> Self {
54 TestElementParamsBreakable {
55 preferred_height_break_count: 0,
56 full_height: 10.,
57 }
58 }
59}
60
61#[derive(Debug)]
62pub struct ElementTestOutput {
63 pub size: ElementSize,
64 pub breakable: Option<ElementTestOutputBreakable>,
65}
66
67#[derive(Debug)]
68pub struct ElementTestOutputBreakable {
69 pub break_count: u32,
70 pub extra_location_min_height: Option<f32>,
71
72 pub first_location_usage: FirstLocationUsage,
73}
74
75impl ElementTestOutput {
76 pub fn assert_no_breaks(&self) -> &Self {
77 if let Some(b) = &self.breakable {
78 b.assert_break_count(0);
79 }
80
81 self
82 }
83
84 pub fn assert_size(&self, size: ElementSize) -> &Self {
85 assert_eq!(self.size, size);
86 self
87 }
88}
89
90impl ElementTestOutputBreakable {
91 pub fn assert_break_count(&self, break_count: u32) -> &Self {
92 assert_eq!(self.break_count, break_count);
93 self
94 }
95
96 pub fn assert_extra_location_min_height(
97 &self,
98 extra_location_min_height: Option<f32>,
99 ) -> &Self {
100 assert_eq!(self.extra_location_min_height, extra_location_min_height);
101 self
102 }
103
104 pub fn assert_first_location_usage(&self, expected: FirstLocationUsage) -> &Self {
105 assert_eq!(self.first_location_usage, expected);
106 self
107 }
108}
109
110pub fn test_element(
111 TestElementParams {
112 width,
113 first_height,
114 preferred_height,
115 breakable,
116 pos,
117 page_size,
118 }: TestElementParams,
119 build_element: impl Fn(bool, BuildElementCallback) -> BuildElementReturnToken,
120) -> ElementTestOutput {
121 let first_pos = (
122 pos.0,
123 breakable
124 .as_ref()
125 .map_or(pos.1, |b| pos.1 - (b.full_height - first_height)),
126 );
127
128 let element = BuildElement(|_, callback| build_element(false, callback));
129 let element_with_asserts = BuildElement(|_, callback| build_element(true, callback));
130
131 let measure = measure_element(
132 &element,
133 width,
134 first_height,
135 breakable.as_ref().map(|b| b.full_height),
136 );
137 let draw = draw_element(
138 &element_with_asserts,
139 width,
140 first_height,
141 preferred_height,
142 first_pos,
143 page_size,
144 breakable.as_ref().map(|b| BreakableDrawConfig {
145 pos,
146 full_height: b.full_height,
147 preferred_height_break_count: b.preferred_height_break_count,
148 }),
149 );
150
151 assert_eq!(measure.size.width, draw.size.width);
152
153 let preferred_break_count = breakable
154 .as_ref()
155 .map(|b| b.preferred_height_break_count)
156 .unwrap_or(0);
157
158 if measure.extra_location_min_height.is_some() && preferred_break_count > measure.break_count {
159 assert_eq!(draw.break_count, preferred_break_count);
160 assert!(draw.size.height >= measure.extra_location_min_height);
161 assert!(
162 draw.size.height
163 <= max_optional_size(measure.extra_location_min_height, preferred_height)
164 );
165 } else {
166 let preferred = (preferred_break_count, preferred_height);
167 let measured = (measure.break_count, measure.size.height);
168 let drawn = (draw.break_count, draw.size.height);
169
170 type Thing = (u32, Option<f32>);
171
172 fn max(a: Thing, b: Thing) -> Thing {
173 if a > b { a } else { b }
175 }
176
177 assert!(drawn >= measured);
178 assert!(drawn <= max(preferred, measured));
179 }
180
181 let restricted_draw = draw_element(
182 &element,
183 width,
184 first_height,
185 measure.size.height,
186 first_pos,
187 page_size,
188 breakable.as_ref().map(|b| BreakableDrawConfig {
189 pos,
190 full_height: b.full_height,
191 preferred_height_break_count: measure.break_count,
192 }),
193 );
194
195 assert_eq!(measure.break_count, restricted_draw.break_count);
196 assert_eq!(measure.size, restricted_draw.size);
197
198 let text_pieces_cache = TextPiecesCache::new();
199
200 ElementTestOutput {
201 size: draw.size,
202 breakable: breakable.map(|breakable| {
203 let full_height = breakable.full_height;
204 let first_location_usage = element.first_location_usage(FirstLocationUsageCtx {
205 text_pieces_cache: &text_pieces_cache,
206 width,
207 first_height,
208 full_height,
209 });
210
211 match first_location_usage {
212 FirstLocationUsage::NoneHeight => {
213 assert!(measure.size.height.is_none());
214 assert_eq!(measure.break_count, 0);
215 }
216 FirstLocationUsage::WillUse => {
217 assert!(measure.size.height.is_some() || measure.break_count >= 1);
218 }
219 FirstLocationUsage::WillSkip => {
220 assert!(measure.break_count >= 1);
221
222 let skipped_measure =
223 measure_element(&element, width, full_height, Some(full_height));
224
225 assert_eq!(skipped_measure.break_count + 1, measure.break_count);
228 assert_ne!(first_height, full_height);
229 }
230 }
231
232 ElementTestOutputBreakable {
233 break_count: draw.break_count,
234 extra_location_min_height: measure.extra_location_min_height,
235 first_location_usage,
236 }
237 }),
238 }
239}
240
241pub struct DrawStats {
242 break_count: u32,
243 breaks: Vec<u32>,
244 size: ElementSize,
245}
246
247struct BreakableDrawConfig {
248 pos: (f32, f32),
249 full_height: f32,
250 preferred_height_break_count: u32,
251}
252
253fn draw_element<E: Element>(
254 element: &E,
255 width: WidthConstraint,
256 first_height: f32,
257 preferred_height: Option<f32>,
258 first_pos: (f32, f32),
259 page_size: (f32, f32),
260 breakable: Option<BreakableDrawConfig>,
261) -> DrawStats {
262 let mut page_idx = 0;
263
264 let mut pdf = Pdf::new();
265 pdf.add_page(page_size);
266
267 let text_pieces_cache = TextPiecesCache::new();
268
269 let mut breaks = vec![];
270
271 let next_draw_pos = &mut |pdf: &mut Pdf, location_idx, _height| {
272 breaks.push(location_idx);
273
274 while page_idx <= location_idx {
275 pdf.add_page(page_size);
276 page_idx += 1;
277 }
278
279 Location {
280 page_idx: location_idx as usize + 1,
281 layer_idx: 0,
282 pos: breakable.as_ref().unwrap().pos,
283 scale_factor: 1.,
284 }
285 };
286
287 let ctx = DrawCtx {
288 pdf: &mut pdf,
289 text_pieces_cache: &text_pieces_cache,
290 width,
291 location: Location {
292 page_idx: 0,
293 layer_idx: 0,
294 pos: first_pos,
295 scale_factor: 1.,
296 },
297
298 first_height,
299 preferred_height,
300
301 breakable: breakable.as_ref().map(|b| BreakableDraw {
302 full_height: b.full_height,
303 preferred_height_break_count: b.preferred_height_break_count,
304 do_break: next_draw_pos,
305 }),
306 };
307
308 let size = element.draw(ctx);
309
310 DrawStats {
311 break_count: page_idx,
312 breaks,
313 size,
314 }
315}
316
317pub struct MeasureStats {
318 break_count: u32,
319 extra_location_min_height: Option<f32>,
320 size: ElementSize,
321}
322
323pub fn measure_element<E: Element>(
324 element: &E,
325 width: WidthConstraint,
326 first_height: f32,
327 full_height: Option<f32>,
328) -> MeasureStats {
329 let mut break_count = 0;
330 let mut extra_location_min_height = None;
331
332 let ctx = MeasureCtx {
333 text_pieces_cache: &TextPiecesCache::new(),
334 width,
335 first_height,
336 breakable: full_height.map(|full_height| BreakableMeasure {
337 full_height,
338 break_count: &mut break_count,
339 extra_location_min_height: &mut extra_location_min_height,
340 }),
341 };
342
343 let size = element.measure(ctx);
344
345 MeasureStats {
346 break_count,
347 extra_location_min_height,
348 size,
349 }
350}