Skip to main content

laser_pdf/elements/
expand_to_preferred_height.rs

1use crate::{utils::max_optional_size, *};
2
3pub struct ExpandToPreferredHeight<E: Element>(pub E);
4
5impl<E: Element> Element for ExpandToPreferredHeight<E> {
6    fn first_location_usage(&self, ctx: FirstLocationUsageCtx) -> FirstLocationUsage {
7        self.0.first_location_usage(ctx)
8    }
9
10    fn measure(&self, ctx: MeasureCtx) -> ElementSize {
11        self.0.measure(ctx)
12    }
13
14    fn draw(&self, ctx: DrawCtx) -> ElementSize {
15        let preferred_height = ctx.preferred_height;
16        let preferred_breaks = ctx
17            .breakable
18            .as_ref()
19            .map(|b| b.preferred_height_break_count)
20            .unwrap_or(0);
21
22        let size;
23        let height;
24
25        if let Some(breakable) = ctx.breakable {
26            let mut break_count = 0;
27
28            size = self.0.draw(DrawCtx {
29                pdf: ctx.pdf,
30                breakable: Some(BreakableDraw {
31                    do_break: &mut |pdf, location_idx, _height| {
32                        break_count = break_count.max(location_idx + 1);
33
34                        (breakable.do_break)(
35                            pdf,
36                            location_idx,
37                            // we also expand all of the heights
38                            if location_idx == 0 {
39                                Some(ctx.first_height)
40                            } else {
41                                Some(breakable.full_height)
42                            },
43                        )
44                    },
45                    ..breakable
46                }),
47
48                ..ctx
49            });
50
51            match break_count.cmp(&preferred_breaks) {
52                std::cmp::Ordering::Less => {
53                    // We need to go through all of the locations to use the full_height on all of
54                    // them.
55                    for i in break_count..preferred_breaks {
56                        (breakable.do_break)(
57                            ctx.pdf,
58                            i,
59                            if i == 0 {
60                                Some(ctx.first_height)
61                            } else {
62                                Some(breakable.full_height)
63                            },
64                        );
65                    }
66
67                    height = preferred_height;
68                }
69                std::cmp::Ordering::Equal => {
70                    height = max_optional_size(size.height, preferred_height);
71                }
72                std::cmp::Ordering::Greater => {
73                    height = size.height;
74                }
75            }
76        } else {
77            size = self.0.draw(ctx);
78            height = max_optional_size(size.height, preferred_height);
79        }
80
81        ElementSize { height, ..size }
82    }
83}
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use crate::{
88        elements::text::Text, fonts::builtin::BuiltinFont, test_utils::binary_snapshots::*,
89    };
90    use insta::*;
91
92    #[test]
93    fn test_basic() {
94        let bytes = test_element_bytes(
95            TestElementParams {
96                preferred_height: Some(12.),
97                breakable: Some(TestElementParamsBreakable {
98                    preferred_height_break_count: 7,
99                    full_height: TestElementParams::DEFAULT_FULL_HEIGHT,
100                }),
101                ..TestElementParams::breakable()
102            },
103            |mut callback| {
104                let font = BuiltinFont::courier(callback.pdf());
105
106                let content = Text::basic(LOREM_IPSUM, &font, 32.);
107                let content = content.debug(1);
108
109                callback.call(&ExpandToPreferredHeight(content).debug(0));
110            },
111        );
112        assert_binary_snapshot!(".pdf", bytes);
113    }
114
115    #[test]
116    fn test_single_location_content() {
117        let bytes = test_element_bytes(
118            TestElementParams {
119                preferred_height: Some(32.),
120                breakable: Some(TestElementParamsBreakable {
121                    preferred_height_break_count: 3,
122                    full_height: TestElementParams::DEFAULT_FULL_HEIGHT,
123                }),
124                ..TestElementParams::breakable()
125            },
126            |mut callback| {
127                let font = BuiltinFont::courier(callback.pdf());
128
129                let content = Text::basic(LOREM_IPSUM, &font, 12.);
130                let content = content.debug(1);
131
132                callback.call(&ExpandToPreferredHeight(content).debug(0));
133            },
134        );
135        assert_binary_snapshot!(".pdf", bytes);
136    }
137
138    // TODO: Figure out what makes sense here.
139    // #[test]
140    // fn test_collapse() {
141    //     let output = test_element(
142    //         TestElementParams {
143    //             width: WidthConstraint {
144    //                 max: 20.,
145    //                 expand: true,
146    //             },
147    //             first_height: 21.,
148    //             preferred_height: Some(12.),
149    //             breakable: Some(TestElementParamsBreakable {
150    //                 preferred_height_break_count: 7,
151    //                 full_height: 500.,
152    //             }),
153    //             pos: (11., 600.0),
154    //             ..Default::default()
155    //         },
156    //         |assert, callback| {
157    //             let content = RecordPasses::new(NoneElement);
158
159    //             let element = ExpandToPreferredHeight(&content);
160
161    //             let ret = callback.call(element);
162
163    //             if assert {
164    //                 assert_debug_snapshot!(content.into_passes());
165    //             }
166
167    //             ret
168    //         },
169    //     );
170
171    //     assert_debug_snapshot!(output);
172    // }
173}