Skip to main content

laser_pdf/elements/
padding.rs

1use crate::*;
2
3pub struct Padding<E: Element> {
4    pub left: f32,
5    pub right: f32,
6    pub top: f32,
7    pub bottom: f32,
8    pub element: E,
9}
10
11impl<E: Element> Padding<E> {
12    pub fn left(left: f32, element: E) -> Self {
13        Padding {
14            left,
15            right: 0.,
16            top: 0.,
17            bottom: 0.,
18            element,
19        }
20    }
21
22    pub fn right(right: f32, element: E) -> Self {
23        Padding {
24            left: 0.,
25            right,
26            top: 0.,
27            bottom: 0.,
28            element,
29        }
30    }
31
32    pub fn top(top: f32, element: E) -> Self {
33        Padding {
34            left: 0.,
35            right: 0.,
36            top,
37            bottom: 0.,
38            element,
39        }
40    }
41
42    pub fn bottom(bottom: f32, element: E) -> Self {
43        Padding {
44            left: 0.,
45            right: 0.,
46            top: 0.,
47            bottom,
48            element,
49        }
50    }
51}
52
53impl<E: Element> Element for Padding<E> {
54    fn first_location_usage(&self, ctx: FirstLocationUsageCtx) -> FirstLocationUsage {
55        self.element.first_location_usage(FirstLocationUsageCtx {
56            text_pieces_cache: ctx.text_pieces_cache,
57            width: self.width(ctx.width),
58            first_height: self.height(ctx.first_height),
59            full_height: self.height(ctx.full_height),
60        })
61    }
62
63    fn measure(&self, ctx: MeasureCtx) -> ElementSize {
64        let mut break_count = 0;
65        let mut extra_location_min_height = None;
66
67        let size = self.element.measure(MeasureCtx {
68            text_pieces_cache: ctx.text_pieces_cache,
69            width: self.width(ctx.width),
70            first_height: self.height(ctx.first_height),
71            breakable: ctx.breakable.as_ref().map(|b| BreakableMeasure {
72                break_count: &mut break_count,
73                extra_location_min_height: &mut extra_location_min_height,
74                full_height: self.height(b.full_height),
75            }),
76        });
77
78        if let Some(b) = ctx.breakable {
79            *b.break_count = break_count;
80
81            // TODO: Subtract padding
82            *b.extra_location_min_height = extra_location_min_height;
83        }
84
85        self.size(size)
86    }
87
88    fn draw(&self, ctx: DrawCtx) -> ElementSize {
89        let width = self.width(ctx.width);
90
91        let draw_ctx = DrawCtx {
92            pdf: ctx.pdf,
93            text_pieces_cache: ctx.text_pieces_cache,
94
95            location: Location {
96                pos: (
97                    ctx.location.pos.0 + self.left,
98                    ctx.location.pos.1 - self.top,
99                ),
100                ..ctx.location
101            },
102
103            preferred_height: ctx.preferred_height.map(|p| self.height(p)),
104
105            width,
106            first_height: self.height(ctx.first_height),
107            breakable: None,
108        };
109
110        let size = if let Some(breakable) = ctx.breakable {
111            self.element.draw(DrawCtx {
112                breakable: Some(BreakableDraw {
113                    full_height: self.height(breakable.full_height),
114                    preferred_height_break_count: breakable.preferred_height_break_count,
115                    do_break: &mut |pdf, location_idx, height| {
116                        let mut location = (breakable.do_break)(
117                            pdf,
118                            location_idx,
119                            height.map(|h| h + self.top + self.bottom),
120                        );
121
122                        location.pos.0 += self.left;
123                        location.pos.1 -= self.top;
124
125                        location
126                    },
127                }),
128                ..draw_ctx
129            })
130        } else {
131            self.element.draw(draw_ctx)
132        };
133
134        self.size(size)
135    }
136}
137
138impl<E: Element> Padding<E> {
139    fn width(&self, constraint: WidthConstraint) -> WidthConstraint {
140        WidthConstraint {
141            max: constraint.max - self.left - self.right,
142            expand: constraint.expand,
143        }
144    }
145
146    fn height(&self, input: f32) -> f32 {
147        input - self.top - self.bottom
148    }
149
150    fn size(&self, size: ElementSize) -> ElementSize {
151        ElementSize {
152            width: size.width.map(|w| w + self.left + self.right),
153            height: size.height.map(|h| h + self.top + self.bottom),
154        }
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161    use crate::test_utils::*;
162
163    #[test]
164    fn test_padding() {
165        let element = BuildElement(|build_ctx, callback| {
166            let content = FakeText {
167                width: 10.,
168                line_height: 1.,
169                lines: 10,
170            };
171
172            let proxy = ElementProxy {
173                before_draw: &|ctx: &mut DrawCtx| {
174                    assert_eq!(
175                        ctx.width,
176                        WidthConstraint {
177                            max: 40. - 25.,
178                            expand: build_ctx.width.expand,
179                        }
180                    );
181                    assert_eq!(ctx.location.pos.0, 24.);
182                },
183                after_break: &|_location_idx: u32,
184                               location: &Location,
185                               _width: WidthConstraint,
186                               _first_height| {
187                    assert_eq!(location.pos.0, 24.);
188                },
189                ..ElementProxy::new(content)
190            };
191            callback.call(Padding {
192                left: 12.,
193                right: 13.,
194                top: 14.,
195                bottom: 15.,
196                element: proxy,
197            })
198        });
199
200        for output in (ElementTestParams {
201            first_height: 30.1,
202            full_height: 31.5,
203            width: 40.,
204            ..Default::default()
205        })
206        .run(&element)
207        {
208            output.assert_size(ElementSize {
209                width: Some(output.width.constrain(35.)),
210                height: Some(if output.breakable.is_none() {
211                    10. + 29.
212                } else if output.first_height == 30.1 {
213                    1. + 29.
214                } else {
215                    2. + 29.
216                }),
217            });
218
219            if let Some(b) = output.breakable {
220                b.assert_break_count(if output.first_height == 30.1 { 5 } else { 4 })
221                    .assert_extra_location_min_height(None);
222            }
223        }
224    }
225}