Skip to main content

rdocx_layout/
block.rs

1//! Block-level layout: paragraphs and tables as positioned blocks.
2
3use rdocx_oxml::borders::CT_PBdr;
4use rdocx_oxml::shared::ST_Jc;
5
6use crate::line::LayoutLine;
7use crate::output::Color;
8use crate::table::TableBlock;
9
10/// A laid-out block element (paragraph or table).
11#[derive(Debug, Clone)]
12pub enum LayoutBlock {
13    Paragraph(ParagraphBlock),
14    Table(TableBlock),
15}
16
17impl LayoutBlock {
18    /// Total height including spacing.
19    pub fn total_height(&self) -> f64 {
20        match self {
21            LayoutBlock::Paragraph(p) => p.total_height(),
22            LayoutBlock::Table(t) => t.total_height(),
23        }
24    }
25
26    /// Content height without spacing.
27    pub fn content_height(&self) -> f64 {
28        match self {
29            LayoutBlock::Paragraph(p) => p.content_height(),
30            LayoutBlock::Table(t) => t.content_height(),
31        }
32    }
33
34    pub fn space_before(&self) -> f64 {
35        match self {
36            LayoutBlock::Paragraph(p) => p.space_before,
37            LayoutBlock::Table(_) => 0.0,
38        }
39    }
40
41    pub fn space_after(&self) -> f64 {
42        match self {
43            LayoutBlock::Paragraph(p) => p.space_after,
44            LayoutBlock::Table(_) => 0.0,
45        }
46    }
47
48    pub fn keep_next(&self) -> bool {
49        match self {
50            LayoutBlock::Paragraph(p) => p.keep_next,
51            LayoutBlock::Table(_) => false,
52        }
53    }
54
55    pub fn keep_lines(&self) -> bool {
56        match self {
57            LayoutBlock::Paragraph(p) => p.keep_lines,
58            LayoutBlock::Table(_) => false,
59        }
60    }
61
62    pub fn page_break_before(&self) -> bool {
63        match self {
64            LayoutBlock::Paragraph(p) => p.page_break_before,
65            LayoutBlock::Table(_) => false,
66        }
67    }
68
69    pub fn widow_control(&self) -> bool {
70        match self {
71            LayoutBlock::Paragraph(p) => p.widow_control,
72            LayoutBlock::Table(_) => false,
73        }
74    }
75}
76
77/// A laid-out paragraph with its lines and spacing.
78#[derive(Debug, Clone)]
79pub struct ParagraphBlock {
80    /// Laid-out lines.
81    pub lines: Vec<LayoutLine>,
82    /// Space before the paragraph in points.
83    pub space_before: f64,
84    /// Space after the paragraph in points.
85    pub space_after: f64,
86    /// Paragraph borders.
87    pub borders: Option<CT_PBdr>,
88    /// Background shading color.
89    pub shading: Option<Color>,
90    /// Left indent in points.
91    pub indent_left: f64,
92    /// Right indent in points.
93    pub indent_right: f64,
94    /// Paragraph justification.
95    pub jc: Option<ST_Jc>,
96    /// Keep with next paragraph.
97    pub keep_next: bool,
98    /// Keep all lines together on one page.
99    pub keep_lines: bool,
100    /// Force page break before this paragraph.
101    pub page_break_before: bool,
102    /// Widow/orphan control.
103    pub widow_control: bool,
104    /// Heading level (1-9) if this is a heading paragraph, for outline generation.
105    pub heading_level: Option<u32>,
106    /// Heading text for outline generation.
107    pub heading_text: Option<String>,
108}
109
110impl ParagraphBlock {
111    /// Total height of the paragraph lines (not including before/after spacing).
112    pub fn content_height(&self) -> f64 {
113        self.lines.iter().map(|l| l.height).sum()
114    }
115
116    /// Total height including spacing.
117    pub fn total_height(&self) -> f64 {
118        self.space_before + self.content_height() + self.space_after
119    }
120
121    /// Number of lines.
122    pub fn line_count(&self) -> usize {
123        self.lines.len()
124    }
125}
126
127/// Build a ParagraphBlock from resolved properties and layout lines.
128pub fn build_paragraph_block(
129    lines: Vec<LayoutLine>,
130    space_before: f64,
131    space_after: f64,
132    borders: Option<CT_PBdr>,
133    shading: Option<Color>,
134    indent_left: f64,
135    indent_right: f64,
136    jc: Option<ST_Jc>,
137    keep_next: bool,
138    keep_lines: bool,
139    page_break_before: bool,
140    widow_control: bool,
141) -> ParagraphBlock {
142    ParagraphBlock {
143        lines,
144        space_before,
145        space_after,
146        borders,
147        shading,
148        indent_left,
149        indent_right,
150        jc,
151        keep_next,
152        keep_lines,
153        page_break_before,
154        widow_control,
155        heading_level: None,
156        heading_text: None,
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn paragraph_block_height() {
166        let block = ParagraphBlock {
167            lines: vec![
168                LayoutLine {
169                    items: vec![],
170                    width: 0.0,
171                    ascent: 10.0,
172                    descent: 3.0,
173                    height: 13.0,
174                    indent_left: 0.0,
175                    available_width: 468.0,
176                    is_last: false,
177                },
178                LayoutLine {
179                    items: vec![],
180                    width: 0.0,
181                    ascent: 10.0,
182                    descent: 3.0,
183                    height: 13.0,
184                    indent_left: 0.0,
185                    available_width: 468.0,
186                    is_last: true,
187                },
188            ],
189            space_before: 6.0,
190            space_after: 8.0,
191            borders: None,
192            shading: None,
193            indent_left: 0.0,
194            indent_right: 0.0,
195            jc: None,
196            keep_next: false,
197            keep_lines: false,
198            page_break_before: false,
199            widow_control: true,
200            heading_level: None,
201            heading_text: None,
202        };
203        assert!((block.content_height() - 26.0).abs() < 0.01);
204        assert!((block.total_height() - 40.0).abs() < 0.01);
205    }
206}