hwpforge_foundation/
enums.rs

1//! Core enums used throughout HWP document processing.
2//!
3//! All enums are `#[non_exhaustive]` to allow future variant additions
4//! without breaking downstream code. They use `#[repr(u8)]` for compact
5//! storage and provide `TryFrom<u8>` for binary parsing.
6//!
7//! # Examples
8//!
9//! ```
10//! use hwpforge_foundation::Alignment;
11//! use std::str::FromStr;
12//!
13//! let a = Alignment::from_str("Justify").unwrap();
14//! assert_eq!(a, Alignment::Justify);
15//! assert_eq!(a.to_string(), "Justify");
16//! ```
17
18use std::fmt;
19
20use serde::{Deserialize, Serialize};
21
22use crate::error::FoundationError;
23
24// ---------------------------------------------------------------------------
25// Alignment
26// ---------------------------------------------------------------------------
27
28/// Horizontal text alignment within a paragraph.
29///
30/// # Examples
31///
32/// ```
33/// use hwpforge_foundation::Alignment;
34///
35/// assert_eq!(Alignment::default(), Alignment::Left);
36/// ```
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
38#[non_exhaustive]
39#[repr(u8)]
40pub enum Alignment {
41    /// Left-aligned (default).
42    #[default]
43    Left = 0,
44    /// Centered.
45    Center = 1,
46    /// Right-aligned.
47    Right = 2,
48    /// Justified (both edges flush).
49    Justify = 3,
50    /// Distribute spacing evenly between characters.
51    Distribute = 4,
52    /// Distribute spacing evenly between characters, last line flush.
53    DistributeFlush = 5,
54}
55
56impl fmt::Display for Alignment {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        match self {
59            Self::Left => f.write_str("Left"),
60            Self::Center => f.write_str("Center"),
61            Self::Right => f.write_str("Right"),
62            Self::Justify => f.write_str("Justify"),
63            Self::Distribute => f.write_str("Distribute"),
64            Self::DistributeFlush => f.write_str("DistributeFlush"),
65        }
66    }
67}
68
69impl std::str::FromStr for Alignment {
70    type Err = FoundationError;
71
72    fn from_str(s: &str) -> Result<Self, Self::Err> {
73        match s {
74            "Left" | "left" => Ok(Self::Left),
75            "Center" | "center" => Ok(Self::Center),
76            "Right" | "right" => Ok(Self::Right),
77            "Justify" | "justify" => Ok(Self::Justify),
78            "Distribute" | "distribute" => Ok(Self::Distribute),
79            "DistributeFlush" | "distributeflush" | "distribute_flush" => Ok(Self::DistributeFlush),
80            _ => Err(FoundationError::ParseError {
81                type_name: "Alignment".to_string(),
82                value: s.to_string(),
83                valid_values: "Left, Center, Right, Justify, Distribute, DistributeFlush"
84                    .to_string(),
85            }),
86        }
87    }
88}
89
90impl TryFrom<u8> for Alignment {
91    type Error = FoundationError;
92
93    fn try_from(value: u8) -> Result<Self, Self::Error> {
94        match value {
95            0 => Ok(Self::Left),
96            1 => Ok(Self::Center),
97            2 => Ok(Self::Right),
98            3 => Ok(Self::Justify),
99            4 => Ok(Self::Distribute),
100            5 => Ok(Self::DistributeFlush),
101            _ => Err(FoundationError::ParseError {
102                type_name: "Alignment".to_string(),
103                value: value.to_string(),
104                valid_values:
105                    "0 (Left), 1 (Center), 2 (Right), 3 (Justify), 4 (Distribute), 5 (DistributeFlush)"
106                        .to_string(),
107            }),
108        }
109    }
110}
111
112impl schemars::JsonSchema for Alignment {
113    fn schema_name() -> std::borrow::Cow<'static, str> {
114        std::borrow::Cow::Borrowed("Alignment")
115    }
116
117    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
118        gen.subschema_for::<String>()
119    }
120}
121
122// ---------------------------------------------------------------------------
123// LineSpacingType
124// ---------------------------------------------------------------------------
125
126/// How line spacing is calculated.
127///
128/// # Examples
129///
130/// ```
131/// use hwpforge_foundation::LineSpacingType;
132///
133/// assert_eq!(LineSpacingType::default(), LineSpacingType::Percentage);
134/// ```
135#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
136#[non_exhaustive]
137#[repr(u8)]
138pub enum LineSpacingType {
139    /// Spacing as a percentage of the font size (default: 160%).
140    #[default]
141    Percentage = 0,
142    /// Fixed spacing in HwpUnit, regardless of font size.
143    Fixed = 1,
144    /// Space between the bottom of one line and top of the next.
145    BetweenLines = 2,
146}
147
148impl fmt::Display for LineSpacingType {
149    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150        match self {
151            Self::Percentage => f.write_str("Percentage"),
152            Self::Fixed => f.write_str("Fixed"),
153            Self::BetweenLines => f.write_str("BetweenLines"),
154        }
155    }
156}
157
158impl std::str::FromStr for LineSpacingType {
159    type Err = FoundationError;
160
161    fn from_str(s: &str) -> Result<Self, Self::Err> {
162        match s {
163            "Percentage" | "percentage" => Ok(Self::Percentage),
164            "Fixed" | "fixed" => Ok(Self::Fixed),
165            "BetweenLines" | "betweenlines" | "between_lines" => Ok(Self::BetweenLines),
166            _ => Err(FoundationError::ParseError {
167                type_name: "LineSpacingType".to_string(),
168                value: s.to_string(),
169                valid_values: "Percentage, Fixed, BetweenLines".to_string(),
170            }),
171        }
172    }
173}
174
175impl TryFrom<u8> for LineSpacingType {
176    type Error = FoundationError;
177
178    fn try_from(value: u8) -> Result<Self, Self::Error> {
179        match value {
180            0 => Ok(Self::Percentage),
181            1 => Ok(Self::Fixed),
182            2 => Ok(Self::BetweenLines),
183            _ => Err(FoundationError::ParseError {
184                type_name: "LineSpacingType".to_string(),
185                value: value.to_string(),
186                valid_values: "0 (Percentage), 1 (Fixed), 2 (BetweenLines)".to_string(),
187            }),
188        }
189    }
190}
191
192impl schemars::JsonSchema for LineSpacingType {
193    fn schema_name() -> std::borrow::Cow<'static, str> {
194        std::borrow::Cow::Borrowed("LineSpacingType")
195    }
196
197    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
198        gen.subschema_for::<String>()
199    }
200}
201
202// ---------------------------------------------------------------------------
203// BreakType
204// ---------------------------------------------------------------------------
205
206/// Page/column break type before a paragraph.
207///
208/// # Examples
209///
210/// ```
211/// use hwpforge_foundation::BreakType;
212///
213/// assert_eq!(BreakType::default(), BreakType::None);
214/// ```
215#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
216#[non_exhaustive]
217#[repr(u8)]
218pub enum BreakType {
219    /// No break.
220    #[default]
221    None = 0,
222    /// Column break.
223    Column = 1,
224    /// Page break.
225    Page = 2,
226}
227
228impl fmt::Display for BreakType {
229    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230        match self {
231            Self::None => f.write_str("None"),
232            Self::Column => f.write_str("Column"),
233            Self::Page => f.write_str("Page"),
234        }
235    }
236}
237
238impl std::str::FromStr for BreakType {
239    type Err = FoundationError;
240
241    fn from_str(s: &str) -> Result<Self, Self::Err> {
242        match s {
243            "None" | "none" => Ok(Self::None),
244            "Column" | "column" => Ok(Self::Column),
245            "Page" | "page" => Ok(Self::Page),
246            _ => Err(FoundationError::ParseError {
247                type_name: "BreakType".to_string(),
248                value: s.to_string(),
249                valid_values: "None, Column, Page".to_string(),
250            }),
251        }
252    }
253}
254
255impl TryFrom<u8> for BreakType {
256    type Error = FoundationError;
257
258    fn try_from(value: u8) -> Result<Self, Self::Error> {
259        match value {
260            0 => Ok(Self::None),
261            1 => Ok(Self::Column),
262            2 => Ok(Self::Page),
263            _ => Err(FoundationError::ParseError {
264                type_name: "BreakType".to_string(),
265                value: value.to_string(),
266                valid_values: "0 (None), 1 (Column), 2 (Page)".to_string(),
267            }),
268        }
269    }
270}
271
272impl schemars::JsonSchema for BreakType {
273    fn schema_name() -> std::borrow::Cow<'static, str> {
274        std::borrow::Cow::Borrowed("BreakType")
275    }
276
277    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
278        gen.subschema_for::<String>()
279    }
280}
281
282// ---------------------------------------------------------------------------
283// Language
284// ---------------------------------------------------------------------------
285
286/// HWP5 language slots for font assignment.
287///
288/// Each character shape stores a font per language slot.
289/// The discriminant values match the HWP5 specification exactly.
290///
291/// # Examples
292///
293/// ```
294/// use hwpforge_foundation::Language;
295///
296/// assert_eq!(Language::COUNT, 7);
297/// assert_eq!(Language::Korean as u8, 0);
298/// ```
299#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
300#[non_exhaustive]
301#[repr(u8)]
302pub enum Language {
303    /// Korean (slot 0).
304    #[default]
305    Korean = 0,
306    /// English (slot 1).
307    English = 1,
308    /// Chinese characters / Hanja (slot 2).
309    Hanja = 2,
310    /// Japanese (slot 3).
311    Japanese = 3,
312    /// Other languages (slot 4).
313    Other = 4,
314    /// Symbol characters (slot 5).
315    Symbol = 5,
316    /// User-defined (slot 6).
317    User = 6,
318}
319
320impl Language {
321    /// Total number of language slots (7), matching the HWP5 spec.
322    pub const COUNT: usize = 7;
323
324    /// All language variants in slot order.
325    pub const ALL: [Self; 7] = [
326        Self::Korean,
327        Self::English,
328        Self::Hanja,
329        Self::Japanese,
330        Self::Other,
331        Self::Symbol,
332        Self::User,
333    ];
334}
335
336impl fmt::Display for Language {
337    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
338        match self {
339            Self::Korean => f.write_str("Korean"),
340            Self::English => f.write_str("English"),
341            Self::Hanja => f.write_str("Hanja"),
342            Self::Japanese => f.write_str("Japanese"),
343            Self::Other => f.write_str("Other"),
344            Self::Symbol => f.write_str("Symbol"),
345            Self::User => f.write_str("User"),
346        }
347    }
348}
349
350impl std::str::FromStr for Language {
351    type Err = FoundationError;
352
353    fn from_str(s: &str) -> Result<Self, Self::Err> {
354        match s {
355            "Korean" | "korean" => Ok(Self::Korean),
356            "English" | "english" => Ok(Self::English),
357            "Hanja" | "hanja" => Ok(Self::Hanja),
358            "Japanese" | "japanese" => Ok(Self::Japanese),
359            "Other" | "other" => Ok(Self::Other),
360            "Symbol" | "symbol" => Ok(Self::Symbol),
361            "User" | "user" => Ok(Self::User),
362            _ => Err(FoundationError::ParseError {
363                type_name: "Language".to_string(),
364                value: s.to_string(),
365                valid_values: "Korean, English, Hanja, Japanese, Other, Symbol, User".to_string(),
366            }),
367        }
368    }
369}
370
371impl TryFrom<u8> for Language {
372    type Error = FoundationError;
373
374    fn try_from(value: u8) -> Result<Self, Self::Error> {
375        match value {
376            0 => Ok(Self::Korean),
377            1 => Ok(Self::English),
378            2 => Ok(Self::Hanja),
379            3 => Ok(Self::Japanese),
380            4 => Ok(Self::Other),
381            5 => Ok(Self::Symbol),
382            6 => Ok(Self::User),
383            _ => Err(FoundationError::ParseError {
384                type_name: "Language".to_string(),
385                value: value.to_string(),
386                valid_values: "0-6 (Korean, English, Hanja, Japanese, Other, Symbol, User)"
387                    .to_string(),
388            }),
389        }
390    }
391}
392
393impl schemars::JsonSchema for Language {
394    fn schema_name() -> std::borrow::Cow<'static, str> {
395        std::borrow::Cow::Borrowed("Language")
396    }
397
398    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
399        gen.subschema_for::<String>()
400    }
401}
402
403// ---------------------------------------------------------------------------
404// UnderlineType
405// ---------------------------------------------------------------------------
406
407/// Underline decoration type.
408///
409/// # Examples
410///
411/// ```
412/// use hwpforge_foundation::UnderlineType;
413///
414/// assert_eq!(UnderlineType::default(), UnderlineType::None);
415/// ```
416#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
417#[non_exhaustive]
418#[repr(u8)]
419pub enum UnderlineType {
420    /// No underline (default).
421    #[default]
422    None = 0,
423    /// Single straight line below text.
424    Bottom = 1,
425    /// Single line centered on text.
426    Center = 2,
427    /// Single line above text.
428    Top = 3,
429}
430
431impl fmt::Display for UnderlineType {
432    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
433        match self {
434            Self::None => f.write_str("None"),
435            Self::Bottom => f.write_str("Bottom"),
436            Self::Center => f.write_str("Center"),
437            Self::Top => f.write_str("Top"),
438        }
439    }
440}
441
442impl std::str::FromStr for UnderlineType {
443    type Err = FoundationError;
444
445    fn from_str(s: &str) -> Result<Self, Self::Err> {
446        match s {
447            "None" | "none" => Ok(Self::None),
448            "Bottom" | "bottom" => Ok(Self::Bottom),
449            "Center" | "center" => Ok(Self::Center),
450            "Top" | "top" => Ok(Self::Top),
451            _ => Err(FoundationError::ParseError {
452                type_name: "UnderlineType".to_string(),
453                value: s.to_string(),
454                valid_values: "None, Bottom, Center, Top".to_string(),
455            }),
456        }
457    }
458}
459
460impl TryFrom<u8> for UnderlineType {
461    type Error = FoundationError;
462
463    fn try_from(value: u8) -> Result<Self, Self::Error> {
464        match value {
465            0 => Ok(Self::None),
466            1 => Ok(Self::Bottom),
467            2 => Ok(Self::Center),
468            3 => Ok(Self::Top),
469            _ => Err(FoundationError::ParseError {
470                type_name: "UnderlineType".to_string(),
471                value: value.to_string(),
472                valid_values: "0 (None), 1 (Bottom), 2 (Center), 3 (Top)".to_string(),
473            }),
474        }
475    }
476}
477
478impl schemars::JsonSchema for UnderlineType {
479    fn schema_name() -> std::borrow::Cow<'static, str> {
480        std::borrow::Cow::Borrowed("UnderlineType")
481    }
482
483    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
484        gen.subschema_for::<String>()
485    }
486}
487
488// ---------------------------------------------------------------------------
489// StrikeoutShape
490// ---------------------------------------------------------------------------
491
492/// Strikeout line shape.
493///
494/// # Examples
495///
496/// ```
497/// use hwpforge_foundation::StrikeoutShape;
498///
499/// assert_eq!(StrikeoutShape::default(), StrikeoutShape::None);
500/// ```
501#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
502#[non_exhaustive]
503#[repr(u8)]
504pub enum StrikeoutShape {
505    /// No strikeout (default).
506    #[default]
507    None = 0,
508    /// Continuous straight line.
509    Continuous = 1,
510    /// Dashed line.
511    Dash = 2,
512    /// Dotted line.
513    Dot = 3,
514    /// Dash-dot pattern.
515    DashDot = 4,
516    /// Dash-dot-dot pattern.
517    DashDotDot = 5,
518}
519
520impl fmt::Display for StrikeoutShape {
521    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
522        match self {
523            Self::None => f.write_str("None"),
524            Self::Continuous => f.write_str("Continuous"),
525            Self::Dash => f.write_str("Dash"),
526            Self::Dot => f.write_str("Dot"),
527            Self::DashDot => f.write_str("DashDot"),
528            Self::DashDotDot => f.write_str("DashDotDot"),
529        }
530    }
531}
532
533impl std::str::FromStr for StrikeoutShape {
534    type Err = FoundationError;
535
536    fn from_str(s: &str) -> Result<Self, Self::Err> {
537        match s {
538            "None" | "none" => Ok(Self::None),
539            "Continuous" | "continuous" => Ok(Self::Continuous),
540            "Dash" | "dash" => Ok(Self::Dash),
541            "Dot" | "dot" => Ok(Self::Dot),
542            "DashDot" | "dashdot" | "dash_dot" => Ok(Self::DashDot),
543            "DashDotDot" | "dashdotdot" | "dash_dot_dot" => Ok(Self::DashDotDot),
544            _ => Err(FoundationError::ParseError {
545                type_name: "StrikeoutShape".to_string(),
546                value: s.to_string(),
547                valid_values: "None, Continuous, Dash, Dot, DashDot, DashDotDot".to_string(),
548            }),
549        }
550    }
551}
552
553impl TryFrom<u8> for StrikeoutShape {
554    type Error = FoundationError;
555
556    fn try_from(value: u8) -> Result<Self, Self::Error> {
557        match value {
558            0 => Ok(Self::None),
559            1 => Ok(Self::Continuous),
560            2 => Ok(Self::Dash),
561            3 => Ok(Self::Dot),
562            4 => Ok(Self::DashDot),
563            5 => Ok(Self::DashDotDot),
564            _ => Err(FoundationError::ParseError {
565                type_name: "StrikeoutShape".to_string(),
566                value: value.to_string(),
567                valid_values: "0-5 (None, Continuous, Dash, Dot, DashDot, DashDotDot)".to_string(),
568            }),
569        }
570    }
571}
572
573impl schemars::JsonSchema for StrikeoutShape {
574    fn schema_name() -> std::borrow::Cow<'static, str> {
575        std::borrow::Cow::Borrowed("StrikeoutShape")
576    }
577
578    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
579        gen.subschema_for::<String>()
580    }
581}
582
583// ---------------------------------------------------------------------------
584// OutlineType
585// ---------------------------------------------------------------------------
586
587/// Text outline type (1pt border around glyphs).
588///
589/// # Examples
590///
591/// ```
592/// use hwpforge_foundation::OutlineType;
593///
594/// assert_eq!(OutlineType::default(), OutlineType::None);
595/// ```
596#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
597#[non_exhaustive]
598#[repr(u8)]
599pub enum OutlineType {
600    /// No outline (default).
601    #[default]
602    None = 0,
603    /// Solid 1pt outline.
604    Solid = 1,
605}
606
607impl fmt::Display for OutlineType {
608    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
609        match self {
610            Self::None => f.write_str("None"),
611            Self::Solid => f.write_str("Solid"),
612        }
613    }
614}
615
616impl std::str::FromStr for OutlineType {
617    type Err = FoundationError;
618
619    fn from_str(s: &str) -> Result<Self, Self::Err> {
620        match s {
621            "None" | "none" => Ok(Self::None),
622            "Solid" | "solid" => Ok(Self::Solid),
623            _ => Err(FoundationError::ParseError {
624                type_name: "OutlineType".to_string(),
625                value: s.to_string(),
626                valid_values: "None, Solid".to_string(),
627            }),
628        }
629    }
630}
631
632impl TryFrom<u8> for OutlineType {
633    type Error = FoundationError;
634
635    fn try_from(value: u8) -> Result<Self, Self::Error> {
636        match value {
637            0 => Ok(Self::None),
638            1 => Ok(Self::Solid),
639            _ => Err(FoundationError::ParseError {
640                type_name: "OutlineType".to_string(),
641                value: value.to_string(),
642                valid_values: "0 (None), 1 (Solid)".to_string(),
643            }),
644        }
645    }
646}
647
648impl schemars::JsonSchema for OutlineType {
649    fn schema_name() -> std::borrow::Cow<'static, str> {
650        std::borrow::Cow::Borrowed("OutlineType")
651    }
652
653    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
654        gen.subschema_for::<String>()
655    }
656}
657
658// ---------------------------------------------------------------------------
659// ShadowType
660// ---------------------------------------------------------------------------
661
662/// Text shadow type.
663///
664/// # Examples
665///
666/// ```
667/// use hwpforge_foundation::ShadowType;
668///
669/// assert_eq!(ShadowType::default(), ShadowType::None);
670/// ```
671#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
672#[non_exhaustive]
673#[repr(u8)]
674pub enum ShadowType {
675    /// No shadow (default).
676    #[default]
677    None = 0,
678    /// Drop shadow.
679    Drop = 1,
680}
681
682impl fmt::Display for ShadowType {
683    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
684        match self {
685            Self::None => f.write_str("None"),
686            Self::Drop => f.write_str("Drop"),
687        }
688    }
689}
690
691impl std::str::FromStr for ShadowType {
692    type Err = FoundationError;
693
694    fn from_str(s: &str) -> Result<Self, Self::Err> {
695        match s {
696            "None" | "none" => Ok(Self::None),
697            "Drop" | "drop" => Ok(Self::Drop),
698            _ => Err(FoundationError::ParseError {
699                type_name: "ShadowType".to_string(),
700                value: s.to_string(),
701                valid_values: "None, Drop".to_string(),
702            }),
703        }
704    }
705}
706
707impl TryFrom<u8> for ShadowType {
708    type Error = FoundationError;
709
710    fn try_from(value: u8) -> Result<Self, Self::Error> {
711        match value {
712            0 => Ok(Self::None),
713            1 => Ok(Self::Drop),
714            _ => Err(FoundationError::ParseError {
715                type_name: "ShadowType".to_string(),
716                value: value.to_string(),
717                valid_values: "0 (None), 1 (Drop)".to_string(),
718            }),
719        }
720    }
721}
722
723impl schemars::JsonSchema for ShadowType {
724    fn schema_name() -> std::borrow::Cow<'static, str> {
725        std::borrow::Cow::Borrowed("ShadowType")
726    }
727
728    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
729        gen.subschema_for::<String>()
730    }
731}
732
733// ---------------------------------------------------------------------------
734// EmbossType
735// ---------------------------------------------------------------------------
736
737/// Text embossing (raised appearance).
738///
739/// # Examples
740///
741/// ```
742/// use hwpforge_foundation::EmbossType;
743///
744/// assert_eq!(EmbossType::default(), EmbossType::None);
745/// ```
746#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
747#[non_exhaustive]
748#[repr(u8)]
749pub enum EmbossType {
750    /// No emboss (default).
751    #[default]
752    None = 0,
753    /// Raised emboss effect.
754    Emboss = 1,
755}
756
757impl fmt::Display for EmbossType {
758    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
759        match self {
760            Self::None => f.write_str("None"),
761            Self::Emboss => f.write_str("Emboss"),
762        }
763    }
764}
765
766impl std::str::FromStr for EmbossType {
767    type Err = FoundationError;
768
769    fn from_str(s: &str) -> Result<Self, Self::Err> {
770        match s {
771            "None" | "none" => Ok(Self::None),
772            "Emboss" | "emboss" => Ok(Self::Emboss),
773            _ => Err(FoundationError::ParseError {
774                type_name: "EmbossType".to_string(),
775                value: s.to_string(),
776                valid_values: "None, Emboss".to_string(),
777            }),
778        }
779    }
780}
781
782impl TryFrom<u8> for EmbossType {
783    type Error = FoundationError;
784
785    fn try_from(value: u8) -> Result<Self, Self::Error> {
786        match value {
787            0 => Ok(Self::None),
788            1 => Ok(Self::Emboss),
789            _ => Err(FoundationError::ParseError {
790                type_name: "EmbossType".to_string(),
791                value: value.to_string(),
792                valid_values: "0 (None), 1 (Emboss)".to_string(),
793            }),
794        }
795    }
796}
797
798impl schemars::JsonSchema for EmbossType {
799    fn schema_name() -> std::borrow::Cow<'static, str> {
800        std::borrow::Cow::Borrowed("EmbossType")
801    }
802
803    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
804        gen.subschema_for::<String>()
805    }
806}
807
808// ---------------------------------------------------------------------------
809// EngraveType
810// ---------------------------------------------------------------------------
811
812/// Text engraving (sunken appearance).
813///
814/// # Examples
815///
816/// ```
817/// use hwpforge_foundation::EngraveType;
818///
819/// assert_eq!(EngraveType::default(), EngraveType::None);
820/// ```
821#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
822#[non_exhaustive]
823#[repr(u8)]
824pub enum EngraveType {
825    /// No engrave (default).
826    #[default]
827    None = 0,
828    /// Sunken engrave effect.
829    Engrave = 1,
830}
831
832impl fmt::Display for EngraveType {
833    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
834        match self {
835            Self::None => f.write_str("None"),
836            Self::Engrave => f.write_str("Engrave"),
837        }
838    }
839}
840
841impl std::str::FromStr for EngraveType {
842    type Err = FoundationError;
843
844    fn from_str(s: &str) -> Result<Self, Self::Err> {
845        match s {
846            "None" | "none" => Ok(Self::None),
847            "Engrave" | "engrave" => Ok(Self::Engrave),
848            _ => Err(FoundationError::ParseError {
849                type_name: "EngraveType".to_string(),
850                value: s.to_string(),
851                valid_values: "None, Engrave".to_string(),
852            }),
853        }
854    }
855}
856
857impl TryFrom<u8> for EngraveType {
858    type Error = FoundationError;
859
860    fn try_from(value: u8) -> Result<Self, Self::Error> {
861        match value {
862            0 => Ok(Self::None),
863            1 => Ok(Self::Engrave),
864            _ => Err(FoundationError::ParseError {
865                type_name: "EngraveType".to_string(),
866                value: value.to_string(),
867                valid_values: "0 (None), 1 (Engrave)".to_string(),
868            }),
869        }
870    }
871}
872
873impl schemars::JsonSchema for EngraveType {
874    fn schema_name() -> std::borrow::Cow<'static, str> {
875        std::borrow::Cow::Borrowed("EngraveType")
876    }
877
878    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
879        gen.subschema_for::<String>()
880    }
881}
882
883// ---------------------------------------------------------------------------
884// VerticalPosition
885// ---------------------------------------------------------------------------
886
887/// Superscript/subscript position type.
888///
889/// # Examples
890///
891/// ```
892/// use hwpforge_foundation::VerticalPosition;
893///
894/// assert_eq!(VerticalPosition::default(), VerticalPosition::Normal);
895/// ```
896#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
897#[non_exhaustive]
898#[repr(u8)]
899pub enum VerticalPosition {
900    /// Normal baseline (default).
901    #[default]
902    Normal = 0,
903    /// Superscript.
904    Superscript = 1,
905    /// Subscript.
906    Subscript = 2,
907}
908
909impl fmt::Display for VerticalPosition {
910    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
911        match self {
912            Self::Normal => f.write_str("Normal"),
913            Self::Superscript => f.write_str("Superscript"),
914            Self::Subscript => f.write_str("Subscript"),
915        }
916    }
917}
918
919impl std::str::FromStr for VerticalPosition {
920    type Err = FoundationError;
921
922    fn from_str(s: &str) -> Result<Self, Self::Err> {
923        match s {
924            "Normal" | "normal" => Ok(Self::Normal),
925            "Superscript" | "superscript" | "super" => Ok(Self::Superscript),
926            "Subscript" | "subscript" | "sub" => Ok(Self::Subscript),
927            _ => Err(FoundationError::ParseError {
928                type_name: "VerticalPosition".to_string(),
929                value: s.to_string(),
930                valid_values: "Normal, Superscript, Subscript".to_string(),
931            }),
932        }
933    }
934}
935
936impl TryFrom<u8> for VerticalPosition {
937    type Error = FoundationError;
938
939    fn try_from(value: u8) -> Result<Self, Self::Error> {
940        match value {
941            0 => Ok(Self::Normal),
942            1 => Ok(Self::Superscript),
943            2 => Ok(Self::Subscript),
944            _ => Err(FoundationError::ParseError {
945                type_name: "VerticalPosition".to_string(),
946                value: value.to_string(),
947                valid_values: "0 (Normal), 1 (Superscript), 2 (Subscript)".to_string(),
948            }),
949        }
950    }
951}
952
953impl schemars::JsonSchema for VerticalPosition {
954    fn schema_name() -> std::borrow::Cow<'static, str> {
955        std::borrow::Cow::Borrowed("VerticalPosition")
956    }
957
958    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
959        gen.subschema_for::<String>()
960    }
961}
962
963// ---------------------------------------------------------------------------
964// BorderLineType
965// ---------------------------------------------------------------------------
966
967/// Border line type.
968///
969/// # Examples
970///
971/// ```
972/// use hwpforge_foundation::BorderLineType;
973///
974/// assert_eq!(BorderLineType::default(), BorderLineType::None);
975/// ```
976#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
977#[non_exhaustive]
978#[repr(u8)]
979pub enum BorderLineType {
980    /// No border.
981    #[default]
982    None = 0,
983    /// Solid line.
984    Solid = 1,
985    /// Dashed line.
986    Dash = 2,
987    /// Dotted line.
988    Dot = 3,
989    /// Dash-dot pattern.
990    DashDot = 4,
991    /// Dash-dot-dot pattern.
992    DashDotDot = 5,
993    /// Long dash pattern.
994    LongDash = 6,
995    /// Triple dot pattern.
996    TripleDot = 7,
997    /// Double line.
998    Double = 8,
999    /// Thin-thick double.
1000    DoubleSlim = 9,
1001    /// Thick-thin double.
1002    ThickBetweenSlim = 10,
1003}
1004
1005impl fmt::Display for BorderLineType {
1006    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1007        match self {
1008            Self::None => f.write_str("None"),
1009            Self::Solid => f.write_str("Solid"),
1010            Self::Dash => f.write_str("Dash"),
1011            Self::Dot => f.write_str("Dot"),
1012            Self::DashDot => f.write_str("DashDot"),
1013            Self::DashDotDot => f.write_str("DashDotDot"),
1014            Self::LongDash => f.write_str("LongDash"),
1015            Self::TripleDot => f.write_str("TripleDot"),
1016            Self::Double => f.write_str("Double"),
1017            Self::DoubleSlim => f.write_str("DoubleSlim"),
1018            Self::ThickBetweenSlim => f.write_str("ThickBetweenSlim"),
1019        }
1020    }
1021}
1022
1023impl std::str::FromStr for BorderLineType {
1024    type Err = FoundationError;
1025
1026    fn from_str(s: &str) -> Result<Self, Self::Err> {
1027        match s {
1028            "None" | "none" => Ok(Self::None),
1029            "Solid" | "solid" => Ok(Self::Solid),
1030            "Dash" | "dash" => Ok(Self::Dash),
1031            "Dot" | "dot" => Ok(Self::Dot),
1032            "DashDot" | "dashdot" | "dash_dot" => Ok(Self::DashDot),
1033            "DashDotDot" | "dashdotdot" | "dash_dot_dot" => Ok(Self::DashDotDot),
1034            "LongDash" | "longdash" | "long_dash" => Ok(Self::LongDash),
1035            "TripleDot" | "tripledot" | "triple_dot" => Ok(Self::TripleDot),
1036            "Double" | "double" => Ok(Self::Double),
1037            "DoubleSlim" | "doubleslim" | "double_slim" => Ok(Self::DoubleSlim),
1038            "ThickBetweenSlim" | "thickbetweenslim" | "thick_between_slim" => {
1039                Ok(Self::ThickBetweenSlim)
1040            }
1041            _ => Err(FoundationError::ParseError {
1042                type_name: "BorderLineType".to_string(),
1043                value: s.to_string(),
1044                valid_values: "None, Solid, Dash, Dot, DashDot, DashDotDot, LongDash, TripleDot, Double, DoubleSlim, ThickBetweenSlim".to_string(),
1045            }),
1046        }
1047    }
1048}
1049
1050impl TryFrom<u8> for BorderLineType {
1051    type Error = FoundationError;
1052
1053    fn try_from(value: u8) -> Result<Self, Self::Error> {
1054        match value {
1055            0 => Ok(Self::None),
1056            1 => Ok(Self::Solid),
1057            2 => Ok(Self::Dash),
1058            3 => Ok(Self::Dot),
1059            4 => Ok(Self::DashDot),
1060            5 => Ok(Self::DashDotDot),
1061            6 => Ok(Self::LongDash),
1062            7 => Ok(Self::TripleDot),
1063            8 => Ok(Self::Double),
1064            9 => Ok(Self::DoubleSlim),
1065            10 => Ok(Self::ThickBetweenSlim),
1066            _ => Err(FoundationError::ParseError {
1067                type_name: "BorderLineType".to_string(),
1068                value: value.to_string(),
1069                valid_values: "0-10 (None, Solid, Dash, Dot, DashDot, DashDotDot, LongDash, TripleDot, Double, DoubleSlim, ThickBetweenSlim)".to_string(),
1070            }),
1071        }
1072    }
1073}
1074
1075impl schemars::JsonSchema for BorderLineType {
1076    fn schema_name() -> std::borrow::Cow<'static, str> {
1077        std::borrow::Cow::Borrowed("BorderLineType")
1078    }
1079
1080    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
1081        gen.subschema_for::<String>()
1082    }
1083}
1084
1085// ---------------------------------------------------------------------------
1086// FillBrushType
1087// ---------------------------------------------------------------------------
1088
1089/// Fill brush type for backgrounds.
1090///
1091/// # Examples
1092///
1093/// ```
1094/// use hwpforge_foundation::FillBrushType;
1095///
1096/// assert_eq!(FillBrushType::default(), FillBrushType::None);
1097/// ```
1098#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
1099#[non_exhaustive]
1100#[repr(u8)]
1101pub enum FillBrushType {
1102    /// No fill (transparent, default).
1103    #[default]
1104    None = 0,
1105    /// Solid color fill.
1106    Solid = 1,
1107    /// Gradient fill (linear or radial).
1108    Gradient = 2,
1109    /// Pattern fill (hatch, dots, etc.).
1110    Pattern = 3,
1111}
1112
1113impl fmt::Display for FillBrushType {
1114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1115        match self {
1116            Self::None => f.write_str("None"),
1117            Self::Solid => f.write_str("Solid"),
1118            Self::Gradient => f.write_str("Gradient"),
1119            Self::Pattern => f.write_str("Pattern"),
1120        }
1121    }
1122}
1123
1124impl std::str::FromStr for FillBrushType {
1125    type Err = FoundationError;
1126
1127    fn from_str(s: &str) -> Result<Self, Self::Err> {
1128        match s {
1129            "None" | "none" => Ok(Self::None),
1130            "Solid" | "solid" => Ok(Self::Solid),
1131            "Gradient" | "gradient" => Ok(Self::Gradient),
1132            "Pattern" | "pattern" => Ok(Self::Pattern),
1133            _ => Err(FoundationError::ParseError {
1134                type_name: "FillBrushType".to_string(),
1135                value: s.to_string(),
1136                valid_values: "None, Solid, Gradient, Pattern".to_string(),
1137            }),
1138        }
1139    }
1140}
1141
1142impl TryFrom<u8> for FillBrushType {
1143    type Error = FoundationError;
1144
1145    fn try_from(value: u8) -> Result<Self, Self::Error> {
1146        match value {
1147            0 => Ok(Self::None),
1148            1 => Ok(Self::Solid),
1149            2 => Ok(Self::Gradient),
1150            3 => Ok(Self::Pattern),
1151            _ => Err(FoundationError::ParseError {
1152                type_name: "FillBrushType".to_string(),
1153                value: value.to_string(),
1154                valid_values: "0 (None), 1 (Solid), 2 (Gradient), 3 (Pattern)".to_string(),
1155            }),
1156        }
1157    }
1158}
1159
1160impl schemars::JsonSchema for FillBrushType {
1161    fn schema_name() -> std::borrow::Cow<'static, str> {
1162        std::borrow::Cow::Borrowed("FillBrushType")
1163    }
1164
1165    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
1166        gen.subschema_for::<String>()
1167    }
1168}
1169
1170// ---------------------------------------------------------------------------
1171// ApplyPageType
1172// ---------------------------------------------------------------------------
1173
1174/// Which pages a header/footer applies to.
1175///
1176/// # Examples
1177///
1178/// ```
1179/// use hwpforge_foundation::ApplyPageType;
1180///
1181/// assert_eq!(ApplyPageType::default(), ApplyPageType::Both);
1182/// ```
1183#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
1184#[non_exhaustive]
1185#[repr(u8)]
1186pub enum ApplyPageType {
1187    /// Both even and odd pages (default).
1188    #[default]
1189    Both = 0,
1190    /// Even pages only.
1191    Even = 1,
1192    /// Odd pages only.
1193    Odd = 2,
1194}
1195
1196impl fmt::Display for ApplyPageType {
1197    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1198        match self {
1199            Self::Both => f.write_str("Both"),
1200            Self::Even => f.write_str("Even"),
1201            Self::Odd => f.write_str("Odd"),
1202        }
1203    }
1204}
1205
1206impl std::str::FromStr for ApplyPageType {
1207    type Err = FoundationError;
1208
1209    fn from_str(s: &str) -> Result<Self, Self::Err> {
1210        match s {
1211            "Both" | "both" | "BOTH" => Ok(Self::Both),
1212            "Even" | "even" | "EVEN" => Ok(Self::Even),
1213            "Odd" | "odd" | "ODD" => Ok(Self::Odd),
1214            _ => Err(FoundationError::ParseError {
1215                type_name: "ApplyPageType".to_string(),
1216                value: s.to_string(),
1217                valid_values: "Both, Even, Odd".to_string(),
1218            }),
1219        }
1220    }
1221}
1222
1223impl TryFrom<u8> for ApplyPageType {
1224    type Error = FoundationError;
1225
1226    fn try_from(value: u8) -> Result<Self, Self::Error> {
1227        match value {
1228            0 => Ok(Self::Both),
1229            1 => Ok(Self::Even),
1230            2 => Ok(Self::Odd),
1231            _ => Err(FoundationError::ParseError {
1232                type_name: "ApplyPageType".to_string(),
1233                value: value.to_string(),
1234                valid_values: "0 (Both), 1 (Even), 2 (Odd)".to_string(),
1235            }),
1236        }
1237    }
1238}
1239
1240impl schemars::JsonSchema for ApplyPageType {
1241    fn schema_name() -> std::borrow::Cow<'static, str> {
1242        std::borrow::Cow::Borrowed("ApplyPageType")
1243    }
1244
1245    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
1246        gen.subschema_for::<String>()
1247    }
1248}
1249
1250// ---------------------------------------------------------------------------
1251// NumberFormatType
1252// ---------------------------------------------------------------------------
1253
1254/// Number format for page numbering.
1255///
1256/// # Examples
1257///
1258/// ```
1259/// use hwpforge_foundation::NumberFormatType;
1260///
1261/// assert_eq!(NumberFormatType::default(), NumberFormatType::Digit);
1262/// ```
1263#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
1264#[non_exhaustive]
1265#[repr(u8)]
1266pub enum NumberFormatType {
1267    /// Arabic digits: 1, 2, 3, ... (default).
1268    #[default]
1269    Digit = 0,
1270    /// Circled digits: ①, ②, ③, ...
1271    CircledDigit = 1,
1272    /// Roman capitals: I, II, III, ...
1273    RomanCapital = 2,
1274    /// Roman lowercase: i, ii, iii, ...
1275    RomanSmall = 3,
1276    /// Latin capitals: A, B, C, ...
1277    LatinCapital = 4,
1278    /// Latin lowercase: a, b, c, ...
1279    LatinSmall = 5,
1280    /// Hangul syllable: 가, 나, 다, ...
1281    HangulSyllable = 6,
1282    /// Hangul jamo: ㄱ, ㄴ, ㄷ, ...
1283    HangulJamo = 7,
1284    /// Hanja digits: 一, 二, 三, ...
1285    HanjaDigit = 8,
1286    /// Circled Hangul syllable: ㉮, ㉯, ㉰, ... (used for outline level 8).
1287    CircledHangulSyllable = 9,
1288}
1289
1290impl fmt::Display for NumberFormatType {
1291    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1292        match self {
1293            Self::Digit => f.write_str("Digit"),
1294            Self::CircledDigit => f.write_str("CircledDigit"),
1295            Self::RomanCapital => f.write_str("RomanCapital"),
1296            Self::RomanSmall => f.write_str("RomanSmall"),
1297            Self::LatinCapital => f.write_str("LatinCapital"),
1298            Self::LatinSmall => f.write_str("LatinSmall"),
1299            Self::HangulSyllable => f.write_str("HangulSyllable"),
1300            Self::HangulJamo => f.write_str("HangulJamo"),
1301            Self::HanjaDigit => f.write_str("HanjaDigit"),
1302            Self::CircledHangulSyllable => f.write_str("CircledHangulSyllable"),
1303        }
1304    }
1305}
1306
1307impl std::str::FromStr for NumberFormatType {
1308    type Err = FoundationError;
1309
1310    fn from_str(s: &str) -> Result<Self, Self::Err> {
1311        match s {
1312            "Digit" | "digit" | "DIGIT" => Ok(Self::Digit),
1313            "CircledDigit" | "circleddigit" | "CIRCLED_DIGIT" => Ok(Self::CircledDigit),
1314            "RomanCapital" | "romancapital" | "ROMAN_CAPITAL" => Ok(Self::RomanCapital),
1315            "RomanSmall" | "romansmall" | "ROMAN_SMALL" => Ok(Self::RomanSmall),
1316            "LatinCapital" | "latincapital" | "LATIN_CAPITAL" => Ok(Self::LatinCapital),
1317            "LatinSmall" | "latinsmall" | "LATIN_SMALL" => Ok(Self::LatinSmall),
1318            "HangulSyllable" | "hangulsyllable" | "HANGUL_SYLLABLE" => Ok(Self::HangulSyllable),
1319            "HangulJamo" | "hanguljamo" | "HANGUL_JAMO" => Ok(Self::HangulJamo),
1320            "HanjaDigit" | "hanjadigit" | "HANJA_DIGIT" => Ok(Self::HanjaDigit),
1321            "CircledHangulSyllable" | "circledhangulsyllable" | "CIRCLED_HANGUL_SYLLABLE" => {
1322                Ok(Self::CircledHangulSyllable)
1323            }
1324            _ => Err(FoundationError::ParseError {
1325                type_name: "NumberFormatType".to_string(),
1326                value: s.to_string(),
1327                valid_values: "Digit, CircledDigit, RomanCapital, RomanSmall, LatinCapital, LatinSmall, HangulSyllable, HangulJamo, HanjaDigit, CircledHangulSyllable".to_string(),
1328            }),
1329        }
1330    }
1331}
1332
1333impl TryFrom<u8> for NumberFormatType {
1334    type Error = FoundationError;
1335
1336    fn try_from(value: u8) -> Result<Self, Self::Error> {
1337        match value {
1338            0 => Ok(Self::Digit),
1339            1 => Ok(Self::CircledDigit),
1340            2 => Ok(Self::RomanCapital),
1341            3 => Ok(Self::RomanSmall),
1342            4 => Ok(Self::LatinCapital),
1343            5 => Ok(Self::LatinSmall),
1344            6 => Ok(Self::HangulSyllable),
1345            7 => Ok(Self::HangulJamo),
1346            8 => Ok(Self::HanjaDigit),
1347            9 => Ok(Self::CircledHangulSyllable),
1348            _ => Err(FoundationError::ParseError {
1349                type_name: "NumberFormatType".to_string(),
1350                value: value.to_string(),
1351                valid_values: "0-9 (Digit, CircledDigit, RomanCapital, RomanSmall, LatinCapital, LatinSmall, HangulSyllable, HangulJamo, HanjaDigit, CircledHangulSyllable)".to_string(),
1352            }),
1353        }
1354    }
1355}
1356
1357impl schemars::JsonSchema for NumberFormatType {
1358    fn schema_name() -> std::borrow::Cow<'static, str> {
1359        std::borrow::Cow::Borrowed("NumberFormatType")
1360    }
1361
1362    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
1363        gen.subschema_for::<String>()
1364    }
1365}
1366
1367// ---------------------------------------------------------------------------
1368// PageNumberPosition
1369// ---------------------------------------------------------------------------
1370
1371/// Position of page numbers on the page.
1372///
1373/// # Examples
1374///
1375/// ```
1376/// use hwpforge_foundation::PageNumberPosition;
1377///
1378/// assert_eq!(PageNumberPosition::default(), PageNumberPosition::TopCenter);
1379/// ```
1380#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
1381#[non_exhaustive]
1382#[repr(u8)]
1383pub enum PageNumberPosition {
1384    /// No page number.
1385    None = 0,
1386    /// Top left.
1387    TopLeft = 1,
1388    /// Top center (default).
1389    #[default]
1390    TopCenter = 2,
1391    /// Top right.
1392    TopRight = 3,
1393    /// Bottom left.
1394    BottomLeft = 4,
1395    /// Bottom center.
1396    BottomCenter = 5,
1397    /// Bottom right.
1398    BottomRight = 6,
1399    /// Outside top.
1400    OutsideTop = 7,
1401    /// Outside bottom.
1402    OutsideBottom = 8,
1403    /// Inside top.
1404    InsideTop = 9,
1405    /// Inside bottom.
1406    InsideBottom = 10,
1407}
1408
1409impl fmt::Display for PageNumberPosition {
1410    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1411        match self {
1412            Self::None => f.write_str("None"),
1413            Self::TopLeft => f.write_str("TopLeft"),
1414            Self::TopCenter => f.write_str("TopCenter"),
1415            Self::TopRight => f.write_str("TopRight"),
1416            Self::BottomLeft => f.write_str("BottomLeft"),
1417            Self::BottomCenter => f.write_str("BottomCenter"),
1418            Self::BottomRight => f.write_str("BottomRight"),
1419            Self::OutsideTop => f.write_str("OutsideTop"),
1420            Self::OutsideBottom => f.write_str("OutsideBottom"),
1421            Self::InsideTop => f.write_str("InsideTop"),
1422            Self::InsideBottom => f.write_str("InsideBottom"),
1423        }
1424    }
1425}
1426
1427impl std::str::FromStr for PageNumberPosition {
1428    type Err = FoundationError;
1429
1430    fn from_str(s: &str) -> Result<Self, Self::Err> {
1431        match s {
1432            "None" | "none" | "NONE" => Ok(Self::None),
1433            "TopLeft" | "topleft" | "TOP_LEFT" | "top-left" => Ok(Self::TopLeft),
1434            "TopCenter" | "topcenter" | "TOP_CENTER" | "top-center" => Ok(Self::TopCenter),
1435            "TopRight" | "topright" | "TOP_RIGHT" | "top-right" => Ok(Self::TopRight),
1436            "BottomLeft" | "bottomleft" | "BOTTOM_LEFT" | "bottom-left" => Ok(Self::BottomLeft),
1437            "BottomCenter" | "bottomcenter" | "BOTTOM_CENTER" | "bottom-center" => {
1438                Ok(Self::BottomCenter)
1439            }
1440            "BottomRight" | "bottomright" | "BOTTOM_RIGHT" | "bottom-right" => {
1441                Ok(Self::BottomRight)
1442            }
1443            "OutsideTop" | "outsidetop" | "OUTSIDE_TOP" | "outside-top" => Ok(Self::OutsideTop),
1444            "OutsideBottom" | "outsidebottom" | "OUTSIDE_BOTTOM" | "outside-bottom" => {
1445                Ok(Self::OutsideBottom)
1446            }
1447            "InsideTop" | "insidetop" | "INSIDE_TOP" | "inside-top" => Ok(Self::InsideTop),
1448            "InsideBottom" | "insidebottom" | "INSIDE_BOTTOM" | "inside-bottom" => {
1449                Ok(Self::InsideBottom)
1450            }
1451            _ => Err(FoundationError::ParseError {
1452                type_name: "PageNumberPosition".to_string(),
1453                value: s.to_string(),
1454                valid_values: "None, TopLeft, TopCenter, TopRight, BottomLeft, BottomCenter, BottomRight, OutsideTop, OutsideBottom, InsideTop, InsideBottom".to_string(),
1455            }),
1456        }
1457    }
1458}
1459
1460impl TryFrom<u8> for PageNumberPosition {
1461    type Error = FoundationError;
1462
1463    fn try_from(value: u8) -> Result<Self, Self::Error> {
1464        match value {
1465            0 => Ok(Self::None),
1466            1 => Ok(Self::TopLeft),
1467            2 => Ok(Self::TopCenter),
1468            3 => Ok(Self::TopRight),
1469            4 => Ok(Self::BottomLeft),
1470            5 => Ok(Self::BottomCenter),
1471            6 => Ok(Self::BottomRight),
1472            7 => Ok(Self::OutsideTop),
1473            8 => Ok(Self::OutsideBottom),
1474            9 => Ok(Self::InsideTop),
1475            10 => Ok(Self::InsideBottom),
1476            _ => Err(FoundationError::ParseError {
1477                type_name: "PageNumberPosition".to_string(),
1478                value: value.to_string(),
1479                valid_values: "0-10 (None, TopLeft, TopCenter, TopRight, BottomLeft, BottomCenter, BottomRight, OutsideTop, OutsideBottom, InsideTop, InsideBottom)".to_string(),
1480            }),
1481        }
1482    }
1483}
1484
1485impl schemars::JsonSchema for PageNumberPosition {
1486    fn schema_name() -> std::borrow::Cow<'static, str> {
1487        std::borrow::Cow::Borrowed("PageNumberPosition")
1488    }
1489
1490    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
1491        gen.subschema_for::<String>()
1492    }
1493}
1494
1495// ---------------------------------------------------------------------------
1496// WordBreakType
1497// ---------------------------------------------------------------------------
1498
1499/// Word-breaking behavior for paragraph text justification.
1500///
1501/// Controls how 한글 distributes extra space in justified text.
1502/// `KeepWord` preserves word boundaries (natural spacing),
1503/// `BreakWord` allows breaking at any character (stretched spacing).
1504///
1505/// # Examples
1506///
1507/// ```
1508/// use hwpforge_foundation::WordBreakType;
1509///
1510/// assert_eq!(WordBreakType::default(), WordBreakType::KeepWord);
1511/// assert_eq!(WordBreakType::KeepWord.to_string(), "KEEP_WORD");
1512/// ```
1513#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
1514#[non_exhaustive]
1515#[repr(u8)]
1516pub enum WordBreakType {
1517    /// Keep words intact — distribute space between words only (한글 default).
1518    #[default]
1519    KeepWord = 0,
1520    /// Allow breaking at any character — distribute space between all characters.
1521    BreakWord = 1,
1522}
1523
1524impl fmt::Display for WordBreakType {
1525    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1526        match self {
1527            Self::KeepWord => f.write_str("KEEP_WORD"),
1528            Self::BreakWord => f.write_str("BREAK_WORD"),
1529        }
1530    }
1531}
1532
1533impl std::str::FromStr for WordBreakType {
1534    type Err = FoundationError;
1535
1536    fn from_str(s: &str) -> Result<Self, Self::Err> {
1537        match s {
1538            "KEEP_WORD" | "KeepWord" | "keep_word" => Ok(Self::KeepWord),
1539            "BREAK_WORD" | "BreakWord" | "break_word" => Ok(Self::BreakWord),
1540            _ => Err(FoundationError::ParseError {
1541                type_name: "WordBreakType".to_string(),
1542                value: s.to_string(),
1543                valid_values: "KEEP_WORD, BREAK_WORD".to_string(),
1544            }),
1545        }
1546    }
1547}
1548
1549impl TryFrom<u8> for WordBreakType {
1550    type Error = FoundationError;
1551
1552    fn try_from(value: u8) -> Result<Self, Self::Error> {
1553        match value {
1554            0 => Ok(Self::KeepWord),
1555            1 => Ok(Self::BreakWord),
1556            _ => Err(FoundationError::ParseError {
1557                type_name: "WordBreakType".to_string(),
1558                value: value.to_string(),
1559                valid_values: "0 (KeepWord), 1 (BreakWord)".to_string(),
1560            }),
1561        }
1562    }
1563}
1564
1565impl schemars::JsonSchema for WordBreakType {
1566    fn schema_name() -> std::borrow::Cow<'static, str> {
1567        std::borrow::Cow::Borrowed("WordBreakType")
1568    }
1569
1570    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
1571        gen.subschema_for::<String>()
1572    }
1573}
1574
1575// ---------------------------------------------------------------------------
1576// EmphasisType
1577// ---------------------------------------------------------------------------
1578
1579/// Character emphasis mark (symMark attribute in HWPX).
1580///
1581/// Controls the emphasis symbol displayed above or below characters.
1582/// Maps to HWPX `symMark` attribute values.
1583///
1584/// # Examples
1585///
1586/// ```
1587/// use hwpforge_foundation::EmphasisType;
1588///
1589/// assert_eq!(EmphasisType::default(), EmphasisType::None);
1590/// ```
1591#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
1592#[non_exhaustive]
1593#[repr(u8)]
1594pub enum EmphasisType {
1595    /// No emphasis mark (default).
1596    #[default]
1597    None = 0,
1598    /// Dot above character.
1599    DotAbove = 1,
1600    /// Ring above character.
1601    RingAbove = 2,
1602    /// Tilde above character.
1603    Tilde = 3,
1604    /// Caron (hacek) above character.
1605    Caron = 4,
1606    /// Side dot.
1607    Side = 5,
1608    /// Colon mark.
1609    Colon = 6,
1610    /// Grave accent.
1611    GraveAccent = 7,
1612    /// Acute accent.
1613    AcuteAccent = 8,
1614    /// Circumflex accent.
1615    Circumflex = 9,
1616    /// Macron (overline).
1617    Macron = 10,
1618    /// Hook above.
1619    HookAbove = 11,
1620    /// Dot below character.
1621    DotBelow = 12,
1622}
1623
1624impl fmt::Display for EmphasisType {
1625    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1626        match self {
1627            Self::None => f.write_str("None"),
1628            Self::DotAbove => f.write_str("DotAbove"),
1629            Self::RingAbove => f.write_str("RingAbove"),
1630            Self::Tilde => f.write_str("Tilde"),
1631            Self::Caron => f.write_str("Caron"),
1632            Self::Side => f.write_str("Side"),
1633            Self::Colon => f.write_str("Colon"),
1634            Self::GraveAccent => f.write_str("GraveAccent"),
1635            Self::AcuteAccent => f.write_str("AcuteAccent"),
1636            Self::Circumflex => f.write_str("Circumflex"),
1637            Self::Macron => f.write_str("Macron"),
1638            Self::HookAbove => f.write_str("HookAbove"),
1639            Self::DotBelow => f.write_str("DotBelow"),
1640        }
1641    }
1642}
1643
1644impl std::str::FromStr for EmphasisType {
1645    type Err = FoundationError;
1646
1647    fn from_str(s: &str) -> Result<Self, Self::Err> {
1648        match s {
1649            "NONE" | "None" | "none" => Ok(Self::None),
1650            "DOT_ABOVE" | "DotAbove" | "dot_above" => Ok(Self::DotAbove),
1651            "RING_ABOVE" | "RingAbove" | "ring_above" => Ok(Self::RingAbove),
1652            "TILDE" | "Tilde" | "tilde" => Ok(Self::Tilde),
1653            "CARON" | "Caron" | "caron" => Ok(Self::Caron),
1654            "SIDE" | "Side" | "side" => Ok(Self::Side),
1655            "COLON" | "Colon" | "colon" => Ok(Self::Colon),
1656            "GRAVE_ACCENT" | "GraveAccent" | "grave_accent" => Ok(Self::GraveAccent),
1657            "ACUTE_ACCENT" | "AcuteAccent" | "acute_accent" => Ok(Self::AcuteAccent),
1658            "CIRCUMFLEX" | "Circumflex" | "circumflex" => Ok(Self::Circumflex),
1659            "MACRON" | "Macron" | "macron" => Ok(Self::Macron),
1660            "HOOK_ABOVE" | "HookAbove" | "hook_above" => Ok(Self::HookAbove),
1661            "DOT_BELOW" | "DotBelow" | "dot_below" => Ok(Self::DotBelow),
1662            _ => Err(FoundationError::ParseError {
1663                type_name: "EmphasisType".to_string(),
1664                value: s.to_string(),
1665                valid_values:
1666                    "NONE, DOT_ABOVE, RING_ABOVE, TILDE, CARON, SIDE, COLON, GRAVE_ACCENT, ACUTE_ACCENT, CIRCUMFLEX, MACRON, HOOK_ABOVE, DOT_BELOW"
1667                        .to_string(),
1668            }),
1669        }
1670    }
1671}
1672
1673impl TryFrom<u8> for EmphasisType {
1674    type Error = FoundationError;
1675
1676    fn try_from(value: u8) -> Result<Self, Self::Error> {
1677        match value {
1678            0 => Ok(Self::None),
1679            1 => Ok(Self::DotAbove),
1680            2 => Ok(Self::RingAbove),
1681            3 => Ok(Self::Tilde),
1682            4 => Ok(Self::Caron),
1683            5 => Ok(Self::Side),
1684            6 => Ok(Self::Colon),
1685            7 => Ok(Self::GraveAccent),
1686            8 => Ok(Self::AcuteAccent),
1687            9 => Ok(Self::Circumflex),
1688            10 => Ok(Self::Macron),
1689            11 => Ok(Self::HookAbove),
1690            12 => Ok(Self::DotBelow),
1691            _ => Err(FoundationError::ParseError {
1692                type_name: "EmphasisType".to_string(),
1693                value: value.to_string(),
1694                valid_values: "0-12 (None through DotBelow)".to_string(),
1695            }),
1696        }
1697    }
1698}
1699
1700impl schemars::JsonSchema for EmphasisType {
1701    fn schema_name() -> std::borrow::Cow<'static, str> {
1702        std::borrow::Cow::Borrowed("EmphasisType")
1703    }
1704
1705    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
1706        gen.subschema_for::<String>()
1707    }
1708}
1709
1710// ---------------------------------------------------------------------------
1711// HeadingType
1712// ---------------------------------------------------------------------------
1713
1714/// Paragraph heading type for outline/numbering classification.
1715///
1716/// Controls how a paragraph participates in document outline or numbering.
1717/// Maps to the HWPX `<hh:heading type="...">` attribute.
1718///
1719/// # Examples
1720///
1721/// ```
1722/// use hwpforge_foundation::HeadingType;
1723///
1724/// assert_eq!(HeadingType::default(), HeadingType::None);
1725/// ```
1726#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
1727#[non_exhaustive]
1728#[repr(u8)]
1729pub enum HeadingType {
1730    /// No heading (body text, default).
1731    #[default]
1732    None = 0,
1733    /// Outline heading (개요).
1734    Outline = 1,
1735    /// Number heading.
1736    Number = 2,
1737    /// Bullet heading.
1738    Bullet = 3,
1739}
1740
1741impl HeadingType {
1742    /// Converts to the HWPX XML attribute string.
1743    pub fn to_hwpx_str(self) -> &'static str {
1744        match self {
1745            Self::None => "NONE",
1746            Self::Outline => "OUTLINE",
1747            Self::Number => "NUMBER",
1748            Self::Bullet => "BULLET",
1749        }
1750    }
1751
1752    /// Parses a HWPX XML attribute string.
1753    pub fn from_hwpx_str(s: &str) -> Self {
1754        match s {
1755            "NONE" => Self::None,
1756            "OUTLINE" => Self::Outline,
1757            "NUMBER" => Self::Number,
1758            "BULLET" => Self::Bullet,
1759            _ => Self::None,
1760        }
1761    }
1762}
1763
1764impl fmt::Display for HeadingType {
1765    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1766        match self {
1767            Self::None => f.write_str("None"),
1768            Self::Outline => f.write_str("Outline"),
1769            Self::Number => f.write_str("Number"),
1770            Self::Bullet => f.write_str("Bullet"),
1771        }
1772    }
1773}
1774
1775impl std::str::FromStr for HeadingType {
1776    type Err = FoundationError;
1777
1778    fn from_str(s: &str) -> Result<Self, Self::Err> {
1779        match s {
1780            "None" | "none" | "NONE" => Ok(Self::None),
1781            "Outline" | "outline" | "OUTLINE" => Ok(Self::Outline),
1782            "Number" | "number" | "NUMBER" => Ok(Self::Number),
1783            "Bullet" | "bullet" | "BULLET" => Ok(Self::Bullet),
1784            _ => Err(FoundationError::ParseError {
1785                type_name: "HeadingType".to_string(),
1786                value: s.to_string(),
1787                valid_values: "None, Outline, Number, Bullet".to_string(),
1788            }),
1789        }
1790    }
1791}
1792
1793impl TryFrom<u8> for HeadingType {
1794    type Error = FoundationError;
1795
1796    fn try_from(value: u8) -> Result<Self, Self::Error> {
1797        match value {
1798            0 => Ok(Self::None),
1799            1 => Ok(Self::Outline),
1800            2 => Ok(Self::Number),
1801            3 => Ok(Self::Bullet),
1802            _ => Err(FoundationError::ParseError {
1803                type_name: "HeadingType".to_string(),
1804                value: value.to_string(),
1805                valid_values: "0 (None), 1 (Outline), 2 (Number), 3 (Bullet)".to_string(),
1806            }),
1807        }
1808    }
1809}
1810
1811impl schemars::JsonSchema for HeadingType {
1812    fn schema_name() -> std::borrow::Cow<'static, str> {
1813        std::borrow::Cow::Borrowed("HeadingType")
1814    }
1815
1816    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
1817        gen.subschema_for::<String>()
1818    }
1819}
1820
1821// ---------------------------------------------------------------------------
1822// GutterType
1823// ---------------------------------------------------------------------------
1824
1825/// Gutter position type for page margins.
1826///
1827/// Controls where the binding gutter space is placed on the page.
1828/// Used in `<hp:pagePr gutterType="...">`.
1829///
1830/// # Examples
1831///
1832/// ```
1833/// use hwpforge_foundation::GutterType;
1834///
1835/// assert_eq!(GutterType::default(), GutterType::LeftOnly);
1836/// assert_eq!(GutterType::LeftOnly.to_string(), "LeftOnly");
1837/// ```
1838#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
1839#[non_exhaustive]
1840#[repr(u8)]
1841pub enum GutterType {
1842    /// Gutter on the left side only (default).
1843    #[default]
1844    LeftOnly = 0,
1845    /// Gutter on the left and right sides.
1846    LeftRight = 1,
1847    /// Gutter on the top side only.
1848    TopOnly = 2,
1849    /// Gutter on the top and bottom sides.
1850    TopBottom = 3,
1851}
1852
1853impl fmt::Display for GutterType {
1854    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1855        match self {
1856            Self::LeftOnly => f.write_str("LeftOnly"),
1857            Self::LeftRight => f.write_str("LeftRight"),
1858            Self::TopOnly => f.write_str("TopOnly"),
1859            Self::TopBottom => f.write_str("TopBottom"),
1860        }
1861    }
1862}
1863
1864impl std::str::FromStr for GutterType {
1865    type Err = FoundationError;
1866
1867    fn from_str(s: &str) -> Result<Self, Self::Err> {
1868        match s {
1869            "LeftOnly" | "LEFT_ONLY" | "left_only" => Ok(Self::LeftOnly),
1870            "LeftRight" | "LEFT_RIGHT" | "left_right" => Ok(Self::LeftRight),
1871            "TopOnly" | "TOP_ONLY" | "top_only" => Ok(Self::TopOnly),
1872            "TopBottom" | "TOP_BOTTOM" | "top_bottom" => Ok(Self::TopBottom),
1873            _ => Err(FoundationError::ParseError {
1874                type_name: "GutterType".to_string(),
1875                value: s.to_string(),
1876                valid_values: "LeftOnly, LeftRight, TopOnly, TopBottom".to_string(),
1877            }),
1878        }
1879    }
1880}
1881
1882impl TryFrom<u8> for GutterType {
1883    type Error = FoundationError;
1884
1885    fn try_from(value: u8) -> Result<Self, Self::Error> {
1886        match value {
1887            0 => Ok(Self::LeftOnly),
1888            1 => Ok(Self::LeftRight),
1889            2 => Ok(Self::TopOnly),
1890            3 => Ok(Self::TopBottom),
1891            _ => Err(FoundationError::ParseError {
1892                type_name: "GutterType".to_string(),
1893                value: value.to_string(),
1894                valid_values: "0 (LeftOnly), 1 (LeftRight), 2 (TopOnly), 3 (TopBottom)".to_string(),
1895            }),
1896        }
1897    }
1898}
1899
1900impl schemars::JsonSchema for GutterType {
1901    fn schema_name() -> std::borrow::Cow<'static, str> {
1902        std::borrow::Cow::Borrowed("GutterType")
1903    }
1904
1905    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
1906        gen.subschema_for::<String>()
1907    }
1908}
1909
1910// ---------------------------------------------------------------------------
1911// ShowMode
1912// ---------------------------------------------------------------------------
1913
1914/// Visibility mode for page borders and fills.
1915///
1916/// Controls on which pages the border or fill is displayed.
1917/// Used in `<hp:visibility border="..." fill="...">`.
1918///
1919/// # Examples
1920///
1921/// ```
1922/// use hwpforge_foundation::ShowMode;
1923///
1924/// assert_eq!(ShowMode::default(), ShowMode::ShowAll);
1925/// assert_eq!(ShowMode::ShowAll.to_string(), "ShowAll");
1926/// ```
1927#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
1928#[non_exhaustive]
1929#[repr(u8)]
1930pub enum ShowMode {
1931    /// Show on all pages (default).
1932    #[default]
1933    ShowAll = 0,
1934    /// Hide on all pages.
1935    HideAll = 1,
1936    /// Show on odd pages only.
1937    ShowOdd = 2,
1938    /// Show on even pages only.
1939    ShowEven = 3,
1940}
1941
1942impl fmt::Display for ShowMode {
1943    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1944        match self {
1945            Self::ShowAll => f.write_str("ShowAll"),
1946            Self::HideAll => f.write_str("HideAll"),
1947            Self::ShowOdd => f.write_str("ShowOdd"),
1948            Self::ShowEven => f.write_str("ShowEven"),
1949        }
1950    }
1951}
1952
1953impl std::str::FromStr for ShowMode {
1954    type Err = FoundationError;
1955
1956    fn from_str(s: &str) -> Result<Self, Self::Err> {
1957        match s {
1958            "ShowAll" | "SHOW_ALL" | "show_all" => Ok(Self::ShowAll),
1959            "HideAll" | "HIDE_ALL" | "hide_all" => Ok(Self::HideAll),
1960            "ShowOdd" | "SHOW_ODD" | "show_odd" => Ok(Self::ShowOdd),
1961            "ShowEven" | "SHOW_EVEN" | "show_even" => Ok(Self::ShowEven),
1962            _ => Err(FoundationError::ParseError {
1963                type_name: "ShowMode".to_string(),
1964                value: s.to_string(),
1965                valid_values: "ShowAll, HideAll, ShowOdd, ShowEven".to_string(),
1966            }),
1967        }
1968    }
1969}
1970
1971impl TryFrom<u8> for ShowMode {
1972    type Error = FoundationError;
1973
1974    fn try_from(value: u8) -> Result<Self, Self::Error> {
1975        match value {
1976            0 => Ok(Self::ShowAll),
1977            1 => Ok(Self::HideAll),
1978            2 => Ok(Self::ShowOdd),
1979            3 => Ok(Self::ShowEven),
1980            _ => Err(FoundationError::ParseError {
1981                type_name: "ShowMode".to_string(),
1982                value: value.to_string(),
1983                valid_values: "0 (ShowAll), 1 (HideAll), 2 (ShowOdd), 3 (ShowEven)".to_string(),
1984            }),
1985        }
1986    }
1987}
1988
1989impl schemars::JsonSchema for ShowMode {
1990    fn schema_name() -> std::borrow::Cow<'static, str> {
1991        std::borrow::Cow::Borrowed("ShowMode")
1992    }
1993
1994    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
1995        gen.subschema_for::<String>()
1996    }
1997}
1998
1999// ---------------------------------------------------------------------------
2000// RestartType
2001// ---------------------------------------------------------------------------
2002
2003/// Line number restart type.
2004///
2005/// Controls when line numbering restarts to 1.
2006/// Used in `<hp:lineNumberShape restartType="...">`.
2007///
2008/// # Examples
2009///
2010/// ```
2011/// use hwpforge_foundation::RestartType;
2012///
2013/// assert_eq!(RestartType::default(), RestartType::Continuous);
2014/// assert_eq!(RestartType::Continuous.to_string(), "Continuous");
2015/// ```
2016#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
2017#[non_exhaustive]
2018#[repr(u8)]
2019pub enum RestartType {
2020    /// Continuous numbering throughout the document (default).
2021    #[default]
2022    Continuous = 0,
2023    /// Restart numbering at each section.
2024    Section = 1,
2025    /// Restart numbering at each page.
2026    Page = 2,
2027}
2028
2029impl fmt::Display for RestartType {
2030    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2031        match self {
2032            Self::Continuous => f.write_str("Continuous"),
2033            Self::Section => f.write_str("Section"),
2034            Self::Page => f.write_str("Page"),
2035        }
2036    }
2037}
2038
2039impl std::str::FromStr for RestartType {
2040    type Err = FoundationError;
2041
2042    fn from_str(s: &str) -> Result<Self, Self::Err> {
2043        match s {
2044            "Continuous" | "continuous" | "0" => Ok(Self::Continuous),
2045            "Section" | "section" | "1" => Ok(Self::Section),
2046            "Page" | "page" | "2" => Ok(Self::Page),
2047            _ => Err(FoundationError::ParseError {
2048                type_name: "RestartType".to_string(),
2049                value: s.to_string(),
2050                valid_values: "Continuous, Section, Page".to_string(),
2051            }),
2052        }
2053    }
2054}
2055
2056impl TryFrom<u8> for RestartType {
2057    type Error = FoundationError;
2058
2059    fn try_from(value: u8) -> Result<Self, Self::Error> {
2060        match value {
2061            0 => Ok(Self::Continuous),
2062            1 => Ok(Self::Section),
2063            2 => Ok(Self::Page),
2064            _ => Err(FoundationError::ParseError {
2065                type_name: "RestartType".to_string(),
2066                value: value.to_string(),
2067                valid_values: "0 (Continuous), 1 (Section), 2 (Page)".to_string(),
2068            }),
2069        }
2070    }
2071}
2072
2073impl schemars::JsonSchema for RestartType {
2074    fn schema_name() -> std::borrow::Cow<'static, str> {
2075        std::borrow::Cow::Borrowed("RestartType")
2076    }
2077
2078    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
2079        gen.subschema_for::<String>()
2080    }
2081}
2082
2083// ---------------------------------------------------------------------------
2084// TextBorderType
2085// ---------------------------------------------------------------------------
2086
2087/// Reference frame for page border offset measurement.
2088///
2089/// Controls whether page border offsets are measured from the paper edge
2090/// or from the content area.
2091///
2092/// # Examples
2093///
2094/// ```
2095/// use hwpforge_foundation::TextBorderType;
2096///
2097/// assert_eq!(TextBorderType::default(), TextBorderType::Paper);
2098/// assert_eq!(TextBorderType::Paper.to_string(), "Paper");
2099/// ```
2100#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
2101#[non_exhaustive]
2102#[repr(u8)]
2103pub enum TextBorderType {
2104    /// Offsets measured from paper edge (default).
2105    #[default]
2106    Paper = 0,
2107    /// Offsets measured from content area.
2108    Content = 1,
2109}
2110
2111impl fmt::Display for TextBorderType {
2112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2113        match self {
2114            Self::Paper => f.write_str("Paper"),
2115            Self::Content => f.write_str("Content"),
2116        }
2117    }
2118}
2119
2120impl std::str::FromStr for TextBorderType {
2121    type Err = FoundationError;
2122
2123    fn from_str(s: &str) -> Result<Self, Self::Err> {
2124        match s {
2125            "Paper" | "PAPER" | "paper" => Ok(Self::Paper),
2126            "Content" | "CONTENT" | "content" => Ok(Self::Content),
2127            _ => Err(FoundationError::ParseError {
2128                type_name: "TextBorderType".to_string(),
2129                value: s.to_string(),
2130                valid_values: "Paper, Content".to_string(),
2131            }),
2132        }
2133    }
2134}
2135
2136impl TryFrom<u8> for TextBorderType {
2137    type Error = FoundationError;
2138
2139    fn try_from(value: u8) -> Result<Self, Self::Error> {
2140        match value {
2141            0 => Ok(Self::Paper),
2142            1 => Ok(Self::Content),
2143            _ => Err(FoundationError::ParseError {
2144                type_name: "TextBorderType".to_string(),
2145                value: value.to_string(),
2146                valid_values: "0 (Paper), 1 (Content)".to_string(),
2147            }),
2148        }
2149    }
2150}
2151
2152impl schemars::JsonSchema for TextBorderType {
2153    fn schema_name() -> std::borrow::Cow<'static, str> {
2154        std::borrow::Cow::Borrowed("TextBorderType")
2155    }
2156
2157    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
2158        gen.subschema_for::<String>()
2159    }
2160}
2161
2162// ---------------------------------------------------------------------------
2163// Flip
2164// ---------------------------------------------------------------------------
2165
2166/// Flip/mirror state for drawing shapes.
2167///
2168/// Controls horizontal and/or vertical mirroring of a shape.
2169///
2170/// # Examples
2171///
2172/// ```
2173/// use hwpforge_foundation::Flip;
2174///
2175/// assert_eq!(Flip::default(), Flip::None);
2176/// assert_eq!(Flip::Horizontal.to_string(), "Horizontal");
2177/// ```
2178#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
2179#[non_exhaustive]
2180#[repr(u8)]
2181pub enum Flip {
2182    /// No flip (default).
2183    #[default]
2184    None = 0,
2185    /// Mirrored horizontally.
2186    Horizontal = 1,
2187    /// Mirrored vertically.
2188    Vertical = 2,
2189    /// Mirrored both horizontally and vertically.
2190    Both = 3,
2191}
2192
2193impl fmt::Display for Flip {
2194    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2195        match self {
2196            Self::None => f.write_str("None"),
2197            Self::Horizontal => f.write_str("Horizontal"),
2198            Self::Vertical => f.write_str("Vertical"),
2199            Self::Both => f.write_str("Both"),
2200        }
2201    }
2202}
2203
2204impl std::str::FromStr for Flip {
2205    type Err = FoundationError;
2206
2207    fn from_str(s: &str) -> Result<Self, Self::Err> {
2208        match s {
2209            "None" | "NONE" | "none" => Ok(Self::None),
2210            "Horizontal" | "HORIZONTAL" | "horizontal" => Ok(Self::Horizontal),
2211            "Vertical" | "VERTICAL" | "vertical" => Ok(Self::Vertical),
2212            "Both" | "BOTH" | "both" => Ok(Self::Both),
2213            _ => Err(FoundationError::ParseError {
2214                type_name: "Flip".to_string(),
2215                value: s.to_string(),
2216                valid_values: "None, Horizontal, Vertical, Both".to_string(),
2217            }),
2218        }
2219    }
2220}
2221
2222impl TryFrom<u8> for Flip {
2223    type Error = FoundationError;
2224
2225    fn try_from(value: u8) -> Result<Self, Self::Error> {
2226        match value {
2227            0 => Ok(Self::None),
2228            1 => Ok(Self::Horizontal),
2229            2 => Ok(Self::Vertical),
2230            3 => Ok(Self::Both),
2231            _ => Err(FoundationError::ParseError {
2232                type_name: "Flip".to_string(),
2233                value: value.to_string(),
2234                valid_values: "0 (None), 1 (Horizontal), 2 (Vertical), 3 (Both)".to_string(),
2235            }),
2236        }
2237    }
2238}
2239
2240impl schemars::JsonSchema for Flip {
2241    fn schema_name() -> std::borrow::Cow<'static, str> {
2242        std::borrow::Cow::Borrowed("Flip")
2243    }
2244
2245    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
2246        gen.subschema_for::<String>()
2247    }
2248}
2249
2250// ---------------------------------------------------------------------------
2251// ArcType
2252// ---------------------------------------------------------------------------
2253
2254/// Arc drawing type for ellipse-based arc shapes.
2255///
2256/// # Examples
2257///
2258/// ```
2259/// use hwpforge_foundation::ArcType;
2260///
2261/// assert_eq!(ArcType::default(), ArcType::Normal);
2262/// ```
2263#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
2264#[non_exhaustive]
2265#[repr(u8)]
2266pub enum ArcType {
2267    /// Open arc (just the curved edge).
2268    #[default]
2269    Normal = 0,
2270    /// Pie/sector (arc + two radii closing to center).
2271    Pie = 1,
2272    /// Chord (arc + straight line closing endpoints).
2273    Chord = 2,
2274}
2275
2276impl fmt::Display for ArcType {
2277    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2278        match self {
2279            Self::Normal => f.write_str("NORMAL"),
2280            Self::Pie => f.write_str("PIE"),
2281            Self::Chord => f.write_str("CHORD"),
2282        }
2283    }
2284}
2285
2286impl std::str::FromStr for ArcType {
2287    type Err = FoundationError;
2288
2289    fn from_str(s: &str) -> Result<Self, Self::Err> {
2290        match s {
2291            "NORMAL" | "Normal" | "normal" => Ok(Self::Normal),
2292            "PIE" | "Pie" | "pie" => Ok(Self::Pie),
2293            "CHORD" | "Chord" | "chord" => Ok(Self::Chord),
2294            _ => Err(FoundationError::ParseError {
2295                type_name: "ArcType".to_string(),
2296                value: s.to_string(),
2297                valid_values: "NORMAL, PIE, CHORD".to_string(),
2298            }),
2299        }
2300    }
2301}
2302
2303impl TryFrom<u8> for ArcType {
2304    type Error = FoundationError;
2305
2306    fn try_from(value: u8) -> Result<Self, Self::Error> {
2307        match value {
2308            0 => Ok(Self::Normal),
2309            1 => Ok(Self::Pie),
2310            2 => Ok(Self::Chord),
2311            _ => Err(FoundationError::ParseError {
2312                type_name: "ArcType".to_string(),
2313                value: value.to_string(),
2314                valid_values: "0 (Normal), 1 (Pie), 2 (Chord)".to_string(),
2315            }),
2316        }
2317    }
2318}
2319
2320impl schemars::JsonSchema for ArcType {
2321    fn schema_name() -> std::borrow::Cow<'static, str> {
2322        std::borrow::Cow::Borrowed("ArcType")
2323    }
2324
2325    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
2326        gen.subschema_for::<String>()
2327    }
2328}
2329
2330// ---------------------------------------------------------------------------
2331// ArrowType
2332// ---------------------------------------------------------------------------
2333
2334/// Arrowhead shape for line endpoints.
2335///
2336/// # Examples
2337///
2338/// ```
2339/// use hwpforge_foundation::ArrowType;
2340///
2341/// assert_eq!(ArrowType::default(), ArrowType::None);
2342/// ```
2343#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
2344#[non_exhaustive]
2345#[repr(u8)]
2346pub enum ArrowType {
2347    /// No arrowhead (default).
2348    #[default]
2349    None = 0,
2350    /// Standard filled arrowhead.
2351    Normal = 1,
2352    /// Arrow-shaped arrowhead.
2353    Arrow = 2,
2354    /// Concave arrowhead.
2355    Concave = 3,
2356    /// Diamond arrowhead.
2357    Diamond = 4,
2358    /// Oval/circle arrowhead.
2359    Oval = 5,
2360    /// Open (unfilled) arrowhead.
2361    Open = 6,
2362}
2363
2364impl fmt::Display for ArrowType {
2365    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2366        // KS X 6101 ArrowType values.
2367        // Diamond/Oval/Open default to FILLED_ variants here;
2368        // the encoder resolves FILLED_ vs EMPTY_ based on ArrowStyle.filled.
2369        match self {
2370            Self::None => f.write_str("NORMAL"),
2371            Self::Normal => f.write_str("ARROW"),
2372            Self::Arrow => f.write_str("SPEAR"),
2373            Self::Concave => f.write_str("CONCAVE_ARROW"),
2374            Self::Diamond => f.write_str("FILLED_DIAMOND"),
2375            Self::Oval => f.write_str("FILLED_CIRCLE"),
2376            Self::Open => f.write_str("EMPTY_BOX"),
2377        }
2378    }
2379}
2380
2381impl std::str::FromStr for ArrowType {
2382    type Err = FoundationError;
2383
2384    fn from_str(s: &str) -> Result<Self, Self::Err> {
2385        // KS X 6101 ArrowType values (primary) + legacy aliases for backward compat.
2386        match s {
2387            "NORMAL" => Ok(Self::None),
2388            "ARROW" => Ok(Self::Normal),
2389            "SPEAR" => Ok(Self::Arrow),
2390            "CONCAVE_ARROW" => Ok(Self::Concave),
2391            "FILLED_DIAMOND" | "EMPTY_DIAMOND" => Ok(Self::Diamond),
2392            "FILLED_CIRCLE" | "EMPTY_CIRCLE" => Ok(Self::Oval),
2393            "FILLED_BOX" | "EMPTY_BOX" => Ok(Self::Open),
2394            _ => Err(FoundationError::ParseError {
2395                type_name: "ArrowType".to_string(),
2396                value: s.to_string(),
2397                valid_values: "NORMAL, ARROW, SPEAR, CONCAVE_ARROW, FILLED_DIAMOND, EMPTY_DIAMOND, FILLED_CIRCLE, EMPTY_CIRCLE, FILLED_BOX, EMPTY_BOX"
2398                    .to_string(),
2399            }),
2400        }
2401    }
2402}
2403
2404impl TryFrom<u8> for ArrowType {
2405    type Error = FoundationError;
2406
2407    fn try_from(value: u8) -> Result<Self, Self::Error> {
2408        match value {
2409            0 => Ok(Self::None),
2410            1 => Ok(Self::Normal),
2411            2 => Ok(Self::Arrow),
2412            3 => Ok(Self::Concave),
2413            4 => Ok(Self::Diamond),
2414            5 => Ok(Self::Oval),
2415            6 => Ok(Self::Open),
2416            _ => Err(FoundationError::ParseError {
2417                type_name: "ArrowType".to_string(),
2418                value: value.to_string(),
2419                valid_values:
2420                    "0 (None), 1 (Normal), 2 (Arrow), 3 (Concave), 4 (Diamond), 5 (Oval), 6 (Open)"
2421                        .to_string(),
2422            }),
2423        }
2424    }
2425}
2426
2427impl schemars::JsonSchema for ArrowType {
2428    fn schema_name() -> std::borrow::Cow<'static, str> {
2429        std::borrow::Cow::Borrowed("ArrowType")
2430    }
2431
2432    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
2433        gen.subschema_for::<String>()
2434    }
2435}
2436
2437// ---------------------------------------------------------------------------
2438// ArrowSize
2439// ---------------------------------------------------------------------------
2440
2441/// Arrowhead size for line endpoints.
2442///
2443/// Encoded as `{HEAD}_{TAIL}` string in HWPX (e.g. `"MEDIUM_MEDIUM"`).
2444///
2445/// # Examples
2446///
2447/// ```
2448/// use hwpforge_foundation::ArrowSize;
2449///
2450/// assert_eq!(ArrowSize::default(), ArrowSize::Medium);
2451/// ```
2452#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
2453#[non_exhaustive]
2454#[repr(u8)]
2455pub enum ArrowSize {
2456    /// Small arrowhead.
2457    Small = 0,
2458    /// Medium arrowhead (default).
2459    #[default]
2460    Medium = 1,
2461    /// Large arrowhead.
2462    Large = 2,
2463}
2464
2465impl fmt::Display for ArrowSize {
2466    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2467        match self {
2468            Self::Small => f.write_str("SMALL_SMALL"),
2469            Self::Medium => f.write_str("MEDIUM_MEDIUM"),
2470            Self::Large => f.write_str("LARGE_LARGE"),
2471        }
2472    }
2473}
2474
2475impl std::str::FromStr for ArrowSize {
2476    type Err = FoundationError;
2477
2478    fn from_str(s: &str) -> Result<Self, Self::Err> {
2479        match s {
2480            "SMALL_SMALL" | "Small" | "small" => Ok(Self::Small),
2481            "MEDIUM_MEDIUM" | "Medium" | "medium" => Ok(Self::Medium),
2482            "LARGE_LARGE" | "Large" | "large" => Ok(Self::Large),
2483            _ => Err(FoundationError::ParseError {
2484                type_name: "ArrowSize".to_string(),
2485                value: s.to_string(),
2486                valid_values: "SMALL_SMALL, MEDIUM_MEDIUM, LARGE_LARGE".to_string(),
2487            }),
2488        }
2489    }
2490}
2491
2492impl TryFrom<u8> for ArrowSize {
2493    type Error = FoundationError;
2494
2495    fn try_from(value: u8) -> Result<Self, Self::Error> {
2496        match value {
2497            0 => Ok(Self::Small),
2498            1 => Ok(Self::Medium),
2499            2 => Ok(Self::Large),
2500            _ => Err(FoundationError::ParseError {
2501                type_name: "ArrowSize".to_string(),
2502                value: value.to_string(),
2503                valid_values: "0 (Small), 1 (Medium), 2 (Large)".to_string(),
2504            }),
2505        }
2506    }
2507}
2508
2509impl schemars::JsonSchema for ArrowSize {
2510    fn schema_name() -> std::borrow::Cow<'static, str> {
2511        std::borrow::Cow::Borrowed("ArrowSize")
2512    }
2513
2514    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
2515        gen.subschema_for::<String>()
2516    }
2517}
2518
2519// ---------------------------------------------------------------------------
2520// GradientType
2521// ---------------------------------------------------------------------------
2522
2523/// Gradient fill direction type.
2524///
2525/// # Examples
2526///
2527/// ```
2528/// use hwpforge_foundation::GradientType;
2529///
2530/// assert_eq!(GradientType::default(), GradientType::Linear);
2531/// ```
2532#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
2533#[non_exhaustive]
2534#[repr(u8)]
2535pub enum GradientType {
2536    /// Linear gradient (default).
2537    #[default]
2538    Linear = 0,
2539    /// Radial gradient (from center outward).
2540    Radial = 1,
2541    /// Square/rectangular gradient.
2542    Square = 2,
2543    /// Conical gradient (angular sweep).
2544    Conical = 3,
2545}
2546
2547impl fmt::Display for GradientType {
2548    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2549        match self {
2550            Self::Linear => f.write_str("LINEAR"),
2551            Self::Radial => f.write_str("RADIAL"),
2552            Self::Square => f.write_str("SQUARE"),
2553            Self::Conical => f.write_str("CONICAL"),
2554        }
2555    }
2556}
2557
2558impl std::str::FromStr for GradientType {
2559    type Err = FoundationError;
2560
2561    fn from_str(s: &str) -> Result<Self, Self::Err> {
2562        match s {
2563            "LINEAR" | "Linear" | "linear" => Ok(Self::Linear),
2564            "RADIAL" | "Radial" | "radial" => Ok(Self::Radial),
2565            "SQUARE" | "Square" | "square" => Ok(Self::Square),
2566            "CONICAL" | "Conical" | "conical" => Ok(Self::Conical),
2567            _ => Err(FoundationError::ParseError {
2568                type_name: "GradientType".to_string(),
2569                value: s.to_string(),
2570                valid_values: "LINEAR, RADIAL, SQUARE, CONICAL".to_string(),
2571            }),
2572        }
2573    }
2574}
2575
2576impl TryFrom<u8> for GradientType {
2577    type Error = FoundationError;
2578
2579    fn try_from(value: u8) -> Result<Self, Self::Error> {
2580        match value {
2581            0 => Ok(Self::Linear),
2582            1 => Ok(Self::Radial),
2583            2 => Ok(Self::Square),
2584            3 => Ok(Self::Conical),
2585            _ => Err(FoundationError::ParseError {
2586                type_name: "GradientType".to_string(),
2587                value: value.to_string(),
2588                valid_values: "0 (Linear), 1 (Radial), 2 (Square), 3 (Conical)".to_string(),
2589            }),
2590        }
2591    }
2592}
2593
2594impl schemars::JsonSchema for GradientType {
2595    fn schema_name() -> std::borrow::Cow<'static, str> {
2596        std::borrow::Cow::Borrowed("GradientType")
2597    }
2598
2599    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
2600        gen.subschema_for::<String>()
2601    }
2602}
2603
2604// ---------------------------------------------------------------------------
2605// PatternType
2606// ---------------------------------------------------------------------------
2607
2608/// Hatch/pattern fill type for shapes.
2609///
2610/// # Examples
2611///
2612/// ```
2613/// use hwpforge_foundation::PatternType;
2614///
2615/// assert_eq!(PatternType::default(), PatternType::Horizontal);
2616/// ```
2617#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
2618#[non_exhaustive]
2619#[repr(u8)]
2620pub enum PatternType {
2621    /// Horizontal lines (default).
2622    #[default]
2623    Horizontal = 0,
2624    /// Vertical lines.
2625    Vertical = 1,
2626    /// Backslash diagonal lines.
2627    BackSlash = 2,
2628    /// Forward slash diagonal lines.
2629    Slash = 3,
2630    /// Cross-hatch (horizontal + vertical).
2631    Cross = 4,
2632    /// Cross-diagonal hatch.
2633    CrossDiagonal = 5,
2634}
2635
2636impl fmt::Display for PatternType {
2637    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2638        match self {
2639            Self::Horizontal => f.write_str("HORIZONTAL"),
2640            Self::Vertical => f.write_str("VERTICAL"),
2641            Self::BackSlash => f.write_str("BACK_SLASH"),
2642            Self::Slash => f.write_str("SLASH"),
2643            Self::Cross => f.write_str("CROSS"),
2644            Self::CrossDiagonal => f.write_str("CROSS_DIAGONAL"),
2645        }
2646    }
2647}
2648
2649impl std::str::FromStr for PatternType {
2650    type Err = FoundationError;
2651
2652    fn from_str(s: &str) -> Result<Self, Self::Err> {
2653        match s {
2654            "HORIZONTAL" | "Horizontal" | "horizontal" => Ok(Self::Horizontal),
2655            "VERTICAL" | "Vertical" | "vertical" => Ok(Self::Vertical),
2656            "BACK_SLASH" | "BackSlash" | "backslash" => Ok(Self::BackSlash),
2657            "SLASH" | "Slash" | "slash" => Ok(Self::Slash),
2658            "CROSS" | "Cross" | "cross" => Ok(Self::Cross),
2659            "CROSS_DIAGONAL" | "CrossDiagonal" | "crossdiagonal" => Ok(Self::CrossDiagonal),
2660            _ => Err(FoundationError::ParseError {
2661                type_name: "PatternType".to_string(),
2662                value: s.to_string(),
2663                valid_values: "HORIZONTAL, VERTICAL, BACK_SLASH, SLASH, CROSS, CROSS_DIAGONAL"
2664                    .to_string(),
2665            }),
2666        }
2667    }
2668}
2669
2670impl TryFrom<u8> for PatternType {
2671    type Error = FoundationError;
2672
2673    fn try_from(value: u8) -> Result<Self, Self::Error> {
2674        match value {
2675            0 => Ok(Self::Horizontal),
2676            1 => Ok(Self::Vertical),
2677            2 => Ok(Self::BackSlash),
2678            3 => Ok(Self::Slash),
2679            4 => Ok(Self::Cross),
2680            5 => Ok(Self::CrossDiagonal),
2681            _ => Err(FoundationError::ParseError {
2682                type_name: "PatternType".to_string(),
2683                value: value.to_string(),
2684                valid_values:
2685                    "0 (Horizontal), 1 (Vertical), 2 (BackSlash), 3 (Slash), 4 (Cross), 5 (CrossDiagonal)"
2686                        .to_string(),
2687            }),
2688        }
2689    }
2690}
2691
2692impl schemars::JsonSchema for PatternType {
2693    fn schema_name() -> std::borrow::Cow<'static, str> {
2694        std::borrow::Cow::Borrowed("PatternType")
2695    }
2696
2697    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
2698        gen.subschema_for::<String>()
2699    }
2700}
2701
2702// ---------------------------------------------------------------------------
2703// ImageFillMode
2704// ---------------------------------------------------------------------------
2705
2706/// How an image is fitted within a shape fill area.
2707///
2708/// # Examples
2709///
2710/// ```
2711/// use hwpforge_foundation::ImageFillMode;
2712///
2713/// assert_eq!(ImageFillMode::default(), ImageFillMode::Tile);
2714/// ```
2715#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
2716#[non_exhaustive]
2717#[repr(u8)]
2718pub enum ImageFillMode {
2719    /// Tile the image to fill the area (default).
2720    #[default]
2721    Tile = 0,
2722    /// Center the image without scaling.
2723    Center = 1,
2724    /// Stretch the image to fit exactly.
2725    Stretch = 2,
2726    /// Scale proportionally to fit all within the area.
2727    FitAll = 3,
2728}
2729
2730impl fmt::Display for ImageFillMode {
2731    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2732        match self {
2733            Self::Tile => f.write_str("TILE"),
2734            Self::Center => f.write_str("CENTER"),
2735            Self::Stretch => f.write_str("STRETCH"),
2736            Self::FitAll => f.write_str("FIT_ALL"),
2737        }
2738    }
2739}
2740
2741impl std::str::FromStr for ImageFillMode {
2742    type Err = FoundationError;
2743
2744    fn from_str(s: &str) -> Result<Self, Self::Err> {
2745        match s {
2746            "TILE" | "Tile" | "tile" => Ok(Self::Tile),
2747            "CENTER" | "Center" | "center" => Ok(Self::Center),
2748            "STRETCH" | "Stretch" | "stretch" => Ok(Self::Stretch),
2749            "FIT_ALL" | "FitAll" | "fit_all" => Ok(Self::FitAll),
2750            _ => Err(FoundationError::ParseError {
2751                type_name: "ImageFillMode".to_string(),
2752                value: s.to_string(),
2753                valid_values: "TILE, CENTER, STRETCH, FIT_ALL".to_string(),
2754            }),
2755        }
2756    }
2757}
2758
2759impl TryFrom<u8> for ImageFillMode {
2760    type Error = FoundationError;
2761
2762    fn try_from(value: u8) -> Result<Self, Self::Error> {
2763        match value {
2764            0 => Ok(Self::Tile),
2765            1 => Ok(Self::Center),
2766            2 => Ok(Self::Stretch),
2767            3 => Ok(Self::FitAll),
2768            _ => Err(FoundationError::ParseError {
2769                type_name: "ImageFillMode".to_string(),
2770                value: value.to_string(),
2771                valid_values: "0 (Tile), 1 (Center), 2 (Stretch), 3 (FitAll)".to_string(),
2772            }),
2773        }
2774    }
2775}
2776
2777impl schemars::JsonSchema for ImageFillMode {
2778    fn schema_name() -> std::borrow::Cow<'static, str> {
2779        std::borrow::Cow::Borrowed("ImageFillMode")
2780    }
2781
2782    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
2783        gen.subschema_for::<String>()
2784    }
2785}
2786
2787// ---------------------------------------------------------------------------
2788// CurveSegmentType
2789// ---------------------------------------------------------------------------
2790
2791/// Segment type within a curve path.
2792///
2793/// # Examples
2794///
2795/// ```
2796/// use hwpforge_foundation::CurveSegmentType;
2797///
2798/// assert_eq!(CurveSegmentType::default(), CurveSegmentType::Line);
2799/// ```
2800#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
2801#[non_exhaustive]
2802#[repr(u8)]
2803pub enum CurveSegmentType {
2804    /// Straight line segment (default).
2805    #[default]
2806    Line = 0,
2807    /// Cubic bezier curve segment.
2808    Curve = 1,
2809}
2810
2811impl fmt::Display for CurveSegmentType {
2812    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2813        match self {
2814            Self::Line => f.write_str("LINE"),
2815            Self::Curve => f.write_str("CURVE"),
2816        }
2817    }
2818}
2819
2820impl std::str::FromStr for CurveSegmentType {
2821    type Err = FoundationError;
2822
2823    fn from_str(s: &str) -> Result<Self, Self::Err> {
2824        match s {
2825            "LINE" | "Line" | "line" => Ok(Self::Line),
2826            "CURVE" | "Curve" | "curve" => Ok(Self::Curve),
2827            _ => Err(FoundationError::ParseError {
2828                type_name: "CurveSegmentType".to_string(),
2829                value: s.to_string(),
2830                valid_values: "LINE, CURVE".to_string(),
2831            }),
2832        }
2833    }
2834}
2835
2836impl TryFrom<u8> for CurveSegmentType {
2837    type Error = FoundationError;
2838
2839    fn try_from(value: u8) -> Result<Self, Self::Error> {
2840        match value {
2841            0 => Ok(Self::Line),
2842            1 => Ok(Self::Curve),
2843            _ => Err(FoundationError::ParseError {
2844                type_name: "CurveSegmentType".to_string(),
2845                value: value.to_string(),
2846                valid_values: "0 (Line), 1 (Curve)".to_string(),
2847            }),
2848        }
2849    }
2850}
2851
2852impl schemars::JsonSchema for CurveSegmentType {
2853    fn schema_name() -> std::borrow::Cow<'static, str> {
2854        std::borrow::Cow::Borrowed("CurveSegmentType")
2855    }
2856
2857    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
2858        gen.subschema_for::<String>()
2859    }
2860}
2861
2862// ---------------------------------------------------------------------------
2863// BookmarkType
2864// ---------------------------------------------------------------------------
2865
2866/// Type of bookmark in an HWPX document.
2867///
2868/// Bookmarks can mark a single point or span a range of content
2869/// (start/end pair using `fieldBegin`/`fieldEnd`).
2870#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
2871#[non_exhaustive]
2872#[repr(u8)]
2873pub enum BookmarkType {
2874    /// A point bookmark at a single location (direct serde in `<hp:ctrl>`).
2875    #[default]
2876    Point = 0,
2877    /// Start of a span bookmark (`fieldBegin type="BOOKMARK"`).
2878    SpanStart = 1,
2879    /// End of a span bookmark (`fieldEnd beginIDRef`).
2880    SpanEnd = 2,
2881}
2882
2883impl fmt::Display for BookmarkType {
2884    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2885        match self {
2886            Self::Point => f.write_str("Point"),
2887            Self::SpanStart => f.write_str("SpanStart"),
2888            Self::SpanEnd => f.write_str("SpanEnd"),
2889        }
2890    }
2891}
2892
2893impl std::str::FromStr for BookmarkType {
2894    type Err = FoundationError;
2895
2896    fn from_str(s: &str) -> Result<Self, Self::Err> {
2897        match s {
2898            "Point" | "point" => Ok(Self::Point),
2899            "SpanStart" | "span_start" => Ok(Self::SpanStart),
2900            "SpanEnd" | "span_end" => Ok(Self::SpanEnd),
2901            _ => Err(FoundationError::ParseError {
2902                type_name: "BookmarkType".to_string(),
2903                value: s.to_string(),
2904                valid_values: "Point, SpanStart, SpanEnd".to_string(),
2905            }),
2906        }
2907    }
2908}
2909
2910impl TryFrom<u8> for BookmarkType {
2911    type Error = FoundationError;
2912
2913    fn try_from(value: u8) -> Result<Self, Self::Error> {
2914        match value {
2915            0 => Ok(Self::Point),
2916            1 => Ok(Self::SpanStart),
2917            2 => Ok(Self::SpanEnd),
2918            _ => Err(FoundationError::ParseError {
2919                type_name: "BookmarkType".to_string(),
2920                value: value.to_string(),
2921                valid_values: "0 (Point), 1 (SpanStart), 2 (SpanEnd)".to_string(),
2922            }),
2923        }
2924    }
2925}
2926
2927impl schemars::JsonSchema for BookmarkType {
2928    fn schema_name() -> std::borrow::Cow<'static, str> {
2929        std::borrow::Cow::Borrowed("BookmarkType")
2930    }
2931
2932    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
2933        gen.subschema_for::<String>()
2934    }
2935}
2936
2937// ---------------------------------------------------------------------------
2938// FieldType
2939// ---------------------------------------------------------------------------
2940
2941/// Type of a press-field (누름틀) in an HWPX document.
2942///
2943/// Press-fields are interactive form fields that users can click to fill in.
2944#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
2945#[non_exhaustive]
2946#[repr(u8)]
2947pub enum FieldType {
2948    /// Click-here placeholder field (default).
2949    #[default]
2950    ClickHere = 0,
2951    /// Automatic date field.
2952    Date = 1,
2953    /// Automatic time field.
2954    Time = 2,
2955    /// Page number field.
2956    PageNum = 3,
2957    /// Document summary field.
2958    DocSummary = 4,
2959    /// User information field.
2960    UserInfo = 5,
2961}
2962
2963impl fmt::Display for FieldType {
2964    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2965        match self {
2966            Self::ClickHere => f.write_str("CLICK_HERE"),
2967            Self::Date => f.write_str("DATE"),
2968            Self::Time => f.write_str("TIME"),
2969            Self::PageNum => f.write_str("PAGE_NUM"),
2970            Self::DocSummary => f.write_str("DOC_SUMMARY"),
2971            Self::UserInfo => f.write_str("USER_INFO"),
2972        }
2973    }
2974}
2975
2976impl std::str::FromStr for FieldType {
2977    type Err = FoundationError;
2978
2979    fn from_str(s: &str) -> Result<Self, Self::Err> {
2980        match s {
2981            "CLICK_HERE" | "ClickHere" | "click_here" => Ok(Self::ClickHere),
2982            "DATE" | "Date" | "date" => Ok(Self::Date),
2983            "TIME" | "Time" | "time" => Ok(Self::Time),
2984            "PAGE_NUM" | "PageNum" | "page_num" => Ok(Self::PageNum),
2985            "DOC_SUMMARY" | "DocSummary" | "doc_summary" => Ok(Self::DocSummary),
2986            "USER_INFO" | "UserInfo" | "user_info" => Ok(Self::UserInfo),
2987            _ => Err(FoundationError::ParseError {
2988                type_name: "FieldType".to_string(),
2989                value: s.to_string(),
2990                valid_values: "CLICK_HERE, DATE, TIME, PAGE_NUM, DOC_SUMMARY, USER_INFO"
2991                    .to_string(),
2992            }),
2993        }
2994    }
2995}
2996
2997impl TryFrom<u8> for FieldType {
2998    type Error = FoundationError;
2999
3000    fn try_from(value: u8) -> Result<Self, Self::Error> {
3001        match value {
3002            0 => Ok(Self::ClickHere),
3003            1 => Ok(Self::Date),
3004            2 => Ok(Self::Time),
3005            3 => Ok(Self::PageNum),
3006            4 => Ok(Self::DocSummary),
3007            5 => Ok(Self::UserInfo),
3008            _ => Err(FoundationError::ParseError {
3009                type_name: "FieldType".to_string(),
3010                value: value.to_string(),
3011                valid_values: "0..5 (ClickHere..UserInfo)".to_string(),
3012            }),
3013        }
3014    }
3015}
3016
3017impl schemars::JsonSchema for FieldType {
3018    fn schema_name() -> std::borrow::Cow<'static, str> {
3019        std::borrow::Cow::Borrowed("FieldType")
3020    }
3021
3022    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
3023        gen.subschema_for::<String>()
3024    }
3025}
3026
3027// ---------------------------------------------------------------------------
3028// RefType
3029// ---------------------------------------------------------------------------
3030
3031/// Target type of a cross-reference (상호참조).
3032#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
3033#[non_exhaustive]
3034#[repr(u8)]
3035pub enum RefType {
3036    /// Reference to a bookmark target.
3037    #[default]
3038    Bookmark = 0,
3039    /// Reference to a table caption number.
3040    Table = 1,
3041    /// Reference to a figure/image caption number.
3042    Figure = 2,
3043    /// Reference to an equation number.
3044    Equation = 3,
3045}
3046
3047impl fmt::Display for RefType {
3048    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3049        match self {
3050            Self::Bookmark => f.write_str("TARGET_BOOKMARK"),
3051            Self::Table => f.write_str("TARGET_TABLE"),
3052            Self::Figure => f.write_str("TARGET_FIGURE"),
3053            Self::Equation => f.write_str("TARGET_EQUATION"),
3054        }
3055    }
3056}
3057
3058impl std::str::FromStr for RefType {
3059    type Err = FoundationError;
3060
3061    fn from_str(s: &str) -> Result<Self, Self::Err> {
3062        match s {
3063            "TARGET_BOOKMARK" | "Bookmark" | "bookmark" => Ok(Self::Bookmark),
3064            "TARGET_TABLE" | "Table" | "table" => Ok(Self::Table),
3065            "TARGET_FIGURE" | "Figure" | "figure" => Ok(Self::Figure),
3066            "TARGET_EQUATION" | "Equation" | "equation" => Ok(Self::Equation),
3067            _ => Err(FoundationError::ParseError {
3068                type_name: "RefType".to_string(),
3069                value: s.to_string(),
3070                valid_values: "TARGET_BOOKMARK, TARGET_TABLE, TARGET_FIGURE, TARGET_EQUATION"
3071                    .to_string(),
3072            }),
3073        }
3074    }
3075}
3076
3077impl TryFrom<u8> for RefType {
3078    type Error = FoundationError;
3079
3080    fn try_from(value: u8) -> Result<Self, Self::Error> {
3081        match value {
3082            0 => Ok(Self::Bookmark),
3083            1 => Ok(Self::Table),
3084            2 => Ok(Self::Figure),
3085            3 => Ok(Self::Equation),
3086            _ => Err(FoundationError::ParseError {
3087                type_name: "RefType".to_string(),
3088                value: value.to_string(),
3089                valid_values: "0..3 (Bookmark..Equation)".to_string(),
3090            }),
3091        }
3092    }
3093}
3094
3095impl schemars::JsonSchema for RefType {
3096    fn schema_name() -> std::borrow::Cow<'static, str> {
3097        std::borrow::Cow::Borrowed("RefType")
3098    }
3099
3100    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
3101        gen.subschema_for::<String>()
3102    }
3103}
3104
3105// ---------------------------------------------------------------------------
3106// RefContentType
3107// ---------------------------------------------------------------------------
3108
3109/// Content display type for a cross-reference (what to show at the reference site).
3110#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
3111#[non_exhaustive]
3112#[repr(u8)]
3113pub enum RefContentType {
3114    /// Show page number where the target appears.
3115    #[default]
3116    Page = 0,
3117    /// Show the target's numbering (e.g. "표 3", "그림 2").
3118    Number = 1,
3119    /// Show the target's content text.
3120    Contents = 2,
3121    /// Show relative position ("위" / "아래").
3122    UpDownPos = 3,
3123}
3124
3125impl fmt::Display for RefContentType {
3126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3127        match self {
3128            Self::Page => f.write_str("OBJECT_TYPE_PAGE"),
3129            Self::Number => f.write_str("OBJECT_TYPE_NUMBER"),
3130            Self::Contents => f.write_str("OBJECT_TYPE_CONTENTS"),
3131            Self::UpDownPos => f.write_str("OBJECT_TYPE_UPDOWNPOS"),
3132        }
3133    }
3134}
3135
3136impl std::str::FromStr for RefContentType {
3137    type Err = FoundationError;
3138
3139    fn from_str(s: &str) -> Result<Self, Self::Err> {
3140        match s {
3141            "OBJECT_TYPE_PAGE" | "Page" | "page" => Ok(Self::Page),
3142            "OBJECT_TYPE_NUMBER" | "Number" | "number" => Ok(Self::Number),
3143            "OBJECT_TYPE_CONTENTS" | "Contents" | "contents" => Ok(Self::Contents),
3144            "OBJECT_TYPE_UPDOWNPOS" | "UpDownPos" | "updownpos" => Ok(Self::UpDownPos),
3145            _ => Err(FoundationError::ParseError {
3146                type_name: "RefContentType".to_string(),
3147                value: s.to_string(),
3148                valid_values:
3149                    "OBJECT_TYPE_PAGE, OBJECT_TYPE_NUMBER, OBJECT_TYPE_CONTENTS, OBJECT_TYPE_UPDOWNPOS"
3150                        .to_string(),
3151            }),
3152        }
3153    }
3154}
3155
3156impl TryFrom<u8> for RefContentType {
3157    type Error = FoundationError;
3158
3159    fn try_from(value: u8) -> Result<Self, Self::Error> {
3160        match value {
3161            0 => Ok(Self::Page),
3162            1 => Ok(Self::Number),
3163            2 => Ok(Self::Contents),
3164            3 => Ok(Self::UpDownPos),
3165            _ => Err(FoundationError::ParseError {
3166                type_name: "RefContentType".to_string(),
3167                value: value.to_string(),
3168                valid_values: "0..3 (Page..UpDownPos)".to_string(),
3169            }),
3170        }
3171    }
3172}
3173
3174impl schemars::JsonSchema for RefContentType {
3175    fn schema_name() -> std::borrow::Cow<'static, str> {
3176        std::borrow::Cow::Borrowed("RefContentType")
3177    }
3178
3179    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
3180        gen.subschema_for::<String>()
3181    }
3182}
3183
3184/// Drop cap style for floating shape objects (HWPX `dropcapstyle` attribute).
3185///
3186/// Controls whether a shape (text box, image, table, etc.) is formatted as a
3187/// drop capital that occupies multiple lines at the start of a paragraph.
3188///
3189/// # HWPX Values
3190///
3191/// | Variant      | HWPX string     |
3192/// |--------------|-----------------|
3193/// | `None`       | `"None"`        |
3194/// | `DoubleLine` | `"DoubleLine"`  |
3195/// | `TripleLine` | `"TripleLine"`  |
3196/// | `Margin`     | `"Margin"`      |
3197#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
3198#[non_exhaustive]
3199pub enum DropCapStyle {
3200    /// No drop cap (default).
3201    #[default]
3202    None = 0,
3203    /// Drop cap spanning 2 lines.
3204    DoubleLine = 1,
3205    /// Drop cap spanning 3 lines.
3206    TripleLine = 2,
3207    /// Drop cap positioned in the margin.
3208    Margin = 3,
3209}
3210
3211impl fmt::Display for DropCapStyle {
3212    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3213        match self {
3214            Self::None => f.write_str("None"),
3215            Self::DoubleLine => f.write_str("DoubleLine"),
3216            Self::TripleLine => f.write_str("TripleLine"),
3217            Self::Margin => f.write_str("Margin"),
3218        }
3219    }
3220}
3221
3222impl DropCapStyle {
3223    /// Parses an HWPX `dropcapstyle` attribute value (PascalCase).
3224    ///
3225    /// Unknown values fall back to `None` (default) for forward compatibility.
3226    pub fn from_hwpx_str(s: &str) -> Self {
3227        match s {
3228            "DoubleLine" => Self::DoubleLine,
3229            "TripleLine" => Self::TripleLine,
3230            "Margin" => Self::Margin,
3231            _ => Self::None,
3232        }
3233    }
3234}
3235
3236impl std::str::FromStr for DropCapStyle {
3237    type Err = FoundationError;
3238
3239    fn from_str(s: &str) -> Result<Self, Self::Err> {
3240        match s {
3241            "None" | "NONE" | "none" => Ok(Self::None),
3242            "DoubleLine" | "DOUBLE_LINE" => Ok(Self::DoubleLine),
3243            "TripleLine" | "TRIPLE_LINE" => Ok(Self::TripleLine),
3244            "Margin" | "MARGIN" => Ok(Self::Margin),
3245            _ => Err(FoundationError::ParseError {
3246                type_name: "DropCapStyle".to_string(),
3247                value: s.to_string(),
3248                valid_values: "None, DoubleLine, TripleLine, Margin".to_string(),
3249            }),
3250        }
3251    }
3252}
3253
3254impl schemars::JsonSchema for DropCapStyle {
3255    fn schema_name() -> std::borrow::Cow<'static, str> {
3256        std::borrow::Cow::Borrowed("DropCapStyle")
3257    }
3258
3259    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
3260        gen.subschema_for::<String>()
3261    }
3262}
3263
3264impl serde::Serialize for DropCapStyle {
3265    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3266        serializer.serialize_str(&self.to_string())
3267    }
3268}
3269
3270impl<'de> serde::Deserialize<'de> for DropCapStyle {
3271    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3272        let s = String::deserialize(deserializer)?;
3273        s.parse().map_err(serde::de::Error::custom)
3274    }
3275}
3276
3277// Compile-time size assertions: all enums are 1 byte
3278const _: () = assert!(std::mem::size_of::<DropCapStyle>() == 1);
3279const _: () = assert!(std::mem::size_of::<Alignment>() == 1);
3280const _: () = assert!(std::mem::size_of::<LineSpacingType>() == 1);
3281const _: () = assert!(std::mem::size_of::<BreakType>() == 1);
3282const _: () = assert!(std::mem::size_of::<Language>() == 1);
3283const _: () = assert!(std::mem::size_of::<UnderlineType>() == 1);
3284const _: () = assert!(std::mem::size_of::<StrikeoutShape>() == 1);
3285const _: () = assert!(std::mem::size_of::<OutlineType>() == 1);
3286const _: () = assert!(std::mem::size_of::<ShadowType>() == 1);
3287const _: () = assert!(std::mem::size_of::<EmbossType>() == 1);
3288const _: () = assert!(std::mem::size_of::<EngraveType>() == 1);
3289const _: () = assert!(std::mem::size_of::<VerticalPosition>() == 1);
3290const _: () = assert!(std::mem::size_of::<BorderLineType>() == 1);
3291const _: () = assert!(std::mem::size_of::<FillBrushType>() == 1);
3292const _: () = assert!(std::mem::size_of::<ApplyPageType>() == 1);
3293const _: () = assert!(std::mem::size_of::<NumberFormatType>() == 1);
3294const _: () = assert!(std::mem::size_of::<PageNumberPosition>() == 1);
3295const _: () = assert!(std::mem::size_of::<WordBreakType>() == 1);
3296const _: () = assert!(std::mem::size_of::<EmphasisType>() == 1);
3297const _: () = assert!(std::mem::size_of::<HeadingType>() == 1);
3298const _: () = assert!(std::mem::size_of::<GutterType>() == 1);
3299const _: () = assert!(std::mem::size_of::<ShowMode>() == 1);
3300const _: () = assert!(std::mem::size_of::<RestartType>() == 1);
3301const _: () = assert!(std::mem::size_of::<TextBorderType>() == 1);
3302const _: () = assert!(std::mem::size_of::<Flip>() == 1);
3303const _: () = assert!(std::mem::size_of::<ArcType>() == 1);
3304const _: () = assert!(std::mem::size_of::<ArrowType>() == 1);
3305const _: () = assert!(std::mem::size_of::<ArrowSize>() == 1);
3306const _: () = assert!(std::mem::size_of::<GradientType>() == 1);
3307const _: () = assert!(std::mem::size_of::<PatternType>() == 1);
3308const _: () = assert!(std::mem::size_of::<ImageFillMode>() == 1);
3309const _: () = assert!(std::mem::size_of::<CurveSegmentType>() == 1);
3310const _: () = assert!(std::mem::size_of::<BookmarkType>() == 1);
3311const _: () = assert!(std::mem::size_of::<FieldType>() == 1);
3312const _: () = assert!(std::mem::size_of::<RefType>() == 1);
3313const _: () = assert!(std::mem::size_of::<RefContentType>() == 1);
3314
3315// ---------------------------------------------------------------------------
3316// TextDirection
3317// ---------------------------------------------------------------------------
3318
3319/// Text writing direction for sections and sub-lists.
3320///
3321/// Controls whether text flows horizontally (가로쓰기) or vertically (세로쓰기).
3322/// Used in `<hp:secPr textDirection="...">` and `<hp:subList textDirection="...">`.
3323///
3324/// # Examples
3325///
3326/// ```
3327/// use hwpforge_foundation::TextDirection;
3328///
3329/// assert_eq!(TextDirection::default(), TextDirection::Horizontal);
3330/// assert_eq!(TextDirection::Horizontal.to_string(), "HORIZONTAL");
3331/// ```
3332#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
3333#[non_exhaustive]
3334pub enum TextDirection {
3335    /// Horizontal writing (가로쓰기) — default.
3336    #[default]
3337    Horizontal,
3338    /// Vertical writing with Latin chars rotated 90° (세로쓰기 영문 눕힘).
3339    Vertical,
3340    /// Vertical writing with Latin chars upright (세로쓰기 영문 세움).
3341    VerticalAll,
3342}
3343
3344impl TextDirection {
3345    /// Parses a HWPX XML attribute string (e.g. `"VERTICAL"`).
3346    ///
3347    /// Unknown values fall back to [`TextDirection::Horizontal`].
3348    pub fn from_hwpx_str(s: &str) -> Self {
3349        match s {
3350            "VERTICAL" => Self::Vertical,
3351            "VERTICALALL" => Self::VerticalAll,
3352            _ => Self::Horizontal,
3353        }
3354    }
3355}
3356
3357impl fmt::Display for TextDirection {
3358    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3359        match self {
3360            Self::Horizontal => f.write_str("HORIZONTAL"),
3361            Self::Vertical => f.write_str("VERTICAL"),
3362            Self::VerticalAll => f.write_str("VERTICALALL"),
3363        }
3364    }
3365}
3366
3367impl schemars::JsonSchema for TextDirection {
3368    fn schema_name() -> std::borrow::Cow<'static, str> {
3369        std::borrow::Cow::Borrowed("TextDirection")
3370    }
3371
3372    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
3373        gen.subschema_for::<String>()
3374    }
3375}
3376
3377const _: () = assert!(std::mem::size_of::<TextDirection>() == 1);
3378
3379#[cfg(test)]
3380mod tests {
3381    use super::*;
3382    use std::str::FromStr;
3383
3384    // ===================================================================
3385    // Alignment (10+ tests)
3386    // ===================================================================
3387
3388    #[test]
3389    fn alignment_default_is_left() {
3390        assert_eq!(Alignment::default(), Alignment::Left);
3391    }
3392
3393    #[test]
3394    fn alignment_display_all_variants() {
3395        assert_eq!(Alignment::Left.to_string(), "Left");
3396        assert_eq!(Alignment::Center.to_string(), "Center");
3397        assert_eq!(Alignment::Right.to_string(), "Right");
3398        assert_eq!(Alignment::Justify.to_string(), "Justify");
3399        assert_eq!(Alignment::Distribute.to_string(), "Distribute");
3400        assert_eq!(Alignment::DistributeFlush.to_string(), "DistributeFlush");
3401    }
3402
3403    #[test]
3404    fn alignment_from_str_pascal_case() {
3405        assert_eq!(Alignment::from_str("Left").unwrap(), Alignment::Left);
3406        assert_eq!(Alignment::from_str("Center").unwrap(), Alignment::Center);
3407        assert_eq!(Alignment::from_str("Right").unwrap(), Alignment::Right);
3408        assert_eq!(Alignment::from_str("Justify").unwrap(), Alignment::Justify);
3409        assert_eq!(Alignment::from_str("Distribute").unwrap(), Alignment::Distribute);
3410        assert_eq!(Alignment::from_str("DistributeFlush").unwrap(), Alignment::DistributeFlush);
3411    }
3412
3413    #[test]
3414    fn alignment_from_str_lower_case() {
3415        assert_eq!(Alignment::from_str("left").unwrap(), Alignment::Left);
3416        assert_eq!(Alignment::from_str("center").unwrap(), Alignment::Center);
3417        assert_eq!(Alignment::from_str("distribute").unwrap(), Alignment::Distribute);
3418        assert_eq!(Alignment::from_str("distributeflush").unwrap(), Alignment::DistributeFlush);
3419        assert_eq!(Alignment::from_str("distribute_flush").unwrap(), Alignment::DistributeFlush);
3420    }
3421
3422    #[test]
3423    fn alignment_from_str_invalid() {
3424        let err = Alignment::from_str("leftt").unwrap_err();
3425        match err {
3426            FoundationError::ParseError { ref type_name, ref value, .. } => {
3427                assert_eq!(type_name, "Alignment");
3428                assert_eq!(value, "leftt");
3429            }
3430            other => panic!("unexpected: {other}"),
3431        }
3432    }
3433
3434    #[test]
3435    fn alignment_try_from_u8() {
3436        assert_eq!(Alignment::try_from(0u8).unwrap(), Alignment::Left);
3437        assert_eq!(Alignment::try_from(1u8).unwrap(), Alignment::Center);
3438        assert_eq!(Alignment::try_from(2u8).unwrap(), Alignment::Right);
3439        assert_eq!(Alignment::try_from(3u8).unwrap(), Alignment::Justify);
3440        assert_eq!(Alignment::try_from(4u8).unwrap(), Alignment::Distribute);
3441        assert_eq!(Alignment::try_from(5u8).unwrap(), Alignment::DistributeFlush);
3442        assert!(Alignment::try_from(6u8).is_err());
3443        assert!(Alignment::try_from(255u8).is_err());
3444    }
3445
3446    #[test]
3447    fn alignment_repr_values() {
3448        assert_eq!(Alignment::Left as u8, 0);
3449        assert_eq!(Alignment::Center as u8, 1);
3450        assert_eq!(Alignment::Right as u8, 2);
3451        assert_eq!(Alignment::Justify as u8, 3);
3452        assert_eq!(Alignment::Distribute as u8, 4);
3453        assert_eq!(Alignment::DistributeFlush as u8, 5);
3454    }
3455
3456    #[test]
3457    fn alignment_serde_roundtrip() {
3458        for variant in &[
3459            Alignment::Left,
3460            Alignment::Center,
3461            Alignment::Right,
3462            Alignment::Justify,
3463            Alignment::Distribute,
3464            Alignment::DistributeFlush,
3465        ] {
3466            let json = serde_json::to_string(variant).unwrap();
3467            let back: Alignment = serde_json::from_str(&json).unwrap();
3468            assert_eq!(&back, variant);
3469        }
3470    }
3471
3472    #[test]
3473    fn alignment_str_roundtrip() {
3474        for variant in &[
3475            Alignment::Left,
3476            Alignment::Center,
3477            Alignment::Right,
3478            Alignment::Justify,
3479            Alignment::Distribute,
3480            Alignment::DistributeFlush,
3481        ] {
3482            let s = variant.to_string();
3483            let back = Alignment::from_str(&s).unwrap();
3484            assert_eq!(&back, variant);
3485        }
3486    }
3487
3488    #[test]
3489    fn alignment_copy_and_hash() {
3490        use std::collections::HashSet;
3491        let a = Alignment::Left;
3492        let b = a; // Copy
3493        assert_eq!(a, b);
3494
3495        let mut set = HashSet::new();
3496        set.insert(Alignment::Left);
3497        set.insert(Alignment::Right);
3498        assert_eq!(set.len(), 2);
3499    }
3500
3501    // ===================================================================
3502    // LineSpacingType
3503    // ===================================================================
3504
3505    #[test]
3506    fn line_spacing_default_is_percentage() {
3507        assert_eq!(LineSpacingType::default(), LineSpacingType::Percentage);
3508    }
3509
3510    #[test]
3511    fn line_spacing_display() {
3512        assert_eq!(LineSpacingType::Percentage.to_string(), "Percentage");
3513        assert_eq!(LineSpacingType::Fixed.to_string(), "Fixed");
3514        assert_eq!(LineSpacingType::BetweenLines.to_string(), "BetweenLines");
3515    }
3516
3517    #[test]
3518    fn line_spacing_from_str() {
3519        assert_eq!(LineSpacingType::from_str("Percentage").unwrap(), LineSpacingType::Percentage);
3520        assert_eq!(LineSpacingType::from_str("Fixed").unwrap(), LineSpacingType::Fixed);
3521        assert_eq!(
3522            LineSpacingType::from_str("BetweenLines").unwrap(),
3523            LineSpacingType::BetweenLines
3524        );
3525        assert!(LineSpacingType::from_str("invalid").is_err());
3526    }
3527
3528    #[test]
3529    fn line_spacing_try_from_u8() {
3530        assert_eq!(LineSpacingType::try_from(0u8).unwrap(), LineSpacingType::Percentage);
3531        assert_eq!(LineSpacingType::try_from(1u8).unwrap(), LineSpacingType::Fixed);
3532        assert_eq!(LineSpacingType::try_from(2u8).unwrap(), LineSpacingType::BetweenLines);
3533        assert!(LineSpacingType::try_from(3u8).is_err());
3534    }
3535
3536    #[test]
3537    fn line_spacing_str_roundtrip() {
3538        for v in
3539            &[LineSpacingType::Percentage, LineSpacingType::Fixed, LineSpacingType::BetweenLines]
3540        {
3541            let s = v.to_string();
3542            let back = LineSpacingType::from_str(&s).unwrap();
3543            assert_eq!(&back, v);
3544        }
3545    }
3546
3547    // ===================================================================
3548    // BreakType
3549    // ===================================================================
3550
3551    #[test]
3552    fn break_type_default_is_none() {
3553        assert_eq!(BreakType::default(), BreakType::None);
3554    }
3555
3556    #[test]
3557    fn break_type_display() {
3558        assert_eq!(BreakType::None.to_string(), "None");
3559        assert_eq!(BreakType::Column.to_string(), "Column");
3560        assert_eq!(BreakType::Page.to_string(), "Page");
3561    }
3562
3563    #[test]
3564    fn break_type_from_str() {
3565        assert_eq!(BreakType::from_str("None").unwrap(), BreakType::None);
3566        assert_eq!(BreakType::from_str("Column").unwrap(), BreakType::Column);
3567        assert_eq!(BreakType::from_str("Page").unwrap(), BreakType::Page);
3568        assert!(BreakType::from_str("section").is_err());
3569    }
3570
3571    #[test]
3572    fn break_type_try_from_u8() {
3573        assert_eq!(BreakType::try_from(0u8).unwrap(), BreakType::None);
3574        assert_eq!(BreakType::try_from(1u8).unwrap(), BreakType::Column);
3575        assert_eq!(BreakType::try_from(2u8).unwrap(), BreakType::Page);
3576        assert!(BreakType::try_from(3u8).is_err());
3577    }
3578
3579    #[test]
3580    fn break_type_str_roundtrip() {
3581        for v in &[BreakType::None, BreakType::Column, BreakType::Page] {
3582            let s = v.to_string();
3583            let back = BreakType::from_str(&s).unwrap();
3584            assert_eq!(&back, v);
3585        }
3586    }
3587
3588    // ===================================================================
3589    // Language
3590    // ===================================================================
3591
3592    #[test]
3593    fn language_count_is_7() {
3594        assert_eq!(Language::COUNT, 7);
3595        assert_eq!(Language::ALL.len(), 7);
3596    }
3597
3598    #[test]
3599    fn language_default_is_korean() {
3600        assert_eq!(Language::default(), Language::Korean);
3601    }
3602
3603    #[test]
3604    fn language_discriminants() {
3605        assert_eq!(Language::Korean as u8, 0);
3606        assert_eq!(Language::English as u8, 1);
3607        assert_eq!(Language::Hanja as u8, 2);
3608        assert_eq!(Language::Japanese as u8, 3);
3609        assert_eq!(Language::Other as u8, 4);
3610        assert_eq!(Language::Symbol as u8, 5);
3611        assert_eq!(Language::User as u8, 6);
3612    }
3613
3614    #[test]
3615    fn language_display() {
3616        assert_eq!(Language::Korean.to_string(), "Korean");
3617        assert_eq!(Language::English.to_string(), "English");
3618        assert_eq!(Language::Japanese.to_string(), "Japanese");
3619    }
3620
3621    #[test]
3622    fn language_from_str() {
3623        for lang in &Language::ALL {
3624            let s = lang.to_string();
3625            let back = Language::from_str(&s).unwrap();
3626            assert_eq!(&back, lang);
3627        }
3628        assert!(Language::from_str("invalid").is_err());
3629    }
3630
3631    #[test]
3632    fn language_try_from_u8() {
3633        for (i, expected) in Language::ALL.iter().enumerate() {
3634            let parsed = Language::try_from(i as u8).unwrap();
3635            assert_eq!(&parsed, expected);
3636        }
3637        assert!(Language::try_from(7u8).is_err());
3638        assert!(Language::try_from(255u8).is_err());
3639    }
3640
3641    #[test]
3642    fn language_all_used_as_index() {
3643        // Common pattern: fonts[lang as usize]
3644        let fonts: [&str; Language::COUNT] =
3645            ["Batang", "Arial", "SimSun", "MS Mincho", "Arial", "Symbol", "Arial"];
3646        for lang in &Language::ALL {
3647            let _ = fonts[*lang as usize];
3648        }
3649    }
3650
3651    #[test]
3652    fn language_serde_roundtrip() {
3653        for lang in &Language::ALL {
3654            let json = serde_json::to_string(lang).unwrap();
3655            let back: Language = serde_json::from_str(&json).unwrap();
3656            assert_eq!(&back, lang);
3657        }
3658    }
3659
3660    // ===================================================================
3661    // UnderlineType
3662    // ===================================================================
3663
3664    #[test]
3665    fn underline_type_default_is_none() {
3666        assert_eq!(UnderlineType::default(), UnderlineType::None);
3667    }
3668
3669    #[test]
3670    fn underline_type_display() {
3671        assert_eq!(UnderlineType::None.to_string(), "None");
3672        assert_eq!(UnderlineType::Bottom.to_string(), "Bottom");
3673        assert_eq!(UnderlineType::Center.to_string(), "Center");
3674        assert_eq!(UnderlineType::Top.to_string(), "Top");
3675    }
3676
3677    #[test]
3678    fn underline_type_from_str() {
3679        assert_eq!(UnderlineType::from_str("None").unwrap(), UnderlineType::None);
3680        assert_eq!(UnderlineType::from_str("Bottom").unwrap(), UnderlineType::Bottom);
3681        assert_eq!(UnderlineType::from_str("center").unwrap(), UnderlineType::Center);
3682        assert!(UnderlineType::from_str("invalid").is_err());
3683    }
3684
3685    #[test]
3686    fn underline_type_try_from_u8() {
3687        assert_eq!(UnderlineType::try_from(0u8).unwrap(), UnderlineType::None);
3688        assert_eq!(UnderlineType::try_from(1u8).unwrap(), UnderlineType::Bottom);
3689        assert_eq!(UnderlineType::try_from(2u8).unwrap(), UnderlineType::Center);
3690        assert_eq!(UnderlineType::try_from(3u8).unwrap(), UnderlineType::Top);
3691        assert!(UnderlineType::try_from(4u8).is_err());
3692    }
3693
3694    #[test]
3695    fn underline_type_str_roundtrip() {
3696        for v in
3697            &[UnderlineType::None, UnderlineType::Bottom, UnderlineType::Center, UnderlineType::Top]
3698        {
3699            let s = v.to_string();
3700            let back = UnderlineType::from_str(&s).unwrap();
3701            assert_eq!(&back, v);
3702        }
3703    }
3704
3705    // ===================================================================
3706    // StrikeoutShape
3707    // ===================================================================
3708
3709    #[test]
3710    fn strikeout_shape_default_is_none() {
3711        assert_eq!(StrikeoutShape::default(), StrikeoutShape::None);
3712    }
3713
3714    #[test]
3715    fn strikeout_shape_display() {
3716        assert_eq!(StrikeoutShape::None.to_string(), "None");
3717        assert_eq!(StrikeoutShape::Continuous.to_string(), "Continuous");
3718        assert_eq!(StrikeoutShape::Dash.to_string(), "Dash");
3719        assert_eq!(StrikeoutShape::DashDotDot.to_string(), "DashDotDot");
3720    }
3721
3722    #[test]
3723    fn strikeout_shape_from_str() {
3724        assert_eq!(StrikeoutShape::from_str("None").unwrap(), StrikeoutShape::None);
3725        assert_eq!(StrikeoutShape::from_str("continuous").unwrap(), StrikeoutShape::Continuous);
3726        assert_eq!(StrikeoutShape::from_str("dash_dot").unwrap(), StrikeoutShape::DashDot);
3727        assert!(StrikeoutShape::from_str("invalid").is_err());
3728    }
3729
3730    #[test]
3731    fn strikeout_shape_try_from_u8() {
3732        assert_eq!(StrikeoutShape::try_from(0u8).unwrap(), StrikeoutShape::None);
3733        assert_eq!(StrikeoutShape::try_from(1u8).unwrap(), StrikeoutShape::Continuous);
3734        assert_eq!(StrikeoutShape::try_from(5u8).unwrap(), StrikeoutShape::DashDotDot);
3735        assert!(StrikeoutShape::try_from(6u8).is_err());
3736    }
3737
3738    #[test]
3739    fn strikeout_shape_str_roundtrip() {
3740        for v in &[
3741            StrikeoutShape::None,
3742            StrikeoutShape::Continuous,
3743            StrikeoutShape::Dash,
3744            StrikeoutShape::Dot,
3745            StrikeoutShape::DashDot,
3746            StrikeoutShape::DashDotDot,
3747        ] {
3748            let s = v.to_string();
3749            let back = StrikeoutShape::from_str(&s).unwrap();
3750            assert_eq!(&back, v);
3751        }
3752    }
3753
3754    // ===================================================================
3755    // OutlineType
3756    // ===================================================================
3757
3758    #[test]
3759    fn outline_type_default_is_none() {
3760        assert_eq!(OutlineType::default(), OutlineType::None);
3761    }
3762
3763    #[test]
3764    fn outline_type_display() {
3765        assert_eq!(OutlineType::None.to_string(), "None");
3766        assert_eq!(OutlineType::Solid.to_string(), "Solid");
3767    }
3768
3769    #[test]
3770    fn outline_type_from_str() {
3771        assert_eq!(OutlineType::from_str("None").unwrap(), OutlineType::None);
3772        assert_eq!(OutlineType::from_str("solid").unwrap(), OutlineType::Solid);
3773        assert!(OutlineType::from_str("dashed").is_err());
3774    }
3775
3776    #[test]
3777    fn outline_type_try_from_u8() {
3778        assert_eq!(OutlineType::try_from(0u8).unwrap(), OutlineType::None);
3779        assert_eq!(OutlineType::try_from(1u8).unwrap(), OutlineType::Solid);
3780        assert!(OutlineType::try_from(2u8).is_err());
3781    }
3782
3783    // ===================================================================
3784    // ShadowType
3785    // ===================================================================
3786
3787    #[test]
3788    fn shadow_type_default_is_none() {
3789        assert_eq!(ShadowType::default(), ShadowType::None);
3790    }
3791
3792    #[test]
3793    fn shadow_type_display() {
3794        assert_eq!(ShadowType::None.to_string(), "None");
3795        assert_eq!(ShadowType::Drop.to_string(), "Drop");
3796    }
3797
3798    #[test]
3799    fn shadow_type_from_str() {
3800        assert_eq!(ShadowType::from_str("None").unwrap(), ShadowType::None);
3801        assert_eq!(ShadowType::from_str("drop").unwrap(), ShadowType::Drop);
3802        assert!(ShadowType::from_str("shadow").is_err());
3803    }
3804
3805    #[test]
3806    fn shadow_type_try_from_u8() {
3807        assert_eq!(ShadowType::try_from(0u8).unwrap(), ShadowType::None);
3808        assert_eq!(ShadowType::try_from(1u8).unwrap(), ShadowType::Drop);
3809        assert!(ShadowType::try_from(2u8).is_err());
3810    }
3811
3812    // ===================================================================
3813    // EmbossType
3814    // ===================================================================
3815
3816    #[test]
3817    fn emboss_type_default_is_none() {
3818        assert_eq!(EmbossType::default(), EmbossType::None);
3819    }
3820
3821    #[test]
3822    fn emboss_type_display() {
3823        assert_eq!(EmbossType::None.to_string(), "None");
3824        assert_eq!(EmbossType::Emboss.to_string(), "Emboss");
3825    }
3826
3827    #[test]
3828    fn emboss_type_from_str() {
3829        assert_eq!(EmbossType::from_str("None").unwrap(), EmbossType::None);
3830        assert_eq!(EmbossType::from_str("emboss").unwrap(), EmbossType::Emboss);
3831        assert!(EmbossType::from_str("raised").is_err());
3832    }
3833
3834    #[test]
3835    fn emboss_type_try_from_u8() {
3836        assert_eq!(EmbossType::try_from(0u8).unwrap(), EmbossType::None);
3837        assert_eq!(EmbossType::try_from(1u8).unwrap(), EmbossType::Emboss);
3838        assert!(EmbossType::try_from(2u8).is_err());
3839    }
3840
3841    // ===================================================================
3842    // EngraveType
3843    // ===================================================================
3844
3845    #[test]
3846    fn engrave_type_default_is_none() {
3847        assert_eq!(EngraveType::default(), EngraveType::None);
3848    }
3849
3850    #[test]
3851    fn engrave_type_display() {
3852        assert_eq!(EngraveType::None.to_string(), "None");
3853        assert_eq!(EngraveType::Engrave.to_string(), "Engrave");
3854    }
3855
3856    #[test]
3857    fn engrave_type_from_str() {
3858        assert_eq!(EngraveType::from_str("None").unwrap(), EngraveType::None);
3859        assert_eq!(EngraveType::from_str("engrave").unwrap(), EngraveType::Engrave);
3860        assert!(EngraveType::from_str("sunken").is_err());
3861    }
3862
3863    #[test]
3864    fn engrave_type_try_from_u8() {
3865        assert_eq!(EngraveType::try_from(0u8).unwrap(), EngraveType::None);
3866        assert_eq!(EngraveType::try_from(1u8).unwrap(), EngraveType::Engrave);
3867        assert!(EngraveType::try_from(2u8).is_err());
3868    }
3869
3870    // ===================================================================
3871    // VerticalPosition
3872    // ===================================================================
3873
3874    #[test]
3875    fn vertical_position_default_is_normal() {
3876        assert_eq!(VerticalPosition::default(), VerticalPosition::Normal);
3877    }
3878
3879    #[test]
3880    fn vertical_position_display() {
3881        assert_eq!(VerticalPosition::Normal.to_string(), "Normal");
3882        assert_eq!(VerticalPosition::Superscript.to_string(), "Superscript");
3883        assert_eq!(VerticalPosition::Subscript.to_string(), "Subscript");
3884    }
3885
3886    #[test]
3887    fn vertical_position_from_str() {
3888        assert_eq!(VerticalPosition::from_str("Normal").unwrap(), VerticalPosition::Normal);
3889        assert_eq!(
3890            VerticalPosition::from_str("superscript").unwrap(),
3891            VerticalPosition::Superscript
3892        );
3893        assert_eq!(VerticalPosition::from_str("sub").unwrap(), VerticalPosition::Subscript);
3894        assert!(VerticalPosition::from_str("middle").is_err());
3895    }
3896
3897    #[test]
3898    fn vertical_position_try_from_u8() {
3899        assert_eq!(VerticalPosition::try_from(0u8).unwrap(), VerticalPosition::Normal);
3900        assert_eq!(VerticalPosition::try_from(1u8).unwrap(), VerticalPosition::Superscript);
3901        assert_eq!(VerticalPosition::try_from(2u8).unwrap(), VerticalPosition::Subscript);
3902        assert!(VerticalPosition::try_from(3u8).is_err());
3903    }
3904
3905    #[test]
3906    fn vertical_position_str_roundtrip() {
3907        for v in
3908            &[VerticalPosition::Normal, VerticalPosition::Superscript, VerticalPosition::Subscript]
3909        {
3910            let s = v.to_string();
3911            let back = VerticalPosition::from_str(&s).unwrap();
3912            assert_eq!(&back, v);
3913        }
3914    }
3915
3916    // ===================================================================
3917    // BorderLineType
3918    // ===================================================================
3919
3920    #[test]
3921    fn border_line_type_default_is_none() {
3922        assert_eq!(BorderLineType::default(), BorderLineType::None);
3923    }
3924
3925    #[test]
3926    fn border_line_type_display() {
3927        assert_eq!(BorderLineType::None.to_string(), "None");
3928        assert_eq!(BorderLineType::Solid.to_string(), "Solid");
3929        assert_eq!(BorderLineType::DashDot.to_string(), "DashDot");
3930        assert_eq!(BorderLineType::ThickBetweenSlim.to_string(), "ThickBetweenSlim");
3931    }
3932
3933    #[test]
3934    fn border_line_type_from_str() {
3935        assert_eq!(BorderLineType::from_str("None").unwrap(), BorderLineType::None);
3936        assert_eq!(BorderLineType::from_str("solid").unwrap(), BorderLineType::Solid);
3937        assert_eq!(BorderLineType::from_str("dash_dot").unwrap(), BorderLineType::DashDot);
3938        assert_eq!(BorderLineType::from_str("double").unwrap(), BorderLineType::Double);
3939        assert!(BorderLineType::from_str("wavy").is_err());
3940    }
3941
3942    #[test]
3943    fn border_line_type_try_from_u8() {
3944        assert_eq!(BorderLineType::try_from(0u8).unwrap(), BorderLineType::None);
3945        assert_eq!(BorderLineType::try_from(1u8).unwrap(), BorderLineType::Solid);
3946        assert_eq!(BorderLineType::try_from(10u8).unwrap(), BorderLineType::ThickBetweenSlim);
3947        assert!(BorderLineType::try_from(11u8).is_err());
3948    }
3949
3950    #[test]
3951    fn border_line_type_str_roundtrip() {
3952        for v in &[
3953            BorderLineType::None,
3954            BorderLineType::Solid,
3955            BorderLineType::Dash,
3956            BorderLineType::Dot,
3957            BorderLineType::DashDot,
3958            BorderLineType::DashDotDot,
3959            BorderLineType::LongDash,
3960            BorderLineType::TripleDot,
3961            BorderLineType::Double,
3962            BorderLineType::DoubleSlim,
3963            BorderLineType::ThickBetweenSlim,
3964        ] {
3965            let s = v.to_string();
3966            let back = BorderLineType::from_str(&s).unwrap();
3967            assert_eq!(&back, v);
3968        }
3969    }
3970
3971    // ===================================================================
3972    // FillBrushType
3973    // ===================================================================
3974
3975    #[test]
3976    fn fill_brush_type_default_is_none() {
3977        assert_eq!(FillBrushType::default(), FillBrushType::None);
3978    }
3979
3980    #[test]
3981    fn fill_brush_type_display() {
3982        assert_eq!(FillBrushType::None.to_string(), "None");
3983        assert_eq!(FillBrushType::Solid.to_string(), "Solid");
3984        assert_eq!(FillBrushType::Gradient.to_string(), "Gradient");
3985        assert_eq!(FillBrushType::Pattern.to_string(), "Pattern");
3986    }
3987
3988    #[test]
3989    fn fill_brush_type_from_str() {
3990        assert_eq!(FillBrushType::from_str("None").unwrap(), FillBrushType::None);
3991        assert_eq!(FillBrushType::from_str("solid").unwrap(), FillBrushType::Solid);
3992        assert_eq!(FillBrushType::from_str("gradient").unwrap(), FillBrushType::Gradient);
3993        assert!(FillBrushType::from_str("texture").is_err());
3994    }
3995
3996    #[test]
3997    fn fill_brush_type_try_from_u8() {
3998        assert_eq!(FillBrushType::try_from(0u8).unwrap(), FillBrushType::None);
3999        assert_eq!(FillBrushType::try_from(1u8).unwrap(), FillBrushType::Solid);
4000        assert_eq!(FillBrushType::try_from(2u8).unwrap(), FillBrushType::Gradient);
4001        assert_eq!(FillBrushType::try_from(3u8).unwrap(), FillBrushType::Pattern);
4002        assert!(FillBrushType::try_from(4u8).is_err());
4003    }
4004
4005    #[test]
4006    fn fill_brush_type_str_roundtrip() {
4007        for v in &[
4008            FillBrushType::None,
4009            FillBrushType::Solid,
4010            FillBrushType::Gradient,
4011            FillBrushType::Pattern,
4012        ] {
4013            let s = v.to_string();
4014            let back = FillBrushType::from_str(&s).unwrap();
4015            assert_eq!(&back, v);
4016        }
4017    }
4018
4019    // ===================================================================
4020    // Cross-enum size assertions (compile-time already, but test at runtime too)
4021    // ===================================================================
4022
4023    #[test]
4024    fn all_enums_are_one_byte() {
4025        assert_eq!(std::mem::size_of::<Alignment>(), 1);
4026        assert_eq!(std::mem::size_of::<LineSpacingType>(), 1);
4027        assert_eq!(std::mem::size_of::<BreakType>(), 1);
4028        assert_eq!(std::mem::size_of::<Language>(), 1);
4029        assert_eq!(std::mem::size_of::<UnderlineType>(), 1);
4030        assert_eq!(std::mem::size_of::<StrikeoutShape>(), 1);
4031        assert_eq!(std::mem::size_of::<OutlineType>(), 1);
4032        assert_eq!(std::mem::size_of::<ShadowType>(), 1);
4033        assert_eq!(std::mem::size_of::<EmbossType>(), 1);
4034        assert_eq!(std::mem::size_of::<EngraveType>(), 1);
4035        assert_eq!(std::mem::size_of::<VerticalPosition>(), 1);
4036        assert_eq!(std::mem::size_of::<BorderLineType>(), 1);
4037        assert_eq!(std::mem::size_of::<FillBrushType>(), 1);
4038        assert_eq!(std::mem::size_of::<ApplyPageType>(), 1);
4039        assert_eq!(std::mem::size_of::<NumberFormatType>(), 1);
4040        assert_eq!(std::mem::size_of::<PageNumberPosition>(), 1);
4041    }
4042
4043    // ===================================================================
4044    // ApplyPageType
4045    // ===================================================================
4046
4047    #[test]
4048    fn apply_page_type_default_is_both() {
4049        assert_eq!(ApplyPageType::default(), ApplyPageType::Both);
4050    }
4051
4052    #[test]
4053    fn apply_page_type_display() {
4054        assert_eq!(ApplyPageType::Both.to_string(), "Both");
4055        assert_eq!(ApplyPageType::Even.to_string(), "Even");
4056        assert_eq!(ApplyPageType::Odd.to_string(), "Odd");
4057    }
4058
4059    #[test]
4060    fn apply_page_type_from_str() {
4061        assert_eq!(ApplyPageType::from_str("Both").unwrap(), ApplyPageType::Both);
4062        assert_eq!(ApplyPageType::from_str("BOTH").unwrap(), ApplyPageType::Both);
4063        assert_eq!(ApplyPageType::from_str("even").unwrap(), ApplyPageType::Even);
4064        assert_eq!(ApplyPageType::from_str("ODD").unwrap(), ApplyPageType::Odd);
4065        assert!(ApplyPageType::from_str("invalid").is_err());
4066    }
4067
4068    #[test]
4069    fn apply_page_type_try_from_u8() {
4070        assert_eq!(ApplyPageType::try_from(0u8).unwrap(), ApplyPageType::Both);
4071        assert_eq!(ApplyPageType::try_from(1u8).unwrap(), ApplyPageType::Even);
4072        assert_eq!(ApplyPageType::try_from(2u8).unwrap(), ApplyPageType::Odd);
4073        assert!(ApplyPageType::try_from(3u8).is_err());
4074    }
4075
4076    #[test]
4077    fn apply_page_type_str_roundtrip() {
4078        for v in &[ApplyPageType::Both, ApplyPageType::Even, ApplyPageType::Odd] {
4079            let s = v.to_string();
4080            let back = ApplyPageType::from_str(&s).unwrap();
4081            assert_eq!(&back, v);
4082        }
4083    }
4084
4085    // ===================================================================
4086    // NumberFormatType
4087    // ===================================================================
4088
4089    #[test]
4090    fn number_format_type_default_is_digit() {
4091        assert_eq!(NumberFormatType::default(), NumberFormatType::Digit);
4092    }
4093
4094    #[test]
4095    fn number_format_type_display() {
4096        assert_eq!(NumberFormatType::Digit.to_string(), "Digit");
4097        assert_eq!(NumberFormatType::CircledDigit.to_string(), "CircledDigit");
4098        assert_eq!(NumberFormatType::RomanCapital.to_string(), "RomanCapital");
4099        assert_eq!(NumberFormatType::HanjaDigit.to_string(), "HanjaDigit");
4100    }
4101
4102    #[test]
4103    fn number_format_type_from_str() {
4104        assert_eq!(NumberFormatType::from_str("Digit").unwrap(), NumberFormatType::Digit);
4105        assert_eq!(NumberFormatType::from_str("DIGIT").unwrap(), NumberFormatType::Digit);
4106        assert_eq!(
4107            NumberFormatType::from_str("CircledDigit").unwrap(),
4108            NumberFormatType::CircledDigit
4109        );
4110        assert_eq!(
4111            NumberFormatType::from_str("ROMAN_CAPITAL").unwrap(),
4112            NumberFormatType::RomanCapital
4113        );
4114        assert!(NumberFormatType::from_str("invalid").is_err());
4115    }
4116
4117    #[test]
4118    fn number_format_type_try_from_u8() {
4119        assert_eq!(NumberFormatType::try_from(0u8).unwrap(), NumberFormatType::Digit);
4120        assert_eq!(NumberFormatType::try_from(1u8).unwrap(), NumberFormatType::CircledDigit);
4121        assert_eq!(NumberFormatType::try_from(8u8).unwrap(), NumberFormatType::HanjaDigit);
4122        assert_eq!(
4123            NumberFormatType::try_from(9u8).unwrap(),
4124            NumberFormatType::CircledHangulSyllable
4125        );
4126        assert!(NumberFormatType::try_from(10u8).is_err());
4127    }
4128
4129    #[test]
4130    fn number_format_type_circled_hangul_syllable() {
4131        assert_eq!(NumberFormatType::CircledHangulSyllable.to_string(), "CircledHangulSyllable");
4132        assert_eq!(
4133            NumberFormatType::from_str("CircledHangulSyllable").unwrap(),
4134            NumberFormatType::CircledHangulSyllable
4135        );
4136        assert_eq!(
4137            NumberFormatType::from_str("CIRCLED_HANGUL_SYLLABLE").unwrap(),
4138            NumberFormatType::CircledHangulSyllable
4139        );
4140    }
4141
4142    #[test]
4143    fn number_format_type_str_roundtrip() {
4144        for v in &[
4145            NumberFormatType::Digit,
4146            NumberFormatType::CircledDigit,
4147            NumberFormatType::RomanCapital,
4148            NumberFormatType::RomanSmall,
4149            NumberFormatType::LatinCapital,
4150            NumberFormatType::LatinSmall,
4151            NumberFormatType::HangulSyllable,
4152            NumberFormatType::HangulJamo,
4153            NumberFormatType::HanjaDigit,
4154            NumberFormatType::CircledHangulSyllable,
4155        ] {
4156            let s = v.to_string();
4157            let back = NumberFormatType::from_str(&s).unwrap();
4158            assert_eq!(&back, v);
4159        }
4160    }
4161
4162    // ===================================================================
4163    // PageNumberPosition
4164    // ===================================================================
4165
4166    #[test]
4167    fn page_number_position_default_is_top_center() {
4168        assert_eq!(PageNumberPosition::default(), PageNumberPosition::TopCenter);
4169    }
4170
4171    #[test]
4172    fn page_number_position_display() {
4173        assert_eq!(PageNumberPosition::None.to_string(), "None");
4174        assert_eq!(PageNumberPosition::TopCenter.to_string(), "TopCenter");
4175        assert_eq!(PageNumberPosition::BottomCenter.to_string(), "BottomCenter");
4176        assert_eq!(PageNumberPosition::InsideBottom.to_string(), "InsideBottom");
4177    }
4178
4179    #[test]
4180    fn page_number_position_from_str() {
4181        assert_eq!(PageNumberPosition::from_str("None").unwrap(), PageNumberPosition::None);
4182        assert_eq!(
4183            PageNumberPosition::from_str("BOTTOM_CENTER").unwrap(),
4184            PageNumberPosition::BottomCenter
4185        );
4186        assert_eq!(
4187            PageNumberPosition::from_str("bottom-center").unwrap(),
4188            PageNumberPosition::BottomCenter
4189        );
4190        assert_eq!(PageNumberPosition::from_str("TopLeft").unwrap(), PageNumberPosition::TopLeft);
4191        assert!(PageNumberPosition::from_str("invalid").is_err());
4192    }
4193
4194    #[test]
4195    fn page_number_position_try_from_u8() {
4196        assert_eq!(PageNumberPosition::try_from(0u8).unwrap(), PageNumberPosition::None);
4197        assert_eq!(PageNumberPosition::try_from(2u8).unwrap(), PageNumberPosition::TopCenter);
4198        assert_eq!(PageNumberPosition::try_from(5u8).unwrap(), PageNumberPosition::BottomCenter);
4199        assert_eq!(PageNumberPosition::try_from(10u8).unwrap(), PageNumberPosition::InsideBottom);
4200        assert!(PageNumberPosition::try_from(11u8).is_err());
4201    }
4202
4203    #[test]
4204    fn page_number_position_str_roundtrip() {
4205        for v in &[
4206            PageNumberPosition::None,
4207            PageNumberPosition::TopLeft,
4208            PageNumberPosition::TopCenter,
4209            PageNumberPosition::TopRight,
4210            PageNumberPosition::BottomLeft,
4211            PageNumberPosition::BottomCenter,
4212            PageNumberPosition::BottomRight,
4213            PageNumberPosition::OutsideTop,
4214            PageNumberPosition::OutsideBottom,
4215            PageNumberPosition::InsideTop,
4216            PageNumberPosition::InsideBottom,
4217        ] {
4218            let s = v.to_string();
4219            let back = PageNumberPosition::from_str(&s).unwrap();
4220            assert_eq!(&back, v);
4221        }
4222    }
4223
4224    // ===================================================================
4225    // WordBreakType
4226    // ===================================================================
4227
4228    #[test]
4229    fn word_break_type_default_is_keep_word() {
4230        assert_eq!(WordBreakType::default(), WordBreakType::KeepWord);
4231    }
4232
4233    #[test]
4234    fn word_break_type_display() {
4235        assert_eq!(WordBreakType::KeepWord.to_string(), "KEEP_WORD");
4236        assert_eq!(WordBreakType::BreakWord.to_string(), "BREAK_WORD");
4237    }
4238
4239    #[test]
4240    fn word_break_type_from_str() {
4241        assert_eq!(WordBreakType::from_str("KEEP_WORD").unwrap(), WordBreakType::KeepWord);
4242        assert_eq!(WordBreakType::from_str("KeepWord").unwrap(), WordBreakType::KeepWord);
4243        assert_eq!(WordBreakType::from_str("keep_word").unwrap(), WordBreakType::KeepWord);
4244        assert_eq!(WordBreakType::from_str("BREAK_WORD").unwrap(), WordBreakType::BreakWord);
4245        assert_eq!(WordBreakType::from_str("BreakWord").unwrap(), WordBreakType::BreakWord);
4246        assert_eq!(WordBreakType::from_str("break_word").unwrap(), WordBreakType::BreakWord);
4247        assert!(WordBreakType::from_str("invalid").is_err());
4248    }
4249
4250    #[test]
4251    fn word_break_type_try_from_u8() {
4252        assert_eq!(WordBreakType::try_from(0u8).unwrap(), WordBreakType::KeepWord);
4253        assert_eq!(WordBreakType::try_from(1u8).unwrap(), WordBreakType::BreakWord);
4254        assert!(WordBreakType::try_from(2u8).is_err());
4255    }
4256
4257    #[test]
4258    fn word_break_type_serde_roundtrip() {
4259        for v in &[WordBreakType::KeepWord, WordBreakType::BreakWord] {
4260            let json = serde_json::to_string(v).unwrap();
4261            let back: WordBreakType = serde_json::from_str(&json).unwrap();
4262            assert_eq!(&back, v);
4263        }
4264    }
4265
4266    #[test]
4267    fn word_break_type_str_roundtrip() {
4268        for v in &[WordBreakType::KeepWord, WordBreakType::BreakWord] {
4269            let s = v.to_string();
4270            let back = WordBreakType::from_str(&s).unwrap();
4271            assert_eq!(&back, v);
4272        }
4273    }
4274
4275    // ===================================================================
4276    // EmphasisType
4277    // ===================================================================
4278
4279    #[test]
4280    fn emphasis_type_default_is_none() {
4281        assert_eq!(EmphasisType::default(), EmphasisType::None);
4282    }
4283
4284    #[test]
4285    fn emphasis_type_display_pascal_case() {
4286        assert_eq!(EmphasisType::None.to_string(), "None");
4287        assert_eq!(EmphasisType::DotAbove.to_string(), "DotAbove");
4288        assert_eq!(EmphasisType::RingAbove.to_string(), "RingAbove");
4289        assert_eq!(EmphasisType::Tilde.to_string(), "Tilde");
4290        assert_eq!(EmphasisType::Caron.to_string(), "Caron");
4291        assert_eq!(EmphasisType::Side.to_string(), "Side");
4292        assert_eq!(EmphasisType::Colon.to_string(), "Colon");
4293        assert_eq!(EmphasisType::GraveAccent.to_string(), "GraveAccent");
4294        assert_eq!(EmphasisType::AcuteAccent.to_string(), "AcuteAccent");
4295        assert_eq!(EmphasisType::Circumflex.to_string(), "Circumflex");
4296        assert_eq!(EmphasisType::Macron.to_string(), "Macron");
4297        assert_eq!(EmphasisType::HookAbove.to_string(), "HookAbove");
4298        assert_eq!(EmphasisType::DotBelow.to_string(), "DotBelow");
4299    }
4300
4301    #[test]
4302    fn emphasis_type_from_str_screaming_snake_case() {
4303        assert_eq!(EmphasisType::from_str("NONE").unwrap(), EmphasisType::None);
4304        assert_eq!(EmphasisType::from_str("DOT_ABOVE").unwrap(), EmphasisType::DotAbove);
4305        assert_eq!(EmphasisType::from_str("RING_ABOVE").unwrap(), EmphasisType::RingAbove);
4306        assert_eq!(EmphasisType::from_str("GRAVE_ACCENT").unwrap(), EmphasisType::GraveAccent);
4307        assert_eq!(EmphasisType::from_str("DOT_BELOW").unwrap(), EmphasisType::DotBelow);
4308    }
4309
4310    #[test]
4311    fn emphasis_type_from_str_pascal_case() {
4312        assert_eq!(EmphasisType::from_str("None").unwrap(), EmphasisType::None);
4313        assert_eq!(EmphasisType::from_str("DotAbove").unwrap(), EmphasisType::DotAbove);
4314        assert_eq!(EmphasisType::from_str("HookAbove").unwrap(), EmphasisType::HookAbove);
4315    }
4316
4317    #[test]
4318    fn emphasis_type_from_str_invalid() {
4319        let err = EmphasisType::from_str("INVALID").unwrap_err();
4320        match err {
4321            FoundationError::ParseError { ref type_name, ref value, .. } => {
4322                assert_eq!(type_name, "EmphasisType");
4323                assert_eq!(value, "INVALID");
4324            }
4325            other => panic!("unexpected: {other}"),
4326        }
4327    }
4328
4329    #[test]
4330    fn emphasis_type_try_from_u8() {
4331        assert_eq!(EmphasisType::try_from(0u8).unwrap(), EmphasisType::None);
4332        assert_eq!(EmphasisType::try_from(1u8).unwrap(), EmphasisType::DotAbove);
4333        assert_eq!(EmphasisType::try_from(12u8).unwrap(), EmphasisType::DotBelow);
4334        assert!(EmphasisType::try_from(13u8).is_err());
4335        assert!(EmphasisType::try_from(255u8).is_err());
4336    }
4337
4338    #[test]
4339    fn emphasis_type_repr_values() {
4340        assert_eq!(EmphasisType::None as u8, 0);
4341        assert_eq!(EmphasisType::DotAbove as u8, 1);
4342        assert_eq!(EmphasisType::DotBelow as u8, 12);
4343    }
4344
4345    #[test]
4346    fn emphasis_type_serde_roundtrip() {
4347        for variant in &[
4348            EmphasisType::None,
4349            EmphasisType::DotAbove,
4350            EmphasisType::RingAbove,
4351            EmphasisType::DotBelow,
4352        ] {
4353            let json = serde_json::to_string(variant).unwrap();
4354            let back: EmphasisType = serde_json::from_str(&json).unwrap();
4355            assert_eq!(&back, variant);
4356        }
4357    }
4358
4359    #[test]
4360    fn emphasis_type_str_roundtrip() {
4361        for variant in &[
4362            EmphasisType::None,
4363            EmphasisType::DotAbove,
4364            EmphasisType::GraveAccent,
4365            EmphasisType::DotBelow,
4366        ] {
4367            let s = variant.to_string();
4368            let back = EmphasisType::from_str(&s).unwrap();
4369            assert_eq!(&back, variant);
4370        }
4371    }
4372}