Skip to main content

fop_layout/layout/table/
mod.rs

1//! Table layout algorithm
2//!
3//! Implements fixed and auto table layout modes for positioning cells in a grid.
4
5pub mod border;
6pub mod column;
7pub mod grid;
8pub mod types;
9
10pub use types::{
11    BorderCollapse, CellCollapsedBorders, CollapsedBorder, ColumnInfo, ColumnWidth, GridCell,
12    TableLayout, TableLayoutMode,
13};
14
15#[cfg(test)]
16mod tests {
17    use super::*;
18    use crate::area::{AreaId, AreaTree};
19    use fop_types::Length;
20
21    #[test]
22    fn test_table_layout_creation() {
23        let layout = TableLayout::new(Length::from_pt(500.0));
24        assert_eq!(layout.available_width, Length::from_pt(500.0));
25    }
26
27    #[test]
28    fn test_fixed_width_single_column() {
29        let layout = TableLayout::new(Length::from_pt(500.0));
30        let columns = vec![ColumnWidth::Fixed(Length::from_pt(200.0))];
31
32        let widths = layout.compute_fixed_widths(&columns);
33
34        assert_eq!(widths.len(), 1);
35        assert_eq!(widths[0], Length::from_pt(200.0));
36    }
37
38    #[test]
39    fn test_fixed_width_proportional() {
40        let layout = TableLayout::new(Length::from_pt(500.0));
41        let columns = vec![
42            ColumnWidth::Proportional(1.0),
43            ColumnWidth::Proportional(2.0),
44        ];
45
46        let widths = layout.compute_fixed_widths(&columns);
47
48        assert_eq!(widths.len(), 2);
49        // Total available after spacing: 500 - 3*2 = 494
50        // 1:2 ratio means 164.67 and 329.33 approximately
51        assert!(widths[0].to_pt() < widths[1].to_pt());
52    }
53
54    #[test]
55    fn test_fixed_width_mixed() {
56        let layout = TableLayout::new(Length::from_pt(500.0));
57        let columns = vec![
58            ColumnWidth::Fixed(Length::from_pt(100.0)),
59            ColumnWidth::Proportional(1.0),
60            ColumnWidth::Proportional(1.0),
61        ];
62
63        let widths = layout.compute_fixed_widths(&columns);
64
65        assert_eq!(widths.len(), 3);
66        assert_eq!(widths[0], Length::from_pt(100.0));
67        // Remaining 494 - 100 = 394 split 1:1
68        assert_eq!(widths[1], widths[2]);
69    }
70
71    #[test]
72    fn test_create_grid() {
73        let layout = TableLayout::new(Length::from_pt(500.0));
74        let grid = layout.create_grid(3, 4);
75
76        assert_eq!(grid.len(), 3);
77        assert_eq!(grid[0].len(), 4);
78        assert!(grid[0][0].is_none());
79    }
80
81    #[test]
82    fn test_place_cell() {
83        let layout = TableLayout::new(Length::from_pt(500.0));
84        let mut grid = layout.create_grid(3, 3);
85
86        let cell = GridCell {
87            row: 0,
88            col: 0,
89            rowspan: 1,
90            colspan: 1,
91            content_id: None,
92        };
93
94        layout
95            .place_cell(&mut grid, cell)
96            .expect("test: should succeed");
97        assert!(grid[0][0].is_some());
98    }
99
100    #[test]
101    fn test_place_cell_with_colspan() {
102        let layout = TableLayout::new(Length::from_pt(500.0));
103        let mut grid = layout.create_grid(3, 3);
104
105        let cell = GridCell {
106            row: 0,
107            col: 0,
108            rowspan: 1,
109            colspan: 2,
110            content_id: None,
111        };
112
113        layout
114            .place_cell(&mut grid, cell)
115            .expect("test: should succeed");
116        assert!(grid[0][0].is_some());
117        assert!(grid[0][1].is_some()); // Spanned
118        assert!(grid[0][2].is_none()); // Not spanned
119    }
120
121    #[test]
122    fn test_auto_width_with_column_info() {
123        let layout = TableLayout::new(Length::from_pt(500.0));
124        let column_info = vec![
125            ColumnInfo {
126                width_spec: ColumnWidth::Auto,
127                computed_width: Length::ZERO,
128                min_width: Length::from_pt(50.0),
129                max_width: Length::from_pt(150.0),
130            },
131            ColumnInfo {
132                width_spec: ColumnWidth::Fixed(Length::from_pt(200.0)),
133                computed_width: Length::ZERO,
134                min_width: Length::from_pt(200.0),
135                max_width: Length::from_pt(200.0),
136            },
137        ];
138
139        let widths = layout.compute_auto_widths(&column_info);
140
141        assert_eq!(widths.len(), 2);
142        assert_eq!(widths[1], Length::from_pt(200.0)); // Fixed
143        assert!(widths[0] > Length::ZERO); // Auto got some width
144    }
145
146    #[test]
147    fn test_table_layout_mode_default() {
148        let layout = TableLayout::new(Length::from_pt(500.0));
149        assert_eq!(layout.layout_mode(), TableLayoutMode::Fixed);
150    }
151
152    #[test]
153    fn test_table_layout_mode_auto() {
154        let layout =
155            TableLayout::new(Length::from_pt(500.0)).with_layout_mode(TableLayoutMode::Auto);
156        assert_eq!(layout.layout_mode(), TableLayoutMode::Auto);
157    }
158
159    #[test]
160    fn test_column_info_new() {
161        let info = ColumnInfo::new(ColumnWidth::Auto);
162        assert!(matches!(info.width_spec, ColumnWidth::Auto));
163        assert_eq!(info.min_width, Length::ZERO);
164        assert_eq!(info.max_width, Length::ZERO);
165    }
166
167    #[test]
168    fn test_column_info_with_widths() {
169        let info = ColumnInfo::with_widths(
170            ColumnWidth::Auto,
171            Length::from_pt(50.0),
172            Length::from_pt(200.0),
173        );
174        assert_eq!(info.min_width, Length::from_pt(50.0));
175        assert_eq!(info.max_width, Length::from_pt(200.0));
176    }
177
178    #[test]
179    fn test_auto_width_plenty_of_space() {
180        let layout = TableLayout::new(Length::from_pt(1000.0));
181        let column_info = vec![
182            ColumnInfo::with_widths(
183                ColumnWidth::Auto,
184                Length::from_pt(50.0),
185                Length::from_pt(100.0),
186            ),
187            ColumnInfo::with_widths(
188                ColumnWidth::Auto,
189                Length::from_pt(60.0),
190                Length::from_pt(120.0),
191            ),
192        ];
193
194        let widths = layout.compute_auto_widths(&column_info);
195
196        // With plenty of space, should use max widths
197        assert_eq!(widths[0], Length::from_pt(100.0));
198        assert_eq!(widths[1], Length::from_pt(120.0));
199    }
200
201    #[test]
202    fn test_auto_width_tight_space() {
203        let layout = TableLayout::new(Length::from_pt(200.0));
204        let column_info = vec![
205            ColumnInfo::with_widths(
206                ColumnWidth::Auto,
207                Length::from_pt(50.0),
208                Length::from_pt(100.0),
209            ),
210            ColumnInfo::with_widths(
211                ColumnWidth::Auto,
212                Length::from_pt(60.0),
213                Length::from_pt(120.0),
214            ),
215        ];
216
217        let widths = layout.compute_auto_widths(&column_info);
218
219        // With tight space, should be between min and max
220        assert!(widths[0] >= Length::from_pt(50.0));
221        assert!(widths[0] <= Length::from_pt(100.0));
222        assert!(widths[1] >= Length::from_pt(60.0));
223        assert!(widths[1] <= Length::from_pt(120.0));
224    }
225
226    #[test]
227    fn test_auto_width_with_fixed_columns() {
228        let layout = TableLayout::new(Length::from_pt(500.0));
229        let column_info = vec![
230            ColumnInfo::with_widths(
231                ColumnWidth::Fixed(Length::from_pt(150.0)),
232                Length::ZERO,
233                Length::ZERO,
234            ),
235            ColumnInfo::with_widths(
236                ColumnWidth::Auto,
237                Length::from_pt(50.0),
238                Length::from_pt(200.0),
239            ),
240            ColumnInfo::with_widths(
241                ColumnWidth::Auto,
242                Length::from_pt(50.0),
243                Length::from_pt(200.0),
244            ),
245        ];
246
247        let widths = layout.compute_auto_widths(&column_info);
248
249        // Fixed column should keep its width
250        assert_eq!(widths[0], Length::from_pt(150.0));
251        // Auto columns share remaining space
252        assert!(widths[1] > Length::ZERO);
253        assert!(widths[2] > Length::ZERO);
254    }
255
256    #[test]
257    fn test_measure_column_widths() {
258        let layout = TableLayout::new(Length::from_pt(500.0));
259        let grid = layout.create_grid(2, 3);
260
261        let (min, max) = layout.measure_column_widths(&grid, 0);
262
263        // Should return some default widths
264        assert!(min >= Length::ZERO);
265        assert!(max >= min);
266    }
267
268    #[test]
269    fn test_update_column_info_from_grid() {
270        let layout = TableLayout::new(Length::from_pt(500.0));
271        let mut column_info = vec![
272            ColumnInfo::new(ColumnWidth::Auto),
273            ColumnInfo::new(ColumnWidth::Fixed(Length::from_pt(100.0))),
274        ];
275        let mut grid = layout.create_grid(2, 2);
276
277        // Add a cell to the grid so there's content to measure
278        let cell = GridCell {
279            row: 0,
280            col: 0,
281            rowspan: 1,
282            colspan: 1,
283            content_id: None,
284        };
285        layout
286            .place_cell(&mut grid, cell)
287            .expect("test: should succeed");
288
289        layout.update_column_info_from_grid(&mut column_info, &grid);
290
291        // Auto column should have measured widths (from cell content)
292        assert!(column_info[0].min_width > Length::ZERO);
293        assert!(column_info[0].max_width >= column_info[0].min_width);
294        // Fixed column should remain unchanged
295        assert_eq!(column_info[1].min_width, Length::ZERO);
296    }
297
298    #[test]
299    fn test_distribute_colspan_widths() {
300        let layout = TableLayout::new(Length::from_pt(500.0));
301        let mut column_info = vec![
302            ColumnInfo::new(ColumnWidth::Auto),
303            ColumnInfo::new(ColumnWidth::Auto),
304        ];
305
306        let mut grid = layout.create_grid(1, 2);
307        let cell = GridCell {
308            row: 0,
309            col: 0,
310            rowspan: 1,
311            colspan: 2,
312            content_id: None,
313        };
314        layout
315            .place_cell(&mut grid, cell)
316            .expect("test: should succeed");
317
318        layout.distribute_colspan_widths(&mut column_info, &grid);
319
320        // Columns should have increased min widths due to colspan
321        assert!(column_info[0].min_width > Length::ZERO);
322        assert!(column_info[1].min_width > Length::ZERO);
323    }
324
325    #[test]
326    fn test_border_collapse_default() {
327        let layout = TableLayout::new(Length::from_pt(500.0));
328        assert_eq!(layout.border_collapse(), BorderCollapse::Separate);
329    }
330
331    #[test]
332    fn test_border_collapse_setting() {
333        let layout =
334            TableLayout::new(Length::from_pt(500.0)).with_border_collapse(BorderCollapse::Collapse);
335        assert_eq!(layout.border_collapse(), BorderCollapse::Collapse);
336    }
337
338    #[test]
339    fn test_fixed_widths_with_collapsed_borders() {
340        let layout =
341            TableLayout::new(Length::from_pt(500.0)).with_border_collapse(BorderCollapse::Collapse);
342        let columns = vec![
343            ColumnWidth::Fixed(Length::from_pt(100.0)),
344            ColumnWidth::Fixed(Length::from_pt(200.0)),
345        ];
346
347        let widths = layout.compute_fixed_widths(&columns);
348
349        // With collapsed borders, no spacing is subtracted
350        // Total: 100 + 200 = 300, remaining: 500 - 300 = 200
351        assert_eq!(widths[0], Length::from_pt(100.0));
352        assert_eq!(widths[1], Length::from_pt(200.0));
353    }
354
355    #[test]
356    fn test_fixed_widths_with_separate_borders() {
357        let layout = TableLayout::new(Length::from_pt(500.0))
358            .with_border_spacing(Length::from_pt(5.0))
359            .with_border_collapse(BorderCollapse::Separate);
360        let columns = vec![
361            ColumnWidth::Fixed(Length::from_pt(100.0)),
362            ColumnWidth::Fixed(Length::from_pt(200.0)),
363        ];
364
365        let widths = layout.compute_fixed_widths(&columns);
366
367        // With separate borders, spacing is subtracted: 500 - (3 * 5) = 485 available
368        assert_eq!(widths[0], Length::from_pt(100.0));
369        assert_eq!(widths[1], Length::from_pt(200.0));
370    }
371
372    #[test]
373    fn test_border_conflict_resolution_hidden() {
374        use crate::area::BorderStyle;
375        let layout = TableLayout::new(Length::from_pt(500.0));
376
377        let border1 = CollapsedBorder::new(
378            Length::from_pt(2.0),
379            fop_types::Color::RED,
380            BorderStyle::Hidden,
381        );
382        let border2 = CollapsedBorder::new(
383            Length::from_pt(5.0),
384            fop_types::Color::BLUE,
385            BorderStyle::Solid,
386        );
387
388        let result = layout.resolve_border_conflict(border1, border2);
389        // Hidden always wins
390        assert_eq!(result.style, BorderStyle::Hidden);
391    }
392
393    #[test]
394    fn test_border_conflict_resolution_width() {
395        use crate::area::BorderStyle;
396        let layout = TableLayout::new(Length::from_pt(500.0));
397
398        let border1 = CollapsedBorder::new(
399            Length::from_pt(5.0),
400            fop_types::Color::RED,
401            BorderStyle::Solid,
402        );
403        let border2 = CollapsedBorder::new(
404            Length::from_pt(2.0),
405            fop_types::Color::BLUE,
406            BorderStyle::Solid,
407        );
408
409        let result = layout.resolve_border_conflict(border1, border2);
410        // Wider border wins
411        assert_eq!(result.width, Length::from_pt(5.0));
412        assert_eq!(result.color, fop_types::Color::RED);
413    }
414
415    #[test]
416    fn test_border_conflict_resolution_style() {
417        use crate::area::BorderStyle;
418        let layout = TableLayout::new(Length::from_pt(500.0));
419
420        let border1 = CollapsedBorder::new(
421            Length::from_pt(2.0),
422            fop_types::Color::RED,
423            BorderStyle::Double,
424        );
425        let border2 = CollapsedBorder::new(
426            Length::from_pt(2.0),
427            fop_types::Color::BLUE,
428            BorderStyle::Dashed,
429        );
430
431        let result = layout.resolve_border_conflict(border1, border2);
432        // Double has higher precedence than Dashed
433        assert_eq!(result.style, BorderStyle::Double);
434        assert_eq!(result.color, fop_types::Color::RED);
435    }
436
437    #[test]
438    fn test_border_conflict_resolution_none() {
439        use crate::area::BorderStyle;
440        let layout = TableLayout::new(Length::from_pt(500.0));
441
442        let border1 = CollapsedBorder::new(
443            Length::from_pt(2.0),
444            fop_types::Color::RED,
445            BorderStyle::None,
446        );
447        let border2 = CollapsedBorder::new(
448            Length::from_pt(2.0),
449            fop_types::Color::BLUE,
450            BorderStyle::Solid,
451        );
452
453        let result = layout.resolve_border_conflict(border1, border2);
454        // Solid wins over None
455        assert_eq!(result.style, BorderStyle::Solid);
456        assert_eq!(result.color, fop_types::Color::BLUE);
457    }
458
459    #[test]
460    fn test_collapsed_border_visible() {
461        use crate::area::BorderStyle;
462
463        let visible = CollapsedBorder::new(
464            Length::from_pt(2.0),
465            fop_types::Color::RED,
466            BorderStyle::Solid,
467        );
468        assert!(visible.is_visible());
469
470        let invisible_none = CollapsedBorder::new(
471            Length::from_pt(2.0),
472            fop_types::Color::RED,
473            BorderStyle::None,
474        );
475        assert!(!invisible_none.is_visible());
476
477        let invisible_hidden = CollapsedBorder::new(
478            Length::from_pt(2.0),
479            fop_types::Color::RED,
480            BorderStyle::Hidden,
481        );
482        assert!(!invisible_hidden.is_visible());
483
484        let invisible_zero =
485            CollapsedBorder::new(Length::ZERO, fop_types::Color::RED, BorderStyle::Solid);
486        assert!(!invisible_zero.is_visible());
487    }
488
489    #[test]
490    fn test_border_collapse_display() {
491        assert_eq!(format!("{}", BorderCollapse::Separate), "separate");
492        assert_eq!(format!("{}", BorderCollapse::Collapse), "collapse");
493    }
494
495    // ======================================================================
496    // NEW COMPREHENSIVE TESTS (added to reach 50+ total)
497    // ======================================================================
498
499    // ------------------------------------------------------------------
500    // 1. TableLayout struct construction, getters, setters
501    // ------------------------------------------------------------------
502
503    #[test]
504    fn test_table_layout_default_border_spacing() {
505        let layout = TableLayout::new(Length::from_pt(400.0));
506        // Default border_spacing is 2pt (set in ::new)
507        assert_eq!(layout.border_spacing, Length::from_pt(2.0));
508    }
509
510    #[test]
511    fn test_table_layout_with_border_spacing_getter() {
512        let layout =
513            TableLayout::new(Length::from_pt(400.0)).with_border_spacing(Length::from_pt(8.0));
514        assert_eq!(layout.border_spacing, Length::from_pt(8.0));
515    }
516
517    #[test]
518    fn test_table_layout_available_width_stored() {
519        let layout = TableLayout::new(Length::from_pt(720.0));
520        assert_eq!(layout.available_width, Length::from_pt(720.0));
521    }
522
523    #[test]
524    fn test_table_layout_chained_builder() {
525        let layout = TableLayout::new(Length::from_pt(500.0))
526            .with_border_spacing(Length::from_pt(4.0))
527            .with_layout_mode(TableLayoutMode::Auto)
528            .with_border_collapse(BorderCollapse::Collapse);
529        assert_eq!(layout.layout_mode(), TableLayoutMode::Auto);
530        assert_eq!(layout.border_collapse(), BorderCollapse::Collapse);
531        assert_eq!(layout.border_spacing, Length::from_pt(4.0));
532    }
533
534    // ------------------------------------------------------------------
535    // 2. Column width calculation — fixed widths
536    // ------------------------------------------------------------------
537
538    #[test]
539    fn test_fixed_widths_empty_columns() {
540        let layout = TableLayout::new(Length::from_pt(500.0));
541        let widths = layout.compute_fixed_widths(&[]);
542        assert!(widths.is_empty());
543    }
544
545    #[test]
546    fn test_fixed_widths_three_equal_fixed_columns() {
547        let layout = TableLayout::new(Length::from_pt(600.0))
548            .with_border_spacing(Length::ZERO)
549            .with_border_collapse(BorderCollapse::Collapse);
550        let columns = vec![
551            ColumnWidth::Fixed(Length::from_pt(100.0)),
552            ColumnWidth::Fixed(Length::from_pt(200.0)),
553            ColumnWidth::Fixed(Length::from_pt(150.0)),
554        ];
555        let widths = layout.compute_fixed_widths(&columns);
556        assert_eq!(widths.len(), 3);
557        assert_eq!(widths[0], Length::from_pt(100.0));
558        assert_eq!(widths[1], Length::from_pt(200.0));
559        assert_eq!(widths[2], Length::from_pt(150.0));
560    }
561
562    #[test]
563    fn test_fixed_widths_auto_columns_share_remaining() {
564        // collapsed so no spacing: 500 total, 200 fixed → 300 for two auto cols
565        let layout =
566            TableLayout::new(Length::from_pt(500.0)).with_border_collapse(BorderCollapse::Collapse);
567        let columns = vec![
568            ColumnWidth::Fixed(Length::from_pt(200.0)),
569            ColumnWidth::Auto,
570            ColumnWidth::Auto,
571        ];
572        let widths = layout.compute_fixed_widths(&columns);
573        assert_eq!(widths.len(), 3);
574        assert_eq!(widths[0], Length::from_pt(200.0));
575        // two auto columns split 300 equally
576        assert_eq!(widths[1], widths[2]);
577        assert!((widths[1].to_pt() - 150.0).abs() < 0.01);
578    }
579
580    #[test]
581    fn test_fixed_widths_single_auto_column_takes_all_remaining() {
582        let layout =
583            TableLayout::new(Length::from_pt(500.0)).with_border_collapse(BorderCollapse::Collapse);
584        let columns = vec![
585            ColumnWidth::Fixed(Length::from_pt(100.0)),
586            ColumnWidth::Auto,
587        ];
588        let widths = layout.compute_fixed_widths(&columns);
589        assert!((widths[1].to_pt() - 400.0).abs() < 0.01);
590    }
591
592    #[test]
593    fn test_fixed_widths_proportional_single() {
594        // Only one proportional column gets all the available space
595        let layout =
596            TableLayout::new(Length::from_pt(400.0)).with_border_collapse(BorderCollapse::Collapse);
597        let columns = vec![ColumnWidth::Proportional(1.0)];
598        let widths = layout.compute_fixed_widths(&columns);
599        assert_eq!(widths.len(), 1);
600        // All 400pt should be assigned
601        assert!((widths[0].to_pt() - 400.0).abs() < 0.01);
602    }
603
604    #[test]
605    fn test_fixed_widths_proportional_3_1_ratio() {
606        let layout =
607            TableLayout::new(Length::from_pt(400.0)).with_border_collapse(BorderCollapse::Collapse);
608        let columns = vec![
609            ColumnWidth::Proportional(3.0),
610            ColumnWidth::Proportional(1.0),
611        ];
612        let widths = layout.compute_fixed_widths(&columns);
613        // 3:1 ratio → 300 and 100
614        assert!((widths[0].to_pt() - 300.0).abs() < 0.01);
615        assert!((widths[1].to_pt() - 100.0).abs() < 0.01);
616    }
617
618    // ------------------------------------------------------------------
619    // 3. Column width calculation — auto layout
620    // ------------------------------------------------------------------
621
622    #[test]
623    fn test_auto_widths_empty_columns() {
624        let layout = TableLayout::new(Length::from_pt(500.0));
625        let widths = layout.compute_auto_widths(&[]);
626        assert!(widths.is_empty());
627    }
628
629    #[test]
630    fn test_auto_widths_all_fixed() {
631        let layout =
632            TableLayout::new(Length::from_pt(500.0)).with_border_collapse(BorderCollapse::Collapse);
633        let infos = vec![
634            ColumnInfo::with_widths(
635                ColumnWidth::Fixed(Length::from_pt(120.0)),
636                Length::ZERO,
637                Length::ZERO,
638            ),
639            ColumnInfo::with_widths(
640                ColumnWidth::Fixed(Length::from_pt(80.0)),
641                Length::ZERO,
642                Length::ZERO,
643            ),
644        ];
645        let widths = layout.compute_auto_widths(&infos);
646        assert_eq!(widths[0], Length::from_pt(120.0));
647        assert_eq!(widths[1], Length::from_pt(80.0));
648    }
649
650    #[test]
651    fn test_auto_widths_proportional_columns() {
652        let layout =
653            TableLayout::new(Length::from_pt(400.0)).with_border_collapse(BorderCollapse::Collapse);
654        let infos = vec![
655            ColumnInfo::new(ColumnWidth::Proportional(1.0)),
656            ColumnInfo::new(ColumnWidth::Proportional(1.0)),
657        ];
658        let widths = layout.compute_auto_widths(&infos);
659        // Equal proportions → 200 each
660        assert!((widths[0].to_pt() - 200.0).abs() < 0.01);
661        assert!((widths[1].to_pt() - 200.0).abs() < 0.01);
662    }
663
664    #[test]
665    fn test_auto_widths_tight_all_min() {
666        // Space is below total_min — widths should scale down
667        let layout =
668            TableLayout::new(Length::from_pt(50.0)).with_border_collapse(BorderCollapse::Collapse);
669        let infos = vec![
670            ColumnInfo::with_widths(
671                ColumnWidth::Auto,
672                Length::from_pt(40.0),
673                Length::from_pt(100.0),
674            ),
675            ColumnInfo::with_widths(
676                ColumnWidth::Auto,
677                Length::from_pt(40.0),
678                Length::from_pt(100.0),
679            ),
680        ];
681        let widths = layout.compute_auto_widths(&infos);
682        // Both columns scaled from min(40) proportionally; total must be <= 50
683        assert!(widths[0] > Length::ZERO);
684        assert!(widths[1] > Length::ZERO);
685        let total = widths[0].to_pt() + widths[1].to_pt();
686        assert!(total <= 50.0 + 0.01);
687    }
688
689    #[test]
690    fn test_auto_widths_between_min_and_max_interpolation() {
691        // Space between total_min and total_max → interpolation
692        let layout =
693            TableLayout::new(Length::from_pt(200.0)).with_border_collapse(BorderCollapse::Collapse);
694        let infos = vec![
695            ColumnInfo::with_widths(
696                ColumnWidth::Auto,
697                Length::from_pt(50.0),
698                Length::from_pt(150.0),
699            ),
700            ColumnInfo::with_widths(
701                ColumnWidth::Auto,
702                Length::from_pt(50.0),
703                Length::from_pt(150.0),
704            ),
705        ];
706        let widths = layout.compute_auto_widths(&infos);
707        // Each column between 50 and 150
708        assert!(widths[0] >= Length::from_pt(50.0));
709        assert!(widths[0] <= Length::from_pt(150.0));
710        assert!(widths[1] >= Length::from_pt(50.0));
711        assert!(widths[1] <= Length::from_pt(150.0));
712    }
713
714    #[test]
715    fn test_auto_widths_same_min_max_uses_max_widths() {
716        // When min == max for all auto columns and available >= total_max,
717        // the algorithm uses max widths (plenty-of-space branch).
718        let layout =
719            TableLayout::new(Length::from_pt(300.0)).with_border_collapse(BorderCollapse::Collapse);
720        let infos = vec![
721            ColumnInfo::with_widths(
722                ColumnWidth::Auto,
723                Length::from_pt(80.0),
724                Length::from_pt(80.0),
725            ),
726            ColumnInfo::with_widths(
727                ColumnWidth::Auto,
728                Length::from_pt(80.0),
729                Length::from_pt(80.0),
730            ),
731        ];
732        // available=300 >= total_max=160 → "plenty of space" branch → max widths (80 each)
733        let widths = layout.compute_auto_widths(&infos);
734        assert!((widths[0].to_pt() - 80.0).abs() < 0.01);
735        assert!((widths[1].to_pt() - 80.0).abs() < 0.01);
736    }
737
738    // ------------------------------------------------------------------
739    // 4. Cell positioning — GridCell and place_cell
740    // ------------------------------------------------------------------
741
742    #[test]
743    fn test_grid_cell_rowspan_marks_spanned_rows() {
744        let layout = TableLayout::new(Length::from_pt(500.0));
745        let mut grid = layout.create_grid(3, 2);
746        let cell = GridCell {
747            row: 0,
748            col: 0,
749            rowspan: 3,
750            colspan: 1,
751            content_id: None,
752        };
753        layout
754            .place_cell(&mut grid, cell)
755            .expect("test: should succeed");
756        // Row 0 has the real cell
757        let top = grid[0][0].as_ref().expect("test: should succeed");
758        assert_eq!(top.rowspan, 3);
759        // Rows 1 and 2 are span markers (rowspan == 0)
760        let marker1 = grid[1][0].as_ref().expect("test: should succeed");
761        assert_eq!(marker1.rowspan, 0);
762        let marker2 = grid[2][0].as_ref().expect("test: should succeed");
763        assert_eq!(marker2.rowspan, 0);
764        // Other column unaffected
765        assert!(grid[0][1].is_none());
766    }
767
768    #[test]
769    fn test_grid_cell_colspan_marks_spanned_cols() {
770        let layout = TableLayout::new(Length::from_pt(500.0));
771        let mut grid = layout.create_grid(2, 4);
772        let cell = GridCell {
773            row: 0,
774            col: 1,
775            rowspan: 1,
776            colspan: 3,
777            content_id: None,
778        };
779        layout
780            .place_cell(&mut grid, cell)
781            .expect("test: should succeed");
782        // col 1 has real cell
783        assert_eq!(
784            grid[0][1].as_ref().expect("test: should succeed").colspan,
785            3
786        );
787        // cols 2, 3 are span markers
788        assert_eq!(
789            grid[0][2].as_ref().expect("test: should succeed").colspan,
790            0
791        );
792        assert_eq!(
793            grid[0][3].as_ref().expect("test: should succeed").colspan,
794            0
795        );
796        // col 0 unaffected
797        assert!(grid[0][0].is_none());
798    }
799
800    #[test]
801    fn test_grid_cell_rowspan_colspan_combined() {
802        let layout = TableLayout::new(Length::from_pt(500.0));
803        let mut grid = layout.create_grid(3, 3);
804        let cell = GridCell {
805            row: 0,
806            col: 0,
807            rowspan: 2,
808            colspan: 2,
809            content_id: None,
810        };
811        layout
812            .place_cell(&mut grid, cell)
813            .expect("test: should succeed");
814        // Real cell at (0,0)
815        let real = grid[0][0].as_ref().expect("test: should succeed");
816        assert_eq!(real.rowspan, 2);
817        assert_eq!(real.colspan, 2);
818        // Span markers at (0,1), (1,0), (1,1)
819        assert_eq!(
820            grid[0][1].as_ref().expect("test: should succeed").rowspan,
821            0
822        );
823        assert_eq!(
824            grid[1][0].as_ref().expect("test: should succeed").rowspan,
825            0
826        );
827        assert_eq!(
828            grid[1][1].as_ref().expect("test: should succeed").rowspan,
829            0
830        );
831        // Unaffected positions
832        assert!(grid[0][2].is_none());
833        assert!(grid[2][0].is_none());
834    }
835
836    #[test]
837    fn test_place_cell_out_of_bounds_row() {
838        let layout = TableLayout::new(Length::from_pt(500.0));
839        let mut grid = layout.create_grid(2, 2);
840        let cell = GridCell {
841            row: 5,
842            col: 0,
843            rowspan: 1,
844            colspan: 1,
845            content_id: None,
846        };
847        assert!(layout.place_cell(&mut grid, cell).is_err());
848    }
849
850    #[test]
851    fn test_place_cell_out_of_bounds_col() {
852        let layout = TableLayout::new(Length::from_pt(500.0));
853        let mut grid = layout.create_grid(2, 2);
854        let cell = GridCell {
855            row: 0,
856            col: 5,
857            rowspan: 1,
858            colspan: 1,
859            content_id: None,
860        };
861        assert!(layout.place_cell(&mut grid, cell).is_err());
862    }
863
864    #[test]
865    fn test_place_cell_span_clamped_at_bounds() {
866        // Span that extends beyond grid should be clamped
867        let layout = TableLayout::new(Length::from_pt(500.0));
868        let mut grid = layout.create_grid(2, 3);
869        let cell = GridCell {
870            row: 0,
871            col: 1,
872            rowspan: 5, // extends beyond 2 rows
873            colspan: 5, // extends beyond 3 cols
874            content_id: None,
875        };
876        // Should not panic
877        layout
878            .place_cell(&mut grid, cell)
879            .expect("test: should succeed");
880    }
881
882    #[test]
883    fn test_grid_cell_with_content_id() {
884        let layout = TableLayout::new(Length::from_pt(500.0));
885        let mut grid = layout.create_grid(1, 1);
886        let area_id = AreaId::from_index(42);
887        let cell = GridCell {
888            row: 0,
889            col: 0,
890            rowspan: 1,
891            colspan: 1,
892            content_id: Some(area_id),
893        };
894        layout
895            .place_cell(&mut grid, cell)
896            .expect("test: should succeed");
897        let stored = grid[0][0].as_ref().expect("test: should succeed");
898        assert_eq!(stored.content_id, Some(area_id));
899    }
900
901    // ------------------------------------------------------------------
902    // 5. Border collapse model
903    // ------------------------------------------------------------------
904
905    #[test]
906    fn test_border_conflict_both_hidden() {
907        use crate::area::BorderStyle;
908        let layout = TableLayout::new(Length::from_pt(500.0));
909        let b1 = CollapsedBorder::new(
910            Length::from_pt(1.0),
911            fop_types::Color::BLACK,
912            BorderStyle::Hidden,
913        );
914        let b2 = CollapsedBorder::new(
915            Length::from_pt(3.0),
916            fop_types::Color::RED,
917            BorderStyle::Hidden,
918        );
919        // First hidden takes precedence
920        let result = layout.resolve_border_conflict(b1, b2);
921        assert!(matches!(result.style, BorderStyle::Hidden));
922    }
923
924    #[test]
925    fn test_border_conflict_both_none() {
926        use crate::area::BorderStyle;
927        let layout = TableLayout::new(Length::from_pt(500.0));
928        let b1 = CollapsedBorder::new(
929            Length::from_pt(1.0),
930            fop_types::Color::BLACK,
931            BorderStyle::None,
932        );
933        let b2 = CollapsedBorder::new(
934            Length::from_pt(1.0),
935            fop_types::Color::RED,
936            BorderStyle::None,
937        );
938        let result = layout.resolve_border_conflict(b1, b2);
939        assert!(matches!(result.style, BorderStyle::None));
940    }
941
942    #[test]
943    fn test_border_conflict_equal_width_style_precedence() {
944        use crate::area::BorderStyle;
945        let layout = TableLayout::new(Length::from_pt(500.0));
946        // Solid > Dotted when widths equal
947        let b1 = CollapsedBorder::new(
948            Length::from_pt(2.0),
949            fop_types::Color::BLACK,
950            BorderStyle::Dotted,
951        );
952        let b2 = CollapsedBorder::new(
953            Length::from_pt(2.0),
954            fop_types::Color::RED,
955            BorderStyle::Solid,
956        );
957        let result = layout.resolve_border_conflict(b1, b2);
958        assert!(matches!(result.style, BorderStyle::Solid));
959    }
960
961    #[test]
962    fn test_border_conflict_double_vs_solid() {
963        use crate::area::BorderStyle;
964        let layout = TableLayout::new(Length::from_pt(500.0));
965        let b1 = CollapsedBorder::new(
966            Length::from_pt(2.0),
967            fop_types::Color::BLACK,
968            BorderStyle::Solid,
969        );
970        let b2 = CollapsedBorder::new(
971            Length::from_pt(2.0),
972            fop_types::Color::RED,
973            BorderStyle::Double,
974        );
975        let result = layout.resolve_border_conflict(b1, b2);
976        assert!(matches!(result.style, BorderStyle::Double));
977    }
978
979    #[test]
980    fn test_border_conflict_groove_inset_precedence() {
981        use crate::area::BorderStyle;
982        let layout = TableLayout::new(Length::from_pt(500.0));
983        // Groove (3) > Inset (2) in precedence
984        let b1 = CollapsedBorder::new(
985            Length::from_pt(2.0),
986            fop_types::Color::BLACK,
987            BorderStyle::Groove,
988        );
989        let b2 = CollapsedBorder::new(
990            Length::from_pt(2.0),
991            fop_types::Color::RED,
992            BorderStyle::Inset,
993        );
994        let result = layout.resolve_border_conflict(b1, b2);
995        assert!(matches!(result.style, BorderStyle::Groove));
996    }
997
998    #[test]
999    fn test_border_conflict_wider_wins_over_better_style() {
1000        use crate::area::BorderStyle;
1001        let layout = TableLayout::new(Length::from_pt(500.0));
1002        // Narrow Double vs Wide Dotted → Wide wins regardless of style
1003        let b1 = CollapsedBorder::new(
1004            Length::from_pt(1.0),
1005            fop_types::Color::BLACK,
1006            BorderStyle::Double,
1007        );
1008        let b2 = CollapsedBorder::new(
1009            Length::from_pt(5.0),
1010            fop_types::Color::BLUE,
1011            BorderStyle::Dotted,
1012        );
1013        let result = layout.resolve_border_conflict(b1, b2);
1014        assert_eq!(result.width, Length::from_pt(5.0));
1015        assert!(matches!(result.style, BorderStyle::Dotted));
1016    }
1017
1018    #[test]
1019    fn test_collapsed_border_none_factory() {
1020        let b = CollapsedBorder::none();
1021        assert_eq!(b.width, Length::ZERO);
1022        assert!(!b.is_visible());
1023    }
1024
1025    #[test]
1026    fn test_cell_collapsed_borders_default() {
1027        let borders = CellCollapsedBorders::default();
1028        assert!(!borders.top.is_visible());
1029        assert!(!borders.right.is_visible());
1030        assert!(!borders.bottom.is_visible());
1031        assert!(!borders.left.is_visible());
1032    }
1033
1034    #[test]
1035    fn test_separate_border_spacing_affects_available_width() {
1036        // With separate borders, total spacing is border_spacing * (n_cols + 1)
1037        let layout = TableLayout::new(Length::from_pt(500.0))
1038            .with_border_spacing(Length::from_pt(10.0))
1039            .with_border_collapse(BorderCollapse::Separate);
1040        // 4 columns → spacing = 10 * 5 = 50 → available_for_cols = 450
1041        let columns = vec![
1042            ColumnWidth::Auto,
1043            ColumnWidth::Auto,
1044            ColumnWidth::Auto,
1045            ColumnWidth::Auto,
1046        ];
1047        let widths = layout.compute_fixed_widths(&columns);
1048        // 450 / 4 = 112.5 each
1049        for w in &widths {
1050            assert!((w.to_pt() - 112.5).abs() < 0.01);
1051        }
1052    }
1053
1054    #[test]
1055    fn test_collapse_model_no_spacing_deducted() {
1056        let layout = TableLayout::new(Length::from_pt(400.0))
1057            .with_border_spacing(Length::from_pt(10.0))
1058            .with_border_collapse(BorderCollapse::Collapse);
1059        // With collapse, spacing is zero regardless of border_spacing setting
1060        let columns = vec![ColumnWidth::Auto, ColumnWidth::Auto];
1061        let widths = layout.compute_fixed_widths(&columns);
1062        // Should get 200 each (400 / 2)
1063        for w in &widths {
1064            assert!((w.to_pt() - 200.0).abs() < 0.01);
1065        }
1066    }
1067
1068    // ------------------------------------------------------------------
1069    // 6. ColumnInfo properties
1070    // ------------------------------------------------------------------
1071
1072    #[test]
1073    fn test_column_info_computed_width_initial_zero() {
1074        let info = ColumnInfo::new(ColumnWidth::Fixed(Length::from_pt(100.0)));
1075        assert_eq!(info.computed_width, Length::ZERO);
1076    }
1077
1078    #[test]
1079    fn test_column_info_with_widths_computed_still_zero() {
1080        let info = ColumnInfo::with_widths(
1081            ColumnWidth::Proportional(2.0),
1082            Length::from_pt(30.0),
1083            Length::from_pt(90.0),
1084        );
1085        assert_eq!(info.computed_width, Length::ZERO);
1086        assert_eq!(info.min_width, Length::from_pt(30.0));
1087        assert_eq!(info.max_width, Length::from_pt(90.0));
1088    }
1089
1090    #[test]
1091    fn test_column_info_width_spec_variants() {
1092        let fixed = ColumnInfo::new(ColumnWidth::Fixed(Length::from_pt(50.0)));
1093        assert!(matches!(fixed.width_spec, ColumnWidth::Fixed(_)));
1094
1095        let prop = ColumnInfo::new(ColumnWidth::Proportional(1.5));
1096        assert!(matches!(prop.width_spec, ColumnWidth::Proportional(_)));
1097
1098        let auto = ColumnInfo::new(ColumnWidth::Auto);
1099        assert!(matches!(auto.width_spec, ColumnWidth::Auto));
1100    }
1101
1102    // ------------------------------------------------------------------
1103    // 7. GridCell start/end column and row span
1104    // ------------------------------------------------------------------
1105
1106    #[test]
1107    fn test_grid_cell_end_column_computed() {
1108        let cell = GridCell {
1109            row: 0,
1110            col: 2,
1111            rowspan: 1,
1112            colspan: 3,
1113            content_id: None,
1114        };
1115        // end column = col + colspan - 1 = 4
1116        assert_eq!(cell.col + cell.colspan - 1, 4);
1117    }
1118
1119    #[test]
1120    fn test_grid_cell_single_cell_span() {
1121        let cell = GridCell {
1122            row: 0,
1123            col: 0,
1124            rowspan: 1,
1125            colspan: 1,
1126            content_id: None,
1127        };
1128        assert_eq!(cell.rowspan, 1);
1129        assert_eq!(cell.colspan, 1);
1130    }
1131
1132    #[test]
1133    fn test_grid_cell_marker_zeroed_spans() {
1134        let layout = TableLayout::new(Length::from_pt(500.0));
1135        let mut grid = layout.create_grid(2, 2);
1136        let cell = GridCell {
1137            row: 0,
1138            col: 0,
1139            rowspan: 2,
1140            colspan: 2,
1141            content_id: None,
1142        };
1143        layout
1144            .place_cell(&mut grid, cell)
1145            .expect("test: should succeed");
1146        // Markers at (0,1), (1,0), (1,1)
1147        for &(r, c) in &[(0usize, 1usize), (1, 0), (1, 1)] {
1148            let marker = grid[r][c].as_ref().expect("test: should succeed");
1149            assert_eq!(marker.rowspan, 0, "row={r} col={c} should be marker");
1150            assert_eq!(marker.colspan, 0, "row={r} col={c} should be marker");
1151        }
1152    }
1153
1154    #[test]
1155    fn test_grid_cell_marker_points_to_origin() {
1156        let layout = TableLayout::new(Length::from_pt(500.0));
1157        let mut grid = layout.create_grid(2, 2);
1158        let cell = GridCell {
1159            row: 1,
1160            col: 1,
1161            rowspan: 1,
1162            colspan: 1,
1163            content_id: None,
1164        };
1165        layout
1166            .place_cell(&mut grid, cell)
1167            .expect("test: should succeed");
1168        let stored = grid[1][1].as_ref().expect("test: should succeed");
1169        assert_eq!(stored.row, 1);
1170        assert_eq!(stored.col, 1);
1171    }
1172
1173    // ------------------------------------------------------------------
1174    // 8. Edge cases: empty table, single-cell, 1-column, 1-row
1175    // ------------------------------------------------------------------
1176
1177    #[test]
1178    fn test_empty_table_zero_columns() {
1179        let layout = TableLayout::new(Length::from_pt(500.0));
1180        let widths = layout.compute_fixed_widths(&[]);
1181        assert!(widths.is_empty());
1182    }
1183
1184    #[test]
1185    fn test_single_cell_table() {
1186        let layout =
1187            TableLayout::new(Length::from_pt(300.0)).with_border_collapse(BorderCollapse::Collapse);
1188        let mut grid = layout.create_grid(1, 1);
1189        let cell = GridCell {
1190            row: 0,
1191            col: 0,
1192            rowspan: 1,
1193            colspan: 1,
1194            content_id: None,
1195        };
1196        layout
1197            .place_cell(&mut grid, cell)
1198            .expect("test: should succeed");
1199        assert!(grid[0][0].is_some());
1200        // Column widths
1201        let widths = layout.compute_fixed_widths(&[ColumnWidth::Auto]);
1202        assert_eq!(widths.len(), 1);
1203        assert!((widths[0].to_pt() - 300.0).abs() < 0.01);
1204    }
1205
1206    #[test]
1207    fn test_one_column_table_many_rows() {
1208        let layout =
1209            TableLayout::new(Length::from_pt(200.0)).with_border_collapse(BorderCollapse::Collapse);
1210        let mut grid = layout.create_grid(5, 1);
1211        for r in 0..5 {
1212            let cell = GridCell {
1213                row: r,
1214                col: 0,
1215                rowspan: 1,
1216                colspan: 1,
1217                content_id: None,
1218            };
1219            layout
1220                .place_cell(&mut grid, cell)
1221                .expect("test: should succeed");
1222        }
1223        for row in grid.iter().take(5) {
1224            assert!(row[0].is_some());
1225        }
1226        let widths = layout.compute_fixed_widths(&[ColumnWidth::Auto]);
1227        assert_eq!(widths.len(), 1);
1228        assert!((widths[0].to_pt() - 200.0).abs() < 0.01);
1229    }
1230
1231    #[test]
1232    fn test_one_row_table_many_columns() {
1233        let layout =
1234            TableLayout::new(Length::from_pt(600.0)).with_border_collapse(BorderCollapse::Collapse);
1235        let n = 6;
1236        let mut grid = layout.create_grid(1, n);
1237        for c in 0..n {
1238            let cell = GridCell {
1239                row: 0,
1240                col: c,
1241                rowspan: 1,
1242                colspan: 1,
1243                content_id: None,
1244            };
1245            layout
1246                .place_cell(&mut grid, cell)
1247                .expect("test: should succeed");
1248        }
1249        let col_specs: Vec<ColumnWidth> = (0..n).map(|_| ColumnWidth::Auto).collect();
1250        let widths = layout.compute_fixed_widths(&col_specs);
1251        assert_eq!(widths.len(), n);
1252        for w in &widths {
1253            assert!(
1254                (w.to_pt() - 100.0).abs() < 0.01,
1255                "expected 100pt, got {}pt",
1256                w.to_pt()
1257            );
1258        }
1259    }
1260
1261    #[test]
1262    fn test_single_proportional_column_zero_proportional_sum() {
1263        // Edge: only one proportional column with value 0.0
1264        let layout =
1265            TableLayout::new(Length::from_pt(300.0)).with_border_collapse(BorderCollapse::Collapse);
1266        let columns = vec![ColumnWidth::Proportional(0.0)];
1267        let widths = layout.compute_fixed_widths(&columns);
1268        // total_proportional == 0.0 → width is ZERO
1269        assert_eq!(widths[0], Length::ZERO);
1270    }
1271
1272    // ------------------------------------------------------------------
1273    // 9. Table width fitting within page margins
1274    // ------------------------------------------------------------------
1275
1276    #[test]
1277    fn test_fixed_widths_fit_within_available_width() {
1278        let available = Length::from_pt(500.0);
1279        let layout = TableLayout::new(available).with_border_collapse(BorderCollapse::Collapse);
1280        let columns = vec![
1281            ColumnWidth::Proportional(1.0),
1282            ColumnWidth::Proportional(1.0),
1283            ColumnWidth::Proportional(1.0),
1284        ];
1285        let widths = layout.compute_fixed_widths(&columns);
1286        let total: f64 = widths.iter().map(|w| w.to_pt()).sum();
1287        assert!(
1288            total <= available.to_pt() + 0.01,
1289            "total {total} > available {}",
1290            available.to_pt()
1291        );
1292    }
1293
1294    #[test]
1295    fn test_auto_widths_fit_within_available_width() {
1296        let available = Length::from_pt(400.0);
1297        let layout = TableLayout::new(available).with_border_collapse(BorderCollapse::Collapse);
1298        let infos = vec![
1299            ColumnInfo::with_widths(
1300                ColumnWidth::Auto,
1301                Length::from_pt(60.0),
1302                Length::from_pt(180.0),
1303            ),
1304            ColumnInfo::with_widths(
1305                ColumnWidth::Auto,
1306                Length::from_pt(60.0),
1307                Length::from_pt(180.0),
1308            ),
1309        ];
1310        let widths = layout.compute_auto_widths(&infos);
1311        let total: f64 = widths.iter().map(|w| w.to_pt()).sum();
1312        assert!(total <= available.to_pt() + 0.01);
1313    }
1314
1315    #[test]
1316    fn test_table_layout_separates_border_spacing_in_auto_widths() {
1317        let layout = TableLayout::new(Length::from_pt(500.0))
1318            .with_border_spacing(Length::from_pt(5.0))
1319            .with_border_collapse(BorderCollapse::Separate);
1320        let infos = vec![
1321            ColumnInfo::with_widths(
1322                ColumnWidth::Auto,
1323                Length::from_pt(10.0),
1324                Length::from_pt(500.0),
1325            ),
1326            ColumnInfo::with_widths(
1327                ColumnWidth::Auto,
1328                Length::from_pt(10.0),
1329                Length::from_pt(500.0),
1330            ),
1331        ];
1332        // total_spacing = 5 * 3 = 15 → available_for_cols = 485
1333        // space (485) < max (1000) but space >= min (20) → interpolate
1334        let widths = layout.compute_auto_widths(&infos);
1335        let total: f64 = widths.iter().map(|w| w.to_pt()).sum();
1336        // Columns should not exceed the spacing-adjusted available width
1337        assert!(total <= 500.0 + 0.01);
1338    }
1339
1340    // ------------------------------------------------------------------
1341    // 10. ColumnWidth enum variants and display
1342    // ------------------------------------------------------------------
1343
1344    #[test]
1345    fn test_column_width_fixed_variant() {
1346        let cw = ColumnWidth::Fixed(Length::from_pt(75.0));
1347        if let ColumnWidth::Fixed(l) = cw {
1348            assert!((l.to_pt() - 75.0).abs() < 0.01);
1349        } else {
1350            panic!("Expected Fixed variant");
1351        }
1352    }
1353
1354    #[test]
1355    fn test_column_width_proportional_variant() {
1356        let cw = ColumnWidth::Proportional(2.5);
1357        if let ColumnWidth::Proportional(p) = cw {
1358            assert!((p - 2.5).abs() < 0.001);
1359        } else {
1360            panic!("Expected Proportional variant");
1361        }
1362    }
1363
1364    #[test]
1365    fn test_column_width_auto_variant() {
1366        let cw = ColumnWidth::Auto;
1367        assert!(matches!(cw, ColumnWidth::Auto));
1368    }
1369
1370    #[test]
1371    fn test_column_width_equality() {
1372        assert_eq!(ColumnWidth::Auto, ColumnWidth::Auto);
1373        assert_eq!(
1374            ColumnWidth::Fixed(Length::from_pt(10.0)),
1375            ColumnWidth::Fixed(Length::from_pt(10.0))
1376        );
1377        assert_ne!(ColumnWidth::Auto, ColumnWidth::Fixed(Length::from_pt(0.0)));
1378    }
1379
1380    // ------------------------------------------------------------------
1381    // 11. TableLayoutMode enum
1382    // ------------------------------------------------------------------
1383
1384    #[test]
1385    fn test_table_layout_mode_equality() {
1386        assert_eq!(TableLayoutMode::Fixed, TableLayoutMode::Fixed);
1387        assert_eq!(TableLayoutMode::Auto, TableLayoutMode::Auto);
1388        assert_ne!(TableLayoutMode::Fixed, TableLayoutMode::Auto);
1389    }
1390
1391    #[test]
1392    fn test_table_layout_mode_default_is_fixed() {
1393        assert_eq!(TableLayoutMode::default(), TableLayoutMode::Fixed);
1394    }
1395
1396    // ------------------------------------------------------------------
1397    // 12. BorderCollapse enum
1398    // ------------------------------------------------------------------
1399
1400    #[test]
1401    fn test_border_collapse_equality() {
1402        assert_eq!(BorderCollapse::Separate, BorderCollapse::Separate);
1403        assert_eq!(BorderCollapse::Collapse, BorderCollapse::Collapse);
1404        assert_ne!(BorderCollapse::Separate, BorderCollapse::Collapse);
1405    }
1406
1407    #[test]
1408    fn test_border_collapse_default_is_separate() {
1409        assert_eq!(BorderCollapse::default(), BorderCollapse::Separate);
1410    }
1411
1412    // ------------------------------------------------------------------
1413    // 13. CollapsedBorder creation and visibility
1414    // ------------------------------------------------------------------
1415
1416    #[test]
1417    fn test_collapsed_border_dotted_visible() {
1418        use crate::area::BorderStyle;
1419        let b = CollapsedBorder::new(
1420            Length::from_pt(1.0),
1421            fop_types::Color::BLACK,
1422            BorderStyle::Dotted,
1423        );
1424        assert!(b.is_visible());
1425    }
1426
1427    #[test]
1428    fn test_collapsed_border_dashed_visible() {
1429        use crate::area::BorderStyle;
1430        let b = CollapsedBorder::new(
1431            Length::from_pt(0.5),
1432            fop_types::Color::BLACK,
1433            BorderStyle::Dashed,
1434        );
1435        assert!(b.is_visible());
1436    }
1437
1438    #[test]
1439    fn test_collapsed_border_double_visible() {
1440        use crate::area::BorderStyle;
1441        let b = CollapsedBorder::new(
1442            Length::from_pt(3.0),
1443            fop_types::Color::RED,
1444            BorderStyle::Double,
1445        );
1446        assert!(b.is_visible());
1447    }
1448
1449    #[test]
1450    fn test_collapsed_border_groove_visible() {
1451        use crate::area::BorderStyle;
1452        let b = CollapsedBorder::new(
1453            Length::from_pt(2.0),
1454            fop_types::Color::BLACK,
1455            BorderStyle::Groove,
1456        );
1457        assert!(b.is_visible());
1458    }
1459
1460    #[test]
1461    fn test_collapsed_border_ridge_visible() {
1462        use crate::area::BorderStyle;
1463        let b = CollapsedBorder::new(
1464            Length::from_pt(2.0),
1465            fop_types::Color::BLACK,
1466            BorderStyle::Ridge,
1467        );
1468        assert!(b.is_visible());
1469    }
1470
1471    // ------------------------------------------------------------------
1472    // 14. Grid creation edge cases
1473    // ------------------------------------------------------------------
1474
1475    #[test]
1476    fn test_create_grid_zero_rows() {
1477        let layout = TableLayout::new(Length::from_pt(500.0));
1478        let grid = layout.create_grid(0, 4);
1479        assert_eq!(grid.len(), 0);
1480    }
1481
1482    #[test]
1483    fn test_create_grid_zero_cols() {
1484        let layout = TableLayout::new(Length::from_pt(500.0));
1485        let grid = layout.create_grid(3, 0);
1486        assert_eq!(grid.len(), 3);
1487        assert_eq!(grid[0].len(), 0);
1488    }
1489
1490    #[test]
1491    fn test_create_grid_all_cells_initially_none() {
1492        let layout = TableLayout::new(Length::from_pt(500.0));
1493        let grid = layout.create_grid(4, 5);
1494        for row in &grid {
1495            for cell in row {
1496                assert!(cell.is_none());
1497            }
1498        }
1499    }
1500
1501    // ------------------------------------------------------------------
1502    // 15. Distribute colspan widths
1503    // ------------------------------------------------------------------
1504
1505    #[test]
1506    fn test_distribute_colspan_three_cols_one_span() {
1507        let layout = TableLayout::new(Length::from_pt(500.0));
1508        let mut column_info = vec![
1509            ColumnInfo::new(ColumnWidth::Auto),
1510            ColumnInfo::new(ColumnWidth::Auto),
1511            ColumnInfo::new(ColumnWidth::Auto),
1512        ];
1513        let mut grid = layout.create_grid(1, 3);
1514        let cell = GridCell {
1515            row: 0,
1516            col: 0,
1517            rowspan: 1,
1518            colspan: 3,
1519            content_id: None,
1520        };
1521        layout
1522            .place_cell(&mut grid, cell)
1523            .expect("test: should succeed");
1524        layout.distribute_colspan_widths(&mut column_info, &grid);
1525        // All three columns should get min widths boosted
1526        for info in &column_info {
1527            assert!(info.min_width > Length::ZERO);
1528        }
1529    }
1530
1531    #[test]
1532    fn test_distribute_colspan_fixed_column_unaffected() {
1533        let layout = TableLayout::new(Length::from_pt(500.0));
1534        let mut column_info = vec![
1535            ColumnInfo::with_widths(
1536                ColumnWidth::Fixed(Length::from_pt(100.0)),
1537                Length::from_pt(100.0),
1538                Length::from_pt(100.0),
1539            ),
1540            ColumnInfo::new(ColumnWidth::Auto),
1541        ];
1542        let mut grid = layout.create_grid(1, 2);
1543        let cell = GridCell {
1544            row: 0,
1545            col: 0,
1546            rowspan: 1,
1547            colspan: 2,
1548            content_id: None,
1549        };
1550        layout
1551            .place_cell(&mut grid, cell)
1552            .expect("test: should succeed");
1553        layout.distribute_colspan_widths(&mut column_info, &grid);
1554        // Fixed column min_width stays at 100
1555        assert_eq!(column_info[0].min_width, Length::from_pt(100.0));
1556    }
1557
1558    // ------------------------------------------------------------------
1559    // 16. Measure column widths with populated grid
1560    // ------------------------------------------------------------------
1561
1562    #[test]
1563    fn test_measure_column_widths_with_cell_returns_nonzero() {
1564        let layout = TableLayout::new(Length::from_pt(500.0));
1565        let mut grid = layout.create_grid(2, 2);
1566        let cell = GridCell {
1567            row: 0,
1568            col: 0,
1569            rowspan: 1,
1570            colspan: 1,
1571            content_id: None,
1572        };
1573        layout
1574            .place_cell(&mut grid, cell)
1575            .expect("test: should succeed");
1576        let (min, max) = layout.measure_column_widths(&grid, 0);
1577        // measure_column_widths uses 30pt min and 200pt max heuristics
1578        assert!((min.to_pt() - 30.0).abs() < 0.01);
1579        assert!((max.to_pt() - 200.0).abs() < 0.01);
1580    }
1581
1582    #[test]
1583    fn test_measure_column_widths_empty_column_returns_zero() {
1584        let layout = TableLayout::new(Length::from_pt(500.0));
1585        let grid = layout.create_grid(3, 3);
1586        let (min, max) = layout.measure_column_widths(&grid, 1);
1587        assert_eq!(min, Length::ZERO);
1588        assert_eq!(max, Length::ZERO);
1589    }
1590
1591    #[test]
1592    fn test_measure_column_widths_max_gte_min() {
1593        let layout = TableLayout::new(Length::from_pt(500.0));
1594        let mut grid = layout.create_grid(1, 1);
1595        let cell = GridCell {
1596            row: 0,
1597            col: 0,
1598            rowspan: 1,
1599            colspan: 1,
1600            content_id: None,
1601        };
1602        layout
1603            .place_cell(&mut grid, cell)
1604            .expect("test: should succeed");
1605        let (min, max) = layout.measure_column_widths(&grid, 0);
1606        assert!(max >= min);
1607    }
1608
1609    // ------------------------------------------------------------------
1610    // 17. layout_table integration smoke tests
1611    // ------------------------------------------------------------------
1612
1613    #[test]
1614    fn test_layout_table_returns_area_id() {
1615        let layout = TableLayout::new(Length::from_pt(500.0));
1616        let mut tree = AreaTree::new();
1617        let column_widths = vec![Length::from_pt(100.0), Length::from_pt(200.0)];
1618        let grid = layout.create_grid(2, 2);
1619        let result = layout.layout_table(&mut tree, &column_widths, &grid, Length::ZERO);
1620        assert!(result.is_ok());
1621    }
1622
1623    #[test]
1624    fn test_layout_table_empty_grid() {
1625        let layout = TableLayout::new(Length::from_pt(500.0));
1626        let mut tree = AreaTree::new();
1627        let column_widths: Vec<Length> = vec![];
1628        let grid: Vec<Vec<Option<GridCell>>> = vec![];
1629        let result = layout.layout_table(&mut tree, &column_widths, &grid, Length::ZERO);
1630        assert!(result.is_ok());
1631    }
1632
1633    #[test]
1634    fn test_layout_table_with_y_offset() {
1635        let layout = TableLayout::new(Length::from_pt(500.0));
1636        let mut tree = AreaTree::new();
1637        let column_widths = vec![Length::from_pt(200.0)];
1638        let grid = layout.create_grid(1, 1);
1639        let y = Length::from_pt(50.0);
1640        let result = layout.layout_table(&mut tree, &column_widths, &grid, y);
1641        assert!(result.is_ok());
1642    }
1643}