1use crate::table::TableBlock;
4use oxml_layout::{Align, Color, InlineItem, LayoutLine, LineBreakParams, MediaId};
5use rdocx_oxml::borders::CT_PBdr;
6use rdocx_oxml::drawing::{
7 AnchorAlignH, AnchorAlignV, ST_RelativeFromH, ST_RelativeFromV, WrapType,
8};
9
10#[derive(Debug, Clone)]
17pub struct AnchoredDrawing {
18 pub behind_doc: bool,
20 pub rel_h: ST_RelativeFromH,
22 pub off_h: f64,
24 pub rel_v: ST_RelativeFromV,
26 pub off_v: f64,
28 pub width: f64,
30 pub height: f64,
32 pub wrap: WrapType,
34 pub dist_top: f64,
37 pub dist_bottom: f64,
38 pub dist_left: f64,
39 pub dist_right: f64,
40 pub align_h: Option<AnchorAlignH>,
42 pub align_v: Option<AnchorAlignV>,
44 pub content: AnchoredContent,
46}
47
48#[derive(Debug, Clone)]
50pub enum AnchoredContent {
51 Image { media_id: MediaId },
53 Shape {
58 preset: ShapePreset,
60 fill: Option<Color>,
63 text: Vec<ParagraphBlock>,
65 },
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum ShapePreset {
71 Rect,
73 Line,
75 Unsupported,
77}
78
79impl ShapePreset {
80 pub fn from_prst(prst: Option<&str>) -> Self {
82 match prst {
83 Some("rect") => ShapePreset::Rect,
84 Some("line") | Some("straightConnector1") => ShapePreset::Line,
85 _ => ShapePreset::Unsupported,
86 }
87 }
88}
89
90#[derive(Debug, Clone)]
92pub enum LayoutBlock {
93 Paragraph(ParagraphBlock),
94 Table(TableBlock),
95}
96
97impl LayoutBlock {
98 pub fn total_height(&self) -> f64 {
100 match self {
101 LayoutBlock::Paragraph(p) => p.total_height(),
102 LayoutBlock::Table(t) => t.total_height(),
103 }
104 }
105
106 pub fn content_height(&self) -> f64 {
108 match self {
109 LayoutBlock::Paragraph(p) => p.content_height(),
110 LayoutBlock::Table(t) => t.content_height(),
111 }
112 }
113
114 pub fn space_before(&self) -> f64 {
115 match self {
116 LayoutBlock::Paragraph(p) => p.space_before,
117 LayoutBlock::Table(_) => 0.0,
118 }
119 }
120
121 pub fn space_after(&self) -> f64 {
122 match self {
123 LayoutBlock::Paragraph(p) => p.space_after,
124 LayoutBlock::Table(_) => 0.0,
125 }
126 }
127
128 pub fn keep_next(&self) -> bool {
129 match self {
130 LayoutBlock::Paragraph(p) => p.keep_next,
131 LayoutBlock::Table(_) => false,
132 }
133 }
134
135 pub fn keep_lines(&self) -> bool {
136 match self {
137 LayoutBlock::Paragraph(p) => p.keep_lines,
138 LayoutBlock::Table(_) => false,
139 }
140 }
141
142 pub fn page_break_before(&self) -> bool {
143 match self {
144 LayoutBlock::Paragraph(p) => p.page_break_before,
145 LayoutBlock::Table(_) => false,
146 }
147 }
148
149 pub fn widow_control(&self) -> bool {
150 match self {
151 LayoutBlock::Paragraph(p) => p.widow_control,
152 LayoutBlock::Table(_) => false,
153 }
154 }
155}
156
157#[derive(Debug, Clone)]
167pub struct ParagraphReflow {
168 pub items: Vec<InlineItem>,
169 pub params: LineBreakParams,
170}
171
172#[derive(Debug, Clone)]
174pub struct ParagraphBlock {
175 pub lines: Vec<LayoutLine>,
177 pub anchored: Vec<AnchoredDrawing>,
183 pub space_before: f64,
185 pub space_after: f64,
187 pub borders: Option<CT_PBdr>,
189 pub shading: Option<Color>,
191 pub indent_left: f64,
193 pub indent_right: f64,
195 pub jc: Option<Align>,
197 pub keep_next: bool,
199 pub keep_lines: bool,
201 pub page_break_before: bool,
203 pub widow_control: bool,
205 pub heading_level: Option<u32>,
207 pub heading_text: Option<String>,
209 pub reflow: Option<Box<ParagraphReflow>>,
213 pub content_offset_top: f64,
216}
217
218impl ParagraphBlock {
219 pub fn content_height(&self) -> f64 {
221 self.content_offset_top + self.lines.iter().map(|l| l.height).sum::<f64>()
222 }
223
224 pub fn total_height(&self) -> f64 {
226 self.space_before + self.content_height() + self.space_after
227 }
228
229 pub fn line_count(&self) -> usize {
231 self.lines.len()
232 }
233}
234
235pub fn build_paragraph_block(
237 lines: Vec<LayoutLine>,
238 space_before: f64,
239 space_after: f64,
240 borders: Option<CT_PBdr>,
241 shading: Option<Color>,
242 indent_left: f64,
243 indent_right: f64,
244 jc: Option<Align>,
245 keep_next: bool,
246 keep_lines: bool,
247 page_break_before: bool,
248 widow_control: bool,
249) -> ParagraphBlock {
250 ParagraphBlock {
251 lines,
252 anchored: Vec::new(),
253 space_before,
254 space_after,
255 borders,
256 shading,
257 indent_left,
258 indent_right,
259 jc,
260 keep_next,
261 keep_lines,
262 page_break_before,
263 widow_control,
264 heading_level: None,
265 heading_text: None,
266 reflow: None,
267 content_offset_top: 0.0,
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn paragraph_block_height() {
277 let block = ParagraphBlock {
278 anchored: Vec::new(),
279 lines: vec![
280 LayoutLine {
281 items: vec![],
282 width: 0.0,
283 ascent: 10.0,
284 descent: 3.0,
285 line_gap: 0.0,
286 height: 13.0,
287 indent_left: 0.0,
288 available_width: 468.0,
289 is_last: false,
290 },
291 LayoutLine {
292 items: vec![],
293 width: 0.0,
294 ascent: 10.0,
295 descent: 3.0,
296 line_gap: 0.0,
297 height: 13.0,
298 indent_left: 0.0,
299 available_width: 468.0,
300 is_last: true,
301 },
302 ],
303 space_before: 6.0,
304 space_after: 8.0,
305 borders: None,
306 shading: None,
307 indent_left: 0.0,
308 indent_right: 0.0,
309 jc: None,
310 keep_next: false,
311 keep_lines: false,
312 page_break_before: false,
313 widow_control: true,
314 heading_level: None,
315 heading_text: None,
316 reflow: None,
317 content_offset_top: 0.0,
318 };
319 assert!((block.content_height() - 26.0).abs() < 0.01);
320 assert!((block.total_height() - 40.0).abs() < 0.01);
321 }
322
323 #[test]
326 fn shape_presets_map_to_what_we_can_draw() {
327 assert_eq!(ShapePreset::from_prst(Some("rect")), ShapePreset::Rect);
328 assert_eq!(ShapePreset::from_prst(Some("line")), ShapePreset::Line);
329 assert_eq!(
330 ShapePreset::from_prst(Some("straightConnector1")),
331 ShapePreset::Line
332 );
333 assert_eq!(
334 ShapePreset::from_prst(Some("roundRect")),
335 ShapePreset::Unsupported,
336 "an unhandled preset must not silently draw as a plain rectangle"
337 );
338 assert_eq!(ShapePreset::from_prst(None), ShapePreset::Unsupported);
339 }
340}