1use crate::*;
2
3#[derive(Copy, Clone, Serialize, Deserialize)]
4pub enum HorizontalAlignment {
5 Left,
6 Center,
7 Right,
8}
9
10pub struct HAlign<E: Element>(pub HorizontalAlignment, pub E);
11
12impl<E: Element> Element for HAlign<E> {
13 fn first_location_usage(&self, ctx: FirstLocationUsageCtx) -> FirstLocationUsage {
14 self.1.first_location_usage(FirstLocationUsageCtx {
15 width: WidthConstraint {
16 expand: false,
17 ..ctx.width
18 },
19 ..ctx
20 })
21 }
22
23 fn measure(&self, ctx: MeasureCtx) -> ElementSize {
24 let width = ctx.width;
25
26 let size = self.1.measure(MeasureCtx {
27 width: WidthConstraint {
28 expand: false,
29 max: width.max,
30 },
31 ..ctx
32 });
33
34 ElementSize {
35 width: size.width.map(|w| width.constrain(w)),
36 height: size.height,
37 }
38 }
39
40 fn draw(&self, mut ctx: DrawCtx) -> ElementSize {
41 let width = ctx.width;
42
43 let size = if width.expand {
44 let mut break_count = 0;
45 let mut extra_location_min_height = None;
46
47 let element_size = self.1.measure(MeasureCtx {
48 text_pieces_cache: ctx.text_pieces_cache,
49 width: WidthConstraint {
50 max: width.max,
51 expand: false,
52 },
53 first_height: ctx.first_height,
54 breakable: ctx.breakable.as_ref().map(|b| BreakableMeasure {
55 full_height: b.full_height,
56 break_count: &mut break_count,
57 extra_location_min_height: &mut extra_location_min_height,
58 }),
59 });
60
61 let x_offset;
62 let element_width;
63
64 if let Some(w) = element_size.width {
65 x_offset = match self.0 {
66 HorizontalAlignment::Left => 0.,
67 HorizontalAlignment::Center => (width.max - w) / 2.0,
68 HorizontalAlignment::Right => width.max - w,
69 };
70 element_width = w;
71 } else {
72 x_offset = 0.;
73 element_width = ctx.width.max;
74 }
75
76 ctx.location.pos.0 += x_offset;
77
78 let width_constraint = WidthConstraint {
79 max: element_width,
80 expand: true,
81 };
82
83 if let Some(breakable) = ctx.breakable {
84 self.1.draw(DrawCtx {
85 width: width_constraint,
86 breakable: Some(BreakableDraw {
87 full_height: breakable.full_height,
88 preferred_height_break_count: breakable.preferred_height_break_count,
89 do_break: &mut |pdf, location_id, height| {
90 let mut location = (breakable.do_break)(pdf, location_id, height);
91
92 location.pos.0 += x_offset;
93
94 location
95 },
96 }),
97 ..ctx
98 })
99 } else {
100 self.1.draw(DrawCtx {
101 width: width_constraint,
102 breakable: None,
103 ..ctx
104 })
105 }
106 } else {
107 self.1.draw(ctx)
108 };
109
110 ElementSize {
111 width: size.width.map(|w| width.constrain(w)),
112 height: size.height,
113 }
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use crate::{
120 elements::none::NoneElement,
121 test_utils::{BuildElement, ElementProxy, ElementTestParams, FakeImage, FakeText},
122 };
123
124 use super::*;
125 use HorizontalAlignment::*;
126
127 #[test]
128 fn test_h_align_none() {
129 for output in ElementTestParams::default().run(&HAlign(Center, NoneElement)) {
130 output.assert_size(ElementSize {
131 width: None,
132 height: None,
133 });
134
135 if let Some(b) = output.breakable {
136 b.assert_break_count(0)
137 .assert_extra_location_min_height(None);
138 }
139 }
140 }
141
142 #[test]
143 fn test_h_align_fake_text() {
144 let element = BuildElement(|build_ctx, callback| {
145 let content = FakeText {
146 width: 5.,
147 line_height: 1.,
148 lines: 10,
149 };
150
151 let proxy = ElementProxy {
152 before_draw: &|ctx: &mut DrawCtx| {
153 assert_eq!(
154 ctx.width,
155 WidthConstraint {
156 max: if build_ctx.width.expand { 5. } else { 6. },
157 expand: build_ctx.width.expand,
158 }
159 );
160 assert_eq!(
161 ctx.location.pos.0,
162 12. + if ctx.width.expand { 0.5 } else { 0. }
163 );
164 },
165 after_break: &|_location_idx: u32,
166 location: &Location,
167 width: WidthConstraint,
168 _first_height| {
169 assert_eq!(location.pos.0, 12. + if width.expand { 0.5 } else { 0. });
170 },
171 ..ElementProxy::new(content)
172 };
173 callback.call(HAlign(Center, proxy))
174 });
175
176 for output in (ElementTestParams {
177 first_height: 1.,
178 full_height: 2.,
179 width: 6.,
180 ..Default::default()
181 })
182 .run(&element)
183 {
184 output.assert_size(ElementSize {
185 width: Some(output.width.constrain(5.)),
186 height: Some(if output.breakable.is_none() {
187 10.
188 } else if output.first_height == 1. {
189 1.
190 } else {
191 2.
192 }),
193 });
194
195 if let Some(b) = output.breakable {
196 b.assert_break_count(if output.first_height == 1. { 5 } else { 4 })
197 .assert_extra_location_min_height(None);
198 }
199 }
200 }
201
202 #[test]
203 fn test_h_align_too_wide() {
204 let element = BuildElement(|build_ctx, callback| {
208 let content = FakeText {
209 width: 5.,
210 line_height: 1.,
211 lines: 2,
212 };
213
214 let proxy = ElementProxy {
215 before_draw: &|ctx: &mut DrawCtx| {
216 assert_eq!(
217 ctx.width,
218 WidthConstraint {
219 max: 4.,
220 expand: build_ctx.width.expand,
221 }
222 );
223 assert_eq!(ctx.location.pos.0, 12.);
224 },
225 after_break: &|_location_idx: u32,
226 location: &Location,
227 _width: WidthConstraint,
228 _first_height| {
229 assert_eq!(location.pos.0, 12.);
230 },
231 ..ElementProxy::new(content)
232 };
233 callback.call(HAlign(Center, proxy))
234 });
235
236 for output in (ElementTestParams {
237 first_height: 1.,
238 full_height: 2.,
239 width: 4.,
240 ..Default::default()
241 })
242 .run(&element)
243 {
244 output.assert_size(ElementSize {
245 width: Some(output.width.constrain(5.)),
246 height: Some(if output.breakable.is_none() {
247 2.
248 } else if output.first_height == 1. {
249 1.
250 } else {
251 2.
252 }),
253 });
254
255 if let Some(b) = output.breakable {
256 b.assert_break_count(if output.first_height == 1. { 1 } else { 0 })
257 .assert_extra_location_min_height(None);
258 }
259 }
260 }
261
262 #[test]
263 fn test_overdraw() {
264 struct Overdraw;
265
266 impl Element for Overdraw {
267 fn measure(&self, _: MeasureCtx) -> ElementSize {
268 ElementSize {
269 width: Some(5.),
270 height: None,
271 }
272 }
273
274 fn draw(&self, _: DrawCtx) -> ElementSize {
275 ElementSize {
276 width: Some(5.),
277 height: None,
278 }
279 }
280 }
281
282 for output in (ElementTestParams {
283 first_height: 1.,
284 full_height: 2.,
285 width: 4.,
286 ..Default::default()
287 })
288 .run(&HAlign(Center, Overdraw))
289 {
290 output.assert_size(ElementSize {
291 width: Some(4.),
292 height: None,
293 });
294
295 if let Some(b) = output.breakable {
296 b.assert_break_count(0)
297 .assert_extra_location_min_height(None);
298 }
299 }
300 }
301
302 #[test]
303 fn test_h_align_fake_image() {
304 let element = BuildElement(|build_ctx, callback| {
305 let content = FakeImage {
306 width: 5.,
307 height: 2.,
308 };
309
310 let proxy = ElementProxy {
311 before_draw: &|ctx: &mut DrawCtx| {
312 assert_eq!(
313 ctx.width,
314 WidthConstraint {
315 max: if build_ctx.width.expand { 5. } else { 10. },
316 expand: build_ctx.width.expand,
317 }
318 );
319 assert_eq!(
320 ctx.location.pos.0,
321 12. + if ctx.width.expand { 5. } else { 0. }
322 );
323 },
324 after_break: &|_location_idx: u32,
325 location: &Location,
326 width: WidthConstraint,
327 _first_height| {
328 assert_eq!(location.pos.0, 12. + if width.expand { 5. } else { 0. });
329 },
330 ..ElementProxy::new(content)
331 };
332 callback.call(HAlign(Right, proxy))
333 });
334
335 for output in (ElementTestParams {
336 first_height: 1.,
337 full_height: 4.,
338 width: 10.,
339 ..Default::default()
340 })
341 .run(&element)
342 {
343 output.assert_size(ElementSize {
344 width: Some(output.width.constrain(5.)),
345 height: Some(2.),
346 });
347
348 if let Some(b) = output.breakable {
349 b.assert_break_count(if output.first_height == 1. { 1 } else { 0 })
350 .assert_extra_location_min_height(None);
351 }
352 }
353 }
354
355 #[test]
356 fn test_h_align_fake_image_too_wide() {
357 let element = BuildElement(|build_ctx, callback| {
358 let content = FakeImage {
359 width: 10.,
360 height: 4.,
361 };
362
363 let proxy = ElementProxy {
364 before_draw: &|ctx: &mut DrawCtx| {
365 assert_eq!(
366 ctx.width,
367 WidthConstraint {
368 max: 5.,
369 expand: build_ctx.width.expand,
370 }
371 );
372 assert_eq!(ctx.location.pos.0, 12.);
373 },
374 after_break: &|_location_idx: u32,
375 location: &Location,
376 _width: WidthConstraint,
377 _first_height| {
378 assert_eq!(location.pos.0, 12.);
379 },
380 ..ElementProxy::new(content)
381 };
382 callback.call(HAlign(Right, proxy))
383 });
384
385 for output in (ElementTestParams {
386 first_height: 1.,
387 full_height: 4.,
388 width: 5.,
389 ..Default::default()
390 })
391 .run(&element)
392 {
393 output.assert_size(ElementSize {
394 width: Some(5.),
395 height: Some(2.),
396 });
397
398 if let Some(b) = output.breakable {
399 b.assert_break_count(if output.first_height == 1. { 1 } else { 0 })
400 .assert_extra_location_min_height(None);
401 }
402 }
403 }
404
405 #[test]
406 fn test_h_align_cancel_out() {
407 let element = BuildElement(|build_ctx, callback| {
410 let content = FakeImage {
411 width: 5.,
412 height: 2.,
413 };
414
415 let proxy = ElementProxy {
416 before_draw: &|ctx: &mut DrawCtx| {
417 assert_eq!(
418 ctx.width,
419 WidthConstraint {
420 max: if build_ctx.width.expand { 5. } else { 10. },
421 expand: build_ctx.width.expand,
422 }
423 );
424 assert_eq!(ctx.location.pos.0, 12.);
425 },
426 after_break: &|_location_idx: u32,
427 location: &Location,
428 _width: WidthConstraint,
429 _first_height| {
430 assert_eq!(location.pos.0, 12.);
431 },
432 ..ElementProxy::new(content)
433 };
434 callback.call(HAlign(Left, HAlign(Right, proxy)))
435 });
436
437 for output in (ElementTestParams {
438 first_height: 1.,
439 full_height: 4.,
440 width: 10.,
441 ..Default::default()
442 })
443 .run(&element)
444 {
445 output.assert_size(ElementSize {
446 width: Some(output.width.constrain(5.)),
447 height: Some(2.),
448 });
449
450 if let Some(b) = output.breakable {
451 b.assert_break_count(if output.first_height == 1. { 1 } else { 0 })
452 .assert_extra_location_min_height(None);
453 }
454 }
455 }
456}