Skip to main content

laser_pdf/elements/
stack.rs

1use crate::{utils::max_optional_size, *};
2
3pub struct Stack<C: Fn(&mut StackContent)> {
4    pub content: C,
5    pub expand: bool,
6}
7
8impl<C: Fn(&mut StackContent)> Element for Stack<C> {
9    fn first_location_usage(&self, ctx: FirstLocationUsageCtx) -> FirstLocationUsage {
10        let mut ret = FirstLocationUsage::NoneHeight;
11
12        let mut content = StackContent(Pass::FirstLocationUsage { ctx, ret: &mut ret });
13
14        (self.content)(&mut content);
15
16        ret
17    }
18
19    fn measure(&self, ctx: MeasureCtx) -> ElementSize {
20        let mut size = ElementSize {
21            width: None,
22            height: None,
23        };
24
25        let mut content = StackContent(Pass::Measure {
26            ctx,
27            size: &mut size,
28        });
29
30        (self.content)(&mut content);
31
32        size
33    }
34
35    fn draw(&self, mut ctx: DrawCtx) -> ElementSize {
36        let mut size = ElementSize {
37            width: None,
38            height: None,
39        };
40
41        if self.expand {
42            let mut break_count = 0;
43            let mut extra_location_min_height = None;
44            let mut size = ElementSize {
45                width: None,
46                height: None,
47            };
48
49            let mut content = StackContent(Pass::Measure {
50                ctx: MeasureCtx {
51                    text_pieces_cache: ctx.text_pieces_cache,
52                    width: ctx.width,
53                    first_height: ctx.first_height,
54                    breakable: ctx.breakable.as_ref().map(|b| BreakableMeasure {
55                        full_height: b.full_height,
56                        break_count: &mut break_count,
57                        extra_location_min_height: &mut extra_location_min_height,
58                    }),
59                },
60                size: &mut size,
61            });
62
63            (self.content)(&mut content);
64
65            if let Some(ref mut breakable) = ctx.breakable {
66                match break_count.cmp(&breakable.preferred_height_break_count) {
67                    std::cmp::Ordering::Less => (),
68                    std::cmp::Ordering::Equal => {
69                        ctx.preferred_height = max_optional_size(ctx.preferred_height, size.height);
70                    }
71                    std::cmp::Ordering::Greater => {
72                        breakable.preferred_height_break_count = break_count;
73                        ctx.preferred_height = size.height;
74                    }
75                }
76            } else {
77                ctx.preferred_height = max_optional_size(ctx.preferred_height, size.height);
78            }
79        } else {
80            ctx.preferred_height = None;
81
82            if let Some(ref mut breakable) = ctx.breakable {
83                breakable.preferred_height_break_count = 0;
84            }
85        }
86
87        let mut content = StackContent(Pass::Draw {
88            ctx,
89            size: &mut size,
90            max_break_count: 0,
91        });
92
93        (self.content)(&mut content);
94
95        size
96    }
97}
98
99pub struct StackContent<'pdf, 'a, 'r>(Pass<'pdf, 'a, 'r>);
100
101enum Pass<'pdf, 'a, 'r> {
102    FirstLocationUsage {
103        ctx: FirstLocationUsageCtx<'pdf>,
104        ret: &'r mut FirstLocationUsage,
105    },
106    Measure {
107        ctx: MeasureCtx<'a>,
108        size: &'r mut ElementSize,
109    },
110    Draw {
111        ctx: DrawCtx<'pdf, 'a>,
112        size: &'r mut ElementSize,
113        max_break_count: u32,
114    },
115}
116
117impl<'pdf, 'a, 'r> StackContent<'pdf, 'a, 'r> {
118    pub fn add(&mut self, element: &impl Element) {
119        match self.0 {
120            Pass::FirstLocationUsage {
121                ref mut ctx,
122                ret: &mut ref mut ret,
123            } => {
124                use FirstLocationUsage::*;
125
126                let first_location_usage =
127                    element.first_location_usage(FirstLocationUsageCtx { ..*ctx });
128
129                match ret {
130                    WillUse => {}
131                    NoneHeight => {
132                        *ret = first_location_usage;
133                    }
134                    WillSkip => {
135                        if first_location_usage == WillUse {
136                            *ret = WillUse;
137                        }
138                    }
139                }
140            }
141            Pass::Measure {
142                ref mut ctx,
143                size: &mut ref mut size,
144            } => {
145                let mut break_count = 0;
146                let mut extra_location_min_height = None;
147
148                let element_size = element.measure(MeasureCtx {
149                    breakable: ctx.breakable.as_mut().map(|b| BreakableMeasure {
150                        full_height: b.full_height,
151                        break_count: &mut break_count,
152                        extra_location_min_height: &mut extra_location_min_height,
153                    }),
154                    ..*ctx
155                });
156
157                size.width = max_optional_size(size.width, element_size.width);
158
159                if let Some(ref mut breakable) = ctx.breakable {
160                    size.height = match break_count.cmp(breakable.break_count) {
161                        std::cmp::Ordering::Less => size.height,
162                        std::cmp::Ordering::Equal => {
163                            max_optional_size(size.height, element_size.height)
164                        }
165                        std::cmp::Ordering::Greater => {
166                            *breakable.break_count = break_count;
167                            element_size.height
168                        }
169                    }
170                } else {
171                    size.height = max_optional_size(size.height, element_size.height);
172                }
173            }
174            Pass::Draw {
175                ref mut ctx,
176                size: &mut ref mut size,
177                ref mut max_break_count,
178            } => {
179                let mut break_count = 0;
180
181                let element_size = element.draw(DrawCtx {
182                    pdf: ctx.pdf,
183                    location: ctx.location.clone(),
184                    breakable: ctx
185                        .breakable
186                        .as_mut()
187                        .map(|b| {
188                            (
189                                b.full_height,
190                                b.preferred_height_break_count,
191                                |pdf: &mut Pdf, location_idx: u32, _| {
192                                    break_count = break_count.max(location_idx + 1);
193                                    (b.do_break)(
194                                        pdf,
195                                        location_idx,
196                                        Some(if location_idx == 0 {
197                                            ctx.first_height
198                                        } else {
199                                            b.full_height
200                                        }),
201                                    )
202                                },
203                            )
204                        })
205                        .as_mut()
206                        .map(
207                            |&mut (
208                                full_height,
209                                preferred_height_break_count,
210                                ref mut get_location,
211                            )| {
212                                BreakableDraw {
213                                    full_height,
214                                    preferred_height_break_count,
215                                    do_break: get_location,
216                                }
217                            },
218                        ),
219                    ..*ctx
220                });
221
222                size.width = max_optional_size(size.width, element_size.width);
223
224                if ctx.breakable.is_some() {
225                    size.height = match break_count.cmp(max_break_count) {
226                        std::cmp::Ordering::Less => size.height,
227                        std::cmp::Ordering::Equal => {
228                            max_optional_size(size.height, element_size.height)
229                        }
230                        std::cmp::Ordering::Greater => {
231                            *max_break_count = break_count;
232                            element_size.height
233                        }
234                    }
235                } else {
236                    size.height = max_optional_size(size.height, element_size.height);
237                }
238            }
239        }
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246    use crate::test_utils::{
247        record_passes::{Break, BreakableDraw, DrawPass, RecordPasses},
248        *,
249    };
250
251    #[test]
252    fn test() {
253        let width = WidthConstraint {
254            max: 3.,
255            expand: false,
256        };
257        let first_height = 12.;
258        let full_height = 20.;
259        let pos = (2., 10.);
260
261        let output = test_element(
262            TestElementParams {
263                width,
264                first_height,
265                breakable: Some(TestElementParamsBreakable {
266                    full_height,
267                    ..Default::default()
268                }),
269                pos,
270                ..Default::default()
271            },
272            |assert, callback| {
273                let a = RecordPasses::new(FakeText {
274                    lines: 3,
275                    line_height: 5.,
276                    width: 2.5,
277                });
278
279                let b = RecordPasses::new(FakeText {
280                    lines: 40,
281                    line_height: 1.,
282                    width: 2.,
283                });
284
285                let c = RecordPasses::new(FakeText {
286                    lines: 3,
287                    line_height: 3.9,
288                    width: 2.9,
289                });
290
291                let element = Stack {
292                    content: |content| {
293                        content.add(&a);
294                        content.add(&b);
295                        content.add(&c);
296                    },
297                    expand: false,
298                };
299
300                let ret = callback.call(element);
301
302                if assert {
303                    a.assert_first_location_usage_count(0);
304                    b.assert_first_location_usage_count(0);
305                    c.assert_first_location_usage_count(0);
306                    a.assert_measure_count(0);
307                    b.assert_measure_count(0);
308                    c.assert_measure_count(0);
309
310                    let draw_pass = DrawPass {
311                        width,
312                        first_height,
313                        preferred_height: None,
314                        page: 0,
315                        layer: 0,
316                        pos: (2., 2.),
317                        breakable: None,
318                    };
319
320                    a.assert_draw(DrawPass {
321                        breakable: Some(BreakableDraw {
322                            full_height,
323                            preferred_height_break_count: 0,
324                            breaks: vec![Break {
325                                page: 1,
326                                layer: 0,
327                                pos,
328                            }],
329                        }),
330                        ..draw_pass
331                    });
332
333                    b.assert_draw(DrawPass {
334                        breakable: Some(BreakableDraw {
335                            full_height,
336                            preferred_height_break_count: 0,
337                            breaks: vec![
338                                Break {
339                                    page: 1,
340                                    layer: 0,
341                                    pos,
342                                },
343                                Break {
344                                    page: 2,
345                                    layer: 0,
346                                    pos,
347                                },
348                            ],
349                        }),
350                        ..draw_pass
351                    });
352
353                    c.assert_draw(DrawPass {
354                        breakable: Some(BreakableDraw {
355                            full_height,
356                            preferred_height_break_count: 0,
357                            breaks: vec![],
358                        }),
359                        ..draw_pass
360                    });
361                }
362
363                ret
364            },
365        );
366
367        output.assert_size(ElementSize {
368            width: Some(2.9),
369            height: Some(8.),
370        });
371
372        output
373            .breakable
374            .unwrap()
375            .assert_break_count(2)
376            .assert_extra_location_min_height(None)
377            .assert_first_location_usage(FirstLocationUsage::WillUse);
378    }
379}