1use crate::{utils::max_optional_size, *};
2
3pub const LOREM_IPSUM: &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut \
4 labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco \
5 laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in \
6 voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat \
7 non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
8
9#[derive(Clone, Copy)]
10pub struct TestElementParams {
11 pub width: WidthConstraint,
12 pub first_height: f32,
13 pub preferred_height: Option<f32>,
14 pub breakable: Option<TestElementParamsBreakable>,
15 pub pos: (f32, f32),
16 pub page_size: (f32, f32),
17}
18
19#[derive(Clone, Copy)]
20pub struct TestElementParamsBreakable {
21 pub preferred_height_break_count: u32,
22 pub full_height: f32,
23}
24
25impl TestElementParams {
26 pub const DEFAULT_MAX_WIDTH: f32 = 210. - 2. * 8.;
27 pub const DEFAULT_FULL_HEIGHT: f32 = 297. - 2. * 16.;
28 pub const DEFAULT_REDUCED_HEIGHT: f32 = 100.;
29
30 pub fn unbreakable() -> Self {
31 TestElementParams {
32 width: WidthConstraint {
33 max: Self::DEFAULT_MAX_WIDTH,
34 expand: true,
35 },
36 first_height: Self::DEFAULT_FULL_HEIGHT,
37 preferred_height: None,
38 breakable: None,
39 pos: (8., 297. - 16.),
40 page_size: (210., 297.),
41 }
42 }
43
44 pub fn breakable() -> Self {
45 TestElementParams {
46 width: WidthConstraint {
47 max: Self::DEFAULT_MAX_WIDTH,
48 expand: true,
49 },
50 first_height: Self::DEFAULT_REDUCED_HEIGHT,
51 preferred_height: None,
52 breakable: Some(TestElementParamsBreakable {
53 preferred_height_break_count: 0,
54 full_height: Self::DEFAULT_FULL_HEIGHT,
55 }),
56 pos: (8., 297. - 16.),
57 page_size: (210., 297.),
58 }
59 }
60
61 pub fn no_expand(mut self) -> Self {
62 self.width.expand = false;
63 self
64 }
65}
66
67#[derive(Debug)]
68struct MeasureStats {
69 break_count: u32,
70 extra_location_min_height: Option<f32>,
71 size: ElementSize,
72}
73
74#[derive(Debug)]
75pub struct DrawStats {
76 break_count: u32,
77 size: ElementSize,
78}
79
80struct Doc {
81 params: TestElementParams,
82 pdf: Pdf,
83 text_pieces_cache: TextPiecesCache,
84}
85
86impl Doc {
87 fn new(params: TestElementParams) -> Self {
88 let mut pdf = Pdf::new();
89 pdf.add_page(params.page_size);
90
91 Doc {
92 params,
93 pdf,
94 text_pieces_cache: TextPiecesCache::new(),
95 }
96 }
97
98 fn first_location_usage(&mut self, build: impl Fn(Callback)) -> FirstLocationUsage {
99 let mut first_location_usage = None;
100
101 let callback = Callback {
102 doc: self,
103 pass: CallbackPass::FirstLocationUsage {
104 out: &mut first_location_usage,
105 },
106 };
107
108 build(callback);
109
110 first_location_usage.unwrap()
111 }
112
113 fn measure(&mut self, build: impl Fn(Callback)) -> MeasureStats {
114 let mut stats = None;
115
116 let callback = Callback {
117 doc: self,
118 pass: CallbackPass::Measure { out: &mut stats },
119 };
120
121 build(callback);
122
123 stats.unwrap()
124 }
125
126 fn draw(&mut self, build: impl Fn(Callback)) -> DrawStats {
127 let mut stats = None;
128
129 let callback = Callback {
130 doc: self,
131 pass: CallbackPass::Draw { out: &mut stats },
132 };
133
134 build(callback);
135
136 stats.unwrap()
137 }
138}
139
140enum CallbackPass<'a> {
141 FirstLocationUsage {
142 out: &'a mut Option<FirstLocationUsage>,
143 },
144 Measure {
145 out: &'a mut Option<MeasureStats>,
146 },
147 Draw {
148 out: &'a mut Option<DrawStats>,
149 },
150}
151
152pub struct Callback<'a> {
153 doc: &'a mut Doc,
154 pass: CallbackPass<'a>,
155}
156
157impl<'a> Callback<'a> {
158 pub fn pdf(&mut self) -> &mut Pdf {
159 &mut self.doc.pdf
160 }
161
162 pub fn call(self, element: &impl Element) {
163 match self.pass {
164 CallbackPass::FirstLocationUsage { out } => {
165 let params = &self.doc.params;
166
167 *out = Some(element.first_location_usage(FirstLocationUsageCtx {
168 text_pieces_cache: &self.doc.text_pieces_cache,
169 width: params.width,
170 first_height: params.first_height,
171 full_height: params.breakable.as_ref().unwrap().full_height,
172 }));
173 }
174 CallbackPass::Measure { out } => {
175 let mut break_count = 0;
176 let mut extra_location_min_height = None;
177
178 let ctx = MeasureCtx {
179 text_pieces_cache: &self.doc.text_pieces_cache,
180 width: self.doc.params.width,
181 first_height: self.doc.params.first_height,
182 breakable: self
183 .doc
184 .params
185 .breakable
186 .as_ref()
187 .map(|b| BreakableMeasure {
188 full_height: b.full_height,
189 break_count: &mut break_count,
190 extra_location_min_height: &mut extra_location_min_height,
191 }),
192 };
193
194 let size = element.measure(ctx);
195
196 *out = Some(MeasureStats {
197 break_count,
198 extra_location_min_height,
199 size,
200 })
201 }
202 CallbackPass::Draw { out } => {
203 let params = &self.doc.params;
204 let pdf = &mut self.doc.pdf;
205
206 let mut page_idx = 0;
207
208 let next_draw_pos = &mut |pdf: &mut Pdf, location_idx, _height| {
209 while page_idx <= location_idx {
210 pdf.add_page(self.doc.params.page_size);
211 page_idx += 1;
212 }
213
214 Location {
215 page_idx: location_idx as usize + 1,
216 layer_idx: 0,
217 pos: params.pos,
218 scale_factor: 1.,
219 }
220 };
221
222 let first_pos = (
223 params.pos.0,
224 params.breakable.as_ref().map_or(params.pos.1, |b| {
225 params.pos.1 - (b.full_height - params.first_height)
226 }),
227 );
228
229 let ctx = DrawCtx {
230 pdf,
231 text_pieces_cache: &self.doc.text_pieces_cache,
232 width: params.width,
233 location: Location {
234 page_idx: 0,
235 layer_idx: 0,
236 pos: first_pos,
237 scale_factor: 1.,
238 },
239
240 first_height: params.first_height,
241 preferred_height: params.preferred_height,
242
243 breakable: params.breakable.as_ref().map(|b| BreakableDraw {
244 full_height: b.full_height,
245 preferred_height_break_count: b.preferred_height_break_count,
246 do_break: next_draw_pos,
247 }),
248 };
249
250 let size = element.draw(ctx);
251
252 *out = Some(DrawStats {
253 break_count: page_idx,
254 size,
255 });
256 }
257 }
258 }
259}
260
261pub fn test_element_bytes(params: TestElementParams, build_element: impl Fn(Callback)) -> Vec<u8> {
262 let measure = Doc::new(params).measure(&build_element);
263
264 let mut draw_doc = Doc::new(params);
265
266 let draw = draw_doc.draw(&build_element);
267
268 assert_eq!(measure.size.width, draw.size.width);
269
270 let preferred_break_count = params
271 .breakable
272 .as_ref()
273 .map(|b| b.preferred_height_break_count)
274 .unwrap_or(0);
275
276 if measure.extra_location_min_height.is_some() && preferred_break_count > measure.break_count {
277 assert_eq!(draw.break_count, preferred_break_count);
278 assert!(draw.size.height >= measure.extra_location_min_height);
279 assert!(
280 draw.size.height
281 <= max_optional_size(measure.extra_location_min_height, params.preferred_height)
282 );
283 } else {
284 let preferred = (preferred_break_count, params.preferred_height);
285 let measured = (measure.break_count, measure.size.height);
286 let drawn = (draw.break_count, draw.size.height);
287
288 type Thing = (u32, Option<f32>);
289
290 fn max(a: Thing, b: Thing) -> Thing {
291 if a > b { a } else { b }
293 }
294
295 assert!(drawn >= measured);
296 assert!(drawn <= max(preferred, measured));
297 }
298
299 let restricted_draw = Doc::new(TestElementParams {
300 preferred_height: measure.size.height,
301 breakable: params
302 .breakable
303 .as_ref()
304 .map(|b| TestElementParamsBreakable {
305 preferred_height_break_count: measure.break_count,
306 ..*b
307 }),
308 ..params
309 })
310 .draw(&build_element);
311
312 assert_eq!(measure.break_count, restricted_draw.break_count);
313 assert_eq!(measure.size, restricted_draw.size);
314
315 if let Some(breakable) = params.breakable {
316 let full_height = breakable.full_height;
317 let first_location_usage = Doc::new(params).first_location_usage(&build_element);
318
319 match first_location_usage {
320 FirstLocationUsage::NoneHeight => {
321 assert!(measure.size.height.is_none());
322 assert_eq!(measure.break_count, 0);
323 }
324 FirstLocationUsage::WillUse => {
325 assert!(measure.size.height.is_some() || measure.break_count >= 1);
326 }
327 FirstLocationUsage::WillSkip => {
328 assert!(measure.break_count >= 1);
329
330 let _skipped_measure = Doc::new(TestElementParams {
331 first_height: full_height,
332 ..params
333 })
334 .measure(&build_element);
335
336 assert_ne!(params.first_height, full_height);
340 }
341 }
342 }
343
344 draw_doc.pdf.finish()
345}