use crate::table::TableBlock;
use oxml_layout::{Align, Color, InlineItem, LayoutLine, LineBreakParams, MediaId};
use rdocx_oxml::borders::CT_PBdr;
use rdocx_oxml::drawing::{
AnchorAlignH, AnchorAlignV, ST_RelativeFromH, ST_RelativeFromV, WrapType,
};
#[derive(Debug, Clone)]
pub struct AnchoredDrawing {
pub behind_doc: bool,
pub rel_h: ST_RelativeFromH,
pub off_h: f64,
pub rel_v: ST_RelativeFromV,
pub off_v: f64,
pub width: f64,
pub height: f64,
pub wrap: WrapType,
pub dist_top: f64,
pub dist_bottom: f64,
pub dist_left: f64,
pub dist_right: f64,
pub align_h: Option<AnchorAlignH>,
pub align_v: Option<AnchorAlignV>,
pub content: AnchoredContent,
}
#[derive(Debug, Clone)]
pub enum AnchoredContent {
Image { media_id: MediaId },
Shape {
preset: ShapePreset,
fill: Option<Color>,
text: Vec<ParagraphBlock>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShapePreset {
Rect,
Line,
Unsupported,
}
impl ShapePreset {
pub fn from_prst(prst: Option<&str>) -> Self {
match prst {
Some("rect") => ShapePreset::Rect,
Some("line") | Some("straightConnector1") => ShapePreset::Line,
_ => ShapePreset::Unsupported,
}
}
}
#[derive(Debug, Clone)]
pub enum LayoutBlock {
Paragraph(ParagraphBlock),
Table(TableBlock),
}
impl LayoutBlock {
pub fn total_height(&self) -> f64 {
match self {
LayoutBlock::Paragraph(p) => p.total_height(),
LayoutBlock::Table(t) => t.total_height(),
}
}
pub fn content_height(&self) -> f64 {
match self {
LayoutBlock::Paragraph(p) => p.content_height(),
LayoutBlock::Table(t) => t.content_height(),
}
}
pub fn space_before(&self) -> f64 {
match self {
LayoutBlock::Paragraph(p) => p.space_before,
LayoutBlock::Table(_) => 0.0,
}
}
pub fn space_after(&self) -> f64 {
match self {
LayoutBlock::Paragraph(p) => p.space_after,
LayoutBlock::Table(_) => 0.0,
}
}
pub fn keep_next(&self) -> bool {
match self {
LayoutBlock::Paragraph(p) => p.keep_next,
LayoutBlock::Table(_) => false,
}
}
pub fn keep_lines(&self) -> bool {
match self {
LayoutBlock::Paragraph(p) => p.keep_lines,
LayoutBlock::Table(_) => false,
}
}
pub fn page_break_before(&self) -> bool {
match self {
LayoutBlock::Paragraph(p) => p.page_break_before,
LayoutBlock::Table(_) => false,
}
}
pub fn widow_control(&self) -> bool {
match self {
LayoutBlock::Paragraph(p) => p.widow_control,
LayoutBlock::Table(_) => false,
}
}
}
#[derive(Debug, Clone)]
pub struct ParagraphReflow {
pub items: Vec<InlineItem>,
pub params: LineBreakParams,
}
#[derive(Debug, Clone)]
pub struct ParagraphBlock {
pub lines: Vec<LayoutLine>,
pub anchored: Vec<AnchoredDrawing>,
pub space_before: f64,
pub space_after: f64,
pub borders: Option<CT_PBdr>,
pub shading: Option<Color>,
pub indent_left: f64,
pub indent_right: f64,
pub jc: Option<Align>,
pub keep_next: bool,
pub keep_lines: bool,
pub page_break_before: bool,
pub widow_control: bool,
pub heading_level: Option<u32>,
pub heading_text: Option<String>,
pub reflow: Option<Box<ParagraphReflow>>,
pub content_offset_top: f64,
}
impl ParagraphBlock {
pub fn content_height(&self) -> f64 {
self.content_offset_top + self.lines.iter().map(|l| l.height).sum::<f64>()
}
pub fn total_height(&self) -> f64 {
self.space_before + self.content_height() + self.space_after
}
pub fn line_count(&self) -> usize {
self.lines.len()
}
}
pub fn build_paragraph_block(
lines: Vec<LayoutLine>,
space_before: f64,
space_after: f64,
borders: Option<CT_PBdr>,
shading: Option<Color>,
indent_left: f64,
indent_right: f64,
jc: Option<Align>,
keep_next: bool,
keep_lines: bool,
page_break_before: bool,
widow_control: bool,
) -> ParagraphBlock {
ParagraphBlock {
lines,
anchored: Vec::new(),
space_before,
space_after,
borders,
shading,
indent_left,
indent_right,
jc,
keep_next,
keep_lines,
page_break_before,
widow_control,
heading_level: None,
heading_text: None,
reflow: None,
content_offset_top: 0.0,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn paragraph_block_height() {
let block = ParagraphBlock {
anchored: Vec::new(),
lines: vec![
LayoutLine {
items: vec![],
width: 0.0,
ascent: 10.0,
descent: 3.0,
line_gap: 0.0,
height: 13.0,
indent_left: 0.0,
available_width: 468.0,
is_last: false,
},
LayoutLine {
items: vec![],
width: 0.0,
ascent: 10.0,
descent: 3.0,
line_gap: 0.0,
height: 13.0,
indent_left: 0.0,
available_width: 468.0,
is_last: true,
},
],
space_before: 6.0,
space_after: 8.0,
borders: None,
shading: None,
indent_left: 0.0,
indent_right: 0.0,
jc: None,
keep_next: false,
keep_lines: false,
page_break_before: false,
widow_control: true,
heading_level: None,
heading_text: None,
reflow: None,
content_offset_top: 0.0,
};
assert!((block.content_height() - 26.0).abs() < 0.01);
assert!((block.total_height() - 40.0).abs() < 0.01);
}
#[test]
fn shape_presets_map_to_what_we_can_draw() {
assert_eq!(ShapePreset::from_prst(Some("rect")), ShapePreset::Rect);
assert_eq!(ShapePreset::from_prst(Some("line")), ShapePreset::Line);
assert_eq!(
ShapePreset::from_prst(Some("straightConnector1")),
ShapePreset::Line
);
assert_eq!(
ShapePreset::from_prst(Some("roundRect")),
ShapePreset::Unsupported,
"an unhandled preset must not silently draw as a plain rectangle"
);
assert_eq!(ShapePreset::from_prst(None), ShapePreset::Unsupported);
}
}