1use fop_types::{Length, Point, Rect, Result, Size};
6
7pub struct BlockLayoutContext {
9 pub available_width: Length,
11
12 pub current_y: Length,
14
15 pub max_width: Length,
17}
18
19impl BlockLayoutContext {
20 pub fn new(available_width: Length) -> Self {
22 Self {
23 available_width,
24 current_y: Length::ZERO,
25 max_width: Length::ZERO,
26 }
27 }
28
29 pub fn allocate(&mut self, width: Length, height: Length) -> Rect {
31 let rect = Rect::from_point_size(
32 Point::new(Length::ZERO, self.current_y),
33 Size::new(width, height),
34 );
35
36 self.current_y += height;
37 if width > self.max_width {
38 self.max_width = width;
39 }
40
41 rect
42 }
43
44 pub fn allocate_with_spacing(
46 &mut self,
47 width: Length,
48 height: Length,
49 space_before: Length,
50 space_after: Length,
51 ) -> Rect {
52 self.current_y += space_before;
54
55 let rect = Rect::from_point_size(
56 Point::new(Length::ZERO, self.current_y),
57 Size::new(width, height),
58 );
59
60 self.current_y += height;
61 self.current_y += space_after;
62
63 if width > self.max_width {
64 self.max_width = width;
65 }
66
67 rect
68 }
69
70 pub fn add_space(&mut self, space: Length) {
72 self.current_y += space;
73 }
74
75 pub fn total_height(&self) -> Length {
77 self.current_y
78 }
79}
80
81pub fn stack_blocks(
83 blocks: Vec<(Length, Length)>, available_width: Length,
85) -> Result<Vec<Rect>> {
86 let mut context = BlockLayoutContext::new(available_width);
87 let mut rects = Vec::new();
88
89 for (width, height) in blocks {
90 let rect = context.allocate(width, height);
91 rects.push(rect);
92 }
93
94 Ok(rects)
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
104 fn test_block_context_new_initial_state() {
105 let ctx = BlockLayoutContext::new(Length::from_pt(200.0));
106 assert_eq!(ctx.available_width, Length::from_pt(200.0));
107 assert_eq!(ctx.current_y, Length::ZERO);
108 assert_eq!(ctx.max_width, Length::ZERO);
109 }
110
111 #[test]
112 fn test_block_context_zero_width() {
113 let ctx = BlockLayoutContext::new(Length::ZERO);
114 assert_eq!(ctx.available_width, Length::ZERO);
115 assert_eq!(ctx.current_y, Length::ZERO);
116 assert_eq!(ctx.max_width, Length::ZERO);
117 }
118
119 #[test]
122 fn test_allocate_first_block_starts_at_origin() {
123 let mut ctx = BlockLayoutContext::new(Length::from_pt(100.0));
124 let rect = ctx.allocate(Length::from_pt(80.0), Length::from_pt(25.0));
125 assert_eq!(rect.x, Length::ZERO);
126 assert_eq!(rect.y, Length::ZERO);
127 assert_eq!(rect.width, Length::from_pt(80.0));
128 assert_eq!(rect.height, Length::from_pt(25.0));
129 }
130
131 #[test]
132 fn test_allocate_advances_current_y() {
133 let mut ctx = BlockLayoutContext::new(Length::from_pt(100.0));
134 ctx.allocate(Length::from_pt(100.0), Length::from_pt(20.0));
135 assert_eq!(ctx.current_y, Length::from_pt(20.0));
136 }
137
138 #[test]
139 fn test_allocate_second_block_stacks_below_first() {
140 let mut ctx = BlockLayoutContext::new(Length::from_pt(100.0));
141 let _r1 = ctx.allocate(Length::from_pt(100.0), Length::from_pt(20.0));
142 let r2 = ctx.allocate(Length::from_pt(100.0), Length::from_pt(30.0));
143 assert_eq!(r2.y, Length::from_pt(20.0));
144 assert_eq!(r2.height, Length::from_pt(30.0));
145 }
146
147 #[test]
148 fn test_allocate_total_height_accumulates() {
149 let mut ctx = BlockLayoutContext::new(Length::from_pt(100.0));
150 ctx.allocate(Length::from_pt(100.0), Length::from_pt(10.0));
151 ctx.allocate(Length::from_pt(100.0), Length::from_pt(20.0));
152 ctx.allocate(Length::from_pt(100.0), Length::from_pt(30.0));
153 assert_eq!(ctx.total_height(), Length::from_pt(60.0));
154 }
155
156 #[test]
159 fn test_max_width_updated_on_wider_block() {
160 let mut ctx = BlockLayoutContext::new(Length::from_pt(200.0));
161 ctx.allocate(Length::from_pt(80.0), Length::from_pt(10.0));
162 assert_eq!(ctx.max_width, Length::from_pt(80.0));
163 ctx.allocate(Length::from_pt(120.0), Length::from_pt(10.0));
164 assert_eq!(ctx.max_width, Length::from_pt(120.0));
165 }
166
167 #[test]
168 fn test_max_width_not_reduced_by_narrower_block() {
169 let mut ctx = BlockLayoutContext::new(Length::from_pt(200.0));
170 ctx.allocate(Length::from_pt(120.0), Length::from_pt(10.0));
171 ctx.allocate(Length::from_pt(60.0), Length::from_pt(10.0));
172 assert_eq!(ctx.max_width, Length::from_pt(120.0));
173 }
174
175 #[test]
176 fn test_max_width_equal_block_does_not_change() {
177 let mut ctx = BlockLayoutContext::new(Length::from_pt(200.0));
178 ctx.allocate(Length::from_pt(100.0), Length::from_pt(10.0));
179 ctx.allocate(Length::from_pt(100.0), Length::from_pt(10.0));
180 assert_eq!(ctx.max_width, Length::from_pt(100.0));
181 }
182
183 #[test]
186 fn test_spacing_space_before_offsets_rect_y() {
187 let mut ctx = BlockLayoutContext::new(Length::from_pt(100.0));
188 let rect = ctx.allocate_with_spacing(
189 Length::from_pt(100.0),
190 Length::from_pt(20.0),
191 Length::from_pt(10.0), Length::ZERO,
193 );
194 assert_eq!(rect.y, Length::from_pt(10.0));
195 }
196
197 #[test]
198 fn test_spacing_space_after_advances_current_y() {
199 let mut ctx = BlockLayoutContext::new(Length::from_pt(100.0));
200 ctx.allocate_with_spacing(
201 Length::from_pt(100.0),
202 Length::from_pt(20.0),
203 Length::ZERO,
204 Length::from_pt(5.0), );
206 assert_eq!(ctx.current_y, Length::from_pt(25.0));
208 }
209
210 #[test]
211 fn test_spacing_combined_space_before_and_after() {
212 let mut ctx = BlockLayoutContext::new(Length::from_pt(100.0));
213 let rect = ctx.allocate_with_spacing(
214 Length::from_pt(100.0),
215 Length::from_pt(20.0),
216 Length::from_pt(10.0),
217 Length::from_pt(5.0),
218 );
219 assert_eq!(rect.y, Length::from_pt(10.0));
221 assert_eq!(rect.height, Length::from_pt(20.0));
222 assert_eq!(ctx.current_y, Length::from_pt(35.0));
224 assert_eq!(ctx.total_height(), Length::from_pt(35.0));
225 }
226
227 #[test]
228 fn test_spacing_second_block_stacks_correctly() {
229 let mut ctx = BlockLayoutContext::new(Length::from_pt(100.0));
230 ctx.allocate_with_spacing(
231 Length::from_pt(100.0),
232 Length::from_pt(20.0),
233 Length::from_pt(10.0),
234 Length::from_pt(5.0),
235 );
236 let rect2 = ctx.allocate_with_spacing(
238 Length::from_pt(100.0),
239 Length::from_pt(15.0),
240 Length::from_pt(8.0),
241 Length::from_pt(12.0),
242 );
243 assert_eq!(rect2.y, Length::from_pt(43.0));
245 assert_eq!(ctx.total_height(), Length::from_pt(70.0));
247 }
248
249 #[test]
250 fn test_spacing_zero_behaves_like_plain_allocate() {
251 let mut ctx_plain = BlockLayoutContext::new(Length::from_pt(100.0));
252 let mut ctx_spaced = BlockLayoutContext::new(Length::from_pt(100.0));
253
254 let r1 = ctx_plain.allocate(Length::from_pt(80.0), Length::from_pt(20.0));
255 let r2 = ctx_spaced.allocate_with_spacing(
256 Length::from_pt(80.0),
257 Length::from_pt(20.0),
258 Length::ZERO,
259 Length::ZERO,
260 );
261
262 assert_eq!(r1.x, r2.x);
263 assert_eq!(r1.y, r2.y);
264 assert_eq!(r1.width, r2.width);
265 assert_eq!(r1.height, r2.height);
266 assert_eq!(ctx_plain.total_height(), ctx_spaced.total_height());
267 }
268
269 #[test]
272 fn test_add_space_increases_current_y() {
273 let mut ctx = BlockLayoutContext::new(Length::from_pt(100.0));
274 ctx.add_space(Length::from_pt(15.0));
275 assert_eq!(ctx.current_y, Length::from_pt(15.0));
276 assert_eq!(ctx.total_height(), Length::from_pt(15.0));
277 }
278
279 #[test]
280 fn test_add_space_cumulative() {
281 let mut ctx = BlockLayoutContext::new(Length::from_pt(100.0));
282 ctx.add_space(Length::from_pt(10.0));
283 ctx.add_space(Length::from_pt(5.0));
284 assert_eq!(ctx.total_height(), Length::from_pt(15.0));
285 }
286
287 #[test]
288 fn test_add_space_after_block() {
289 let mut ctx = BlockLayoutContext::new(Length::from_pt(100.0));
290 ctx.allocate(Length::from_pt(100.0), Length::from_pt(20.0));
291 ctx.add_space(Length::from_pt(10.0));
292 assert_eq!(ctx.total_height(), Length::from_pt(30.0));
293 }
294
295 #[test]
298 fn test_overflow_detected_when_content_exceeds_page_height() {
299 let page_height = Length::from_pt(100.0);
300 let mut ctx = BlockLayoutContext::new(Length::from_pt(100.0));
301 ctx.allocate(Length::from_pt(100.0), Length::from_pt(60.0));
302 ctx.allocate(Length::from_pt(100.0), Length::from_pt(50.0));
303 assert!(ctx.total_height() > page_height);
305 }
306
307 #[test]
308 fn test_no_overflow_when_content_fits_exactly() {
309 let page_height = Length::from_pt(100.0);
310 let mut ctx = BlockLayoutContext::new(Length::from_pt(100.0));
311 ctx.allocate(Length::from_pt(100.0), Length::from_pt(40.0));
312 ctx.allocate(Length::from_pt(100.0), Length::from_pt(60.0));
313 assert!(ctx.total_height() <= page_height);
315 }
316
317 #[test]
320 fn test_stack_blocks_empty_input() {
321 let rects = stack_blocks(vec![], Length::from_pt(100.0)).expect("test: should succeed");
322 assert!(rects.is_empty());
323 }
324
325 #[test]
326 fn test_stack_blocks_single_block() {
327 let blocks = vec![(Length::from_pt(80.0), Length::from_pt(30.0))];
328 let rects = stack_blocks(blocks, Length::from_pt(100.0)).expect("test: should succeed");
329 assert_eq!(rects.len(), 1);
330 assert_eq!(rects[0].x, Length::ZERO);
331 assert_eq!(rects[0].y, Length::ZERO);
332 assert_eq!(rects[0].width, Length::from_pt(80.0));
333 assert_eq!(rects[0].height, Length::from_pt(30.0));
334 }
335
336 #[test]
337 fn test_stack_blocks_three_blocks() {
338 let blocks = vec![
339 (Length::from_pt(100.0), Length::from_pt(20.0)),
340 (Length::from_pt(100.0), Length::from_pt(30.0)),
341 (Length::from_pt(100.0), Length::from_pt(25.0)),
342 ];
343 let rects = stack_blocks(blocks, Length::from_pt(100.0)).expect("test: should succeed");
344 assert_eq!(rects.len(), 3);
345 assert_eq!(rects[0].y, Length::ZERO);
346 assert_eq!(rects[1].y, Length::from_pt(20.0));
347 assert_eq!(rects[2].y, Length::from_pt(50.0));
348 }
349
350 #[test]
351 fn test_stack_blocks_x_always_zero() {
352 let blocks = vec![
353 (Length::from_pt(50.0), Length::from_pt(10.0)),
354 (Length::from_pt(70.0), Length::from_pt(10.0)),
355 ];
356 let rects = stack_blocks(blocks, Length::from_pt(100.0)).expect("test: should succeed");
357 for rect in &rects {
358 assert_eq!(rect.x, Length::ZERO);
359 }
360 }
361
362 #[test]
365 fn test_padding_box_wider_than_content_box() {
366 let content_width = Length::from_pt(80.0);
368 let padding_left = Length::from_pt(10.0);
369 let padding_right = Length::from_pt(10.0);
370 let padding_box_width = content_width + padding_left + padding_right;
371 assert_eq!(padding_box_width, Length::from_pt(100.0));
372
373 let mut ctx = BlockLayoutContext::new(Length::from_pt(100.0));
374 let rect = ctx.allocate(padding_box_width, Length::from_pt(20.0));
375 assert_eq!(rect.width, Length::from_pt(100.0));
376 }
377
378 #[test]
379 fn test_block_height_from_border_and_content() {
380 let border_top = Length::from_pt(2.0);
382 let border_bottom = Length::from_pt(2.0);
383 let content_height = Length::from_pt(20.0);
384 let margin_top = Length::from_pt(5.0);
385 let margin_bottom = Length::from_pt(5.0);
386
387 let mut ctx = BlockLayoutContext::new(Length::from_pt(100.0));
388 let rect = ctx.allocate_with_spacing(
389 Length::from_pt(100.0),
390 border_top + content_height + border_bottom,
391 margin_top,
392 margin_bottom,
393 );
394 assert_eq!(rect.y, Length::from_pt(5.0));
396 assert_eq!(rect.height, Length::from_pt(24.0));
398 assert_eq!(ctx.total_height(), Length::from_pt(34.0));
400 }
401
402 #[test]
405 fn test_background_area_bounds_match_allocated_rect() {
406 let padding_w = Length::from_pt(100.0);
408 let padding_h = Length::from_pt(30.0);
409 let mut ctx = BlockLayoutContext::new(Length::from_pt(120.0));
410 let rect = ctx.allocate(padding_w, padding_h);
411 assert_eq!(rect.width, padding_w);
413 assert_eq!(rect.height, padding_h);
414 assert_eq!(rect.x, Length::ZERO);
415 assert_eq!(rect.y, Length::ZERO);
416 }
417
418 #[test]
421 fn test_keep_constraint_keep_together_active() {
422 use crate::layout::{Keep, KeepConstraint};
423 let mut kc = KeepConstraint::new();
424 kc.keep_together = Keep::Always;
425 assert!(kc.must_keep_together());
426 assert!(kc.has_constraint());
427 }
428
429 #[test]
430 fn test_keep_constraint_keep_with_next_active() {
431 use crate::layout::{Keep, KeepConstraint};
432 let mut kc = KeepConstraint::new();
433 kc.keep_with_next = Keep::Always;
434 assert!(kc.must_keep_with_next());
435 assert!(kc.has_constraint());
436 }
437
438 #[test]
439 fn test_keep_constraint_default_all_inactive() {
440 use crate::layout::KeepConstraint;
441 let kc = KeepConstraint::new();
442 assert!(!kc.has_constraint());
443 assert!(!kc.must_keep_together());
444 assert!(!kc.must_keep_with_next());
445 assert!(!kc.must_keep_with_previous());
446 }
447
448 #[test]
449 fn test_keep_constraint_keep_with_previous_active() {
450 use crate::layout::{Keep, KeepConstraint};
451 let mut kc = KeepConstraint::new();
452 kc.keep_with_previous = Keep::Always;
453 assert!(kc.must_keep_with_previous());
454 assert!(kc.has_constraint());
455 }
456
457 #[test]
458 fn test_keep_integer_strength_ordering() {
459 use crate::layout::Keep;
460 let weak = Keep::Integer(3);
461 let strong = Keep::Integer(7);
462 assert!(weak.is_active());
463 assert!(strong.is_active());
464 assert!(strong.strength() > weak.strength());
465 }
466
467 #[test]
470 fn test_break_value_page_is_page_break() {
471 use crate::layout::BreakValue;
472 assert!(BreakValue::Page.forces_page_break());
473 assert!(BreakValue::Page.forces_break());
474 }
475
476 #[test]
477 fn test_break_value_auto_is_not_active() {
478 use crate::layout::BreakValue;
479 assert!(!BreakValue::Auto.forces_break());
480 assert!(!BreakValue::Auto.forces_page_break());
481 }
482
483 #[test]
484 fn test_break_value_column_active_not_page() {
485 use crate::layout::BreakValue;
486 assert!(BreakValue::Column.forces_break());
487 assert!(!BreakValue::Column.forces_page_break());
488 }
489
490 #[test]
491 fn test_break_value_even_page_is_page_break() {
492 use crate::layout::BreakValue;
493 assert!(BreakValue::EvenPage.forces_page_break());
494 assert!(BreakValue::EvenPage.requires_even_page());
495 }
496
497 #[test]
498 fn test_break_value_odd_page_is_page_break() {
499 use crate::layout::BreakValue;
500 assert!(BreakValue::OddPage.forces_page_break());
501 assert!(BreakValue::OddPage.requires_odd_page());
502 }
503}