1use crate::*;
2
3pub struct Page<P: Element, D: Fn(&mut DecorationElements, usize, usize)> {
4 pub primary: P,
5 pub border_left: f32,
6 pub border_right: f32,
7 pub border_top: f32,
8 pub border_bottom: f32,
9 pub decoration_elements: D,
10}
11
12impl<P: Element, D: Fn(&mut DecorationElements, usize, usize)> Element for Page<P, D> {
13 fn first_location_usage(&self, ctx: FirstLocationUsageCtx) -> FirstLocationUsage {
14 if ctx.first_height < ctx.full_height {
15 FirstLocationUsage::WillSkip
16 } else {
17 FirstLocationUsage::WillUse
18 }
19 }
20
21 fn measure(&self, ctx: MeasureCtx) -> ElementSize {
22 if let Some(breakable) = ctx.breakable {
23 let mut extra_location_min_height = None;
24 let mut break_count = 0;
25
26 let primary_height = self.height(breakable.full_height);
27
28 self.primary.measure(MeasureCtx {
29 text_pieces_cache: ctx.text_pieces_cache,
30 width: WidthConstraint {
31 max: self.width(ctx.width),
32 expand: true,
33 },
34 first_height: primary_height,
35 breakable: Some(BreakableMeasure {
36 full_height: primary_height,
37 break_count: &mut break_count,
38 extra_location_min_height: &mut extra_location_min_height,
39 }),
40 });
41
42 if ctx.first_height < breakable.full_height {
43 break_count += 1;
44 }
45
46 *breakable.break_count = break_count;
47
48 ElementSize {
49 width: Some(ctx.width.max),
50 height: Some(breakable.full_height),
51 }
52 } else {
53 ElementSize {
54 width: Some(ctx.width.max),
55 height: Some(ctx.first_height),
56 }
57 }
58 }
59
60 fn draw(&self, ctx: DrawCtx) -> ElementSize {
61 let primary_width = WidthConstraint {
62 max: self.width(ctx.width),
63 expand: true,
64 };
65
66 let mut breakable = ctx.breakable;
67
68 let height = breakable
69 .as_ref()
70 .map(|b| b.full_height)
71 .unwrap_or(ctx.first_height);
72
73 let primary_height = self.height(height);
74
75 let location;
76 let location_offset;
77
78 match breakable {
79 Some(ref mut breakable) if ctx.first_height < breakable.full_height => {
80 location = (breakable.do_break)(ctx.pdf, 0, None);
81 location_offset = 1;
82 }
83 _ => {
84 location = ctx.location;
85 location_offset = 0;
86 }
87 }
88
89 let mut break_count = 0;
90
91 let primary_location = location.next_layer(ctx.pdf);
94
95 self.primary.draw(DrawCtx {
96 pdf: ctx.pdf,
97 text_pieces_cache: ctx.text_pieces_cache,
98 location: Location {
99 pos: (
100 location.pos.0 + self.border_left,
101 location.pos.1 - self.border_top,
102 ),
103 ..primary_location
104 },
105 width: primary_width,
106 first_height: primary_height,
107 preferred_height: None,
108 breakable: breakable
109 .as_mut()
110 .map(|breakable| {
111 |pdf: &mut Pdf, location_idx: u32, _| {
112 break_count = break_count.max(location_idx + 1);
113 let mut location = (breakable.do_break)(
114 pdf,
115 location_idx + location_offset,
116 Some(breakable.full_height),
117 );
118
119 location = location.next_layer(pdf);
120 location.pos.0 += self.border_left;
121 location.pos.1 -= self.border_top;
122
123 location
124 }
125 })
126 .as_mut()
127 .map(|get_location| BreakableDraw {
128 full_height: primary_height,
129 preferred_height_break_count: 0,
130 do_break: get_location,
131 }),
132 });
133
134 if let Some(breakable) = breakable {
135 for i in 0..=break_count {
136 let location = if i == 0 {
137 location.clone()
138 } else {
139 (breakable.do_break)(
140 ctx.pdf,
141 i + location_offset - 1,
142 Some(breakable.full_height),
143 )
144 };
145
146 (self.decoration_elements)(
147 &mut DecorationElements {
148 pdf: ctx.pdf,
149 text_pieces_cache: ctx.text_pieces_cache,
150 location,
151 width: ctx.width.max,
152 height,
153 },
154 i as usize,
155 (break_count + 1) as usize,
156 );
157 }
158 } else {
159 (self.decoration_elements)(
160 &mut DecorationElements {
161 pdf: ctx.pdf,
162 text_pieces_cache: ctx.text_pieces_cache,
163 location,
164 width: ctx.width.max,
165 height,
166 },
167 0,
168 1,
169 );
170 }
171
172 ElementSize {
173 width: Some(ctx.width.max),
174 height: Some(height),
175 }
176 }
177}
178
179impl<P: Element, D: Fn(&mut DecorationElements, usize, usize)> Page<P, D> {
180 fn width(&self, width: WidthConstraint) -> f32 {
181 width.max - self.border_left - self.border_right
182 }
183
184 fn height(&self, full_height: f32) -> f32 {
185 full_height - self.border_top - self.border_bottom
186 }
187}
188
189pub struct DecorationElements<'a> {
190 pdf: &'a mut Pdf,
191 text_pieces_cache: &'a TextPiecesCache,
192 location: Location,
193 width: f32,
194 height: f32,
195}
196
197#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
198pub enum X {
199 Left(f32),
200 Right(f32),
201}
202
203#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
204pub enum Y {
205 Top(f32),
206 Bottom(f32),
207}
208
209impl<'a> DecorationElements<'a> {
210 pub fn add(&mut self, element: &impl Element, pos: (X, Y), width: Option<f32>) {
211 element.draw(DrawCtx {
212 pdf: self.pdf,
213 text_pieces_cache: self.text_pieces_cache,
214 location: Location {
215 pos: (
216 match pos.0 {
217 X::Left(left) => self.location.pos.0 + left,
218 X::Right(right) => self.location.pos.0 + self.width - right,
219 },
220 match pos.1 {
221 Y::Top(top) => self.location.pos.1 - top,
222 Y::Bottom(bottom) => self.location.pos.1 - self.height + bottom,
223 },
224 ),
225 ..self.location
226 },
227 width: WidthConstraint {
228 max: width.unwrap_or_else(|| match pos.0 {
229 X::Left(left) => self.width - left,
230 X::Right(right) => right,
231 }),
232 expand: width.is_some(),
233 },
234 first_height: match pos.1 {
235 Y::Top(top) => self.height - top,
236 Y::Bottom(bottom) => bottom,
237 },
238 preferred_height: None,
239 breakable: None,
240 });
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use insta::assert_debug_snapshot;
247
248 use super::*;
249 use crate::{
250 elements::ref_element::RefElement,
251 test_utils::{record_passes::RecordPasses, *},
252 };
253 use X::*;
254 use Y::*;
255
256 #[test]
257 fn test_unbreakable() {
258 let output = test_element(
259 TestElementParams {
260 width: WidthConstraint {
261 max: 10.,
262 expand: false,
263 },
264 first_height: 20.,
265 breakable: None,
266 pos: (10., 30.0),
267 ..Default::default()
268 },
269 |assert, callback| {
270 let primary = RecordPasses::new(FakeText {
271 lines: 1,
272 line_height: 5.,
273 width: 3.,
274 });
275
276 let top_left = RecordPasses::new(FakeText {
277 lines: 1,
278 line_height: 5.,
279 width: 6.,
280 });
281
282 let bottom_right = RecordPasses::new(FakeText {
283 lines: 1,
284 line_height: 4.,
285 width: 3.,
286 });
287
288 let element = Page {
289 primary: RefElement(&primary),
290 border_left: 2.,
291 border_right: 3.,
292 border_top: 4.,
293 border_bottom: 5.,
294 decoration_elements: |content: &mut DecorationElements, _, _| {
295 content.add(&top_left, (Left(1.), Top(2.)), None);
296 content.add(&bottom_right, (Right(2.), Bottom(5.)), Some(4.));
297 },
298 };
299
300 let ret = callback.call(element);
301
302 if assert {
303 assert_debug_snapshot!((
304 primary.into_passes(),
305 top_left.into_passes(),
306 bottom_right.into_passes()
307 ));
308 }
309
310 ret
311 },
312 );
313
314 assert_debug_snapshot!(output);
315 }
316
317 #[test]
318 fn test_breakable() {
319 let output = test_element(
320 TestElementParams {
321 width: WidthConstraint {
322 max: 10.,
323 expand: false,
324 },
325 first_height: 19.,
326 breakable: Some(TestElementParamsBreakable {
327 preferred_height_break_count: 5,
328 full_height: 20.,
329 }),
330 pos: (10., 30.0),
331 ..Default::default()
332 },
333 |assert, callback| {
334 let primary = RecordPasses::new(FakeText {
335 lines: 3,
336 line_height: 5.,
337 width: 3.,
338 });
339
340 let top_right = RecordPasses::new(FakeText {
341 lines: 1,
342 line_height: 5.,
343 width: 6.,
344 });
345
346 let bottom_left = RecordPasses::new(FakeText {
347 lines: 1,
348 line_height: 4.,
349 width: 3.,
350 });
351
352 let element = Page {
353 primary: RefElement(&primary),
354 border_left: 2.,
355 border_right: 3.,
356 border_top: 4.,
357 border_bottom: 5.,
358 decoration_elements: |content: &mut DecorationElements, _, _| {
359 content.add(&top_right, (Right(2.5), Top(2.)), None);
360 content.add(&bottom_left, (Left(2.), Bottom(5.)), Some(4.));
361 },
362 };
363
364 let ret = callback.call(element);
365
366 if assert {
367 assert_debug_snapshot!((
368 primary.into_passes(),
369 top_right.into_passes(),
370 bottom_left.into_passes()
371 ));
372 }
373
374 ret
375 },
376 );
377
378 assert_debug_snapshot!(output);
379 }
380}