Skip to main content

fop_layout/layout/
mod.rs

1//! Layout algorithms
2//!
3//! Transforms the FO tree into an area tree through layout algorithms.
4
5pub mod block;
6pub mod engine;
7pub mod inline;
8pub mod knuth_plass;
9pub mod list;
10pub mod page_break;
11pub mod page_number_resolver;
12pub mod properties;
13pub mod streaming;
14pub mod table;
15
16pub use block::BlockLayoutContext;
17pub use engine::{LayoutEngine, MultiColumnLayout};
18pub use inline::{InlineArea, InlineLayoutContext, LineBreaker, TextAlign};
19pub use knuth_plass::KnuthPlassBreaker;
20pub use list::{ListLayout, ListMarkerStyle};
21pub use page_break::PageBreaker;
22pub use page_number_resolver::PageNumberResolver;
23pub use properties::{
24    extract_break_after, extract_break_before, extract_clear, extract_column_count,
25    extract_column_gap, extract_end_indent, extract_keep_constraint, extract_letter_spacing,
26    extract_line_height, extract_opacity, extract_orphans, extract_overflow, extract_space_after,
27    extract_space_before, extract_start_indent, extract_text_indent, extract_traits,
28    extract_widows, extract_word_spacing, BreakValue, Keep, KeepConstraint, OverflowBehavior,
29};
30pub use streaming::{StreamingConfig, StreamingLayoutEngine, StreamingLayoutIterator};
31pub use table::{
32    BorderCollapse, CellCollapsedBorders, CollapsedBorder, ColumnInfo, ColumnWidth, TableLayout,
33    TableLayoutMode,
34};
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39    use fop_types::{Length, Point, Rect, Size};
40
41    fn make_rect(w: f64, h: f64) -> Rect {
42        Rect::from_point_size(
43            Point::ZERO,
44            Size::new(Length::from_pt(w), Length::from_pt(h)),
45        )
46    }
47
48    // ---- BlockLayoutContext tests ----
49
50    #[test]
51    fn test_block_layout_context_new() {
52        let ctx = BlockLayoutContext::new(Length::from_pt(400.0));
53        assert_eq!(ctx.total_height(), Length::ZERO);
54    }
55
56    #[test]
57    fn test_block_layout_context_allocate() {
58        let mut ctx = BlockLayoutContext::new(Length::from_pt(400.0));
59        let rect = ctx.allocate(Length::from_pt(400.0), Length::from_pt(20.0));
60        assert_eq!(rect.width, Length::from_pt(400.0));
61        assert_eq!(rect.height, Length::from_pt(20.0));
62        assert_eq!(ctx.total_height(), Length::from_pt(20.0));
63    }
64
65    #[test]
66    fn test_block_layout_context_allocate_multiple() {
67        let mut ctx = BlockLayoutContext::new(Length::from_pt(400.0));
68        ctx.allocate(Length::from_pt(400.0), Length::from_pt(20.0));
69        ctx.allocate(Length::from_pt(400.0), Length::from_pt(30.0));
70        assert_eq!(ctx.total_height(), Length::from_pt(50.0));
71    }
72
73    #[test]
74    fn test_block_layout_context_add_space() {
75        let mut ctx = BlockLayoutContext::new(Length::from_pt(400.0));
76        ctx.add_space(Length::from_pt(10.0));
77        assert_eq!(ctx.total_height(), Length::from_pt(10.0));
78    }
79
80    #[test]
81    fn test_block_layout_context_allocate_with_spacing() {
82        let mut ctx = BlockLayoutContext::new(Length::from_pt(400.0));
83        let rect = ctx.allocate_with_spacing(
84            Length::from_pt(400.0),
85            Length::from_pt(12.0),
86            Length::from_pt(6.0),
87            Length::from_pt(6.0),
88        );
89        // Width should match available width
90        assert_eq!(rect.width, Length::from_pt(400.0));
91    }
92
93    // ---- TextAlign tests ----
94
95    #[test]
96    fn test_text_align_display_left() {
97        let align = TextAlign::Left;
98        let s = format!("{}", align);
99        assert!(!s.is_empty());
100    }
101
102    #[test]
103    fn test_text_align_display_center() {
104        let align = TextAlign::Center;
105        let s = format!("{}", align);
106        assert!(!s.is_empty());
107    }
108
109    #[test]
110    fn test_text_align_display_right() {
111        let align = TextAlign::Right;
112        let s = format!("{}", align);
113        assert!(!s.is_empty());
114    }
115
116    // ---- InlineLayoutContext tests ----
117
118    #[test]
119    fn test_inline_layout_context_new() {
120        let ctx = InlineLayoutContext::new(Length::from_pt(400.0), Length::from_pt(12.0));
121        assert!(ctx.is_empty());
122        assert_eq!(ctx.used_width(), Length::ZERO);
123    }
124
125    #[test]
126    fn test_inline_layout_context_fits() {
127        let ctx = InlineLayoutContext::new(Length::from_pt(400.0), Length::from_pt(12.0));
128        assert!(ctx.fits(Length::from_pt(100.0)));
129        assert!(!ctx.fits(Length::from_pt(500.0)));
130    }
131
132    #[test]
133    fn test_inline_layout_context_remaining_width() {
134        let ctx = InlineLayoutContext::new(Length::from_pt(400.0), Length::from_pt(12.0));
135        assert_eq!(ctx.remaining_width(), Length::from_pt(400.0));
136    }
137
138    #[test]
139    fn test_inline_layout_context_with_text_align() {
140        let ctx = InlineLayoutContext::new(Length::from_pt(400.0), Length::from_pt(12.0))
141            .with_text_align(TextAlign::Center);
142        // Should not panic and should be constructable
143        let _ = ctx;
144    }
145
146    // ---- LineBreaker tests ----
147
148    #[test]
149    fn test_line_breaker_new() {
150        let breaker = LineBreaker::new(Length::from_pt(400.0));
151        let _ = breaker;
152    }
153
154    #[test]
155    fn test_line_breaker_break_into_words_empty() {
156        let breaker = LineBreaker::new(Length::from_pt(400.0));
157        let words = breaker.break_into_words("");
158        assert!(words.is_empty());
159    }
160
161    #[test]
162    fn test_line_breaker_break_into_words_single() {
163        let breaker = LineBreaker::new(Length::from_pt(400.0));
164        let words = breaker.break_into_words("hello");
165        assert_eq!(words.len(), 1);
166        assert_eq!(words[0], "hello");
167    }
168
169    #[test]
170    fn test_line_breaker_break_into_words_multiple() {
171        let breaker = LineBreaker::new(Length::from_pt(400.0));
172        let words = breaker.break_into_words("hello world foo");
173        assert_eq!(words.len(), 3);
174    }
175
176    #[test]
177    fn test_line_breaker_measure_text() {
178        let breaker = LineBreaker::new(Length::from_pt(400.0));
179        let width = breaker.measure_text("hello", Length::from_pt(12.0));
180        assert!(width > Length::ZERO);
181    }
182
183    #[test]
184    fn test_line_breaker_break_lines() {
185        let breaker = LineBreaker::new(Length::from_pt(400.0));
186        let lines = breaker.break_lines("short text", Length::from_pt(12.0));
187        assert!(!lines.is_empty());
188    }
189
190    // ---- PageNumberResolver tests ----
191
192    #[test]
193    fn test_page_number_resolver_new() {
194        let resolver = PageNumberResolver::new();
195        assert_eq!(resolver.current_page(), 1);
196    }
197
198    #[test]
199    fn test_page_number_resolver_set_current_page() {
200        let mut resolver = PageNumberResolver::new();
201        resolver.set_current_page(5);
202        assert_eq!(resolver.current_page(), 5);
203    }
204
205    #[test]
206    fn test_page_number_resolver_register_element() {
207        use crate::area::{Area, AreaTree, AreaType};
208        let mut resolver = PageNumberResolver::new();
209        let mut tree = AreaTree::new();
210        let id = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
211        resolver.register_element("my-id".to_string(), id);
212        // Should not panic and element should be registered
213        let format = resolver.get_format_for_id("my-id");
214        // format may or may not be set depending on implementation
215        let _ = format;
216    }
217
218    #[test]
219    fn test_page_number_resolver_format() {
220        let mut resolver = PageNumberResolver::new();
221        resolver.set_current_format("1".to_string());
222        assert_eq!(resolver.current_format(), "1");
223    }
224
225    // ---- BreakValue tests ----
226
227    #[test]
228    fn test_break_value_forces_break_auto() {
229        let bv = BreakValue::Auto;
230        assert!(!bv.forces_break());
231    }
232
233    #[test]
234    fn test_break_value_forces_break_page() {
235        let bv = BreakValue::Page;
236        assert!(bv.forces_break());
237    }
238
239    #[test]
240    fn test_break_value_forces_page_break_column() {
241        let bv = BreakValue::Column;
242        assert!(!bv.forces_page_break());
243    }
244
245    // ---- StreamingLayoutEngine re-export tests ----
246
247    #[test]
248    fn test_streaming_config_re_exported() {
249        let config = StreamingConfig::default();
250        assert_eq!(config.max_memory_pages, 10);
251    }
252
253    #[test]
254    fn test_streaming_layout_engine_re_exported() {
255        let engine = StreamingLayoutEngine::new();
256        let _ = engine;
257    }
258}