use hwpforge_foundation::{BulletIndex, HeadingType, NumberFormatType, NumberingIndex};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ParaHead {
pub start: u32,
pub level: u32,
pub num_format: NumberFormatType,
pub text: String,
pub checkable: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct NumberingDef {
pub id: u32,
pub start: u32,
pub levels: Vec<ParaHead>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct BulletDef {
pub id: u32,
pub bullet_char: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub checked_char: Option<String>,
pub use_image: bool,
pub para_head: ParaHead,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ParagraphListRef {
Outline {
level: u8,
},
Number {
numbering_id: NumberingIndex,
level: u8,
},
Bullet {
bullet_id: BulletIndex,
level: u8,
},
CheckBullet {
bullet_id: BulletIndex,
level: u8,
checked: bool,
},
}
impl ParagraphListRef {
pub const MAX_LEVEL: u8 = 9;
pub const fn level(self) -> u8 {
match self {
Self::Outline { level }
| Self::Number { level, .. }
| Self::Bullet { level, .. }
| Self::CheckBullet { level, .. } => level,
}
}
pub const fn heading_type(self) -> HeadingType {
match self {
Self::Outline { .. } => HeadingType::Outline,
Self::Number { .. } => HeadingType::Number,
Self::Bullet { .. } | Self::CheckBullet { .. } => HeadingType::Bullet,
}
}
pub const fn checked(self) -> Option<bool> {
match self {
Self::CheckBullet { checked, .. } => Some(checked),
Self::Outline { .. } | Self::Number { .. } | Self::Bullet { .. } => None,
}
}
}
impl NumberingDef {
pub fn default_outline() -> Self {
Self {
id: 1,
start: 0,
levels: vec![
ParaHead {
start: 1,
level: 1,
num_format: NumberFormatType::Digit,
text: "^1.".into(),
checkable: false,
},
ParaHead {
start: 1,
level: 2,
num_format: NumberFormatType::HangulSyllable,
text: "^2.".into(),
checkable: false,
},
ParaHead {
start: 1,
level: 3,
num_format: NumberFormatType::Digit,
text: "^3)".into(),
checkable: false,
},
ParaHead {
start: 1,
level: 4,
num_format: NumberFormatType::HangulSyllable,
text: "^4)".into(),
checkable: false,
},
ParaHead {
start: 1,
level: 5,
num_format: NumberFormatType::Digit,
text: "(^5)".into(),
checkable: false,
},
ParaHead {
start: 1,
level: 6,
num_format: NumberFormatType::HangulSyllable,
text: "(^6)".into(),
checkable: false,
},
ParaHead {
start: 1,
level: 7,
num_format: NumberFormatType::CircledDigit,
text: "^7".into(),
checkable: true,
},
ParaHead {
start: 1,
level: 8,
num_format: NumberFormatType::CircledHangulSyllable,
text: "^8".into(),
checkable: true,
},
ParaHead {
start: 1,
level: 9,
num_format: NumberFormatType::HangulJamo,
text: String::new(),
checkable: false,
},
ParaHead {
start: 1,
level: 10,
num_format: NumberFormatType::RomanSmall,
text: String::new(),
checkable: true,
},
],
}
}
pub fn para_head(&self, level: u8) -> Option<&ParaHead> {
self.levels.get(level as usize)
}
}
impl BulletDef {
pub fn is_checkable(&self) -> bool {
self.para_head.checkable || self.checked_char.is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_outline_has_10_levels() {
let def = NumberingDef::default_outline();
assert_eq!(def.levels.len(), 10);
assert_eq!(def.id, 1);
assert_eq!(def.start, 0);
}
#[test]
fn default_outline_level_formats() {
let def = NumberingDef::default_outline();
assert_eq!(def.levels[0].num_format, NumberFormatType::Digit);
assert_eq!(def.levels[1].num_format, NumberFormatType::HangulSyllable);
assert_eq!(def.levels[6].num_format, NumberFormatType::CircledDigit);
assert_eq!(def.levels[7].num_format, NumberFormatType::CircledHangulSyllable);
assert_eq!(def.levels[8].num_format, NumberFormatType::HangulJamo);
assert_eq!(def.levels[9].num_format, NumberFormatType::RomanSmall);
}
#[test]
fn default_outline_level_texts() {
let def = NumberingDef::default_outline();
assert_eq!(def.levels[0].text, "^1.");
assert_eq!(def.levels[1].text, "^2.");
assert_eq!(def.levels[2].text, "^3)");
assert_eq!(def.levels[3].text, "^4)");
assert_eq!(def.levels[4].text, "(^5)");
assert_eq!(def.levels[5].text, "(^6)");
assert_eq!(def.levels[6].text, "^7");
assert_eq!(def.levels[7].text, "^8");
assert_eq!(def.levels[8].text, ""); assert_eq!(def.levels[9].text, ""); }
#[test]
fn default_outline_checkable_flags() {
let def = NumberingDef::default_outline();
for i in 0..6 {
assert!(!def.levels[i].checkable, "level {} should not be checkable", i + 1);
}
assert!(def.levels[6].checkable);
assert!(def.levels[7].checkable);
assert!(!def.levels[8].checkable);
assert!(def.levels[9].checkable);
}
#[test]
fn default_outline_levels_are_sequential() {
let def = NumberingDef::default_outline();
for (i, lvl) in def.levels.iter().enumerate() {
assert_eq!(lvl.level, (i + 1) as u32);
}
}
}