use rdocx_oxml::borders::{CT_BorderEdge, CT_PBdr, CT_TabStop, CT_Tabs};
use rdocx_oxml::document::CT_SectPr;
use rdocx_oxml::properties::{CT_PPr, CT_Shd};
use rdocx_oxml::shared::{
ST_Border, ST_Jc, ST_PageOrientation, ST_SectionType, ST_TabJc, ST_TabLeader,
};
use rdocx_oxml::text::{BreakType, CT_P, CT_R, HyperlinkSpan, RunContent};
use rdocx_oxml::units::Twips;
use crate::Length;
use crate::run::{Run, RunRef};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Alignment {
Left,
Center,
Right,
Justify,
}
impl Alignment {
fn to_st_jc(self) -> ST_Jc {
match self {
Alignment::Left => ST_Jc::Left,
Alignment::Center => ST_Jc::Center,
Alignment::Right => ST_Jc::Right,
Alignment::Justify => ST_Jc::Both,
}
}
fn from_st_jc(jc: ST_Jc) -> Self {
match jc {
ST_Jc::Center => Alignment::Center,
ST_Jc::Right | ST_Jc::End => Alignment::Right,
ST_Jc::Both | ST_Jc::Distribute => Alignment::Justify,
_ => Alignment::Left,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BorderStyle {
None,
Single,
Thick,
Double,
Dotted,
Dashed,
DotDash,
Wave,
}
impl BorderStyle {
pub(crate) fn to_st(self) -> ST_Border {
match self {
Self::None => ST_Border::None,
Self::Single => ST_Border::Single,
Self::Thick => ST_Border::Thick,
Self::Double => ST_Border::Double,
Self::Dotted => ST_Border::Dotted,
Self::Dashed => ST_Border::Dashed,
Self::DotDash => ST_Border::DotDash,
Self::Wave => ST_Border::Wave,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TabAlignment {
Left,
Center,
Right,
Decimal,
}
impl TabAlignment {
fn to_st(self) -> ST_TabJc {
match self {
Self::Left => ST_TabJc::Left,
Self::Center => ST_TabJc::Center,
Self::Right => ST_TabJc::Right,
Self::Decimal => ST_TabJc::Decimal,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TabLeader {
None,
Dot,
Hyphen,
Underscore,
}
impl TabLeader {
fn to_st(self) -> ST_TabLeader {
match self {
Self::None => ST_TabLeader::None,
Self::Dot => ST_TabLeader::Dot,
Self::Hyphen => ST_TabLeader::Hyphen,
Self::Underscore => ST_TabLeader::Underscore,
}
}
}
pub struct Paragraph<'a> {
pub(crate) inner: &'a mut CT_P,
}
impl<'a> Paragraph<'a> {
pub fn text(&self) -> String {
self.inner.text()
}
pub fn add_footnote_ref(&mut self, id: i32) {
use rdocx_oxml::text::{CT_R, RunContent};
let mut r = CT_R::new("");
r.content = vec![RunContent::FootnoteRef { id }];
self.inner.runs.push(r);
}
pub fn add_run(&mut self, text: &str) -> Run<'_> {
self.inner.runs.push(CT_R::new(text));
Run {
inner: self.inner.runs.last_mut().unwrap(),
}
}
pub fn add_line_break(&mut self) {
let mut run = CT_R::new("");
run.content = vec![RunContent::Break(BreakType::Line)];
self.inner.runs.push(run);
}
pub fn add_hyperlink(&mut self, text: &str, relationship_id: &str) -> Run<'_> {
let run_start = self.inner.runs.len();
self.inner.runs.push(CT_R::new(text));
self.inner.hyperlinks.push(HyperlinkSpan {
rel_id: Some(relationship_id.to_string()),
anchor: None,
run_start,
run_end: run_start + 1,
});
Run {
inner: self.inner.runs.last_mut().unwrap(),
}
}
pub fn run_count(&self) -> usize {
self.inner.runs.len()
}
pub fn run(&self, index: usize) -> Option<RunRef<'_>> {
self.inner.runs.get(index).map(|inner| RunRef { inner })
}
pub fn run_mut(&mut self, index: usize) -> Option<Run<'_>> {
self.inner.runs.get_mut(index).map(|inner| Run { inner })
}
pub fn runs(&self) -> impl Iterator<Item = RunRef<'_>> {
self.inner.runs.iter().map(|r| RunRef { inner: r })
}
pub fn alignment(mut self, align: Alignment) -> Self {
self.set_alignment(align);
self
}
pub fn set_alignment(&mut self, align: Alignment) {
self.set_alignment_value(Some(align));
}
pub fn set_alignment_value(&mut self, align: Option<Alignment>) {
if align.is_none() && self.inner.properties.is_none() {
return;
}
self.ensure_ppr().jc = align.map(Alignment::to_st_jc);
}
pub fn style(mut self, style_id: &str) -> Self {
self.set_style(style_id);
self
}
pub fn set_style(&mut self, style_id: &str) {
self.ensure_ppr().style_id = Some(style_id.to_string());
}
pub fn numbering(mut self, num_id: u32, level: u32) -> Self {
let _ = self.set_numbering(num_id, level);
self
}
pub fn set_numbering(&mut self, num_id: u32, level: u32) -> bool {
self.set_numbering_value(Some((num_id, level)))
}
pub fn set_numbering_value(&mut self, numbering: Option<(u32, u32)>) -> bool {
if numbering.is_some_and(|(_, level)| level > 8) {
return false;
}
if numbering.is_none() && self.inner.properties.is_none() {
return true;
}
let ppr = self.ensure_ppr();
ppr.num_id = numbering.map(|(num_id, _)| num_id);
ppr.num_ilvl = numbering.map(|(_, level)| level);
true
}
pub fn space_before(mut self, length: Length) -> Self {
self.set_space_before(length);
self
}
pub fn set_space_before(&mut self, length: Length) {
self.set_space_before_value(Some(length));
}
pub fn set_space_before_value(&mut self, length: Option<Length>) {
if length.is_none() && self.inner.properties.is_none() {
return;
}
self.ensure_ppr().space_before = length.map(Length::as_twips);
}
pub fn space_after(mut self, length: Length) -> Self {
self.set_space_after(length);
self
}
pub fn set_space_after(&mut self, length: Length) {
self.set_space_after_value(Some(length));
}
pub fn set_space_after_value(&mut self, length: Option<Length>) {
if length.is_none() && self.inner.properties.is_none() {
return;
}
self.ensure_ppr().space_after = length.map(Length::as_twips);
}
pub fn indent_left(mut self, length: Length) -> Self {
self.set_indent_left(length);
self
}
pub fn set_indent_left(&mut self, length: Length) {
self.set_indent_left_value(Some(length));
}
pub fn set_indent_left_value(&mut self, length: Option<Length>) {
if length.is_none() && self.inner.properties.is_none() {
return;
}
self.ensure_ppr().ind_left = length.map(Length::as_twips);
}
pub fn indent_right(mut self, length: Length) -> Self {
self.set_indent_right(length);
self
}
pub fn set_indent_right(&mut self, length: Length) {
self.set_indent_right_value(Some(length));
}
pub fn set_indent_right_value(&mut self, length: Option<Length>) {
if length.is_none() && self.inner.properties.is_none() {
return;
}
self.ensure_ppr().ind_right = length.map(Length::as_twips);
}
pub fn first_line_indent(mut self, length: Length) -> Self {
self.set_first_line_indent(length);
self
}
pub fn set_first_line_indent(&mut self, length: Length) {
self.ensure_ppr().ind_first_line = Some(length.as_twips());
}
pub fn set_signed_first_line_indent_value(&mut self, length: Option<Length>) {
if length.is_none() && self.inner.properties.is_none() {
return;
}
let ppr = self.ensure_ppr();
match length.map(Length::to_twips) {
Some(twips) if twips < 0 => {
ppr.ind_first_line = None;
ppr.ind_hanging = Some(Twips(twips.saturating_abs()));
}
Some(twips) => {
ppr.ind_first_line = Some(Twips(twips));
ppr.ind_hanging = None;
}
None => {
ppr.ind_first_line = None;
ppr.ind_hanging = None;
}
}
}
pub fn hanging_indent(mut self, length: Length) -> Self {
self.set_hanging_indent(length);
self
}
pub fn set_hanging_indent(&mut self, length: Length) {
self.ensure_ppr().ind_hanging = Some(length.as_twips());
}
pub fn keep_with_next(mut self, val: bool) -> Self {
self.set_keep_with_next(val);
self
}
pub fn set_keep_with_next(&mut self, val: bool) {
self.set_keep_with_next_value(Some(val));
}
pub fn set_keep_with_next_value(&mut self, val: Option<bool>) {
if val.is_none() && self.inner.properties.is_none() {
return;
}
self.ensure_ppr().keep_next = val;
}
pub fn keep_together(mut self, val: bool) -> Self {
self.set_keep_together(val);
self
}
pub fn set_keep_together(&mut self, val: bool) {
self.set_keep_together_value(Some(val));
}
pub fn set_keep_together_value(&mut self, val: Option<bool>) {
if val.is_none() && self.inner.properties.is_none() {
return;
}
self.ensure_ppr().keep_lines = val;
}
pub fn page_break_before(mut self, val: bool) -> Self {
self.set_page_break_before(val);
self
}
pub fn set_page_break_before(&mut self, val: bool) {
self.set_page_break_before_value(Some(val));
}
pub fn set_page_break_before_value(&mut self, val: Option<bool>) {
if val.is_none() && self.inner.properties.is_none() {
return;
}
self.ensure_ppr().page_break_before = val;
}
pub fn widow_control(mut self, val: bool) -> Self {
self.set_widow_control(val);
self
}
pub fn set_widow_control(&mut self, val: bool) {
self.set_widow_control_value(Some(val));
}
pub fn set_widow_control_value(&mut self, val: Option<bool>) {
if val.is_none() && self.inner.properties.is_none() {
return;
}
self.ensure_ppr().widow_control = val;
}
pub fn line_spacing(mut self, pt: f64) -> Self {
self.set_line_spacing(pt);
self
}
pub fn set_line_spacing(&mut self, pt: f64) {
let ppr = self.ensure_ppr();
ppr.line_spacing = Some(Twips::from_pt(pt));
ppr.line_rule = Some("exact".to_string());
}
pub fn clear_line_spacing(&mut self) {
if let Some(ppr) = self.inner.properties.as_mut() {
ppr.line_spacing = None;
ppr.line_rule = None;
}
}
pub fn line_spacing_multiple(mut self, multiple: f64) -> Self {
self.set_line_spacing_multiple(multiple);
self
}
pub fn set_line_spacing_multiple(&mut self, multiple: f64) {
let ppr = self.ensure_ppr();
ppr.line_spacing = Some(Twips((multiple * 240.0) as i32));
ppr.line_rule = Some("auto".to_string());
}
pub fn shading(mut self, fill_color: &str) -> Self {
self.set_shading(fill_color);
self
}
pub fn set_shading(&mut self, fill_color: &str) {
self.ensure_ppr().shading = Some(CT_Shd {
val: "clear".to_string(),
color: Some("auto".to_string()),
fill: Some(fill_color.to_string()),
});
}
pub fn border_all(mut self, style: BorderStyle, size_eighths_pt: u32, color: &str) -> Self {
self.set_border_all(style, size_eighths_pt, color);
self
}
pub fn set_border_all(&mut self, style: BorderStyle, size_eighths_pt: u32, color: &str) {
let edge = CT_BorderEdge {
val: style.to_st(),
sz: Some(size_eighths_pt),
space: Some(1),
color: Some(color.to_string()),
};
self.ensure_ppr().borders = Some(CT_PBdr {
top: Some(edge.clone()),
bottom: Some(edge.clone()),
left: Some(edge.clone()),
right: Some(edge),
between: None,
bar: None,
});
}
pub fn border_bottom(mut self, style: BorderStyle, size_eighths_pt: u32, color: &str) -> Self {
self.set_border_bottom(style, size_eighths_pt, color);
self
}
pub fn set_border_bottom(&mut self, style: BorderStyle, size_eighths_pt: u32, color: &str) {
let edge = CT_BorderEdge {
val: style.to_st(),
sz: Some(size_eighths_pt),
space: Some(1),
color: Some(color.to_string()),
};
let borders = self
.ensure_ppr()
.borders
.get_or_insert_with(CT_PBdr::default);
borders.bottom = Some(edge);
}
pub fn add_tab_stop(mut self, alignment: TabAlignment, position: Length) -> Self {
self.set_add_tab_stop(alignment, position);
self
}
pub fn set_add_tab_stop(&mut self, alignment: TabAlignment, position: Length) {
let tabs = self
.ensure_ppr()
.tabs
.get_or_insert_with(|| CT_Tabs { tabs: Vec::new() });
tabs.tabs.push(CT_TabStop {
val: alignment.to_st(),
pos: position.as_twips(),
leader: None,
source_occurrence: None,
});
}
pub fn add_tab_stop_with_leader(
mut self,
alignment: TabAlignment,
position: Length,
leader: TabLeader,
) -> Self {
self.set_add_tab_stop_with_leader(alignment, position, leader);
self
}
pub fn set_add_tab_stop_with_leader(
&mut self,
alignment: TabAlignment,
position: Length,
leader: TabLeader,
) {
let tabs = self
.ensure_ppr()
.tabs
.get_or_insert_with(|| CT_Tabs { tabs: Vec::new() });
tabs.tabs.push(CT_TabStop {
val: alignment.to_st(),
pos: position.as_twips(),
leader: Some(leader.to_st()),
source_occurrence: None,
});
}
pub fn outline_level(mut self, level: u32) -> Self {
self.set_outline_level(level);
self
}
pub fn set_outline_level(&mut self, level: u32) {
self.ensure_ppr().outline_lvl = Some(level);
}
pub fn section_break(mut self, break_type: SectionBreak) -> Self {
self.set_section_break(break_type);
self
}
pub fn set_section_break(&mut self, break_type: SectionBreak) {
let sect = self.ensure_sect_pr();
sect.section_type = Some(break_type.to_st());
}
pub fn section_landscape(mut self) -> Self {
self.set_section_landscape();
self
}
pub fn set_section_landscape(&mut self) {
let sect = self.ensure_sect_pr();
sect.orientation = Some(ST_PageOrientation::Landscape);
sect.page_width = Some(Twips(15840)); sect.page_height = Some(Twips(12240)); }
pub fn section_portrait(mut self) -> Self {
self.set_section_portrait();
self
}
pub fn set_section_portrait(&mut self) {
let sect = self.ensure_sect_pr();
sect.orientation = Some(ST_PageOrientation::Portrait);
sect.page_width = Some(Twips(12240)); sect.page_height = Some(Twips(15840)); }
pub fn section_page_size(mut self, width: crate::Length, height: crate::Length) -> Self {
self.set_section_page_size(width, height);
self
}
pub fn set_section_page_size(&mut self, width: crate::Length, height: crate::Length) {
let sect = self.ensure_sect_pr();
sect.page_width = Some(width.as_twips());
sect.page_height = Some(height.as_twips());
}
fn ensure_ppr(&mut self) -> &mut CT_PPr {
self.inner.properties.get_or_insert_with(CT_PPr::default)
}
fn ensure_sect_pr(&mut self) -> &mut CT_SectPr {
let ppr = self.ensure_ppr();
ppr.sect_pr.get_or_insert_with(CT_SectPr::default_letter)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SectionBreak {
NextPage,
Continuous,
EvenPage,
OddPage,
}
impl SectionBreak {
fn to_st(self) -> ST_SectionType {
match self {
SectionBreak::NextPage => ST_SectionType::NextPage,
SectionBreak::Continuous => ST_SectionType::Continuous,
SectionBreak::EvenPage => ST_SectionType::EvenPage,
SectionBreak::OddPage => ST_SectionType::OddPage,
}
}
}
pub struct ParagraphRef<'a> {
pub(crate) inner: &'a CT_P,
}
impl<'a> ParagraphRef<'a> {
pub fn text(&self) -> String {
self.inner.text()
}
pub fn run_count(&self) -> usize {
self.inner.runs.len()
}
pub fn run(&self, index: usize) -> Option<RunRef<'_>> {
self.inner.runs.get(index).map(|inner| RunRef { inner })
}
pub fn style_id(&self) -> Option<&str> {
self.inner
.properties
.as_ref()
.and_then(|ppr| ppr.style_id.as_deref())
}
pub fn is_page_break_before(&self) -> bool {
self.page_break_before_value().unwrap_or(false)
}
pub fn keep_with_next_value(&self) -> Option<bool> {
self.inner.properties.as_ref().and_then(|ppr| ppr.keep_next)
}
pub fn keep_together_value(&self) -> Option<bool> {
self.inner
.properties
.as_ref()
.and_then(|ppr| ppr.keep_lines)
}
pub fn page_break_before_value(&self) -> Option<bool> {
self.inner
.properties
.as_ref()
.and_then(|ppr| ppr.page_break_before)
}
pub fn widow_control_value(&self) -> Option<bool> {
self.inner
.properties
.as_ref()
.and_then(|ppr| ppr.widow_control)
}
pub fn line_spacing_multiple(&self) -> Option<f64> {
let ppr = self.inner.properties.as_ref()?;
let line = ppr.line_spacing?;
match ppr.line_rule.as_deref() {
None | Some("auto") => Some(line.0 as f64 / 240.0),
_ => None,
}
}
pub fn line_spacing(&self) -> Option<Length> {
let ppr = self.inner.properties.as_ref()?;
let line = ppr.line_spacing?;
match ppr.line_rule.as_deref() {
Some("exact") | Some("atLeast") => Some(Length::twips(line.0)),
_ => None,
}
}
pub fn hyperlink_spans(&self) -> Vec<(usize, usize, Option<&str>)> {
self.inner
.hyperlinks
.iter()
.map(|h| (h.run_start, h.run_end, h.rel_id.as_deref()))
.collect()
}
pub fn numbering(&self) -> Option<(u32, u32)> {
let ppr = self.inner.properties.as_ref()?;
Some((ppr.num_id?, ppr.num_ilvl.unwrap_or(0)))
}
pub fn alignment(&self) -> Option<Alignment> {
self.inner
.properties
.as_ref()
.and_then(|ppr| ppr.jc)
.map(Alignment::from_st_jc)
}
pub fn space_before(&self) -> Option<Length> {
self.inner
.properties
.as_ref()
.and_then(|ppr| ppr.space_before)
.map(|twips| Length::twips(twips.0))
}
pub fn space_after(&self) -> Option<Length> {
self.inner
.properties
.as_ref()
.and_then(|ppr| ppr.space_after)
.map(|twips| Length::twips(twips.0))
}
pub fn indent_left(&self) -> Option<Length> {
self.inner
.properties
.as_ref()
.and_then(|ppr| ppr.ind_left)
.map(|twips| Length::twips(twips.0))
}
pub fn indent_right(&self) -> Option<Length> {
self.inner
.properties
.as_ref()
.and_then(|ppr| ppr.ind_right)
.map(|twips| Length::twips(twips.0))
}
pub fn first_line_indent(&self) -> Option<Length> {
let ppr = self.inner.properties.as_ref()?;
if let Some(twips) = ppr.ind_first_line {
return Some(Length::twips(twips.0));
}
ppr.ind_hanging
.map(|twips| Length::twips(twips.0.saturating_neg()))
}
pub fn runs(&self) -> impl Iterator<Item = RunRef<'_>> {
self.inner.runs.iter().map(|r| RunRef { inner: r })
}
pub fn has_borders(&self) -> bool {
self.inner
.properties
.as_ref()
.and_then(|ppr| ppr.borders.as_ref())
.map(|b| !b.is_empty())
.unwrap_or(false)
}
pub fn tab_stop_count(&self) -> usize {
self.inner
.properties
.as_ref()
.and_then(|ppr| ppr.tabs.as_ref())
.map(|t| t.tabs.len())
.unwrap_or(0)
}
pub fn shading_fill(&self) -> Option<&str> {
self.inner
.properties
.as_ref()
.and_then(|ppr| ppr.shading.as_ref())
.and_then(|shd| shd.fill.as_deref())
}
}