1use crate::{
2 flex::{DrawLayout, MeasureLayout},
3 utils::{max_optional_size, mm_to_pt, u32_to_color_and_alpha},
4 *,
5};
6
7pub struct TableRow<F: Fn(&mut RowContent)> {
11 pub line_style: LineStyle,
12 pub expand: bool,
13 pub content: F,
14}
15
16impl<F: Fn(&mut RowContent)> Element for TableRow<F> {
17 fn first_location_usage(&self, ctx: FirstLocationUsageCtx) -> FirstLocationUsage {
18 FirstLocationUsage::WillUse
19 }
20
21 fn measure(&self, mut ctx: MeasureCtx) -> ElementSize {
22 let mut measure_layout = MeasureLayout::new(ctx.width.max, self.line_style.thickness);
23
24 let mut max_height = None;
25
26 (self.content)(&mut RowContent {
27 text_pieces_cache: ctx.text_pieces_cache,
28 width: ctx.width,
29 first_height: ctx.first_height,
30 pass: Pass::MeasureNonExpanded {
31 layout: &mut measure_layout,
32 max_height: Some(&mut max_height),
33 breakable: ctx.breakable.as_mut(),
34 },
35 });
36
37 let mut width = measure_layout.no_expand_width();
38
39 let draw_layout = measure_layout.build();
40
41 (self.content)(&mut RowContent {
42 text_pieces_cache: ctx.text_pieces_cache,
43 width: ctx.width,
44 first_height: ctx.first_height,
45 pass: Pass::MeasureExpanded {
46 layout: &draw_layout,
47 max_height: &mut max_height,
48 width: if ctx.width.expand {
49 None
50 } else {
51 Some(&mut width)
52 },
53 gap: self.line_style.thickness,
54 breakable: ctx.breakable.as_mut(),
55 },
56 });
57
58 ElementSize {
59 width: if ctx.width.expand {
60 Some(ctx.width.max)
61 } else {
62 width
63 },
64 height: max_height,
65 }
66 }
67
68 fn draw(&self, mut ctx: DrawCtx) -> ElementSize {
69 let mut measure_layout = MeasureLayout::new(ctx.width.max, self.line_style.thickness);
70
71 let mut max_height = None;
72
73 let mut break_count = 0;
74 let mut extra_location_min_height = None;
75
76 (self.content)(&mut RowContent {
77 text_pieces_cache: ctx.text_pieces_cache,
78 width: ctx.width,
79 first_height: ctx.first_height,
80 pass: Pass::MeasureNonExpanded {
81 layout: &mut measure_layout,
82 max_height: if self.expand {
83 Some(&mut max_height)
84 } else {
85 None
86 },
87 breakable: ctx
88 .breakable
89 .as_ref()
90 .map(|b| BreakableMeasure {
91 full_height: b.full_height,
92
93 break_count: &mut break_count,
95 extra_location_min_height: &mut extra_location_min_height,
96 })
97 .as_mut(),
98 },
99 });
100
101 let draw_layout = measure_layout.build();
102
103 if self.expand {
107 (self.content)(&mut RowContent {
108 text_pieces_cache: ctx.text_pieces_cache,
109 width: ctx.width,
110 first_height: ctx.first_height,
111 pass: Pass::MeasureExpanded {
112 layout: &draw_layout,
113 max_height: &mut max_height,
114 width: None, gap: self.line_style.thickness,
116 breakable: ctx
117 .breakable
118 .as_ref()
119 .map(|b| BreakableMeasure {
120 full_height: b.full_height,
121
122 break_count: &mut break_count,
124 extra_location_min_height: &mut extra_location_min_height,
125 })
126 .as_mut(),
127 },
128 });
129
130 if let Some(ref mut b) = ctx.breakable {
131 match break_count.cmp(&b.preferred_height_break_count) {
132 std::cmp::Ordering::Less => (),
133 std::cmp::Ordering::Equal => {
134 ctx.preferred_height = max_optional_size(ctx.preferred_height, max_height);
135 }
136 std::cmp::Ordering::Greater => {
137 b.preferred_height_break_count = break_count;
138 ctx.preferred_height = max_height;
139 }
140 }
141 } else {
142 ctx.preferred_height = max_optional_size(ctx.preferred_height, max_height);
143 }
144 }
145
146 let mut width = None;
147 let mut break_count = 0;
148
149 (self.content)(&mut RowContent {
150 text_pieces_cache: ctx.text_pieces_cache,
151 width: ctx.width,
152 first_height: ctx.first_height,
153 pass: Pass::Draw {
154 layout: &draw_layout,
155 max_height: &mut max_height,
156 width: &mut width,
157 gap: self.line_style.thickness,
158 pdf: ctx.pdf,
159 location: ctx.location.clone(),
160 preferred_height: ctx.preferred_height,
161 break_count: &mut break_count,
162 breakable: ctx.breakable.as_mut(),
163 },
164 });
165
166 if let Some(height) = max_height {
167 (self.content)(&mut RowContent {
168 text_pieces_cache: ctx.text_pieces_cache,
169 width: ctx.width,
170 first_height: ctx.first_height,
171 pass: Pass::DrawLines {
172 layout: &draw_layout,
173 width: None,
174 height,
175 line_style: self.line_style,
176 pdf: ctx.pdf,
177 location: ctx.location,
178 break_count,
179 breakable: ctx.breakable.as_mut(),
180 },
181 });
182 }
183
184 ElementSize {
185 width: if ctx.width.expand {
186 Some(ctx.width.max)
187 } else {
188 width
189 },
190 height: max_height,
191 }
192 }
193}
194
195pub struct RowContent<'a, 'b, 'c> {
196 text_pieces_cache: &'a TextPiecesCache,
197 width: WidthConstraint,
198 first_height: f32,
199 pass: Pass<'a, 'b, 'c>,
200}
201
202enum Pass<'a, 'b, 'c> {
203 MeasureNonExpanded {
204 layout: &'a mut MeasureLayout,
205 max_height: Option<&'a mut Option<f32>>,
206 breakable: Option<&'a mut BreakableMeasure<'b>>,
207 },
208
209 FirstLocationUsage {},
210
211 MeasureExpanded {
212 layout: &'a DrawLayout,
213 max_height: &'a mut Option<f32>,
214 width: Option<&'a mut Option<f32>>,
215 gap: f32,
216 breakable: Option<&'a mut BreakableMeasure<'b>>,
217 },
218
219 Draw {
220 layout: &'a DrawLayout,
221 max_height: &'a mut Option<f32>,
222 width: &'a mut Option<f32>,
223
224 gap: f32,
225
226 pdf: &'c mut Pdf,
227 location: Location,
228
229 preferred_height: Option<f32>,
230 break_count: &'a mut u32,
231 breakable: Option<&'a mut BreakableDraw<'b>>,
232 },
233
234 DrawLines {
235 layout: &'a DrawLayout,
236 height: f32,
237 width: Option<f32>,
238 break_count: u32,
239
240 line_style: LineStyle,
241 pdf: &'c mut Pdf,
242 location: Location,
243 breakable: Option<&'a mut BreakableDraw<'b>>,
244 },
245}
246
247#[derive(Copy, Clone, Serialize, Deserialize)]
248pub enum Flex {
249 Expand(u8),
250 Fixed(f32),
251}
252
253fn add_height(
254 max_height: &mut Option<f32>,
255 breakable: Option<&mut BreakableMeasure>,
256 size: ElementSize,
257 break_count: u32,
258 extra_location_min_height: Option<f32>,
259) {
260 if let Some(b) = breakable {
261 *b.extra_location_min_height =
262 max_optional_size(extra_location_min_height, *b.extra_location_min_height);
263
264 match break_count.cmp(b.break_count) {
265 std::cmp::Ordering::Less => (),
266 std::cmp::Ordering::Equal => {
267 *max_height = max_optional_size(*max_height, size.height);
268 }
269 std::cmp::Ordering::Greater => {
270 *b.break_count = break_count;
271 *max_height = size.height;
272 }
273 }
274 } else {
275 *max_height = max_optional_size(*max_height, size.height);
276 }
277}
278
279impl<'a, 'b, 'c> RowContent<'a, 'b, 'c> {
280 pub fn add<E: Element>(&mut self, element: &E, flex: Flex) {
281 match self.pass {
282 Pass::MeasureNonExpanded {
283 layout: &mut ref mut layout,
284 ref mut max_height,
285 ref mut breakable,
286 } => match flex {
287 Flex::Expand(fraction) => {
288 layout.add_expand(fraction);
289 }
290 Flex::Fixed(width) => {
291 layout.add_fixed(width);
292
293 if let Some(max_height) = max_height {
294 let mut break_count = 0;
295 let mut extra_location_min_height = None;
296
297 let size = element.measure(MeasureCtx {
298 text_pieces_cache: self.text_pieces_cache,
299 width: WidthConstraint {
300 max: width,
301 expand: true,
302 },
303 first_height: self.first_height,
304 breakable: breakable.as_mut().map(|b| BreakableMeasure {
305 full_height: b.full_height,
306 break_count: &mut break_count,
307 extra_location_min_height: &mut extra_location_min_height,
308 }),
309 });
310
311 add_height(
312 max_height,
313 breakable.as_deref_mut(),
314 size,
315 break_count,
316 extra_location_min_height,
317 );
318 }
319 }
320 },
321
322 Pass::MeasureExpanded {
323 layout,
324 max_height: &mut ref mut max_height,
325 ref mut width,
326 gap,
327 ref mut breakable,
328 } => match flex {
329 Flex::Expand(fraction) => {
330 let element_width = layout.expand_width(fraction);
331
332 let mut break_count = 0;
333 let mut extra_location_min_height = None;
334
335 let size = element.measure(MeasureCtx {
336 text_pieces_cache: self.text_pieces_cache,
337 width: WidthConstraint {
338 max: element_width,
339 expand: true,
340 },
341 first_height: self.first_height,
342 breakable: breakable.as_deref_mut().map(|b| BreakableMeasure {
343 full_height: b.full_height,
344 break_count: &mut break_count,
345 extra_location_min_height: &mut extra_location_min_height,
346 }),
347 });
348
349 add_height(
350 max_height,
351 breakable.as_deref_mut(),
352 size,
353 break_count,
354 extra_location_min_height,
355 );
356
357 if let &mut Some(&mut ref mut width) = width {
358 if let Some(w) = size.width {
359 if let Some(width) = width {
360 *width += gap + w;
361 } else {
362 *width = Some(w);
363 }
364 }
365 }
366 }
367 Flex::Fixed(_) => (),
368 },
369
370 Pass::Draw {
371 layout,
372 max_height: &mut ref mut max_height,
373 width: &mut ref mut width,
374 gap,
375 pdf: &mut ref mut pdf,
376 ref location,
377 preferred_height,
378 break_count: &mut ref mut break_count,
379 ref mut breakable,
380 } => {
381 let width_constraint = match flex {
382 Flex::Expand(fraction) => WidthConstraint {
383 max: layout.expand_width(fraction),
384 expand: true,
385 },
386 Flex::Fixed(width) => WidthConstraint {
387 max: width,
388 expand: true,
389 },
390 };
391
392 let mut element_break_count = 0;
393
394 let x_offset = if let &mut Some(width) = width {
395 width + gap
396 } else {
397 0.
398 };
399
400 let size = element.draw(DrawCtx {
401 pdf,
402 text_pieces_cache: self.text_pieces_cache,
403 location: Location {
404 pos: (location.pos.0 + x_offset, location.pos.1),
405 ..location.clone()
406 },
407
408 width: width_constraint,
409 first_height: self.first_height,
410 preferred_height,
411
412 breakable: breakable
414 .as_deref_mut()
415 .map(|b| {
416 (
417 b.full_height,
418 b.preferred_height_break_count,
419 |pdf: &mut Pdf, location_idx: u32, _| {
420 element_break_count = element_break_count.max(location_idx + 1);
421
422 let mut new_location = (b.do_break)(
423 pdf,
424 location_idx,
425 Some(if location_idx == 0 {
426 self.first_height
427 } else {
428 b.full_height
429 }),
430 );
431 new_location.pos.0 += x_offset;
432 new_location
433 },
434 )
435 })
436 .as_mut()
437 .map(
438 |&mut (
439 full_height,
440 preferred_height_break_count,
441 ref mut get_location,
442 )| {
443 BreakableDraw {
444 full_height,
445 preferred_height_break_count,
446 do_break: get_location,
447 }
448 },
449 ),
450 });
451
452 if breakable.is_some() {
453 match element_break_count.cmp(break_count) {
454 std::cmp::Ordering::Less => (),
455 std::cmp::Ordering::Equal => {
456 *max_height = max_optional_size(*max_height, size.height);
457 }
458 std::cmp::Ordering::Greater => {
459 *break_count = element_break_count;
460 *max_height = size.height;
461 }
462 }
463 } else {
464 *max_height = max_optional_size(*max_height, size.height);
465 }
466
467 let mut width_add = |w| {
468 if let Some(width) = width {
469 *width += gap + w;
470 } else {
471 *width = Some(w);
472 }
473 };
474
475 width_add(width_constraint.max);
476 }
477
478 Pass::DrawLines {
479 layout,
480 height,
481 ref mut width,
482 line_style,
483 pdf: &mut ref mut pdf,
484 ref location,
485 break_count,
486 ref mut breakable,
487 } => {
488 let element_width = match flex {
489 Flex::Expand(fraction) => layout.expand_width(fraction),
490 Flex::Fixed(width) => width,
491 };
492
493 if let Some(width) = width {
494 let draw_line = |pdf: &mut Pdf, location: &Location, height: f32| {
495 let x = location.pos.0 + *width;
496 let y = location.pos.1;
497
498 let (color, _alpha) = u32_to_color_and_alpha(line_style.color);
499 let style = line_style;
500
501 let layer = location.layer(pdf);
502
503 layer
504 .save_state()
505 .set_line_width(mm_to_pt(style.thickness))
506 .set_stroke_rgb(color[0], color[1], color[2])
507 .set_line_cap(style.cap_style.into());
508
509 if let Some(pattern) = style.dash_pattern {
510 layer.set_dash_pattern(
511 pattern.dashes.map(f32::from),
512 pattern.offset as f32,
513 );
514 }
515
516 let line_x = x + line_style.thickness / 2.;
517
518 layer
519 .move_to(mm_to_pt(line_x), mm_to_pt(y))
520 .line_to(mm_to_pt(line_x), mm_to_pt(y - height))
521 .stroke()
522 .restore_state();
523 };
524
525 match breakable {
526 Some(breakable) if break_count > 0 => {
527 draw_line(pdf, location, self.first_height);
528
529 for i in 0..break_count {
530 let location = (breakable.do_break)(
531 pdf,
532 i,
533 Some(if i == 0 {
534 self.first_height
535 } else {
536 breakable.full_height
537 }),
538 );
539 draw_line(
540 pdf,
541 &location,
542 if i == break_count - 1 {
543 height
544 } else {
545 breakable.full_height
546 },
547 );
548 }
549 }
550 _ => {
551 draw_line(pdf, location, height);
552 }
553 }
554
555 *width += line_style.thickness + element_width;
556 } else {
557 *width = Some(element_width);
558 }
559 }
560
561 _ => todo!(),
562 }
563 }
564}