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