1use rdocx_oxml::borders::CT_PBdr;
4use rdocx_oxml::drawing::{ST_RelativeFromH, ST_RelativeFromV};
5use rdocx_oxml::shared::ST_Jc;
6
7use crate::line::LayoutLine;
8use crate::output::Color;
9use crate::table::TableBlock;
10
11#[derive(Debug, Clone)]
18pub struct AnchoredDrawing {
19 pub behind_doc: bool,
21 pub rel_h: ST_RelativeFromH,
23 pub off_h: f64,
25 pub rel_v: ST_RelativeFromV,
27 pub off_v: f64,
29 pub width: f64,
31 pub height: f64,
33 pub content: AnchoredContent,
35}
36
37#[derive(Debug, Clone)]
39pub enum AnchoredContent {
40 Image { embed_id: String },
42 Shape {
47 preset: ShapePreset,
49 fill: Option<Color>,
52 text: Vec<ParagraphBlock>,
54 },
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum ShapePreset {
60 Rect,
62 Line,
64 Unsupported,
66}
67
68impl ShapePreset {
69 pub fn from_prst(prst: Option<&str>) -> Self {
71 match prst {
72 Some("rect") => ShapePreset::Rect,
73 Some("line") | Some("straightConnector1") => ShapePreset::Line,
74 _ => ShapePreset::Unsupported,
75 }
76 }
77}
78
79#[derive(Debug, Clone)]
81pub enum LayoutBlock {
82 Paragraph(ParagraphBlock),
83 Table(TableBlock),
84}
85
86impl LayoutBlock {
87 pub fn total_height(&self) -> f64 {
89 match self {
90 LayoutBlock::Paragraph(p) => p.total_height(),
91 LayoutBlock::Table(t) => t.total_height(),
92 }
93 }
94
95 pub fn content_height(&self) -> f64 {
97 match self {
98 LayoutBlock::Paragraph(p) => p.content_height(),
99 LayoutBlock::Table(t) => t.content_height(),
100 }
101 }
102
103 pub fn space_before(&self) -> f64 {
104 match self {
105 LayoutBlock::Paragraph(p) => p.space_before,
106 LayoutBlock::Table(_) => 0.0,
107 }
108 }
109
110 pub fn space_after(&self) -> f64 {
111 match self {
112 LayoutBlock::Paragraph(p) => p.space_after,
113 LayoutBlock::Table(_) => 0.0,
114 }
115 }
116
117 pub fn keep_next(&self) -> bool {
118 match self {
119 LayoutBlock::Paragraph(p) => p.keep_next,
120 LayoutBlock::Table(_) => false,
121 }
122 }
123
124 pub fn keep_lines(&self) -> bool {
125 match self {
126 LayoutBlock::Paragraph(p) => p.keep_lines,
127 LayoutBlock::Table(_) => false,
128 }
129 }
130
131 pub fn page_break_before(&self) -> bool {
132 match self {
133 LayoutBlock::Paragraph(p) => p.page_break_before,
134 LayoutBlock::Table(_) => false,
135 }
136 }
137
138 pub fn widow_control(&self) -> bool {
139 match self {
140 LayoutBlock::Paragraph(p) => p.widow_control,
141 LayoutBlock::Table(_) => false,
142 }
143 }
144}
145
146#[derive(Debug, Clone)]
148pub struct ParagraphBlock {
149 pub lines: Vec<LayoutLine>,
151 pub anchored: Vec<AnchoredDrawing>,
157 pub space_before: f64,
159 pub space_after: f64,
161 pub borders: Option<CT_PBdr>,
163 pub shading: Option<Color>,
165 pub indent_left: f64,
167 pub indent_right: f64,
169 pub jc: Option<ST_Jc>,
171 pub keep_next: bool,
173 pub keep_lines: bool,
175 pub page_break_before: bool,
177 pub widow_control: bool,
179 pub heading_level: Option<u32>,
181 pub heading_text: Option<String>,
183}
184
185impl ParagraphBlock {
186 pub fn content_height(&self) -> f64 {
188 self.lines.iter().map(|l| l.height).sum()
189 }
190
191 pub fn total_height(&self) -> f64 {
193 self.space_before + self.content_height() + self.space_after
194 }
195
196 pub fn line_count(&self) -> usize {
198 self.lines.len()
199 }
200}
201
202pub fn build_paragraph_block(
204 lines: Vec<LayoutLine>,
205 space_before: f64,
206 space_after: f64,
207 borders: Option<CT_PBdr>,
208 shading: Option<Color>,
209 indent_left: f64,
210 indent_right: f64,
211 jc: Option<ST_Jc>,
212 keep_next: bool,
213 keep_lines: bool,
214 page_break_before: bool,
215 widow_control: bool,
216) -> ParagraphBlock {
217 ParagraphBlock {
218 lines,
219 anchored: Vec::new(),
220 space_before,
221 space_after,
222 borders,
223 shading,
224 indent_left,
225 indent_right,
226 jc,
227 keep_next,
228 keep_lines,
229 page_break_before,
230 widow_control,
231 heading_level: None,
232 heading_text: None,
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239
240 #[test]
241 fn paragraph_block_height() {
242 let block = ParagraphBlock {
243 anchored: Vec::new(),
244 lines: vec![
245 LayoutLine {
246 items: vec![],
247 width: 0.0,
248 ascent: 10.0,
249 descent: 3.0,
250 height: 13.0,
251 indent_left: 0.0,
252 available_width: 468.0,
253 is_last: false,
254 },
255 LayoutLine {
256 items: vec![],
257 width: 0.0,
258 ascent: 10.0,
259 descent: 3.0,
260 height: 13.0,
261 indent_left: 0.0,
262 available_width: 468.0,
263 is_last: true,
264 },
265 ],
266 space_before: 6.0,
267 space_after: 8.0,
268 borders: None,
269 shading: None,
270 indent_left: 0.0,
271 indent_right: 0.0,
272 jc: None,
273 keep_next: false,
274 keep_lines: false,
275 page_break_before: false,
276 widow_control: true,
277 heading_level: None,
278 heading_text: None,
279 };
280 assert!((block.content_height() - 26.0).abs() < 0.01);
281 assert!((block.total_height() - 40.0).abs() < 0.01);
282 }
283
284 #[test]
287 fn shape_presets_map_to_what_we_can_draw() {
288 assert_eq!(ShapePreset::from_prst(Some("rect")), ShapePreset::Rect);
289 assert_eq!(ShapePreset::from_prst(Some("line")), ShapePreset::Line);
290 assert_eq!(
291 ShapePreset::from_prst(Some("straightConnector1")),
292 ShapePreset::Line
293 );
294 assert_eq!(
295 ShapePreset::from_prst(Some("roundRect")),
296 ShapePreset::Unsupported,
297 "an unhandled preset must not silently draw as a plain rectangle"
298 );
299 assert_eq!(ShapePreset::from_prst(None), ShapePreset::Unsupported);
300 }
301}