1use crate::table::TableBlock;
4use oxml_layout::{Align, Color, LayoutLine, MediaId};
5use rdocx_oxml::borders::CT_PBdr;
6use rdocx_oxml::drawing::{ST_RelativeFromH, ST_RelativeFromV};
7
8#[derive(Debug, Clone)]
15pub struct AnchoredDrawing {
16 pub behind_doc: bool,
18 pub rel_h: ST_RelativeFromH,
20 pub off_h: f64,
22 pub rel_v: ST_RelativeFromV,
24 pub off_v: f64,
26 pub width: f64,
28 pub height: f64,
30 pub content: AnchoredContent,
32}
33
34#[derive(Debug, Clone)]
36pub enum AnchoredContent {
37 Image { media_id: MediaId },
39 Shape {
44 preset: ShapePreset,
46 fill: Option<Color>,
49 text: Vec<ParagraphBlock>,
51 },
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum ShapePreset {
57 Rect,
59 Line,
61 Unsupported,
63}
64
65impl ShapePreset {
66 pub fn from_prst(prst: Option<&str>) -> Self {
68 match prst {
69 Some("rect") => ShapePreset::Rect,
70 Some("line") | Some("straightConnector1") => ShapePreset::Line,
71 _ => ShapePreset::Unsupported,
72 }
73 }
74}
75
76#[derive(Debug, Clone)]
78pub enum LayoutBlock {
79 Paragraph(ParagraphBlock),
80 Table(TableBlock),
81}
82
83impl LayoutBlock {
84 pub fn total_height(&self) -> f64 {
86 match self {
87 LayoutBlock::Paragraph(p) => p.total_height(),
88 LayoutBlock::Table(t) => t.total_height(),
89 }
90 }
91
92 pub fn content_height(&self) -> f64 {
94 match self {
95 LayoutBlock::Paragraph(p) => p.content_height(),
96 LayoutBlock::Table(t) => t.content_height(),
97 }
98 }
99
100 pub fn space_before(&self) -> f64 {
101 match self {
102 LayoutBlock::Paragraph(p) => p.space_before,
103 LayoutBlock::Table(_) => 0.0,
104 }
105 }
106
107 pub fn space_after(&self) -> f64 {
108 match self {
109 LayoutBlock::Paragraph(p) => p.space_after,
110 LayoutBlock::Table(_) => 0.0,
111 }
112 }
113
114 pub fn keep_next(&self) -> bool {
115 match self {
116 LayoutBlock::Paragraph(p) => p.keep_next,
117 LayoutBlock::Table(_) => false,
118 }
119 }
120
121 pub fn keep_lines(&self) -> bool {
122 match self {
123 LayoutBlock::Paragraph(p) => p.keep_lines,
124 LayoutBlock::Table(_) => false,
125 }
126 }
127
128 pub fn page_break_before(&self) -> bool {
129 match self {
130 LayoutBlock::Paragraph(p) => p.page_break_before,
131 LayoutBlock::Table(_) => false,
132 }
133 }
134
135 pub fn widow_control(&self) -> bool {
136 match self {
137 LayoutBlock::Paragraph(p) => p.widow_control,
138 LayoutBlock::Table(_) => false,
139 }
140 }
141}
142
143#[derive(Debug, Clone)]
145pub struct ParagraphBlock {
146 pub lines: Vec<LayoutLine>,
148 pub anchored: Vec<AnchoredDrawing>,
154 pub space_before: f64,
156 pub space_after: f64,
158 pub borders: Option<CT_PBdr>,
160 pub shading: Option<Color>,
162 pub indent_left: f64,
164 pub indent_right: f64,
166 pub jc: Option<Align>,
168 pub keep_next: bool,
170 pub keep_lines: bool,
172 pub page_break_before: bool,
174 pub widow_control: bool,
176 pub heading_level: Option<u32>,
178 pub heading_text: Option<String>,
180}
181
182impl ParagraphBlock {
183 pub fn content_height(&self) -> f64 {
185 self.lines.iter().map(|l| l.height).sum()
186 }
187
188 pub fn total_height(&self) -> f64 {
190 self.space_before + self.content_height() + self.space_after
191 }
192
193 pub fn line_count(&self) -> usize {
195 self.lines.len()
196 }
197}
198
199pub fn build_paragraph_block(
201 lines: Vec<LayoutLine>,
202 space_before: f64,
203 space_after: f64,
204 borders: Option<CT_PBdr>,
205 shading: Option<Color>,
206 indent_left: f64,
207 indent_right: f64,
208 jc: Option<Align>,
209 keep_next: bool,
210 keep_lines: bool,
211 page_break_before: bool,
212 widow_control: bool,
213) -> ParagraphBlock {
214 ParagraphBlock {
215 lines,
216 anchored: Vec::new(),
217 space_before,
218 space_after,
219 borders,
220 shading,
221 indent_left,
222 indent_right,
223 jc,
224 keep_next,
225 keep_lines,
226 page_break_before,
227 widow_control,
228 heading_level: None,
229 heading_text: None,
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 #[test]
238 fn paragraph_block_height() {
239 let block = ParagraphBlock {
240 anchored: Vec::new(),
241 lines: vec![
242 LayoutLine {
243 items: vec![],
244 width: 0.0,
245 ascent: 10.0,
246 descent: 3.0,
247 line_gap: 0.0,
248 height: 13.0,
249 indent_left: 0.0,
250 available_width: 468.0,
251 is_last: false,
252 },
253 LayoutLine {
254 items: vec![],
255 width: 0.0,
256 ascent: 10.0,
257 descent: 3.0,
258 line_gap: 0.0,
259 height: 13.0,
260 indent_left: 0.0,
261 available_width: 468.0,
262 is_last: true,
263 },
264 ],
265 space_before: 6.0,
266 space_after: 8.0,
267 borders: None,
268 shading: None,
269 indent_left: 0.0,
270 indent_right: 0.0,
271 jc: None,
272 keep_next: false,
273 keep_lines: false,
274 page_break_before: false,
275 widow_control: true,
276 heading_level: None,
277 heading_text: None,
278 };
279 assert!((block.content_height() - 26.0).abs() < 0.01);
280 assert!((block.total_height() - 40.0).abs() < 0.01);
281 }
282
283 #[test]
286 fn shape_presets_map_to_what_we_can_draw() {
287 assert_eq!(ShapePreset::from_prst(Some("rect")), ShapePreset::Rect);
288 assert_eq!(ShapePreset::from_prst(Some("line")), ShapePreset::Line);
289 assert_eq!(
290 ShapePreset::from_prst(Some("straightConnector1")),
291 ShapePreset::Line
292 );
293 assert_eq!(
294 ShapePreset::from_prst(Some("roundRect")),
295 ShapePreset::Unsupported,
296 "an unhandled preset must not silently draw as a plain rectangle"
297 );
298 assert_eq!(ShapePreset::from_prst(None), ShapePreset::Unsupported);
299 }
300}