1use hwpforge_foundation::{BulletIndex, HeadingType, NumberFormatType, NumberingIndex};
8use schemars::JsonSchema;
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
15pub struct ParaHead {
16 pub start: u32,
18 pub level: u32,
20 pub num_format: NumberFormatType,
22 pub text: String,
25 pub checkable: bool,
27}
28
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
33pub struct NumberingDef {
34 pub id: u32,
36 pub start: u32,
38 pub levels: Vec<ParaHead>,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
46pub struct BulletDef {
47 pub id: u32,
49 pub bullet_char: String,
51 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub checked_char: Option<String>,
54 pub use_image: bool,
56 pub para_head: ParaHead,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
66#[serde(tag = "kind", rename_all = "snake_case")]
67pub enum ParagraphListRef {
68 Outline {
70 level: u8,
72 },
73 Number {
75 numbering_id: NumberingIndex,
77 level: u8,
79 },
80 Bullet {
82 bullet_id: BulletIndex,
84 level: u8,
86 },
87 CheckBullet {
89 bullet_id: BulletIndex,
91 level: u8,
93 checked: bool,
95 },
96}
97
98impl ParagraphListRef {
99 pub const MAX_LEVEL: u8 = 9;
101
102 pub const fn level(self) -> u8 {
104 match self {
105 Self::Outline { level }
106 | Self::Number { level, .. }
107 | Self::Bullet { level, .. }
108 | Self::CheckBullet { level, .. } => level,
109 }
110 }
111
112 pub const fn heading_type(self) -> HeadingType {
114 match self {
115 Self::Outline { .. } => HeadingType::Outline,
116 Self::Number { .. } => HeadingType::Number,
117 Self::Bullet { .. } | Self::CheckBullet { .. } => HeadingType::Bullet,
118 }
119 }
120
121 pub const fn checked(self) -> Option<bool> {
123 match self {
124 Self::CheckBullet { checked, .. } => Some(checked),
125 Self::Outline { .. } | Self::Number { .. } | Self::Bullet { .. } => None,
126 }
127 }
128}
129
130impl NumberingDef {
131 pub fn default_outline() -> Self {
146 Self {
147 id: 1,
148 start: 0,
149 levels: vec![
150 ParaHead {
151 start: 1,
152 level: 1,
153 num_format: NumberFormatType::Digit,
154 text: "^1.".into(),
155 checkable: false,
156 },
157 ParaHead {
158 start: 1,
159 level: 2,
160 num_format: NumberFormatType::HangulSyllable,
161 text: "^2.".into(),
162 checkable: false,
163 },
164 ParaHead {
165 start: 1,
166 level: 3,
167 num_format: NumberFormatType::Digit,
168 text: "^3)".into(),
169 checkable: false,
170 },
171 ParaHead {
172 start: 1,
173 level: 4,
174 num_format: NumberFormatType::HangulSyllable,
175 text: "^4)".into(),
176 checkable: false,
177 },
178 ParaHead {
179 start: 1,
180 level: 5,
181 num_format: NumberFormatType::Digit,
182 text: "(^5)".into(),
183 checkable: false,
184 },
185 ParaHead {
186 start: 1,
187 level: 6,
188 num_format: NumberFormatType::HangulSyllable,
189 text: "(^6)".into(),
190 checkable: false,
191 },
192 ParaHead {
193 start: 1,
194 level: 7,
195 num_format: NumberFormatType::CircledDigit,
196 text: "^7".into(),
197 checkable: true,
198 },
199 ParaHead {
200 start: 1,
201 level: 8,
202 num_format: NumberFormatType::CircledHangulSyllable,
203 text: "^8".into(),
204 checkable: true,
205 },
206 ParaHead {
207 start: 1,
208 level: 9,
209 num_format: NumberFormatType::HangulJamo,
210 text: String::new(),
211 checkable: false,
212 },
213 ParaHead {
214 start: 1,
215 level: 10,
216 num_format: NumberFormatType::RomanSmall,
217 text: String::new(),
218 checkable: true,
219 },
220 ],
221 }
222 }
223
224 pub fn para_head(&self, level: u8) -> Option<&ParaHead> {
226 self.levels.get(level as usize)
227 }
228}
229
230impl BulletDef {
231 pub fn is_checkable(&self) -> bool {
233 self.para_head.checkable || self.checked_char.is_some()
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 #[test]
242 fn default_outline_has_10_levels() {
243 let def = NumberingDef::default_outline();
244 assert_eq!(def.levels.len(), 10);
245 assert_eq!(def.id, 1);
246 assert_eq!(def.start, 0);
247 }
248
249 #[test]
250 fn default_outline_level_formats() {
251 let def = NumberingDef::default_outline();
252 assert_eq!(def.levels[0].num_format, NumberFormatType::Digit);
253 assert_eq!(def.levels[1].num_format, NumberFormatType::HangulSyllable);
254 assert_eq!(def.levels[6].num_format, NumberFormatType::CircledDigit);
255 assert_eq!(def.levels[7].num_format, NumberFormatType::CircledHangulSyllable);
256 assert_eq!(def.levels[8].num_format, NumberFormatType::HangulJamo);
257 assert_eq!(def.levels[9].num_format, NumberFormatType::RomanSmall);
258 }
259
260 #[test]
261 fn default_outline_level_texts() {
262 let def = NumberingDef::default_outline();
263 assert_eq!(def.levels[0].text, "^1.");
264 assert_eq!(def.levels[1].text, "^2.");
265 assert_eq!(def.levels[2].text, "^3)");
266 assert_eq!(def.levels[3].text, "^4)");
267 assert_eq!(def.levels[4].text, "(^5)");
268 assert_eq!(def.levels[5].text, "(^6)");
269 assert_eq!(def.levels[6].text, "^7");
270 assert_eq!(def.levels[7].text, "^8");
271 assert_eq!(def.levels[8].text, ""); assert_eq!(def.levels[9].text, ""); }
274
275 #[test]
276 fn default_outline_checkable_flags() {
277 let def = NumberingDef::default_outline();
278 for i in 0..6 {
280 assert!(!def.levels[i].checkable, "level {} should not be checkable", i + 1);
281 }
282 assert!(def.levels[6].checkable);
284 assert!(def.levels[7].checkable);
286 assert!(!def.levels[8].checkable);
288 assert!(def.levels[9].checkable);
290 }
291
292 #[test]
293 fn default_outline_levels_are_sequential() {
294 let def = NumberingDef::default_outline();
295 for (i, lvl) in def.levels.iter().enumerate() {
296 assert_eq!(lvl.level, (i + 1) as u32);
297 }
298 }
299}