use rdocx_oxml::properties::{CT_RPr, CT_Shd};
use rdocx_oxml::shared::ST_Underline;
use rdocx_oxml::text::{CT_R, CT_Text, RunContent};
use rdocx_oxml::units::{HalfPoint, Twips};
use crate::Length;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnderlineStyle {
None,
Single,
Double,
Thick,
Dotted,
Dash,
Wave,
Words,
}
impl UnderlineStyle {
fn to_code(self) -> i32 {
match self {
Self::None => 0,
Self::Single => 1,
Self::Words => 2,
Self::Double => 3,
Self::Dotted => 4,
Self::Thick => 6,
Self::Dash => 7,
Self::Wave => 11,
}
}
}
fn underline_from_code(code: i32) -> Option<ST_Underline> {
match code {
0 => Some(ST_Underline::None),
1 => Some(ST_Underline::Single),
2 => Some(ST_Underline::Words),
3 => Some(ST_Underline::Double),
4 => Some(ST_Underline::Dotted),
6 => Some(ST_Underline::Thick),
7 => Some(ST_Underline::Dash),
9 => Some(ST_Underline::DotDash),
10 => Some(ST_Underline::DotDotDash),
11 => Some(ST_Underline::Wave),
_ => None,
}
}
fn underline_to_code(value: ST_Underline) -> i32 {
match value {
ST_Underline::None => 0,
ST_Underline::Single => 1,
ST_Underline::Words => 2,
ST_Underline::Double => 3,
ST_Underline::Dotted => 4,
ST_Underline::Thick => 6,
ST_Underline::Dash => 7,
ST_Underline::DotDash => 9,
ST_Underline::DotDotDash => 10,
ST_Underline::Wave => 11,
}
}
pub struct Run<'a> {
pub(crate) inner: &'a mut CT_R,
}
impl<'a> Run<'a> {
pub fn text(&self) -> String {
self.inner.text()
}
pub fn set_text(&mut self, text: &str) {
self.inner.content = vec![RunContent::Text(CT_Text::new(text))];
}
pub fn add_text(&mut self, text: &str) {
self.inner
.content
.push(RunContent::Text(CT_Text::new(text)));
}
pub fn bold(mut self, val: bool) -> Self {
self.set_bold(val);
self
}
pub fn set_bold(&mut self, val: bool) {
self.set_bold_value(Some(val));
}
pub fn set_bold_value(&mut self, val: Option<bool>) {
if val.is_none() && self.inner.properties.is_none() {
return;
}
let rpr = self.ensure_rpr();
rpr.bold = val;
rpr.bold_cs = val;
}
pub fn italic(mut self, val: bool) -> Self {
self.set_italic(val);
self
}
pub fn set_italic(&mut self, val: bool) {
self.set_italic_value(Some(val));
}
pub fn set_italic_value(&mut self, val: Option<bool>) {
if val.is_none() && self.inner.properties.is_none() {
return;
}
let rpr = self.ensure_rpr();
rpr.italic = val;
rpr.italic_cs = val;
}
pub fn underline(mut self, val: bool) -> Self {
self.set_underline(val);
self
}
pub fn set_underline(&mut self, val: bool) {
self.set_underline_style_value(Some(if val {
UnderlineStyle::Single
} else {
UnderlineStyle::None
}));
}
pub fn underline_style(mut self, style: UnderlineStyle) -> Self {
self.set_underline_style(style);
self
}
pub fn set_underline_style(&mut self, style: UnderlineStyle) {
self.set_underline_style_value(Some(style));
}
pub fn set_underline_style_value(&mut self, style: Option<UnderlineStyle>) {
let applied = self.set_underline_code_value(style.map(UnderlineStyle::to_code));
debug_assert!(applied);
}
pub fn set_underline_code_value(&mut self, code: Option<i32>) -> bool {
let underline = match code {
Some(code) => match underline_from_code(code) {
Some(underline) => Some(underline),
None => return false,
},
None => None,
};
if underline.is_none() && self.inner.properties.is_none() {
return true;
}
self.ensure_rpr().underline = underline;
true
}
pub fn size(mut self, pt: f64) -> Self {
self.set_size(pt);
self
}
pub fn set_size(&mut self, pt: f64) {
self.set_size_value(Some(pt));
}
pub fn set_size_value(&mut self, pt: Option<f64>) {
if pt.is_none() && self.inner.properties.is_none() {
return;
}
let hp = pt.map(HalfPoint::from_pt);
let rpr = self.ensure_rpr();
rpr.sz = hp;
rpr.sz_cs = hp;
}
pub fn font(mut self, name: &str) -> Self {
self.set_font(name);
self
}
pub fn set_font(&mut self, name: &str) {
self.set_font_value(Some(name));
}
pub fn set_font_value(&mut self, name: Option<&str>) {
if name.is_none() && self.inner.properties.is_none() {
return;
}
let rpr = self.ensure_rpr();
rpr.font_ascii = name.map(str::to_owned);
rpr.font_hansi = name.map(str::to_owned);
rpr.font_east_asia = name.map(str::to_owned);
rpr.font_cs = name.map(str::to_owned);
}
pub fn color(mut self, hex: &str) -> Self {
self.set_color(hex);
self
}
pub fn set_color(&mut self, hex: &str) {
self.set_color_value(Some(hex));
}
pub fn set_color_value(&mut self, hex: Option<&str>) {
if hex.is_none() && self.inner.properties.is_none() {
return;
}
self.ensure_rpr().color = hex.map(str::to_owned);
}
pub fn highlight(mut self, color: &str) -> Self {
self.set_highlight(color);
self
}
pub fn set_highlight(&mut self, color: &str) {
self.ensure_rpr().shading = Some(CT_Shd {
val: "clear".to_string(),
color: Some("auto".to_string()),
fill: Some(color.to_string()),
});
}
pub fn strike(mut self, val: bool) -> Self {
self.set_strike(val);
self
}
pub fn set_strike(&mut self, val: bool) {
self.set_strike_value(Some(val));
}
pub fn set_strike_value(&mut self, val: Option<bool>) {
if val.is_none() && self.inner.properties.is_none() {
return;
}
self.ensure_rpr().strike = val;
}
pub fn double_strike(mut self, val: bool) -> Self {
self.set_double_strike(val);
self
}
pub fn set_double_strike(&mut self, val: bool) {
self.ensure_rpr().dstrike = Some(val);
}
pub fn all_caps(mut self, val: bool) -> Self {
self.set_all_caps(val);
self
}
pub fn set_all_caps(&mut self, val: bool) {
self.ensure_rpr().caps = Some(val);
}
pub fn small_caps(mut self, val: bool) -> Self {
self.set_small_caps(val);
self
}
pub fn set_small_caps(&mut self, val: bool) {
self.ensure_rpr().small_caps = Some(val);
}
pub fn superscript(mut self) -> Self {
self.set_superscript();
self
}
pub fn set_superscript(&mut self) {
self.ensure_rpr().vert_align = Some("superscript".to_string());
}
pub fn subscript(mut self) -> Self {
self.set_subscript();
self
}
pub fn set_subscript(&mut self) {
self.ensure_rpr().vert_align = Some("subscript".to_string());
}
pub fn character_spacing(mut self, spacing: Length) -> Self {
self.set_character_spacing(spacing);
self
}
pub fn set_character_spacing(&mut self, spacing: Length) {
self.ensure_rpr().spacing = Some(spacing.as_twips());
}
pub fn width_scale(mut self, percent: u32) -> Self {
self.set_width_scale(percent);
self
}
pub fn set_width_scale(&mut self, percent: u32) {
self.ensure_rpr().width_scale = Some(percent);
}
pub fn position(mut self, half_points: i32) -> Self {
self.set_position(half_points);
self
}
pub fn set_position(&mut self, half_points: i32) {
self.ensure_rpr().position = Some(half_points);
}
pub fn hidden(mut self, val: bool) -> Self {
self.set_hidden(val);
self
}
pub fn set_hidden(&mut self, val: bool) {
self.ensure_rpr().vanish = Some(val);
}
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_rpr().style_id = Some(style_id.to_string());
}
fn ensure_rpr(&mut self) -> &mut CT_RPr {
self.inner.properties.get_or_insert_with(CT_RPr::default)
}
}
pub struct RunRef<'a> {
pub(crate) inner: &'a CT_R,
}
impl<'a> RunRef<'a> {
pub fn text(&self) -> String {
self.inner.text()
}
pub fn footnote_id(&self) -> Option<i32> {
use rdocx_oxml::text::RunContent;
self.inner.content.iter().find_map(|c| match c {
RunContent::FootnoteRef { id } => Some(*id),
_ => None,
})
}
pub fn is_bold(&self) -> bool {
self.bold_value().unwrap_or(false)
}
pub fn bold_value(&self) -> Option<bool> {
self.inner.properties.as_ref().and_then(|rpr| rpr.bold)
}
pub fn is_italic(&self) -> bool {
self.italic_value().unwrap_or(false)
}
pub fn italic_value(&self) -> Option<bool> {
self.inner.properties.as_ref().and_then(|rpr| rpr.italic)
}
pub fn is_strike(&self) -> bool {
self.strike_value().unwrap_or(false)
}
pub fn strike_value(&self) -> Option<bool> {
self.inner.properties.as_ref().and_then(|rpr| rpr.strike)
}
pub fn is_underline(&self) -> bool {
self.underline_code_value().is_some_and(|code| code != 0)
}
pub fn underline_code_value(&self) -> Option<i32> {
self.inner
.properties
.as_ref()
.and_then(|rpr| rpr.underline)
.map(underline_to_code)
}
pub fn size(&self) -> Option<f64> {
self.inner
.properties
.as_ref()
.and_then(|rpr| rpr.sz)
.map(|hp| hp.to_pt())
}
pub fn font_name(&self) -> Option<&str> {
self.inner
.properties
.as_ref()
.and_then(|rpr| rpr.font_ascii.as_deref())
}
pub fn color(&self) -> Option<&str> {
self.inner
.properties
.as_ref()
.and_then(|rpr| rpr.color.as_deref())
}
pub fn character_spacing(&self) -> Option<Twips> {
self.inner.properties.as_ref().and_then(|rpr| rpr.spacing)
}
pub fn highlight(&self) -> Option<String> {
let rpr = self.inner.properties.as_ref()?;
if let Some(h) = rpr.highlight {
return Some(h.to_str().to_string());
}
rpr.shading.as_ref().and_then(|sh| sh.fill.clone())
}
pub fn inline_image(&self) -> Option<(&str, Option<&str>)> {
use rdocx_oxml::text::RunContent;
for c in &self.inner.content {
if let RunContent::Drawing(d) = c
&& let Some(inline) = &d.inline
{
return Some((inline.embed_id.as_str(), inline.description.as_deref()));
}
}
None
}
pub fn position(&self) -> Option<i32> {
self.inner.properties.as_ref().and_then(|rpr| rpr.position)
}
pub fn vert_align(&self) -> Option<&str> {
self.inner
.properties
.as_ref()
.and_then(|rpr| rpr.vert_align.as_deref())
}
pub fn style_id(&self) -> Option<&str> {
self.inner
.properties
.as_ref()
.and_then(|rpr| rpr.style_id.as_deref())
}
}