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}