1use crate::{utils::max_optional_size, *};
2
3use self::utils::add_optional_size_with_gap;
4
5pub struct BreakList<C: Fn(BreakListContent) -> Option<()>> {
6 pub gap: f32,
7 pub content: C,
8}
9
10impl<C: Fn(BreakListContent) -> Option<()>> Element for BreakList<C> {
11 fn first_location_usage(&self, ctx: FirstLocationUsageCtx) -> FirstLocationUsage {
12 FirstLocationUsage::WillUse
13 }
14
15 fn measure(&self, mut ctx: MeasureCtx) -> ElementSize {
16 let mut max_width = None;
17 let mut x_offset = None;
18 let mut y_offset = None;
19 let mut line_height = None;
20
21 (self.content)(BreakListContent {
22 pass: Pass::Measure {
23 breakable: ctx.breakable.as_mut(),
24 },
25 text_pieces_cache: ctx.text_pieces_cache,
26 gap: self.gap,
27 width_constraint: ctx.width,
28 height_available: ctx.first_height,
29 max_width: &mut max_width,
30 x_offset: &mut x_offset,
31 y_offset: &mut y_offset,
32 line_height: &mut line_height,
33 });
34
35 ElementSize {
36 width: if ctx.width.expand {
37 Some(ctx.width.max)
38 } else {
39 max_optional_size(max_width, x_offset)
40 },
41 height: match (y_offset, line_height) {
42 (None, None) => None,
43 (None, Some(x)) | (Some(x), None) => Some(x),
44 (Some(y_offset), Some(line_height)) => Some(y_offset + self.gap + line_height),
45 },
46 }
47 }
48
49 fn draw(&self, mut ctx: DrawCtx) -> ElementSize {
50 let mut max_width = None;
51 let mut x_offset = None;
52 let mut y_offset = None;
53 let mut line_height = None;
54
55 (self.content)(BreakListContent {
56 pass: Pass::Draw {
57 pdf: ctx.pdf,
58 location: ctx.location,
59 breakable: ctx.breakable.as_mut().map(|b| (b, 0)),
60 },
61 text_pieces_cache: ctx.text_pieces_cache,
62 gap: self.gap,
63 width_constraint: ctx.width,
64 height_available: ctx.first_height,
65 max_width: &mut max_width,
66 x_offset: &mut x_offset,
67 y_offset: &mut y_offset,
68 line_height: &mut line_height,
69 });
70
71 ElementSize {
72 width: if ctx.width.expand {
73 Some(ctx.width.max)
74 } else {
75 max_optional_size(max_width, x_offset)
76 },
77 height: match (y_offset, line_height) {
78 (None, None) => None,
79 (None, Some(x)) | (Some(x), None) => Some(x),
80 (Some(y_offset), Some(line_height)) => Some(y_offset + self.gap + line_height),
81 },
82 }
83 }
84}
85
86pub struct BreakListContent<'a, 'b, 'c> {
87 pass: Pass<'a, 'b, 'c>,
88
89 text_pieces_cache: &'a TextPiecesCache,
90
91 gap: f32,
92
93 width_constraint: WidthConstraint,
94
95 height_available: f32,
96
97 max_width: &'a mut Option<f32>,
98 x_offset: &'a mut Option<f32>,
99 y_offset: &'a mut Option<f32>,
100 line_height: &'a mut Option<f32>,
101}
102
103enum Pass<'a, 'b, 'c> {
104 FirstLocationUsage {},
105 Measure {
106 breakable: Option<&'a mut BreakableMeasure<'b>>,
107 },
108 Draw {
109 pdf: &'c mut Pdf,
110 breakable: Option<(&'a mut BreakableDraw<'b>, u32)>,
111 location: Location,
112 },
113}
114
115impl<'a, 'b, 'c> BreakListContent<'a, 'b, 'c> {
116 pub fn add<E: Element>(mut self, element: &E) -> Option<Self> {
117 let width_constraint = WidthConstraint {
118 max: self.width_constraint.max,
119 expand: false,
120 };
121
122 let full_height = match self.pass {
123 Pass::FirstLocationUsage { .. } => todo!(),
124 Pass::Measure { ref breakable } => breakable.as_ref().map(|b| b.full_height),
125 Pass::Draw {
126 pdf: &mut ref mut pdf,
127 ref breakable,
128 ..
129 } => breakable.as_ref().map(|b| b.0.full_height),
130 };
131
132 let element_size = element.measure(MeasureCtx {
133 text_pieces_cache: self.text_pieces_cache,
134 width: width_constraint,
135
136 first_height: full_height.unwrap_or(self.height_available),
143
144 breakable: None,
145 });
146
147 if let (Some(x_offset), Some(width)) = (&mut *self.x_offset, element_size.width) {
149 if *x_offset + self.gap + width > self.width_constraint.max {
150 *self.max_width = max_optional_size(*self.max_width, Some(*x_offset));
151 *self.x_offset = None;
152
153 *self.y_offset = match (*self.y_offset, *self.line_height) {
154 (None, None) => None,
155 (None, Some(x)) | (Some(x), None) => Some(x),
156 (Some(y_offset), Some(line_height)) => Some(y_offset + self.gap + line_height),
157 };
158
159 *self.line_height = None;
160 }
161 }
162
163 let break_needed =
164 if let (Some(full_height), Some(height)) = (full_height, element_size.height) {
165 let y_offset = self.y_offset.map(|y| y + self.gap).unwrap_or(0.);
166
167 y_offset + height > self.height_available
168 && (y_offset > 0. || full_height > self.height_available)
169 } else {
170 false
171 };
172
173 match self.pass {
174 Pass::Measure {
175 ref mut breakable, ..
176 } => {
177 if break_needed {
178 *self.x_offset = None;
179 *self.y_offset = None;
180 let breakable = breakable.as_deref_mut().unwrap();
181 *breakable.break_count += 1;
182 self.height_available = breakable.full_height;
183 }
184 }
185 Pass::Draw {
186 pdf: &mut ref mut pdf,
187 ref mut breakable,
188 ref mut location,
189 } => {
190 if break_needed {
191 let &mut (&mut ref mut breakable, ref mut location_idx) =
192 breakable.as_mut().unwrap();
193 *location = (breakable.do_break)(
194 pdf,
195 *location_idx,
196 add_optional_size_with_gap(*self.y_offset, *self.line_height, self.gap),
197 );
198 *self.x_offset = None;
199 *self.y_offset = None;
200 self.height_available = breakable.full_height;
201 *location_idx += 1;
202 }
203
204 let x_offset = if let &mut Some(x_offset) = self.x_offset {
205 x_offset + self.gap
206 } else {
207 0.
208 };
209 let y_offset = self.y_offset.map(|y| y + self.gap).unwrap_or(0.);
210
211 element.draw(DrawCtx {
212 pdf,
213 text_pieces_cache: self.text_pieces_cache,
214 location: Location {
215 pos: (location.pos.0 + x_offset, location.pos.1 - y_offset),
216 ..*location
217 },
218
219 width: width_constraint,
223 first_height: element_size.height.unwrap_or(0.),
224 preferred_height: None,
225 breakable: None,
226 });
227 }
228 _ => todo!(),
229 }
230
231 if let Pass::Measure { .. } | Pass::Draw { .. } = self.pass {
233 *self.x_offset = match (*self.x_offset, element_size.width) {
234 (None, None) => None,
235 (None, Some(x)) | (Some(x), None) => Some(x),
236 (Some(x_offset), Some(width)) => Some(x_offset + self.gap + width),
237 };
238
239 *self.line_height = max_optional_size(*self.line_height, element_size.height);
240 }
241
242 Some(self)
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249 use crate::{
250 elements::{none::NoneElement, rectangle::Rectangle},
251 test_utils::{
252 assert_passes::{AssertPasses, Pass},
253 build_element::BuildElementCtx,
254 *,
255 },
256 };
257
258 #[test]
259 fn test_empty() {
260 let element = BreakList {
261 gap: 12.,
262 content: |_content| None,
263 };
264
265 for output in ElementTestParams::default().run(&element) {
266 output.assert_size(ElementSize {
267 width: if output.width.expand {
268 Some(output.width.max)
269 } else {
270 None
271 },
272 height: None,
273 });
274
275 if let Some(b) = output.breakable {
276 b.assert_break_count(0);
277 b.assert_extra_location_min_height(None);
278 }
279 }
280 }
281
282 #[test]
283 fn test_none() {
284 for configuration in (ElementTestParams {
285 first_height: 1.,
286 width: 1.,
287 full_height: 2.,
288 ..Default::default()
289 })
290 .configurations()
291 {
292 let element = BuildElement(|BuildElementCtx { pass, .. }, callback| {
293 let width = WidthConstraint {
294 max: 1.,
295 expand: false,
296 };
297
298 let measure_pass = Pass::Measure {
299 width,
300 first_height: if configuration.breakable || !configuration.use_first_height {
301 2.
302 } else {
303 1.
304 },
305 full_height: None,
306 };
307
308 let draw_pass = Pass::Draw {
309 width,
310 first_height: 0.,
311 breakable: None,
312 preferred_height: None,
313 page: 0,
314 layer: 0,
315 pos: configuration.params.pos,
316 };
317
318 let child = AssertPasses::new(
319 NoneElement,
320 match pass {
321 build_element::Pass::FirstLocationUsage { .. } => todo!(),
322 build_element::Pass::Measure { .. } => vec![measure_pass],
323 build_element::Pass::Draw { .. } => vec![measure_pass, draw_pass],
324 },
325 );
326
327 let element = BreakList {
328 gap: 12.,
329 content: |content| {
330 content.add(&child);
331
332 None
333 },
334 };
335
336 callback.call(element)
337 });
338
339 let output = configuration.run(&element);
340
341 output.assert_size(ElementSize {
342 width: if output.width.expand {
343 Some(output.width.max)
344 } else {
345 None
346 },
347 height: None,
348 });
349
350 if let Some(b) = output.breakable {
351 b.assert_break_count(0);
352 b.assert_extra_location_min_height(None);
353 }
354 }
355 }
356
357 #[test]
358 fn test_passes() {
359 let gap = 1.;
360
361 for configuration in (ElementTestParams {
362 first_height: 5.,
363 width: 10.,
364 full_height: 10.,
365 pos: (1., 10.),
366 ..Default::default()
367 })
368 .configurations()
369 {
370 let element = BuildElement(|BuildElementCtx { pass, .. }, callback| {
371 let width = WidthConstraint {
372 max: 10.,
373 expand: false,
374 };
375
376 let first_height = if configuration.breakable || !configuration.use_first_height {
377 10.
378 } else {
379 5.
380 };
381
382 let child_0 = {
383 let measure_pass = Pass::Measure {
384 width,
385 first_height,
386 full_height: None,
387 };
388
389 let draw_pass = Pass::Draw {
390 width,
391 first_height: 0.,
392 breakable: None,
393 preferred_height: None,
394 page: 0,
395 layer: 0,
396 pos: (1., 10.),
397 };
398
399 AssertPasses::new(
400 NoneElement,
401 match pass {
402 build_element::Pass::FirstLocationUsage { .. } => todo!(),
403 build_element::Pass::Measure { .. } => vec![measure_pass],
404 build_element::Pass::Draw { .. } => vec![measure_pass, draw_pass],
405 },
406 )
407 };
408
409 let child_1 = {
410 let measure_pass = Pass::Measure {
411 width,
412 first_height,
413 full_height: None,
414 };
415
416 let breaks = configuration.use_first_height && configuration.breakable;
417
418 let draw_pass = Pass::Draw {
419 width,
420 first_height: 6.,
421 breakable: None,
422 preferred_height: None,
423 page: if breaks { 1 } else { 0 },
424 layer: 0,
425 pos: (1., 10.),
426 };
427
428 AssertPasses::new(
429 Rectangle {
430 size: (1., 6.),
431 fill: None,
432 outline: None,
433 },
434 match pass {
435 build_element::Pass::FirstLocationUsage { .. } => todo!(),
436 build_element::Pass::Measure { .. } => vec![measure_pass],
437 build_element::Pass::Draw { .. } => vec![measure_pass, draw_pass],
438 },
439 )
440 };
441
442 let on_page_1 = configuration.use_first_height && configuration.breakable;
443
444 let child_2 = {
445 let measure_pass = Pass::Measure {
446 width,
447 first_height,
448 full_height: None,
449 };
450
451 let draw_pass = Pass::Draw {
452 width,
453 first_height: 4.,
454 breakable: None,
455 preferred_height: None,
456 page: if on_page_1 { 1 } else { 0 },
457 layer: 0,
458 pos: (1. + 1. + gap, 10.),
459 };
460
461 AssertPasses::new(
462 Rectangle {
463 size: (7., 4.),
464 fill: None,
465 outline: None,
466 },
467 match pass {
468 build_element::Pass::FirstLocationUsage { .. } => todo!(),
469 build_element::Pass::Measure { .. } => vec![measure_pass],
470 build_element::Pass::Draw { .. } => vec![measure_pass, draw_pass],
471 },
472 )
473 };
474
475 let child_3 = {
476 let measure_pass = Pass::Measure {
477 width,
478 first_height,
479 full_height: None,
480 };
481
482 let draw_pass = Pass::Draw {
483 width,
484 first_height: 4.,
485 breakable: None,
486 preferred_height: None,
487 page: if on_page_1 {
488 2
489 } else if configuration.breakable {
490 1
491 } else {
492 0
493 },
494 layer: 0,
495 pos: (
496 1.,
497 if configuration.breakable {
498 10.
499 } else {
500 10. - 6. - gap
501 },
502 ),
503 };
504
505 AssertPasses::new(
506 Rectangle {
507 size: (1., 4.),
508 fill: None,
509 outline: None,
510 },
511 match pass {
512 build_element::Pass::FirstLocationUsage { .. } => todo!(),
513 build_element::Pass::Measure { .. } => vec![measure_pass],
514 build_element::Pass::Draw { .. } => vec![measure_pass, draw_pass],
515 },
516 )
517 };
518
519 let element = BreakList {
520 gap,
521 content: |content| {
522 content
523 .add(&child_0)?
524 .add(&child_1)?
525 .add(&child_2)?
526 .add(&child_3)?;
527
528 None
529 },
530 };
531
532 callback.call(element)
533 });
534
535 let output = configuration.run(&element);
536
537 output.assert_size(ElementSize {
538 width: if output.width.expand {
539 Some(output.width.max)
540 } else {
541 Some(1. + gap + 7.)
542 },
543 height: Some(if configuration.breakable {
544 4.
545 } else {
546 6. + gap + 4.
547 }),
548 });
549
550 if let Some(b) = output.breakable {
551 b.assert_break_count(if configuration.use_first_height { 2 } else { 1 });
552 b.assert_extra_location_min_height(None);
553 }
554 }
555 }
556
557 #[test]
558 fn no_unhelpful_breaks() {
559 {
563 let element = BreakList {
564 gap: 1.,
565 content: |content| {
566 content
567 .add(&Rectangle {
568 size: (1., 9.),
569 fill: None,
570 outline: None,
571 })?
572 .add(&Rectangle {
573 size: (1., 9.),
574 fill: None,
575 outline: None,
576 })?
577 .add(&Rectangle {
578 size: (1., 9.),
579 fill: None,
580 outline: None,
581 })?;
582
583 None
584 },
585 };
586
587 let output = test_measure_draw_compatibility(
588 &element,
589 WidthConstraint {
590 max: 3.,
591 expand: true,
592 },
593 8.,
594 Some(8.),
595 (1., 2.),
596 (0., 0.),
597 );
598
599 output.assert_size(ElementSize {
600 width: Some(3.),
601 height: Some(9.),
602 });
603 output.breakable.unwrap().assert_break_count(1);
604 }
605
606 {
607 let element = BreakList {
613 gap: 0.,
614 content: |content| {
615 content
616 .add(&Rectangle {
617 size: (1., 9.),
618 fill: None,
619 outline: None,
620 })?
621 .add(&Rectangle {
622 size: (1.5, 0.),
623 fill: None,
624 outline: None,
625 })?
626 .add(&Rectangle {
627 size: (1., 9.),
628 fill: None,
629 outline: None,
630 })?;
631
632 None
633 },
634 };
635
636 let output = test_measure_draw_compatibility(
637 &element,
638 WidthConstraint {
639 max: 2.,
640 expand: false,
641 },
642 8.,
643 Some(8.),
644 (1., 2.),
645 (0., 0.),
646 );
647
648 output.assert_size(ElementSize {
649 width: Some(1.5),
650 height: Some(9.),
651 });
652 output.breakable.unwrap().assert_break_count(1);
653 }
654 }
655}