1use crate::*;
2
3pub struct AlignLocationBottom<E: Element>(pub E);
4
5impl<E: Element> Element for AlignLocationBottom<E> {
6 fn first_location_usage(&self, ctx: FirstLocationUsageCtx) -> FirstLocationUsage {
7 let layout = self.layout(
8 ctx.text_pieces_cache,
9 ctx.width,
10 ctx.first_height,
11 Some(ctx.full_height),
12 0,
13 );
14
15 if layout.breaks > 0 {
16 FirstLocationUsage::WillSkip
17 } else if layout.size.height.is_some() {
18 FirstLocationUsage::WillUse
19 } else {
20 FirstLocationUsage::NoneHeight
21 }
22 }
23
24 fn measure(&self, mut ctx: MeasureCtx) -> ElementSize {
25 let layout = self.layout(
26 ctx.text_pieces_cache,
27 ctx.width,
28 ctx.first_height,
29 ctx.breakable.as_ref().map(|b| b.full_height),
30 0,
31 );
32
33 let height = if layout.breaks > 0 {
34 let breakable = ctx.breakable.as_mut().unwrap();
35
36 *breakable.break_count = layout.breaks;
37
38 Some(breakable.full_height)
39 } else {
40 layout.size.height.map(|_| ctx.first_height)
41 };
42
43 if let Some(breakable) = ctx.breakable {
44 *breakable.extra_location_min_height = Some(breakable.full_height);
45 }
46
47 ElementSize {
48 width: layout.size.width,
49 height,
50 }
51 }
52
53 fn draw(&self, ctx: DrawCtx) -> ElementSize {
54 let layout = self.layout(
55 ctx.text_pieces_cache,
56 ctx.width,
57 ctx.first_height,
58 ctx.breakable.as_ref().map(|b| b.full_height),
59 ctx.breakable
60 .as_ref()
61 .map(|b| b.preferred_height_break_count)
62 .unwrap_or(0),
63 );
64
65 let height_available;
66 let height;
67 let mut location;
68
69 if layout.breaks > 0 {
70 let breakable = ctx.breakable.unwrap();
71
72 location = (breakable.do_break)(ctx.pdf, layout.breaks - 1, None);
73 height_available = breakable.full_height;
74
75 height = Some(breakable.full_height);
76 } else {
77 location = ctx.location;
78 height_available = ctx.first_height;
79 height = layout.size.height.map(|_| ctx.first_height);
80 }
81
82 location.pos.1 -= layout.y_offset;
83
84 self.0.draw(DrawCtx {
85 pdf: ctx.pdf,
86 text_pieces_cache: ctx.text_pieces_cache,
87 location,
88 width: ctx.width,
89 first_height: height_available,
90 preferred_height: None,
91 breakable: None,
92 });
93
94 ElementSize {
95 width: layout.size.width,
96 height,
97 }
98 }
99}
100
101#[derive(Debug)]
102struct Layout {
103 breaks: u32,
104 y_offset: f32,
105 size: ElementSize,
106}
107
108impl<E: Element> AlignLocationBottom<E> {
109 fn layout(
110 &self,
111 text_pieces_cache: &TextPiecesCache,
112 width: WidthConstraint,
113 first_height: f32,
114 full_height: Option<f32>,
115 preferred_breaks: u32,
116 ) -> Layout {
117 let height_available = full_height.unwrap_or(first_height);
118
119 let size = self.0.measure(MeasureCtx {
120 text_pieces_cache,
121 width,
122 first_height: height_available,
123 breakable: None,
124 });
125
126 let breaks;
127 let location_height;
128
129 if let (Some(height), Some(full_height)) = (size.height, full_height) {
130 breaks = if preferred_breaks == 0 && height > first_height {
131 1
132 } else {
133 preferred_breaks
134 };
135
136 location_height = if breaks > 0 {
137 full_height
138 } else {
139 first_height
140 };
141 } else {
142 breaks = 0;
143 location_height = first_height;
144 };
145
146 let y_offset = if let Some(height) = size.height {
147 location_height - height
148 } else {
149 0.
150 };
151
152 Layout {
153 breaks,
154 y_offset,
155 size,
156 }
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163 use crate::{
164 elements::ref_element::RefElement,
165 test_utils::{record_passes::RecordPasses, *},
166 };
167 use insta::*;
168
169 #[test]
170 fn test_unbreakable() {
171 let output = test_element(
172 TestElementParams {
173 width: WidthConstraint {
174 max: 12.,
175 expand: true,
176 },
177 first_height: 21.,
178 breakable: None,
179 pos: (11., 29.0),
180 ..Default::default()
181 },
182 |assert, callback| {
183 let content = RecordPasses::new(FakeText {
184 lines: 3,
185 line_height: 5.,
186 width: 3.,
187 });
188
189 let element = AlignLocationBottom(RefElement(&content));
190
191 let ret = callback.call(element);
192
193 if assert {
194 assert_debug_snapshot!(content.into_passes());
195 }
196
197 ret
198 },
199 );
200
201 assert_debug_snapshot!(output);
202 }
203
204 #[test]
205 fn test_breakable() {
206 let output = test_element(
207 TestElementParams {
208 width: WidthConstraint {
209 max: 12.,
210 expand: true,
211 },
212 first_height: 21.,
213 breakable: Some(TestElementParamsBreakable {
214 full_height: 25.,
215 ..Default::default()
216 }),
217 pos: (11., 29.0),
218 ..Default::default()
219 },
220 |assert, callback| {
221 let content = RecordPasses::new(FakeText {
222 lines: 3,
223 line_height: 5.,
224 width: 3.,
225 });
226
227 let element = AlignLocationBottom(RefElement(&content));
228
229 let ret = callback.call(element);
230
231 if assert {
232 assert_debug_snapshot!(content.into_passes());
233 }
234
235 ret
236 },
237 );
238
239 assert_debug_snapshot!(output);
240 }
241
242 #[test]
243 fn test_pre_break() {
244 let output = test_element(
245 TestElementParams {
246 width: WidthConstraint {
247 max: 12.,
248 expand: true,
249 },
250 first_height: 21.,
251 breakable: Some(TestElementParamsBreakable {
252 full_height: 26.,
253 ..Default::default()
254 }),
255 pos: (11., 29.0),
256 ..Default::default()
257 },
258 |assert, callback| {
259 let content = RecordPasses::new(FakeText {
260 lines: 5,
261 line_height: 5.,
262 width: 3.,
263 });
264
265 let element = AlignLocationBottom(RefElement(&content));
266
267 let ret = callback.call(element);
268
269 if assert {
270 assert_debug_snapshot!(content.into_passes());
271 }
272
273 ret
274 },
275 );
276
277 assert_debug_snapshot!(output);
278 }
279
280 #[test]
281 fn test_preferred_height() {
282 let output = test_element(
283 TestElementParams {
284 width: WidthConstraint {
285 max: 12.,
286 expand: true,
287 },
288 first_height: 21.,
289 breakable: Some(TestElementParamsBreakable {
290 full_height: 26.,
291 preferred_height_break_count: 4,
292 }),
293 pos: (11., 29.0),
294 preferred_height: None,
295 ..Default::default()
296 },
297 |assert, callback| {
298 let content = RecordPasses::new(FakeText {
299 lines: 5,
300 line_height: 5.,
301 width: 3.,
302 });
303
304 let element = AlignLocationBottom(RefElement(&content));
305
306 let ret = callback.call(element);
307
308 if assert {
309 assert_debug_snapshot!(content.into_passes());
310 }
311
312 ret
313 },
314 );
315
316 assert_debug_snapshot!(output);
317 }
318}