1use crate::*;
2
3use self::utils::add_optional_size_with_gap;
4
5pub struct Column<C: Fn(ColumnContent) -> Option<()>> {
32 pub content: C,
44 pub gap: f32,
46 pub collapse: bool,
49}
50
51impl<C: Fn(ColumnContent) -> Option<()>> Column<C> {
52 pub fn new(content: C) -> Self {
53 Column {
54 content,
55 gap: 0.,
56 collapse: true,
57 }
58 }
59
60 pub fn with_gap(self, gap: f32) -> Self {
61 Column { gap, ..self }
62 }
63
64 pub fn with_collapse(self, collapse: bool) -> Self {
65 Column { collapse, ..self }
66 }
67}
68
69impl<C: Fn(ColumnContent) -> Option<()>> Element for Column<C> {
70 fn first_location_usage(&self, ctx: FirstLocationUsageCtx) -> FirstLocationUsage {
71 let mut ret = FirstLocationUsage::NoneHeight;
72
73 (self.content)(ColumnContent {
74 pass: Pass::InsufficientFirstHeight { ctx, ret: &mut ret },
75 gap: self.gap,
76 });
77
78 if !self.collapse && ret == FirstLocationUsage::NoneHeight {
79 ret = FirstLocationUsage::WillUse;
80 }
81
82 ret
83 }
84
85 fn measure(&self, mut ctx: MeasureCtx) -> ElementSize {
86 let mut width = None;
87 let mut height = None;
88 let mut break_count = 0;
89
90 (self.content)(ColumnContent {
91 pass: Pass::Measure {
92 text_pieces_cache: ctx.text_pieces_cache,
93 width_constraint: ctx.width,
94 breakable: ctx.breakable.as_mut().map(|b| BreakableMeasure {
95 break_count: &mut break_count,
96 extra_location_min_height: b.extra_location_min_height,
97 ..*b
98 }),
99 height_available: ctx.first_height,
100 width: &mut width,
101 height: &mut height,
102 },
103 gap: self.gap,
104 });
105
106 if let Some(breakable) = ctx.breakable {
107 *breakable.break_count = break_count;
108 }
109
110 if !self.collapse {
111 if height.is_none() && break_count == 0 {
112 height = Some(0.);
113 }
114
115 if width.is_none() {
116 width = Some(0.);
117 }
118 }
119
120 ElementSize { width, height }
121 }
122
123 fn draw(&self, ctx: DrawCtx) -> ElementSize {
124 let mut width = None;
125 let mut height = None;
126 let mut location_offset = 0;
127
128 (self.content)(ColumnContent {
129 pass: Pass::Draw {
130 pdf: ctx.pdf,
131 text_pieces_cache: ctx.text_pieces_cache,
132 location: ctx.location,
133 location_offset: &mut location_offset,
134 width_constraint: ctx.width,
135 breakable: ctx.breakable,
136 height_available: ctx.first_height,
137 width: &mut width,
138 height: &mut height,
139 },
140 gap: self.gap,
141 });
142
143 if !self.collapse {
144 if height.is_none() && location_offset == 0 {
145 height = Some(0.);
146 }
147
148 if width.is_none() {
149 width = Some(0.);
150 }
151 }
152
153 ElementSize { width, height }
154 }
155}
156
157pub struct ColumnContent<'a, 'b, 'r> {
158 pass: Pass<'a, 'b, 'r>,
159 gap: f32,
160}
161
162enum Pass<'a, 'b, 'r> {
163 InsufficientFirstHeight {
164 ctx: FirstLocationUsageCtx<'a>,
165 ret: &'r mut FirstLocationUsage,
166 },
167 Measure {
168 text_pieces_cache: &'a TextPiecesCache,
169 width_constraint: WidthConstraint,
170 breakable: Option<BreakableMeasure<'a>>,
171
172 height_available: f32,
174 width: &'r mut Option<f32>,
175 height: &'r mut Option<f32>,
176 },
177 Draw {
178 pdf: &'a mut Pdf,
179 text_pieces_cache: &'a TextPiecesCache,
180 location: Location,
181 location_offset: &'r mut u32,
182 width_constraint: WidthConstraint,
183 breakable: Option<BreakableDraw<'b>>,
184
185 height_available: f32,
187 width: &'r mut Option<f32>,
188 height: &'r mut Option<f32>,
189 },
190}
191
192impl<'a, 'b, 'r> ColumnContent<'a, 'b, 'r> {
193 pub fn add<E: Element>(mut self, element: &E) -> Option<Self> {
194 match self.pass {
195 Pass::InsufficientFirstHeight {
196 ref mut ctx,
197 ret: &mut ref mut ret,
198 } => {
199 let first_location_usage =
200 element.first_location_usage(FirstLocationUsageCtx { ..*ctx });
201
202 if first_location_usage == FirstLocationUsage::NoneHeight {
203 Some(self)
204 } else {
205 *ret = first_location_usage;
206 None
207 }
208 }
209 Pass::Measure {
210 text_pieces_cache,
211 width_constraint,
212 ref mut breakable,
213 ref mut height_available,
214 width: &mut ref mut width,
215 height: &mut ref mut height,
216 } => {
217 let measure_ctx = MeasureCtx {
220 text_pieces_cache,
221 width: width_constraint,
222 first_height: *height_available
223 - height.unwrap_or(0.)
224 - if height.is_some() { self.gap } else { 0. },
225 breakable: None,
226 };
227
228 let size;
229
230 if let Some(b) = breakable {
231 let mut break_count = 0;
232
233 let mut extra_location_min_height = None;
235
236 size = element.measure(MeasureCtx {
237 breakable: Some(BreakableMeasure {
238 full_height: b.full_height,
239 break_count: &mut break_count,
240 extra_location_min_height: &mut extra_location_min_height,
241 }),
242 ..measure_ctx
243 });
244
245 if break_count > 0 {
246 *height_available = b.full_height;
247 *height = None;
248 *b.break_count += break_count;
249 }
250 } else {
251 size = element.measure(measure_ctx);
252 }
253
254 if let Some(h) = size.height {
255 if let Some(height) = height {
256 *height += self.gap;
257 *height += h;
258 } else {
259 *height = Some(h);
260 }
261 }
262
263 if let Some(w) = size.width {
264 if let Some(width) = width {
265 *width = width.max(w);
266 } else {
267 *width = Some(w);
268 }
269 }
270
271 Some(self)
272 }
273 Pass::Draw {
274 pdf: &mut ref mut pdf,
275 text_pieces_cache,
276 ref mut location,
277 location_offset: &mut ref mut location_offset,
278 width_constraint,
279 ref mut breakable,
280 ref mut height_available,
281 width: &mut ref mut width,
282 height: &mut ref mut height,
283 } => {
284 let draw_ctx = DrawCtx {
287 pdf,
288 text_pieces_cache,
289 location: Location {
290 pos: if height.is_some() {
291 (location.pos.0, location.pos.1 - self.gap)
292 } else {
293 location.pos
294 },
295 ..*location
296 },
297 width: width_constraint,
298 first_height: *height_available
299 - height.unwrap_or(0.)
300 - if height.is_some() { self.gap } else { 0. },
301 preferred_height: None,
302 breakable: None,
303 };
304
305 let size = if let Some(b) = breakable {
306 let mut break_count = 0;
307
308 let size = element.draw(DrawCtx {
309 breakable: Some(BreakableDraw {
310 full_height: b.full_height,
311 preferred_height_break_count: 0,
312 do_break: &mut |pdf, location_idx, location_height| {
313 *height_available = b.full_height;
314
315 let location_height = if location_idx == 0 {
316 add_optional_size_with_gap(location_height, *height, self.gap)
317 } else {
318 location_height
319 };
320
321 let new_location = (b.do_break)(
322 pdf,
323 location_idx + *location_offset,
324 location_height,
325 );
326
327 if location_idx + 1 > break_count {
328 break_count = location_idx + 1;
329 *location = new_location.clone();
330 }
331
332 new_location
333 },
334 }),
335 ..draw_ctx
336 });
337
338 if break_count > 0 {
339 *location_offset += break_count;
340 *height_available = b.full_height;
341 *height = None;
342 }
343
344 size
345 } else {
346 element.draw(draw_ctx)
347 };
348
349 if let Some(h) = size.height {
350 if let Some(height) = height {
351 location.pos.1 -= self.gap;
352 *height += self.gap;
353
354 *height += h;
355 } else {
356 *height = Some(h);
357 }
358
359 location.pos.1 -= h;
360 }
361
362 if let Some(w) = size.width {
363 if let Some(width) = width {
364 *width = width.max(w);
365 } else {
366 *width = Some(w);
367 }
368 }
369
370 Some(self)
371 }
372 }
373 }
374}
375
376#[cfg(test)]
377mod tests {
378 use super::*;
379 use crate::{elements::force_break::ForceBreak, elements::none::NoneElement, test_utils::*};
380
381 #[test]
382 fn test_column_empty() {
383 let element = Column {
384 gap: 100.,
385 collapse: true,
386 content: |_| Some(()),
387 };
388
389 for output in ElementTestParams::default().run(&element) {
390 output.assert_size(ElementSize {
391 width: None,
392 height: None,
393 });
394
395 if let Some(b) = output.breakable {
396 b.assert_break_count(0)
397 .assert_extra_location_min_height(None);
398 }
399 }
400 }
401
402 #[test]
403 fn test_column_with_multiple_nones() {
404 use assert_passes::*;
405
406 let element = BuildElement(|build_ctx, callback| {
407 let build = || {
409 AssertPasses::new(
410 NoneElement,
411 match build_ctx.pass {
412 build_element::Pass::FirstLocationUsage { full_height } => {
413 vec![Pass::FirstLocationUsage {
414 width: build_ctx.width,
415 first_height: build_ctx.first_height,
416 full_height,
417 }]
418 }
419 build_element::Pass::Measure { full_height } => vec![Pass::Measure {
420 width: build_ctx.width,
421 first_height: build_ctx.first_height,
422 full_height,
423 }],
424 build_element::Pass::Draw { ref breakable, .. } => vec![Pass::Draw {
425 width: build_ctx.width,
426 first_height: build_ctx.first_height,
427 preferred_height: None,
428
429 page: 0,
430 layer: 0,
431 pos: (3., 12.),
432
433 breakable: breakable.as_ref().map(|b| BreakableDraw {
434 full_height: b.full_height,
435 preferred_height_break_count: 0,
436 breaks: Vec::new(),
437 }),
438 }],
439 },
440 )
441 };
442
443 let none_0 = build();
444 let none_1 = build();
445 let none_2 = build();
446
447 let element = Column {
448 gap: 1.,
449 collapse: true,
450 content: |content| {
451 content.add(&none_0)?.add(&none_1)?.add(&none_2)?;
452
453 None
454 },
455 };
456
457 callback.call(element)
458 });
459
460 for output in (ElementTestParams {
461 first_height: 4.,
462 full_height: 10.,
463 width: 6.,
464 pos: (3., 12.),
465 ..Default::default()
466 })
467 .run(&element)
468 {
469 output.assert_size(ElementSize {
470 width: None,
471 height: None,
472 });
473
474 if let Some(b) = output.breakable {
475 b.assert_break_count(0)
476 .assert_extra_location_min_height(None);
477 }
478 }
479 }
480
481 #[test]
482 fn test_column() {
483 use assert_passes::*;
484
485 let element = BuildElement(|build_ctx, callback| {
486 let less_first_height = build_ctx.first_height == 4.;
487
488 let child_0 = AssertPasses::new(
489 NoneElement,
490 match build_ctx.pass {
491 build_element::Pass::FirstLocationUsage { full_height } => {
492 vec![Pass::FirstLocationUsage {
493 width: build_ctx.width,
494 first_height: build_ctx.first_height,
495 full_height,
496 }]
497 }
498 build_element::Pass::Measure { full_height } => vec![Pass::Measure {
499 width: build_ctx.width,
500 first_height: build_ctx.first_height,
501 full_height,
502 }],
503 build_element::Pass::Draw { ref breakable, .. } => vec![Pass::Draw {
504 width: build_ctx.width,
505 first_height: build_ctx.first_height,
506 preferred_height: None,
507
508 page: 0,
509 layer: 0,
510 pos: (3., 12.),
511
512 breakable: breakable.as_ref().map(|b| BreakableDraw {
513 full_height: b.full_height,
514 preferred_height_break_count: 0,
515 breaks: Vec::new(),
516 }),
517 }],
518 },
519 );
520
521 let child_1 = AssertPasses::new(
522 FakeText {
523 lines: 8,
524 line_height: 2.,
525 width: 5.,
526 },
527 match build_ctx.pass {
528 build_element::Pass::FirstLocationUsage { full_height } => {
529 vec![Pass::FirstLocationUsage {
530 width: build_ctx.width,
531 first_height: build_ctx.first_height,
532 full_height,
533 }]
534 }
535 build_element::Pass::Measure { full_height } => vec![Pass::Measure {
536 width: build_ctx.width,
537 first_height: build_ctx.first_height,
538 full_height,
539 }],
540 build_element::Pass::Draw { ref breakable, .. } => vec![Pass::Draw {
541 width: build_ctx.width,
542 first_height: build_ctx.first_height,
543 preferred_height: None,
544
545 page: 0,
546 layer: 0,
547 pos: (3., 12.),
548
549 breakable: breakable.as_ref().map(|b| BreakableDraw {
550 full_height: b.full_height,
551 preferred_height_break_count: 0,
552 breaks: if less_first_height {
553 vec![
554 Break {
555 page: 1,
556 layer: 0,
557 pos: (3., 12.),
558 },
559 Break {
560 page: 2,
561 layer: 0,
562 pos: (3., 12.),
563 },
564 ]
565 } else {
566 vec![Break {
567 page: 1,
568 layer: 0,
569 pos: (3., 12.),
570 }]
571 },
572 }),
573 }],
574 },
575 );
576
577 let child_2 = {
578 let first_height = match (build_ctx.is_breakable(), less_first_height) {
579 (false, false) => 10. - 16. - 1.,
580 (false, true) => 4. - 16. - 1.,
581 (true, false) => 3.,
582 (true, true) => 7.,
583 };
584
585 AssertPasses::new(
586 ForceBreak,
587 match build_ctx.pass {
588 build_element::Pass::FirstLocationUsage { .. } => vec![],
589 build_element::Pass::Measure { full_height } => vec![Pass::Measure {
590 width: build_ctx.width,
591 first_height,
592 full_height,
593 }],
594 build_element::Pass::Draw { ref breakable, .. } => {
595 vec![if let Some(breakable) = breakable {
596 Pass::Draw {
597 width: build_ctx.width,
598 first_height,
599 preferred_height: None,
600
601 page: if less_first_height { 2 } else { 1 },
602 layer: 0,
603 pos: if less_first_height {
604 (3., 12. - 3.)
605 } else {
606 (3., 12. - 7.)
607 },
608
609 breakable: Some(BreakableDraw {
610 full_height: breakable.full_height,
611 preferred_height_break_count: 0,
612 breaks: if less_first_height {
613 vec![Break {
614 page: 3,
615 layer: 0,
616 pos: (3., 12.),
617 }]
618 } else {
619 vec![Break {
620 page: 2,
621 layer: 0,
622 pos: (3., 12.),
623 }]
624 },
625 }),
626 }
627 } else {
628 Pass::Draw {
629 width: build_ctx.width,
630 first_height,
631 preferred_height: None,
632
633 page: 0,
634 layer: 0,
635 pos: (3., 12. - 16. - 1.),
636
637 breakable: None,
638 }
639 }]
640 }
641 },
642 )
643 };
644
645 let child_3 = {
646 let first_height = match (build_ctx.is_breakable(), less_first_height) {
647 (false, false) => 10. - 16. - 1.,
648 (false, true) => 4. - 16. - 1.,
649 (true, _) => 10.,
650 };
651
652 AssertPasses::new(
653 FranticJumper {
654 jumps: vec![
655 (0, Some(0.)),
656 (5, Some(1.5)),
657 (3, Some(1.5)),
658 (3, Some(1.5)),
659 ],
660 size: ElementSize {
661 width: Some(5.5),
662 height: Some(1.5),
663 },
664 },
665 match build_ctx.pass {
666 build_element::Pass::FirstLocationUsage { .. } => vec![],
667 build_element::Pass::Measure { full_height } => vec![Pass::Measure {
668 width: build_ctx.width,
669 first_height,
670 full_height,
671 }],
672 build_element::Pass::Draw { ref breakable, .. } => {
673 vec![if let Some(breakable) = breakable {
674 let start_page = if less_first_height { 3 } else { 2 };
675
676 Pass::Draw {
677 width: build_ctx.width,
678 first_height,
679 preferred_height: None,
680 page: start_page,
681 layer: 0,
682 pos: (3., 12.),
683 breakable: Some(BreakableDraw {
684 full_height: breakable.full_height,
685 preferred_height_break_count: 0,
686 breaks: vec![
687 Break {
688 page: start_page + 1,
689 layer: 0,
690 pos: (3., 12.),
691 },
692 Break {
693 page: start_page + 6,
694 layer: 0,
695 pos: (3., 12.),
696 },
697 Break {
698 page: start_page + 4,
699 layer: 0,
700 pos: (3., 12.),
701 },
702 Break {
703 page: start_page + 4,
704 layer: 0,
705 pos: (3., 12.),
706 },
707 ],
708 }),
709 }
710 } else {
711 Pass::Draw {
712 width: build_ctx.width,
713 first_height,
714 preferred_height: None,
715
716 page: 0,
717 layer: 0,
718 pos: (3., 12. - 16. - 1.),
719
720 breakable: None,
721 }
722 }]
723 }
724 },
725 )
726 };
727
728 let child_4 = {
729 let first_height = match (build_ctx.is_breakable(), less_first_height) {
730 (false, false) => 10. - 16. - 1. - 1.5 - 1.,
731 (false, true) => 4. - 16. - 1. - 1.5 - 1.,
732 (true, _) => 10. - 1.5 - 1.,
733 };
734
735 AssertPasses::new(
736 NoneElement,
737 match build_ctx.pass {
738 build_element::Pass::FirstLocationUsage { .. } => vec![],
739 build_element::Pass::Measure { full_height } => vec![Pass::Measure {
740 width: build_ctx.width,
741 first_height,
742 full_height,
743 }],
744 build_element::Pass::Draw { ref breakable, .. } => {
745 vec![if let Some(breakable) = breakable {
746 let start_page = if less_first_height { 3 } else { 2 } + 6;
747
748 Pass::Draw {
749 width: build_ctx.width,
750 first_height,
751 preferred_height: None,
752 page: start_page,
753 layer: 0,
754 pos: (3., 12. - 1.5 - 1.),
755 breakable: Some(BreakableDraw {
756 full_height: breakable.full_height,
757 preferred_height_break_count: 0,
758 breaks: vec![],
759 }),
760 }
761 } else {
762 Pass::Draw {
763 width: build_ctx.width,
764 first_height,
765 preferred_height: None,
766
767 page: 0,
768 layer: 0,
769 pos: (3., 12. - 16. - 1. - 1.5 - 1.),
770
771 breakable: None,
772 }
773 }]
774 }
775 },
776 )
777 };
778
779 let element = Column {
780 gap: 1.,
781 collapse: false,
782 content: |content| {
783 content
784 .add(&child_0)?
785 .add(&child_1)?
786 .add(&child_2)?
787 .add(&child_3)?
788 .add(&child_4)?;
789
790 Some(())
791 },
792 };
793
794 callback.call(element)
795 });
796
797 for output in (ElementTestParams {
798 first_height: 4.,
799 full_height: 10.,
800 width: 6.,
801 pos: (3., 12.),
802 ..Default::default()
803 })
804 .run(&element)
805 {
806 output.assert_size(ElementSize {
807 width: Some(output.width.constrain(5.5)),
808 height: Some(if output.breakable.is_some() {
809 1.5
810 } else {
811 16. + 1. + 1.5
812 }),
813 });
814
815 if let Some(b) = output.breakable {
816 b.assert_break_count(if output.first_height == 4. { 9 } else { 8 })
817 .assert_extra_location_min_height(None);
818 }
819 }
820 }
821}