Skip to main content

laser_pdf/elements/
table_row.rs

1use crate::{
2    flex::{DrawLayout, MeasureLayout},
3    utils::{max_optional_size, mm_to_pt, u32_to_color_and_alpha},
4    *,
5};
6
7/// Currently almost a copy of Row, with the difference that there is no self sized and there's
8/// lines instead of gaps. The plan is to eventually replace this with a custom element instead of
9/// a gap in Row.
10pub 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                        // in the non-expand case these will just be ignored
94                        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 we want to expand all of the children to the same size we need an additional pass here
104        // to figure out the maximum height & break count of all of the children. This is part of
105        // the reason why expanding isn't just what Row always does.
106        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, // We'll get that from draw. No point in getting it twice.
115                    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                            // in the non-expand case these will just be ignored
123                            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                    // some trickery to get rust to make a temporary option that owns the closure
413                    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}