Skip to main content

laser_pdf/
test_utils.rs

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            // Beware of wild NaNs, they bite!
174            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                    // TODO: insert draw here
226
227                    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}