Skip to main content

hwpforge_foundation/enums/
char_style.rs

1//! Character-style enums: language, underline, strikeout, outline, shadow, and emphasis.
2
3use crate::error::FoundationError;
4use serde::{Deserialize, Serialize};
5use std::fmt;
6
7// ---------------------------------------------------------------------------
8// Language
9// ---------------------------------------------------------------------------
10
11/// HWP5 language slots for font assignment.
12///
13/// Each character shape stores a font per language slot.
14/// The discriminant values match the HWP5 specification exactly.
15///
16/// # Examples
17///
18/// ```
19/// use hwpforge_foundation::Language;
20///
21/// assert_eq!(Language::COUNT, 7);
22/// assert_eq!(Language::Korean as u8, 0);
23/// ```
24#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
25#[non_exhaustive]
26#[repr(u8)]
27pub enum Language {
28    /// Korean (slot 0).
29    #[default]
30    Korean = 0,
31    /// English (slot 1).
32    English = 1,
33    /// Chinese characters / Hanja (slot 2).
34    Hanja = 2,
35    /// Japanese (slot 3).
36    Japanese = 3,
37    /// Other languages (slot 4).
38    Other = 4,
39    /// Symbol characters (slot 5).
40    Symbol = 5,
41    /// User-defined (slot 6).
42    User = 6,
43}
44
45impl Language {
46    /// Total number of language slots (7), matching the HWP5 spec.
47    pub const COUNT: usize = 7;
48
49    /// All language variants in slot order.
50    pub const ALL: [Self; 7] = [
51        Self::Korean,
52        Self::English,
53        Self::Hanja,
54        Self::Japanese,
55        Self::Other,
56        Self::Symbol,
57        Self::User,
58    ];
59}
60
61impl fmt::Display for Language {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        match self {
64            Self::Korean => f.write_str("Korean"),
65            Self::English => f.write_str("English"),
66            Self::Hanja => f.write_str("Hanja"),
67            Self::Japanese => f.write_str("Japanese"),
68            Self::Other => f.write_str("Other"),
69            Self::Symbol => f.write_str("Symbol"),
70            Self::User => f.write_str("User"),
71        }
72    }
73}
74
75impl std::str::FromStr for Language {
76    type Err = FoundationError;
77
78    fn from_str(s: &str) -> Result<Self, Self::Err> {
79        match s {
80            "Korean" | "korean" => Ok(Self::Korean),
81            "English" | "english" => Ok(Self::English),
82            "Hanja" | "hanja" => Ok(Self::Hanja),
83            "Japanese" | "japanese" => Ok(Self::Japanese),
84            "Other" | "other" => Ok(Self::Other),
85            "Symbol" | "symbol" => Ok(Self::Symbol),
86            "User" | "user" => Ok(Self::User),
87            _ => Err(FoundationError::ParseError {
88                type_name: "Language".to_string(),
89                value: s.to_string(),
90                valid_values: "Korean, English, Hanja, Japanese, Other, Symbol, User".to_string(),
91            }),
92        }
93    }
94}
95
96impl TryFrom<u8> for Language {
97    type Error = FoundationError;
98
99    fn try_from(value: u8) -> Result<Self, Self::Error> {
100        match value {
101            0 => Ok(Self::Korean),
102            1 => Ok(Self::English),
103            2 => Ok(Self::Hanja),
104            3 => Ok(Self::Japanese),
105            4 => Ok(Self::Other),
106            5 => Ok(Self::Symbol),
107            6 => Ok(Self::User),
108            _ => Err(FoundationError::ParseError {
109                type_name: "Language".to_string(),
110                value: value.to_string(),
111                valid_values: "0-6 (Korean, English, Hanja, Japanese, Other, Symbol, User)"
112                    .to_string(),
113            }),
114        }
115    }
116}
117
118impl schemars::JsonSchema for Language {
119    fn schema_name() -> std::borrow::Cow<'static, str> {
120        std::borrow::Cow::Borrowed("Language")
121    }
122
123    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
124        gen.subschema_for::<String>()
125    }
126}
127
128// ---------------------------------------------------------------------------
129// UnderlineType
130// ---------------------------------------------------------------------------
131
132/// Underline decoration type.
133///
134/// # Examples
135///
136/// ```
137/// use hwpforge_foundation::UnderlineType;
138///
139/// assert_eq!(UnderlineType::default(), UnderlineType::None);
140/// ```
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
142#[non_exhaustive]
143#[repr(u8)]
144pub enum UnderlineType {
145    /// No underline (default).
146    #[default]
147    None = 0,
148    /// Single straight line below text.
149    Bottom = 1,
150    /// Single line centered on text.
151    Center = 2,
152    /// Single line above text.
153    Top = 3,
154}
155
156impl fmt::Display for UnderlineType {
157    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158        match self {
159            Self::None => f.write_str("None"),
160            Self::Bottom => f.write_str("Bottom"),
161            Self::Center => f.write_str("Center"),
162            Self::Top => f.write_str("Top"),
163        }
164    }
165}
166
167impl std::str::FromStr for UnderlineType {
168    type Err = FoundationError;
169
170    fn from_str(s: &str) -> Result<Self, Self::Err> {
171        match s {
172            "None" | "none" => Ok(Self::None),
173            "Bottom" | "bottom" => Ok(Self::Bottom),
174            "Center" | "center" => Ok(Self::Center),
175            "Top" | "top" => Ok(Self::Top),
176            _ => Err(FoundationError::ParseError {
177                type_name: "UnderlineType".to_string(),
178                value: s.to_string(),
179                valid_values: "None, Bottom, Center, Top".to_string(),
180            }),
181        }
182    }
183}
184
185impl TryFrom<u8> for UnderlineType {
186    type Error = FoundationError;
187
188    fn try_from(value: u8) -> Result<Self, Self::Error> {
189        match value {
190            0 => Ok(Self::None),
191            1 => Ok(Self::Bottom),
192            2 => Ok(Self::Center),
193            3 => Ok(Self::Top),
194            _ => Err(FoundationError::ParseError {
195                type_name: "UnderlineType".to_string(),
196                value: value.to_string(),
197                valid_values: "0 (None), 1 (Bottom), 2 (Center), 3 (Top)".to_string(),
198            }),
199        }
200    }
201}
202
203impl schemars::JsonSchema for UnderlineType {
204    fn schema_name() -> std::borrow::Cow<'static, str> {
205        std::borrow::Cow::Borrowed("UnderlineType")
206    }
207
208    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
209        gen.subschema_for::<String>()
210    }
211}
212
213// ---------------------------------------------------------------------------
214// UnderlineShape
215// ---------------------------------------------------------------------------
216
217/// Underline line family (e.g. SOLID, DASH, WAVE).
218///
219/// This selects the line *style* used by an underline; the position
220/// (Bottom/Center/Top) is carried separately by [`UnderlineType`].
221///
222/// # Examples
223///
224/// ```
225/// use hwpforge_foundation::UnderlineShape;
226///
227/// assert_eq!(UnderlineShape::default(), UnderlineShape::Solid);
228/// ```
229#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
230#[non_exhaustive]
231#[repr(u8)]
232pub enum UnderlineShape {
233    /// Solid continuous line (default).
234    #[default]
235    Solid = 0,
236    /// Dashed line.
237    Dash = 1,
238    /// Dotted line.
239    Dot = 2,
240    /// Dash-dot pattern.
241    DashDot = 3,
242    /// Dash-dot-dot pattern.
243    DashDotDot = 4,
244    /// Long dash pattern.
245    LongDash = 5,
246    /// Repeating small circles.
247    Circle = 6,
248    /// Double thin line.
249    DoubleSlim = 7,
250    /// Thin then thick double line.
251    SlimThick = 8,
252    /// Thick then thin double line.
253    ThickSlim = 9,
254    /// Thick-thin-thick triple line.
255    ThickSlimThick = 10,
256    /// Wavy line.
257    Wave = 11,
258}
259
260impl fmt::Display for UnderlineShape {
261    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262        match self {
263            Self::Solid => f.write_str("SOLID"),
264            Self::Dash => f.write_str("DASH"),
265            Self::Dot => f.write_str("DOT"),
266            Self::DashDot => f.write_str("DASH_DOT"),
267            Self::DashDotDot => f.write_str("DASH_DOT_DOT"),
268            Self::LongDash => f.write_str("LONG_DASH"),
269            Self::Circle => f.write_str("CIRCLE"),
270            Self::DoubleSlim => f.write_str("DOUBLE_SLIM"),
271            Self::SlimThick => f.write_str("SLIM_THICK"),
272            Self::ThickSlim => f.write_str("THICK_SLIM"),
273            Self::ThickSlimThick => f.write_str("THICK_SLIM_THICK"),
274            Self::Wave => f.write_str("WAVE"),
275        }
276    }
277}
278
279impl std::str::FromStr for UnderlineShape {
280    type Err = FoundationError;
281
282    fn from_str(s: &str) -> Result<Self, Self::Err> {
283        match s {
284            "SOLID" | "Solid" | "solid" => Ok(Self::Solid),
285            "DASH" | "Dash" | "dash" => Ok(Self::Dash),
286            "DOT" | "Dot" | "dot" => Ok(Self::Dot),
287            "DASH_DOT" | "DashDot" | "dash_dot" => Ok(Self::DashDot),
288            "DASH_DOT_DOT" | "DashDotDot" | "dash_dot_dot" => Ok(Self::DashDotDot),
289            "LONG_DASH" | "LongDash" | "long_dash" => Ok(Self::LongDash),
290            "CIRCLE" | "Circle" | "circle" => Ok(Self::Circle),
291            "DOUBLE_SLIM" | "DoubleSlim" | "double_slim" => Ok(Self::DoubleSlim),
292            "SLIM_THICK" | "SlimThick" | "slim_thick" => Ok(Self::SlimThick),
293            "THICK_SLIM" | "ThickSlim" | "thick_slim" => Ok(Self::ThickSlim),
294            "THICK_SLIM_THICK" | "ThickSlimThick" | "thick_slim_thick" => {
295                Ok(Self::ThickSlimThick)
296            }
297            "WAVE" | "Wave" | "wave" => Ok(Self::Wave),
298            _ => Err(FoundationError::ParseError {
299                type_name: "UnderlineShape".to_string(),
300                value: s.to_string(),
301                valid_values: "SOLID, DASH, DOT, DASH_DOT, DASH_DOT_DOT, LONG_DASH, CIRCLE, DOUBLE_SLIM, SLIM_THICK, THICK_SLIM, THICK_SLIM_THICK, WAVE".to_string(),
302            }),
303        }
304    }
305}
306
307impl TryFrom<u8> for UnderlineShape {
308    type Error = FoundationError;
309
310    fn try_from(value: u8) -> Result<Self, Self::Error> {
311        match value {
312            0 => Ok(Self::Solid),
313            1 => Ok(Self::Dash),
314            2 => Ok(Self::Dot),
315            3 => Ok(Self::DashDot),
316            4 => Ok(Self::DashDotDot),
317            5 => Ok(Self::LongDash),
318            6 => Ok(Self::Circle),
319            7 => Ok(Self::DoubleSlim),
320            8 => Ok(Self::SlimThick),
321            9 => Ok(Self::ThickSlim),
322            10 => Ok(Self::ThickSlimThick),
323            11 => Ok(Self::Wave),
324            _ => Err(FoundationError::ParseError {
325                type_name: "UnderlineShape".to_string(),
326                value: value.to_string(),
327                valid_values: "0-11 (SOLID, DASH, DOT, DASH_DOT, DASH_DOT_DOT, LONG_DASH, CIRCLE, DOUBLE_SLIM, SLIM_THICK, THICK_SLIM, THICK_SLIM_THICK, WAVE)".to_string(),
328            }),
329        }
330    }
331}
332
333impl schemars::JsonSchema for UnderlineShape {
334    fn schema_name() -> std::borrow::Cow<'static, str> {
335        std::borrow::Cow::Borrowed("UnderlineShape")
336    }
337
338    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
339        gen.subschema_for::<String>()
340    }
341}
342
343// ---------------------------------------------------------------------------
344// StrikeoutShape
345// ---------------------------------------------------------------------------
346
347/// Strikeout line shape.
348///
349/// This selects the line *family* used by a strikeout. After Wave 1c the
350/// shared IR mirrors the full OWPML strike-shape vocabulary so the HWP5
351/// projection can carry the entire line family rather than collapsing to
352/// `Solid`. The naming aligns with [`UnderlineShape`] so both axes share
353/// vocabulary.
354///
355/// # Examples
356///
357/// ```
358/// use hwpforge_foundation::StrikeoutShape;
359///
360/// assert_eq!(StrikeoutShape::default(), StrikeoutShape::None);
361/// ```
362#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
363#[non_exhaustive]
364#[repr(u8)]
365pub enum StrikeoutShape {
366    /// No strikeout (default).
367    #[default]
368    None = 0,
369    /// Solid continuous line (formerly named `Continuous`).
370    Solid = 1,
371    /// Dashed line.
372    Dash = 2,
373    /// Dotted line.
374    Dot = 3,
375    /// Dash-dot pattern.
376    DashDot = 4,
377    /// Dash-dot-dot pattern.
378    DashDotDot = 5,
379    /// Long dash pattern.
380    LongDash = 6,
381    /// Repeating small circles.
382    Circle = 7,
383    /// Double thin line.
384    DoubleSlim = 8,
385    /// Thin then thick double line.
386    SlimThick = 9,
387    /// Thick then thin double line.
388    ThickSlim = 10,
389    /// Thick-thin-thick triple line.
390    ThickSlimThick = 11,
391    /// Wavy line.
392    Wave = 12,
393}
394
395impl fmt::Display for StrikeoutShape {
396    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
397        match self {
398            Self::None => f.write_str("NONE"),
399            Self::Solid => f.write_str("SOLID"),
400            Self::Dash => f.write_str("DASH"),
401            Self::Dot => f.write_str("DOT"),
402            Self::DashDot => f.write_str("DASH_DOT"),
403            Self::DashDotDot => f.write_str("DASH_DOT_DOT"),
404            Self::LongDash => f.write_str("LONG_DASH"),
405            Self::Circle => f.write_str("CIRCLE"),
406            Self::DoubleSlim => f.write_str("DOUBLE_SLIM"),
407            Self::SlimThick => f.write_str("SLIM_THICK"),
408            Self::ThickSlim => f.write_str("THICK_SLIM"),
409            Self::ThickSlimThick => f.write_str("THICK_SLIM_THICK"),
410            Self::Wave => f.write_str("WAVE"),
411        }
412    }
413}
414
415impl std::str::FromStr for StrikeoutShape {
416    type Err = FoundationError;
417
418    fn from_str(s: &str) -> Result<Self, Self::Err> {
419        match s {
420            "NONE" | "None" | "none" => Ok(Self::None),
421            "SOLID" | "Solid" | "solid" | "Continuous" | "continuous" => Ok(Self::Solid),
422            "DASH" | "Dash" | "dash" => Ok(Self::Dash),
423            "DOT" | "Dot" | "dot" => Ok(Self::Dot),
424            "DASH_DOT" | "DashDot" | "dashdot" | "dash_dot" => Ok(Self::DashDot),
425            "DASH_DOT_DOT" | "DashDotDot" | "dashdotdot" | "dash_dot_dot" => Ok(Self::DashDotDot),
426            "LONG_DASH" | "LongDash" | "long_dash" => Ok(Self::LongDash),
427            "CIRCLE" | "Circle" | "circle" => Ok(Self::Circle),
428            "DOUBLE_SLIM" | "DoubleSlim" | "double_slim" => Ok(Self::DoubleSlim),
429            "SLIM_THICK" | "SlimThick" | "slim_thick" => Ok(Self::SlimThick),
430            "THICK_SLIM" | "ThickSlim" | "thick_slim" => Ok(Self::ThickSlim),
431            "THICK_SLIM_THICK" | "ThickSlimThick" | "thick_slim_thick" => Ok(Self::ThickSlimThick),
432            "WAVE" | "Wave" | "wave" => Ok(Self::Wave),
433            _ => Err(FoundationError::ParseError {
434                type_name: "StrikeoutShape".to_string(),
435                value: s.to_string(),
436                valid_values: "NONE, SOLID, DASH, DOT, DASH_DOT, DASH_DOT_DOT, LONG_DASH, CIRCLE, DOUBLE_SLIM, SLIM_THICK, THICK_SLIM, THICK_SLIM_THICK, WAVE".to_string(),
437            }),
438        }
439    }
440}
441
442impl TryFrom<u8> for StrikeoutShape {
443    type Error = FoundationError;
444
445    fn try_from(value: u8) -> Result<Self, Self::Error> {
446        match value {
447            0 => Ok(Self::None),
448            1 => Ok(Self::Solid),
449            2 => Ok(Self::Dash),
450            3 => Ok(Self::Dot),
451            4 => Ok(Self::DashDot),
452            5 => Ok(Self::DashDotDot),
453            6 => Ok(Self::LongDash),
454            7 => Ok(Self::Circle),
455            8 => Ok(Self::DoubleSlim),
456            9 => Ok(Self::SlimThick),
457            10 => Ok(Self::ThickSlim),
458            11 => Ok(Self::ThickSlimThick),
459            12 => Ok(Self::Wave),
460            _ => Err(FoundationError::ParseError {
461                type_name: "StrikeoutShape".to_string(),
462                value: value.to_string(),
463                valid_values: "0-12 (NONE, SOLID, DASH, DOT, DASH_DOT, DASH_DOT_DOT, LONG_DASH, CIRCLE, DOUBLE_SLIM, SLIM_THICK, THICK_SLIM, THICK_SLIM_THICK, WAVE)".to_string(),
464            }),
465        }
466    }
467}
468
469impl schemars::JsonSchema for StrikeoutShape {
470    fn schema_name() -> std::borrow::Cow<'static, str> {
471        std::borrow::Cow::Borrowed("StrikeoutShape")
472    }
473
474    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
475        gen.subschema_for::<String>()
476    }
477}
478
479// ---------------------------------------------------------------------------
480// OutlineType
481// ---------------------------------------------------------------------------
482
483/// Text outline type (1pt border around glyphs).
484///
485/// # Examples
486///
487/// ```
488/// use hwpforge_foundation::OutlineType;
489///
490/// assert_eq!(OutlineType::default(), OutlineType::None);
491/// ```
492#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
493#[non_exhaustive]
494#[repr(u8)]
495pub enum OutlineType {
496    /// No outline (default).
497    #[default]
498    None = 0,
499    /// Solid 1pt outline.
500    Solid = 1,
501}
502
503impl fmt::Display for OutlineType {
504    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
505        match self {
506            Self::None => f.write_str("None"),
507            Self::Solid => f.write_str("Solid"),
508        }
509    }
510}
511
512impl std::str::FromStr for OutlineType {
513    type Err = FoundationError;
514
515    fn from_str(s: &str) -> Result<Self, Self::Err> {
516        match s {
517            "None" | "none" => Ok(Self::None),
518            "Solid" | "solid" => Ok(Self::Solid),
519            _ => Err(FoundationError::ParseError {
520                type_name: "OutlineType".to_string(),
521                value: s.to_string(),
522                valid_values: "None, Solid".to_string(),
523            }),
524        }
525    }
526}
527
528impl TryFrom<u8> for OutlineType {
529    type Error = FoundationError;
530
531    fn try_from(value: u8) -> Result<Self, Self::Error> {
532        match value {
533            0 => Ok(Self::None),
534            1 => Ok(Self::Solid),
535            _ => Err(FoundationError::ParseError {
536                type_name: "OutlineType".to_string(),
537                value: value.to_string(),
538                valid_values: "0 (None), 1 (Solid)".to_string(),
539            }),
540        }
541    }
542}
543
544impl schemars::JsonSchema for OutlineType {
545    fn schema_name() -> std::borrow::Cow<'static, str> {
546        std::borrow::Cow::Borrowed("OutlineType")
547    }
548
549    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
550        gen.subschema_for::<String>()
551    }
552}
553
554// ---------------------------------------------------------------------------
555// ShadowType
556// ---------------------------------------------------------------------------
557
558/// Text shadow type.
559///
560/// # Examples
561///
562/// ```
563/// use hwpforge_foundation::ShadowType;
564///
565/// assert_eq!(ShadowType::default(), ShadowType::None);
566/// ```
567#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
568#[non_exhaustive]
569#[repr(u8)]
570pub enum ShadowType {
571    /// No shadow (default).
572    #[default]
573    None = 0,
574    /// Drop shadow.
575    Drop = 1,
576}
577
578impl fmt::Display for ShadowType {
579    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
580        match self {
581            Self::None => f.write_str("None"),
582            Self::Drop => f.write_str("Drop"),
583        }
584    }
585}
586
587impl std::str::FromStr for ShadowType {
588    type Err = FoundationError;
589
590    fn from_str(s: &str) -> Result<Self, Self::Err> {
591        match s {
592            "None" | "none" => Ok(Self::None),
593            "Drop" | "drop" => Ok(Self::Drop),
594            _ => Err(FoundationError::ParseError {
595                type_name: "ShadowType".to_string(),
596                value: s.to_string(),
597                valid_values: "None, Drop".to_string(),
598            }),
599        }
600    }
601}
602
603impl TryFrom<u8> for ShadowType {
604    type Error = FoundationError;
605
606    fn try_from(value: u8) -> Result<Self, Self::Error> {
607        match value {
608            0 => Ok(Self::None),
609            1 => Ok(Self::Drop),
610            _ => Err(FoundationError::ParseError {
611                type_name: "ShadowType".to_string(),
612                value: value.to_string(),
613                valid_values: "0 (None), 1 (Drop)".to_string(),
614            }),
615        }
616    }
617}
618
619impl schemars::JsonSchema for ShadowType {
620    fn schema_name() -> std::borrow::Cow<'static, str> {
621        std::borrow::Cow::Borrowed("ShadowType")
622    }
623
624    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
625        gen.subschema_for::<String>()
626    }
627}
628
629// ---------------------------------------------------------------------------
630// EmbossType
631// ---------------------------------------------------------------------------
632
633/// Text embossing (raised appearance).
634///
635/// # Examples
636///
637/// ```
638/// use hwpforge_foundation::EmbossType;
639///
640/// assert_eq!(EmbossType::default(), EmbossType::None);
641/// ```
642#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
643#[non_exhaustive]
644#[repr(u8)]
645pub enum EmbossType {
646    /// No emboss (default).
647    #[default]
648    None = 0,
649    /// Raised emboss effect.
650    Emboss = 1,
651}
652
653impl fmt::Display for EmbossType {
654    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
655        match self {
656            Self::None => f.write_str("None"),
657            Self::Emboss => f.write_str("Emboss"),
658        }
659    }
660}
661
662impl std::str::FromStr for EmbossType {
663    type Err = FoundationError;
664
665    fn from_str(s: &str) -> Result<Self, Self::Err> {
666        match s {
667            "None" | "none" => Ok(Self::None),
668            "Emboss" | "emboss" => Ok(Self::Emboss),
669            _ => Err(FoundationError::ParseError {
670                type_name: "EmbossType".to_string(),
671                value: s.to_string(),
672                valid_values: "None, Emboss".to_string(),
673            }),
674        }
675    }
676}
677
678impl TryFrom<u8> for EmbossType {
679    type Error = FoundationError;
680
681    fn try_from(value: u8) -> Result<Self, Self::Error> {
682        match value {
683            0 => Ok(Self::None),
684            1 => Ok(Self::Emboss),
685            _ => Err(FoundationError::ParseError {
686                type_name: "EmbossType".to_string(),
687                value: value.to_string(),
688                valid_values: "0 (None), 1 (Emboss)".to_string(),
689            }),
690        }
691    }
692}
693
694impl schemars::JsonSchema for EmbossType {
695    fn schema_name() -> std::borrow::Cow<'static, str> {
696        std::borrow::Cow::Borrowed("EmbossType")
697    }
698
699    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
700        gen.subschema_for::<String>()
701    }
702}
703
704// ---------------------------------------------------------------------------
705// EngraveType
706// ---------------------------------------------------------------------------
707
708/// Text engraving (sunken appearance).
709///
710/// # Examples
711///
712/// ```
713/// use hwpforge_foundation::EngraveType;
714///
715/// assert_eq!(EngraveType::default(), EngraveType::None);
716/// ```
717#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
718#[non_exhaustive]
719#[repr(u8)]
720pub enum EngraveType {
721    /// No engrave (default).
722    #[default]
723    None = 0,
724    /// Sunken engrave effect.
725    Engrave = 1,
726}
727
728impl fmt::Display for EngraveType {
729    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
730        match self {
731            Self::None => f.write_str("None"),
732            Self::Engrave => f.write_str("Engrave"),
733        }
734    }
735}
736
737impl std::str::FromStr for EngraveType {
738    type Err = FoundationError;
739
740    fn from_str(s: &str) -> Result<Self, Self::Err> {
741        match s {
742            "None" | "none" => Ok(Self::None),
743            "Engrave" | "engrave" => Ok(Self::Engrave),
744            _ => Err(FoundationError::ParseError {
745                type_name: "EngraveType".to_string(),
746                value: s.to_string(),
747                valid_values: "None, Engrave".to_string(),
748            }),
749        }
750    }
751}
752
753impl TryFrom<u8> for EngraveType {
754    type Error = FoundationError;
755
756    fn try_from(value: u8) -> Result<Self, Self::Error> {
757        match value {
758            0 => Ok(Self::None),
759            1 => Ok(Self::Engrave),
760            _ => Err(FoundationError::ParseError {
761                type_name: "EngraveType".to_string(),
762                value: value.to_string(),
763                valid_values: "0 (None), 1 (Engrave)".to_string(),
764            }),
765        }
766    }
767}
768
769impl schemars::JsonSchema for EngraveType {
770    fn schema_name() -> std::borrow::Cow<'static, str> {
771        std::borrow::Cow::Borrowed("EngraveType")
772    }
773
774    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
775        gen.subschema_for::<String>()
776    }
777}
778
779// ---------------------------------------------------------------------------
780// EmphasisType
781// ---------------------------------------------------------------------------
782
783/// Character emphasis mark (symMark attribute in HWPX).
784///
785/// Controls the emphasis symbol displayed above or below characters.
786/// Maps to HWPX `symMark` attribute values.
787///
788/// # Examples
789///
790/// ```
791/// use hwpforge_foundation::EmphasisType;
792///
793/// assert_eq!(EmphasisType::default(), EmphasisType::None);
794/// ```
795#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
796#[non_exhaustive]
797#[repr(u8)]
798pub enum EmphasisType {
799    /// No emphasis mark (default).
800    #[default]
801    None = 0,
802    /// Dot above character.
803    DotAbove = 1,
804    /// Ring above character.
805    RingAbove = 2,
806    /// Tilde above character.
807    Tilde = 3,
808    /// Caron (hacek) above character.
809    Caron = 4,
810    /// Side dot.
811    Side = 5,
812    /// Colon mark.
813    Colon = 6,
814    /// Grave accent.
815    GraveAccent = 7,
816    /// Acute accent.
817    AcuteAccent = 8,
818    /// Circumflex accent.
819    Circumflex = 9,
820    /// Macron (overline).
821    Macron = 10,
822    /// Hook above.
823    HookAbove = 11,
824    /// Dot below character.
825    DotBelow = 12,
826}
827
828impl fmt::Display for EmphasisType {
829    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
830        match self {
831            Self::None => f.write_str("None"),
832            Self::DotAbove => f.write_str("DotAbove"),
833            Self::RingAbove => f.write_str("RingAbove"),
834            Self::Tilde => f.write_str("Tilde"),
835            Self::Caron => f.write_str("Caron"),
836            Self::Side => f.write_str("Side"),
837            Self::Colon => f.write_str("Colon"),
838            Self::GraveAccent => f.write_str("GraveAccent"),
839            Self::AcuteAccent => f.write_str("AcuteAccent"),
840            Self::Circumflex => f.write_str("Circumflex"),
841            Self::Macron => f.write_str("Macron"),
842            Self::HookAbove => f.write_str("HookAbove"),
843            Self::DotBelow => f.write_str("DotBelow"),
844        }
845    }
846}
847
848impl std::str::FromStr for EmphasisType {
849    type Err = FoundationError;
850
851    fn from_str(s: &str) -> Result<Self, Self::Err> {
852        match s {
853            "NONE" | "None" | "none" => Ok(Self::None),
854            "DOT_ABOVE" | "DotAbove" | "dot_above" => Ok(Self::DotAbove),
855            "RING_ABOVE" | "RingAbove" | "ring_above" => Ok(Self::RingAbove),
856            "TILDE" | "Tilde" | "tilde" => Ok(Self::Tilde),
857            "CARON" | "Caron" | "caron" => Ok(Self::Caron),
858            "SIDE" | "Side" | "side" => Ok(Self::Side),
859            "COLON" | "Colon" | "colon" => Ok(Self::Colon),
860            "GRAVE_ACCENT" | "GraveAccent" | "grave_accent" => Ok(Self::GraveAccent),
861            "ACUTE_ACCENT" | "AcuteAccent" | "acute_accent" => Ok(Self::AcuteAccent),
862            "CIRCUMFLEX" | "Circumflex" | "circumflex" => Ok(Self::Circumflex),
863            "MACRON" | "Macron" | "macron" => Ok(Self::Macron),
864            "HOOK_ABOVE" | "HookAbove" | "hook_above" => Ok(Self::HookAbove),
865            "DOT_BELOW" | "DotBelow" | "dot_below" => Ok(Self::DotBelow),
866            _ => Err(FoundationError::ParseError {
867                type_name: "EmphasisType".to_string(),
868                value: s.to_string(),
869                valid_values:
870                    "NONE, DOT_ABOVE, RING_ABOVE, TILDE, CARON, SIDE, COLON, GRAVE_ACCENT, ACUTE_ACCENT, CIRCUMFLEX, MACRON, HOOK_ABOVE, DOT_BELOW"
871                        .to_string(),
872            }),
873        }
874    }
875}
876
877impl TryFrom<u8> for EmphasisType {
878    type Error = FoundationError;
879
880    fn try_from(value: u8) -> Result<Self, Self::Error> {
881        match value {
882            0 => Ok(Self::None),
883            1 => Ok(Self::DotAbove),
884            2 => Ok(Self::RingAbove),
885            3 => Ok(Self::Tilde),
886            4 => Ok(Self::Caron),
887            5 => Ok(Self::Side),
888            6 => Ok(Self::Colon),
889            7 => Ok(Self::GraveAccent),
890            8 => Ok(Self::AcuteAccent),
891            9 => Ok(Self::Circumflex),
892            10 => Ok(Self::Macron),
893            11 => Ok(Self::HookAbove),
894            12 => Ok(Self::DotBelow),
895            _ => Err(FoundationError::ParseError {
896                type_name: "EmphasisType".to_string(),
897                value: value.to_string(),
898                valid_values: "0-12 (None through DotBelow)".to_string(),
899            }),
900        }
901    }
902}
903
904impl schemars::JsonSchema for EmphasisType {
905    fn schema_name() -> std::borrow::Cow<'static, str> {
906        std::borrow::Cow::Borrowed("EmphasisType")
907    }
908
909    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
910        gen.subschema_for::<String>()
911    }
912}