Skip to main content

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        // 한글 renders BACK_SLASH as `/` and SLASH as `\` — opposite to KS X 6101 XSD docs.
2639        // We swap the mapping so our semantic enum values match actual rendering.
2640        match self {
2641            Self::Horizontal => f.write_str("HORIZONTAL"),
2642            Self::Vertical => f.write_str("VERTICAL"),
2643            Self::BackSlash => f.write_str("SLASH"),
2644            Self::Slash => f.write_str("BACK_SLASH"),
2645            Self::Cross => f.write_str("CROSS"),
2646            Self::CrossDiagonal => f.write_str("CROSS_DIAGONAL"),
2647        }
2648    }
2649}
2650
2651impl std::str::FromStr for PatternType {
2652    type Err = FoundationError;
2653
2654    fn from_str(s: &str) -> Result<Self, Self::Err> {
2655        // Swapped BACK_SLASH/SLASH to match Display (한글 renders them opposite to spec).
2656        // Only SCREAMING_CASE forms used here — PascalCase comes through serde derive.
2657        match s {
2658            "HORIZONTAL" | "horizontal" => Ok(Self::Horizontal),
2659            "VERTICAL" | "vertical" => Ok(Self::Vertical),
2660            "BACK_SLASH" | "backslash" => Ok(Self::Slash),
2661            "SLASH" | "slash" => Ok(Self::BackSlash),
2662            "CROSS" | "cross" => Ok(Self::Cross),
2663            "CROSS_DIAGONAL" | "crossdiagonal" => Ok(Self::CrossDiagonal),
2664            _ => Err(FoundationError::ParseError {
2665                type_name: "PatternType".to_string(),
2666                value: s.to_string(),
2667                valid_values: "HORIZONTAL, VERTICAL, BACK_SLASH, SLASH, CROSS, CROSS_DIAGONAL"
2668                    .to_string(),
2669            }),
2670        }
2671    }
2672}
2673
2674impl TryFrom<u8> for PatternType {
2675    type Error = FoundationError;
2676
2677    fn try_from(value: u8) -> Result<Self, Self::Error> {
2678        match value {
2679            0 => Ok(Self::Horizontal),
2680            1 => Ok(Self::Vertical),
2681            2 => Ok(Self::BackSlash),
2682            3 => Ok(Self::Slash),
2683            4 => Ok(Self::Cross),
2684            5 => Ok(Self::CrossDiagonal),
2685            _ => Err(FoundationError::ParseError {
2686                type_name: "PatternType".to_string(),
2687                value: value.to_string(),
2688                valid_values:
2689                    "0 (Horizontal), 1 (Vertical), 2 (BackSlash), 3 (Slash), 4 (Cross), 5 (CrossDiagonal)"
2690                        .to_string(),
2691            }),
2692        }
2693    }
2694}
2695
2696impl schemars::JsonSchema for PatternType {
2697    fn schema_name() -> std::borrow::Cow<'static, str> {
2698        std::borrow::Cow::Borrowed("PatternType")
2699    }
2700
2701    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
2702        gen.subschema_for::<String>()
2703    }
2704}
2705
2706// ---------------------------------------------------------------------------
2707// ImageFillMode
2708// ---------------------------------------------------------------------------
2709
2710/// How an image is fitted within a shape fill area.
2711///
2712/// # Examples
2713///
2714/// ```
2715/// use hwpforge_foundation::ImageFillMode;
2716///
2717/// assert_eq!(ImageFillMode::default(), ImageFillMode::Tile);
2718/// ```
2719#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
2720#[non_exhaustive]
2721#[repr(u8)]
2722pub enum ImageFillMode {
2723    /// Tile the image to fill the area (default).
2724    #[default]
2725    Tile = 0,
2726    /// Center the image without scaling.
2727    Center = 1,
2728    /// Stretch the image to fit exactly.
2729    Stretch = 2,
2730    /// Scale proportionally to fit all within the area.
2731    FitAll = 3,
2732}
2733
2734impl fmt::Display for ImageFillMode {
2735    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2736        match self {
2737            Self::Tile => f.write_str("TILE"),
2738            Self::Center => f.write_str("CENTER"),
2739            Self::Stretch => f.write_str("STRETCH"),
2740            Self::FitAll => f.write_str("FIT_ALL"),
2741        }
2742    }
2743}
2744
2745impl std::str::FromStr for ImageFillMode {
2746    type Err = FoundationError;
2747
2748    fn from_str(s: &str) -> Result<Self, Self::Err> {
2749        match s {
2750            "TILE" | "Tile" | "tile" => Ok(Self::Tile),
2751            "CENTER" | "Center" | "center" => Ok(Self::Center),
2752            "STRETCH" | "Stretch" | "stretch" => Ok(Self::Stretch),
2753            "FIT_ALL" | "FitAll" | "fit_all" => Ok(Self::FitAll),
2754            _ => Err(FoundationError::ParseError {
2755                type_name: "ImageFillMode".to_string(),
2756                value: s.to_string(),
2757                valid_values: "TILE, CENTER, STRETCH, FIT_ALL".to_string(),
2758            }),
2759        }
2760    }
2761}
2762
2763impl TryFrom<u8> for ImageFillMode {
2764    type Error = FoundationError;
2765
2766    fn try_from(value: u8) -> Result<Self, Self::Error> {
2767        match value {
2768            0 => Ok(Self::Tile),
2769            1 => Ok(Self::Center),
2770            2 => Ok(Self::Stretch),
2771            3 => Ok(Self::FitAll),
2772            _ => Err(FoundationError::ParseError {
2773                type_name: "ImageFillMode".to_string(),
2774                value: value.to_string(),
2775                valid_values: "0 (Tile), 1 (Center), 2 (Stretch), 3 (FitAll)".to_string(),
2776            }),
2777        }
2778    }
2779}
2780
2781impl schemars::JsonSchema for ImageFillMode {
2782    fn schema_name() -> std::borrow::Cow<'static, str> {
2783        std::borrow::Cow::Borrowed("ImageFillMode")
2784    }
2785
2786    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
2787        gen.subschema_for::<String>()
2788    }
2789}
2790
2791// ---------------------------------------------------------------------------
2792// CurveSegmentType
2793// ---------------------------------------------------------------------------
2794
2795/// Segment type within a curve path.
2796///
2797/// # Examples
2798///
2799/// ```
2800/// use hwpforge_foundation::CurveSegmentType;
2801///
2802/// assert_eq!(CurveSegmentType::default(), CurveSegmentType::Line);
2803/// ```
2804#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
2805#[non_exhaustive]
2806#[repr(u8)]
2807pub enum CurveSegmentType {
2808    /// Straight line segment (default).
2809    #[default]
2810    Line = 0,
2811    /// Cubic bezier curve segment.
2812    Curve = 1,
2813}
2814
2815impl fmt::Display for CurveSegmentType {
2816    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2817        match self {
2818            Self::Line => f.write_str("LINE"),
2819            Self::Curve => f.write_str("CURVE"),
2820        }
2821    }
2822}
2823
2824impl std::str::FromStr for CurveSegmentType {
2825    type Err = FoundationError;
2826
2827    fn from_str(s: &str) -> Result<Self, Self::Err> {
2828        match s {
2829            "LINE" | "Line" | "line" => Ok(Self::Line),
2830            "CURVE" | "Curve" | "curve" => Ok(Self::Curve),
2831            _ => Err(FoundationError::ParseError {
2832                type_name: "CurveSegmentType".to_string(),
2833                value: s.to_string(),
2834                valid_values: "LINE, CURVE".to_string(),
2835            }),
2836        }
2837    }
2838}
2839
2840impl TryFrom<u8> for CurveSegmentType {
2841    type Error = FoundationError;
2842
2843    fn try_from(value: u8) -> Result<Self, Self::Error> {
2844        match value {
2845            0 => Ok(Self::Line),
2846            1 => Ok(Self::Curve),
2847            _ => Err(FoundationError::ParseError {
2848                type_name: "CurveSegmentType".to_string(),
2849                value: value.to_string(),
2850                valid_values: "0 (Line), 1 (Curve)".to_string(),
2851            }),
2852        }
2853    }
2854}
2855
2856impl schemars::JsonSchema for CurveSegmentType {
2857    fn schema_name() -> std::borrow::Cow<'static, str> {
2858        std::borrow::Cow::Borrowed("CurveSegmentType")
2859    }
2860
2861    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
2862        gen.subschema_for::<String>()
2863    }
2864}
2865
2866// ---------------------------------------------------------------------------
2867// BookmarkType
2868// ---------------------------------------------------------------------------
2869
2870/// Type of bookmark in an HWPX document.
2871///
2872/// Bookmarks can mark a single point or span a range of content
2873/// (start/end pair using `fieldBegin`/`fieldEnd`).
2874#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
2875#[non_exhaustive]
2876#[repr(u8)]
2877pub enum BookmarkType {
2878    /// A point bookmark at a single location (direct serde in `<hp:ctrl>`).
2879    #[default]
2880    Point = 0,
2881    /// Start of a span bookmark (`fieldBegin type="BOOKMARK"`).
2882    SpanStart = 1,
2883    /// End of a span bookmark (`fieldEnd beginIDRef`).
2884    SpanEnd = 2,
2885}
2886
2887impl fmt::Display for BookmarkType {
2888    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2889        match self {
2890            Self::Point => f.write_str("Point"),
2891            Self::SpanStart => f.write_str("SpanStart"),
2892            Self::SpanEnd => f.write_str("SpanEnd"),
2893        }
2894    }
2895}
2896
2897impl std::str::FromStr for BookmarkType {
2898    type Err = FoundationError;
2899
2900    fn from_str(s: &str) -> Result<Self, Self::Err> {
2901        match s {
2902            "Point" | "point" => Ok(Self::Point),
2903            "SpanStart" | "span_start" => Ok(Self::SpanStart),
2904            "SpanEnd" | "span_end" => Ok(Self::SpanEnd),
2905            _ => Err(FoundationError::ParseError {
2906                type_name: "BookmarkType".to_string(),
2907                value: s.to_string(),
2908                valid_values: "Point, SpanStart, SpanEnd".to_string(),
2909            }),
2910        }
2911    }
2912}
2913
2914impl TryFrom<u8> for BookmarkType {
2915    type Error = FoundationError;
2916
2917    fn try_from(value: u8) -> Result<Self, Self::Error> {
2918        match value {
2919            0 => Ok(Self::Point),
2920            1 => Ok(Self::SpanStart),
2921            2 => Ok(Self::SpanEnd),
2922            _ => Err(FoundationError::ParseError {
2923                type_name: "BookmarkType".to_string(),
2924                value: value.to_string(),
2925                valid_values: "0 (Point), 1 (SpanStart), 2 (SpanEnd)".to_string(),
2926            }),
2927        }
2928    }
2929}
2930
2931impl schemars::JsonSchema for BookmarkType {
2932    fn schema_name() -> std::borrow::Cow<'static, str> {
2933        std::borrow::Cow::Borrowed("BookmarkType")
2934    }
2935
2936    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
2937        gen.subschema_for::<String>()
2938    }
2939}
2940
2941// ---------------------------------------------------------------------------
2942// FieldType
2943// ---------------------------------------------------------------------------
2944
2945/// Type of a press-field (누름틀) in an HWPX document.
2946///
2947/// Press-fields are interactive form fields that users can click to fill in.
2948#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
2949#[non_exhaustive]
2950#[repr(u8)]
2951pub enum FieldType {
2952    /// Click-here placeholder field (default).
2953    #[default]
2954    ClickHere = 0,
2955    /// Automatic date field.
2956    Date = 1,
2957    /// Automatic time field.
2958    Time = 2,
2959    /// Page number field.
2960    PageNum = 3,
2961    /// Document summary field.
2962    DocSummary = 4,
2963    /// User information field.
2964    UserInfo = 5,
2965}
2966
2967impl fmt::Display for FieldType {
2968    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2969        match self {
2970            Self::ClickHere => f.write_str("CLICK_HERE"),
2971            Self::Date => f.write_str("DATE"),
2972            Self::Time => f.write_str("TIME"),
2973            Self::PageNum => f.write_str("PAGE_NUM"),
2974            Self::DocSummary => f.write_str("DOC_SUMMARY"),
2975            Self::UserInfo => f.write_str("USER_INFO"),
2976        }
2977    }
2978}
2979
2980impl std::str::FromStr for FieldType {
2981    type Err = FoundationError;
2982
2983    fn from_str(s: &str) -> Result<Self, Self::Err> {
2984        match s {
2985            "CLICK_HERE" | "ClickHere" | "click_here" => Ok(Self::ClickHere),
2986            "DATE" | "Date" | "date" => Ok(Self::Date),
2987            "TIME" | "Time" | "time" => Ok(Self::Time),
2988            "PAGE_NUM" | "PageNum" | "page_num" => Ok(Self::PageNum),
2989            "DOC_SUMMARY" | "DocSummary" | "doc_summary" => Ok(Self::DocSummary),
2990            "USER_INFO" | "UserInfo" | "user_info" => Ok(Self::UserInfo),
2991            _ => Err(FoundationError::ParseError {
2992                type_name: "FieldType".to_string(),
2993                value: s.to_string(),
2994                valid_values: "CLICK_HERE, DATE, TIME, PAGE_NUM, DOC_SUMMARY, USER_INFO"
2995                    .to_string(),
2996            }),
2997        }
2998    }
2999}
3000
3001impl TryFrom<u8> for FieldType {
3002    type Error = FoundationError;
3003
3004    fn try_from(value: u8) -> Result<Self, Self::Error> {
3005        match value {
3006            0 => Ok(Self::ClickHere),
3007            1 => Ok(Self::Date),
3008            2 => Ok(Self::Time),
3009            3 => Ok(Self::PageNum),
3010            4 => Ok(Self::DocSummary),
3011            5 => Ok(Self::UserInfo),
3012            _ => Err(FoundationError::ParseError {
3013                type_name: "FieldType".to_string(),
3014                value: value.to_string(),
3015                valid_values: "0..5 (ClickHere..UserInfo)".to_string(),
3016            }),
3017        }
3018    }
3019}
3020
3021impl schemars::JsonSchema for FieldType {
3022    fn schema_name() -> std::borrow::Cow<'static, str> {
3023        std::borrow::Cow::Borrowed("FieldType")
3024    }
3025
3026    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
3027        gen.subschema_for::<String>()
3028    }
3029}
3030
3031// ---------------------------------------------------------------------------
3032// RefType
3033// ---------------------------------------------------------------------------
3034
3035/// Target type of a cross-reference (상호참조).
3036#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
3037#[non_exhaustive]
3038#[repr(u8)]
3039pub enum RefType {
3040    /// Reference to a bookmark target.
3041    #[default]
3042    Bookmark = 0,
3043    /// Reference to a table caption number.
3044    Table = 1,
3045    /// Reference to a figure/image caption number.
3046    Figure = 2,
3047    /// Reference to an equation number.
3048    Equation = 3,
3049}
3050
3051impl fmt::Display for RefType {
3052    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3053        match self {
3054            Self::Bookmark => f.write_str("TARGET_BOOKMARK"),
3055            Self::Table => f.write_str("TARGET_TABLE"),
3056            Self::Figure => f.write_str("TARGET_FIGURE"),
3057            Self::Equation => f.write_str("TARGET_EQUATION"),
3058        }
3059    }
3060}
3061
3062impl std::str::FromStr for RefType {
3063    type Err = FoundationError;
3064
3065    fn from_str(s: &str) -> Result<Self, Self::Err> {
3066        match s {
3067            "TARGET_BOOKMARK" | "Bookmark" | "bookmark" => Ok(Self::Bookmark),
3068            "TARGET_TABLE" | "Table" | "table" => Ok(Self::Table),
3069            "TARGET_FIGURE" | "Figure" | "figure" => Ok(Self::Figure),
3070            "TARGET_EQUATION" | "Equation" | "equation" => Ok(Self::Equation),
3071            _ => Err(FoundationError::ParseError {
3072                type_name: "RefType".to_string(),
3073                value: s.to_string(),
3074                valid_values: "TARGET_BOOKMARK, TARGET_TABLE, TARGET_FIGURE, TARGET_EQUATION"
3075                    .to_string(),
3076            }),
3077        }
3078    }
3079}
3080
3081impl TryFrom<u8> for RefType {
3082    type Error = FoundationError;
3083
3084    fn try_from(value: u8) -> Result<Self, Self::Error> {
3085        match value {
3086            0 => Ok(Self::Bookmark),
3087            1 => Ok(Self::Table),
3088            2 => Ok(Self::Figure),
3089            3 => Ok(Self::Equation),
3090            _ => Err(FoundationError::ParseError {
3091                type_name: "RefType".to_string(),
3092                value: value.to_string(),
3093                valid_values: "0..3 (Bookmark..Equation)".to_string(),
3094            }),
3095        }
3096    }
3097}
3098
3099impl schemars::JsonSchema for RefType {
3100    fn schema_name() -> std::borrow::Cow<'static, str> {
3101        std::borrow::Cow::Borrowed("RefType")
3102    }
3103
3104    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
3105        gen.subschema_for::<String>()
3106    }
3107}
3108
3109// ---------------------------------------------------------------------------
3110// RefContentType
3111// ---------------------------------------------------------------------------
3112
3113/// Content display type for a cross-reference (what to show at the reference site).
3114#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
3115#[non_exhaustive]
3116#[repr(u8)]
3117pub enum RefContentType {
3118    /// Show page number where the target appears.
3119    #[default]
3120    Page = 0,
3121    /// Show the target's numbering (e.g. "표 3", "그림 2").
3122    Number = 1,
3123    /// Show the target's content text.
3124    Contents = 2,
3125    /// Show relative position ("위" / "아래").
3126    UpDownPos = 3,
3127}
3128
3129impl fmt::Display for RefContentType {
3130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3131        match self {
3132            Self::Page => f.write_str("OBJECT_TYPE_PAGE"),
3133            Self::Number => f.write_str("OBJECT_TYPE_NUMBER"),
3134            Self::Contents => f.write_str("OBJECT_TYPE_CONTENTS"),
3135            Self::UpDownPos => f.write_str("OBJECT_TYPE_UPDOWNPOS"),
3136        }
3137    }
3138}
3139
3140impl std::str::FromStr for RefContentType {
3141    type Err = FoundationError;
3142
3143    fn from_str(s: &str) -> Result<Self, Self::Err> {
3144        match s {
3145            "OBJECT_TYPE_PAGE" | "Page" | "page" => Ok(Self::Page),
3146            "OBJECT_TYPE_NUMBER" | "Number" | "number" => Ok(Self::Number),
3147            "OBJECT_TYPE_CONTENTS" | "Contents" | "contents" => Ok(Self::Contents),
3148            "OBJECT_TYPE_UPDOWNPOS" | "UpDownPos" | "updownpos" => Ok(Self::UpDownPos),
3149            _ => Err(FoundationError::ParseError {
3150                type_name: "RefContentType".to_string(),
3151                value: s.to_string(),
3152                valid_values:
3153                    "OBJECT_TYPE_PAGE, OBJECT_TYPE_NUMBER, OBJECT_TYPE_CONTENTS, OBJECT_TYPE_UPDOWNPOS"
3154                        .to_string(),
3155            }),
3156        }
3157    }
3158}
3159
3160impl TryFrom<u8> for RefContentType {
3161    type Error = FoundationError;
3162
3163    fn try_from(value: u8) -> Result<Self, Self::Error> {
3164        match value {
3165            0 => Ok(Self::Page),
3166            1 => Ok(Self::Number),
3167            2 => Ok(Self::Contents),
3168            3 => Ok(Self::UpDownPos),
3169            _ => Err(FoundationError::ParseError {
3170                type_name: "RefContentType".to_string(),
3171                value: value.to_string(),
3172                valid_values: "0..3 (Page..UpDownPos)".to_string(),
3173            }),
3174        }
3175    }
3176}
3177
3178impl schemars::JsonSchema for RefContentType {
3179    fn schema_name() -> std::borrow::Cow<'static, str> {
3180        std::borrow::Cow::Borrowed("RefContentType")
3181    }
3182
3183    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
3184        gen.subschema_for::<String>()
3185    }
3186}
3187
3188/// Drop cap style for floating shape objects (HWPX `dropcapstyle` attribute).
3189///
3190/// Controls whether a shape (text box, image, table, etc.) is formatted as a
3191/// drop capital that occupies multiple lines at the start of a paragraph.
3192///
3193/// # HWPX Values
3194///
3195/// | Variant      | HWPX string     |
3196/// |--------------|-----------------|
3197/// | `None`       | `"None"`        |
3198/// | `DoubleLine` | `"DoubleLine"`  |
3199/// | `TripleLine` | `"TripleLine"`  |
3200/// | `Margin`     | `"Margin"`      |
3201#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
3202#[non_exhaustive]
3203pub enum DropCapStyle {
3204    /// No drop cap (default).
3205    #[default]
3206    None = 0,
3207    /// Drop cap spanning 2 lines.
3208    DoubleLine = 1,
3209    /// Drop cap spanning 3 lines.
3210    TripleLine = 2,
3211    /// Drop cap positioned in the margin.
3212    Margin = 3,
3213}
3214
3215impl fmt::Display for DropCapStyle {
3216    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3217        match self {
3218            Self::None => f.write_str("None"),
3219            Self::DoubleLine => f.write_str("DoubleLine"),
3220            Self::TripleLine => f.write_str("TripleLine"),
3221            Self::Margin => f.write_str("Margin"),
3222        }
3223    }
3224}
3225
3226impl DropCapStyle {
3227    /// Parses an HWPX `dropcapstyle` attribute value (PascalCase).
3228    ///
3229    /// Unknown values fall back to `None` (default) for forward compatibility.
3230    pub fn from_hwpx_str(s: &str) -> Self {
3231        match s {
3232            "DoubleLine" => Self::DoubleLine,
3233            "TripleLine" => Self::TripleLine,
3234            "Margin" => Self::Margin,
3235            _ => Self::None,
3236        }
3237    }
3238}
3239
3240impl std::str::FromStr for DropCapStyle {
3241    type Err = FoundationError;
3242
3243    fn from_str(s: &str) -> Result<Self, Self::Err> {
3244        match s {
3245            "None" | "NONE" | "none" => Ok(Self::None),
3246            "DoubleLine" | "DOUBLE_LINE" => Ok(Self::DoubleLine),
3247            "TripleLine" | "TRIPLE_LINE" => Ok(Self::TripleLine),
3248            "Margin" | "MARGIN" => Ok(Self::Margin),
3249            _ => Err(FoundationError::ParseError {
3250                type_name: "DropCapStyle".to_string(),
3251                value: s.to_string(),
3252                valid_values: "None, DoubleLine, TripleLine, Margin".to_string(),
3253            }),
3254        }
3255    }
3256}
3257
3258impl schemars::JsonSchema for DropCapStyle {
3259    fn schema_name() -> std::borrow::Cow<'static, str> {
3260        std::borrow::Cow::Borrowed("DropCapStyle")
3261    }
3262
3263    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
3264        gen.subschema_for::<String>()
3265    }
3266}
3267
3268impl serde::Serialize for DropCapStyle {
3269    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3270        serializer.serialize_str(&self.to_string())
3271    }
3272}
3273
3274impl<'de> serde::Deserialize<'de> for DropCapStyle {
3275    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3276        let s = String::deserialize(deserializer)?;
3277        s.parse().map_err(serde::de::Error::custom)
3278    }
3279}
3280
3281// Compile-time size assertions: all enums are 1 byte
3282const _: () = assert!(std::mem::size_of::<DropCapStyle>() == 1);
3283const _: () = assert!(std::mem::size_of::<Alignment>() == 1);
3284const _: () = assert!(std::mem::size_of::<LineSpacingType>() == 1);
3285const _: () = assert!(std::mem::size_of::<BreakType>() == 1);
3286const _: () = assert!(std::mem::size_of::<Language>() == 1);
3287const _: () = assert!(std::mem::size_of::<UnderlineType>() == 1);
3288const _: () = assert!(std::mem::size_of::<StrikeoutShape>() == 1);
3289const _: () = assert!(std::mem::size_of::<OutlineType>() == 1);
3290const _: () = assert!(std::mem::size_of::<ShadowType>() == 1);
3291const _: () = assert!(std::mem::size_of::<EmbossType>() == 1);
3292const _: () = assert!(std::mem::size_of::<EngraveType>() == 1);
3293const _: () = assert!(std::mem::size_of::<VerticalPosition>() == 1);
3294const _: () = assert!(std::mem::size_of::<BorderLineType>() == 1);
3295const _: () = assert!(std::mem::size_of::<FillBrushType>() == 1);
3296const _: () = assert!(std::mem::size_of::<ApplyPageType>() == 1);
3297const _: () = assert!(std::mem::size_of::<NumberFormatType>() == 1);
3298const _: () = assert!(std::mem::size_of::<PageNumberPosition>() == 1);
3299const _: () = assert!(std::mem::size_of::<WordBreakType>() == 1);
3300const _: () = assert!(std::mem::size_of::<EmphasisType>() == 1);
3301const _: () = assert!(std::mem::size_of::<HeadingType>() == 1);
3302const _: () = assert!(std::mem::size_of::<GutterType>() == 1);
3303const _: () = assert!(std::mem::size_of::<ShowMode>() == 1);
3304const _: () = assert!(std::mem::size_of::<RestartType>() == 1);
3305const _: () = assert!(std::mem::size_of::<TextBorderType>() == 1);
3306const _: () = assert!(std::mem::size_of::<Flip>() == 1);
3307const _: () = assert!(std::mem::size_of::<ArcType>() == 1);
3308const _: () = assert!(std::mem::size_of::<ArrowType>() == 1);
3309const _: () = assert!(std::mem::size_of::<ArrowSize>() == 1);
3310const _: () = assert!(std::mem::size_of::<GradientType>() == 1);
3311const _: () = assert!(std::mem::size_of::<PatternType>() == 1);
3312const _: () = assert!(std::mem::size_of::<ImageFillMode>() == 1);
3313const _: () = assert!(std::mem::size_of::<CurveSegmentType>() == 1);
3314const _: () = assert!(std::mem::size_of::<BookmarkType>() == 1);
3315const _: () = assert!(std::mem::size_of::<FieldType>() == 1);
3316const _: () = assert!(std::mem::size_of::<RefType>() == 1);
3317const _: () = assert!(std::mem::size_of::<RefContentType>() == 1);
3318
3319// ---------------------------------------------------------------------------
3320// TextDirection
3321// ---------------------------------------------------------------------------
3322
3323/// Text writing direction for sections and sub-lists.
3324///
3325/// Controls whether text flows horizontally (가로쓰기) or vertically (세로쓰기).
3326/// Used in `<hp:secPr textDirection="...">` and `<hp:subList textDirection="...">`.
3327///
3328/// # Examples
3329///
3330/// ```
3331/// use hwpforge_foundation::TextDirection;
3332///
3333/// assert_eq!(TextDirection::default(), TextDirection::Horizontal);
3334/// assert_eq!(TextDirection::Horizontal.to_string(), "HORIZONTAL");
3335/// ```
3336#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
3337#[non_exhaustive]
3338pub enum TextDirection {
3339    /// Horizontal writing (가로쓰기) — default.
3340    #[default]
3341    Horizontal,
3342    /// Vertical writing with Latin chars rotated 90° (세로쓰기 영문 눕힘).
3343    Vertical,
3344    /// Vertical writing with Latin chars upright (세로쓰기 영문 세움).
3345    VerticalAll,
3346}
3347
3348impl TextDirection {
3349    /// Parses a HWPX XML attribute string (e.g. `"VERTICAL"`).
3350    ///
3351    /// Unknown values fall back to [`TextDirection::Horizontal`].
3352    pub fn from_hwpx_str(s: &str) -> Self {
3353        match s {
3354            "VERTICAL" => Self::Vertical,
3355            "VERTICALALL" => Self::VerticalAll,
3356            _ => Self::Horizontal,
3357        }
3358    }
3359}
3360
3361impl fmt::Display for TextDirection {
3362    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3363        match self {
3364            Self::Horizontal => f.write_str("HORIZONTAL"),
3365            Self::Vertical => f.write_str("VERTICAL"),
3366            Self::VerticalAll => f.write_str("VERTICALALL"),
3367        }
3368    }
3369}
3370
3371impl schemars::JsonSchema for TextDirection {
3372    fn schema_name() -> std::borrow::Cow<'static, str> {
3373        std::borrow::Cow::Borrowed("TextDirection")
3374    }
3375
3376    fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
3377        gen.subschema_for::<String>()
3378    }
3379}
3380
3381const _: () = assert!(std::mem::size_of::<TextDirection>() == 1);
3382
3383#[cfg(test)]
3384mod tests {
3385    use super::*;
3386    use std::str::FromStr;
3387
3388    // ===================================================================
3389    // Alignment (10+ tests)
3390    // ===================================================================
3391
3392    #[test]
3393    fn alignment_default_is_left() {
3394        assert_eq!(Alignment::default(), Alignment::Left);
3395    }
3396
3397    #[test]
3398    fn alignment_display_all_variants() {
3399        assert_eq!(Alignment::Left.to_string(), "Left");
3400        assert_eq!(Alignment::Center.to_string(), "Center");
3401        assert_eq!(Alignment::Right.to_string(), "Right");
3402        assert_eq!(Alignment::Justify.to_string(), "Justify");
3403        assert_eq!(Alignment::Distribute.to_string(), "Distribute");
3404        assert_eq!(Alignment::DistributeFlush.to_string(), "DistributeFlush");
3405    }
3406
3407    #[test]
3408    fn alignment_from_str_pascal_case() {
3409        assert_eq!(Alignment::from_str("Left").unwrap(), Alignment::Left);
3410        assert_eq!(Alignment::from_str("Center").unwrap(), Alignment::Center);
3411        assert_eq!(Alignment::from_str("Right").unwrap(), Alignment::Right);
3412        assert_eq!(Alignment::from_str("Justify").unwrap(), Alignment::Justify);
3413        assert_eq!(Alignment::from_str("Distribute").unwrap(), Alignment::Distribute);
3414        assert_eq!(Alignment::from_str("DistributeFlush").unwrap(), Alignment::DistributeFlush);
3415    }
3416
3417    #[test]
3418    fn alignment_from_str_lower_case() {
3419        assert_eq!(Alignment::from_str("left").unwrap(), Alignment::Left);
3420        assert_eq!(Alignment::from_str("center").unwrap(), Alignment::Center);
3421        assert_eq!(Alignment::from_str("distribute").unwrap(), Alignment::Distribute);
3422        assert_eq!(Alignment::from_str("distributeflush").unwrap(), Alignment::DistributeFlush);
3423        assert_eq!(Alignment::from_str("distribute_flush").unwrap(), Alignment::DistributeFlush);
3424    }
3425
3426    #[test]
3427    fn alignment_from_str_invalid() {
3428        let err = Alignment::from_str("leftt").unwrap_err();
3429        match err {
3430            FoundationError::ParseError { ref type_name, ref value, .. } => {
3431                assert_eq!(type_name, "Alignment");
3432                assert_eq!(value, "leftt");
3433            }
3434            other => panic!("unexpected: {other}"),
3435        }
3436    }
3437
3438    #[test]
3439    fn alignment_try_from_u8() {
3440        assert_eq!(Alignment::try_from(0u8).unwrap(), Alignment::Left);
3441        assert_eq!(Alignment::try_from(1u8).unwrap(), Alignment::Center);
3442        assert_eq!(Alignment::try_from(2u8).unwrap(), Alignment::Right);
3443        assert_eq!(Alignment::try_from(3u8).unwrap(), Alignment::Justify);
3444        assert_eq!(Alignment::try_from(4u8).unwrap(), Alignment::Distribute);
3445        assert_eq!(Alignment::try_from(5u8).unwrap(), Alignment::DistributeFlush);
3446        assert!(Alignment::try_from(6u8).is_err());
3447        assert!(Alignment::try_from(255u8).is_err());
3448    }
3449
3450    #[test]
3451    fn alignment_repr_values() {
3452        assert_eq!(Alignment::Left as u8, 0);
3453        assert_eq!(Alignment::Center as u8, 1);
3454        assert_eq!(Alignment::Right as u8, 2);
3455        assert_eq!(Alignment::Justify as u8, 3);
3456        assert_eq!(Alignment::Distribute as u8, 4);
3457        assert_eq!(Alignment::DistributeFlush as u8, 5);
3458    }
3459
3460    #[test]
3461    fn alignment_serde_roundtrip() {
3462        for variant in &[
3463            Alignment::Left,
3464            Alignment::Center,
3465            Alignment::Right,
3466            Alignment::Justify,
3467            Alignment::Distribute,
3468            Alignment::DistributeFlush,
3469        ] {
3470            let json = serde_json::to_string(variant).unwrap();
3471            let back: Alignment = serde_json::from_str(&json).unwrap();
3472            assert_eq!(&back, variant);
3473        }
3474    }
3475
3476    #[test]
3477    fn alignment_str_roundtrip() {
3478        for variant in &[
3479            Alignment::Left,
3480            Alignment::Center,
3481            Alignment::Right,
3482            Alignment::Justify,
3483            Alignment::Distribute,
3484            Alignment::DistributeFlush,
3485        ] {
3486            let s = variant.to_string();
3487            let back = Alignment::from_str(&s).unwrap();
3488            assert_eq!(&back, variant);
3489        }
3490    }
3491
3492    #[test]
3493    fn alignment_copy_and_hash() {
3494        use std::collections::HashSet;
3495        let a = Alignment::Left;
3496        let b = a; // Copy
3497        assert_eq!(a, b);
3498
3499        let mut set = HashSet::new();
3500        set.insert(Alignment::Left);
3501        set.insert(Alignment::Right);
3502        assert_eq!(set.len(), 2);
3503    }
3504
3505    // ===================================================================
3506    // LineSpacingType
3507    // ===================================================================
3508
3509    #[test]
3510    fn line_spacing_default_is_percentage() {
3511        assert_eq!(LineSpacingType::default(), LineSpacingType::Percentage);
3512    }
3513
3514    #[test]
3515    fn line_spacing_display() {
3516        assert_eq!(LineSpacingType::Percentage.to_string(), "Percentage");
3517        assert_eq!(LineSpacingType::Fixed.to_string(), "Fixed");
3518        assert_eq!(LineSpacingType::BetweenLines.to_string(), "BetweenLines");
3519    }
3520
3521    #[test]
3522    fn line_spacing_from_str() {
3523        assert_eq!(LineSpacingType::from_str("Percentage").unwrap(), LineSpacingType::Percentage);
3524        assert_eq!(LineSpacingType::from_str("Fixed").unwrap(), LineSpacingType::Fixed);
3525        assert_eq!(
3526            LineSpacingType::from_str("BetweenLines").unwrap(),
3527            LineSpacingType::BetweenLines
3528        );
3529        assert!(LineSpacingType::from_str("invalid").is_err());
3530    }
3531
3532    #[test]
3533    fn line_spacing_try_from_u8() {
3534        assert_eq!(LineSpacingType::try_from(0u8).unwrap(), LineSpacingType::Percentage);
3535        assert_eq!(LineSpacingType::try_from(1u8).unwrap(), LineSpacingType::Fixed);
3536        assert_eq!(LineSpacingType::try_from(2u8).unwrap(), LineSpacingType::BetweenLines);
3537        assert!(LineSpacingType::try_from(3u8).is_err());
3538    }
3539
3540    #[test]
3541    fn line_spacing_str_roundtrip() {
3542        for v in
3543            &[LineSpacingType::Percentage, LineSpacingType::Fixed, LineSpacingType::BetweenLines]
3544        {
3545            let s = v.to_string();
3546            let back = LineSpacingType::from_str(&s).unwrap();
3547            assert_eq!(&back, v);
3548        }
3549    }
3550
3551    // ===================================================================
3552    // BreakType
3553    // ===================================================================
3554
3555    #[test]
3556    fn break_type_default_is_none() {
3557        assert_eq!(BreakType::default(), BreakType::None);
3558    }
3559
3560    #[test]
3561    fn break_type_display() {
3562        assert_eq!(BreakType::None.to_string(), "None");
3563        assert_eq!(BreakType::Column.to_string(), "Column");
3564        assert_eq!(BreakType::Page.to_string(), "Page");
3565    }
3566
3567    #[test]
3568    fn break_type_from_str() {
3569        assert_eq!(BreakType::from_str("None").unwrap(), BreakType::None);
3570        assert_eq!(BreakType::from_str("Column").unwrap(), BreakType::Column);
3571        assert_eq!(BreakType::from_str("Page").unwrap(), BreakType::Page);
3572        assert!(BreakType::from_str("section").is_err());
3573    }
3574
3575    #[test]
3576    fn break_type_try_from_u8() {
3577        assert_eq!(BreakType::try_from(0u8).unwrap(), BreakType::None);
3578        assert_eq!(BreakType::try_from(1u8).unwrap(), BreakType::Column);
3579        assert_eq!(BreakType::try_from(2u8).unwrap(), BreakType::Page);
3580        assert!(BreakType::try_from(3u8).is_err());
3581    }
3582
3583    #[test]
3584    fn break_type_str_roundtrip() {
3585        for v in &[BreakType::None, BreakType::Column, BreakType::Page] {
3586            let s = v.to_string();
3587            let back = BreakType::from_str(&s).unwrap();
3588            assert_eq!(&back, v);
3589        }
3590    }
3591
3592    // ===================================================================
3593    // Language
3594    // ===================================================================
3595
3596    #[test]
3597    fn language_count_is_7() {
3598        assert_eq!(Language::COUNT, 7);
3599        assert_eq!(Language::ALL.len(), 7);
3600    }
3601
3602    #[test]
3603    fn language_default_is_korean() {
3604        assert_eq!(Language::default(), Language::Korean);
3605    }
3606
3607    #[test]
3608    fn language_discriminants() {
3609        assert_eq!(Language::Korean as u8, 0);
3610        assert_eq!(Language::English as u8, 1);
3611        assert_eq!(Language::Hanja as u8, 2);
3612        assert_eq!(Language::Japanese as u8, 3);
3613        assert_eq!(Language::Other as u8, 4);
3614        assert_eq!(Language::Symbol as u8, 5);
3615        assert_eq!(Language::User as u8, 6);
3616    }
3617
3618    #[test]
3619    fn language_display() {
3620        assert_eq!(Language::Korean.to_string(), "Korean");
3621        assert_eq!(Language::English.to_string(), "English");
3622        assert_eq!(Language::Japanese.to_string(), "Japanese");
3623    }
3624
3625    #[test]
3626    fn language_from_str() {
3627        for lang in &Language::ALL {
3628            let s = lang.to_string();
3629            let back = Language::from_str(&s).unwrap();
3630            assert_eq!(&back, lang);
3631        }
3632        assert!(Language::from_str("invalid").is_err());
3633    }
3634
3635    #[test]
3636    fn language_try_from_u8() {
3637        for (i, expected) in Language::ALL.iter().enumerate() {
3638            let parsed = Language::try_from(i as u8).unwrap();
3639            assert_eq!(&parsed, expected);
3640        }
3641        assert!(Language::try_from(7u8).is_err());
3642        assert!(Language::try_from(255u8).is_err());
3643    }
3644
3645    #[test]
3646    fn language_all_used_as_index() {
3647        // Common pattern: fonts[lang as usize]
3648        let fonts: [&str; Language::COUNT] =
3649            ["Batang", "Arial", "SimSun", "MS Mincho", "Arial", "Symbol", "Arial"];
3650        for lang in &Language::ALL {
3651            let _ = fonts[*lang as usize];
3652        }
3653    }
3654
3655    #[test]
3656    fn language_serde_roundtrip() {
3657        for lang in &Language::ALL {
3658            let json = serde_json::to_string(lang).unwrap();
3659            let back: Language = serde_json::from_str(&json).unwrap();
3660            assert_eq!(&back, lang);
3661        }
3662    }
3663
3664    // ===================================================================
3665    // UnderlineType
3666    // ===================================================================
3667
3668    #[test]
3669    fn underline_type_default_is_none() {
3670        assert_eq!(UnderlineType::default(), UnderlineType::None);
3671    }
3672
3673    #[test]
3674    fn underline_type_display() {
3675        assert_eq!(UnderlineType::None.to_string(), "None");
3676        assert_eq!(UnderlineType::Bottom.to_string(), "Bottom");
3677        assert_eq!(UnderlineType::Center.to_string(), "Center");
3678        assert_eq!(UnderlineType::Top.to_string(), "Top");
3679    }
3680
3681    #[test]
3682    fn underline_type_from_str() {
3683        assert_eq!(UnderlineType::from_str("None").unwrap(), UnderlineType::None);
3684        assert_eq!(UnderlineType::from_str("Bottom").unwrap(), UnderlineType::Bottom);
3685        assert_eq!(UnderlineType::from_str("center").unwrap(), UnderlineType::Center);
3686        assert!(UnderlineType::from_str("invalid").is_err());
3687    }
3688
3689    #[test]
3690    fn underline_type_try_from_u8() {
3691        assert_eq!(UnderlineType::try_from(0u8).unwrap(), UnderlineType::None);
3692        assert_eq!(UnderlineType::try_from(1u8).unwrap(), UnderlineType::Bottom);
3693        assert_eq!(UnderlineType::try_from(2u8).unwrap(), UnderlineType::Center);
3694        assert_eq!(UnderlineType::try_from(3u8).unwrap(), UnderlineType::Top);
3695        assert!(UnderlineType::try_from(4u8).is_err());
3696    }
3697
3698    #[test]
3699    fn underline_type_str_roundtrip() {
3700        for v in
3701            &[UnderlineType::None, UnderlineType::Bottom, UnderlineType::Center, UnderlineType::Top]
3702        {
3703            let s = v.to_string();
3704            let back = UnderlineType::from_str(&s).unwrap();
3705            assert_eq!(&back, v);
3706        }
3707    }
3708
3709    // ===================================================================
3710    // StrikeoutShape
3711    // ===================================================================
3712
3713    #[test]
3714    fn strikeout_shape_default_is_none() {
3715        assert_eq!(StrikeoutShape::default(), StrikeoutShape::None);
3716    }
3717
3718    #[test]
3719    fn strikeout_shape_display() {
3720        assert_eq!(StrikeoutShape::None.to_string(), "None");
3721        assert_eq!(StrikeoutShape::Continuous.to_string(), "Continuous");
3722        assert_eq!(StrikeoutShape::Dash.to_string(), "Dash");
3723        assert_eq!(StrikeoutShape::DashDotDot.to_string(), "DashDotDot");
3724    }
3725
3726    #[test]
3727    fn strikeout_shape_from_str() {
3728        assert_eq!(StrikeoutShape::from_str("None").unwrap(), StrikeoutShape::None);
3729        assert_eq!(StrikeoutShape::from_str("continuous").unwrap(), StrikeoutShape::Continuous);
3730        assert_eq!(StrikeoutShape::from_str("dash_dot").unwrap(), StrikeoutShape::DashDot);
3731        assert!(StrikeoutShape::from_str("invalid").is_err());
3732    }
3733
3734    #[test]
3735    fn strikeout_shape_try_from_u8() {
3736        assert_eq!(StrikeoutShape::try_from(0u8).unwrap(), StrikeoutShape::None);
3737        assert_eq!(StrikeoutShape::try_from(1u8).unwrap(), StrikeoutShape::Continuous);
3738        assert_eq!(StrikeoutShape::try_from(5u8).unwrap(), StrikeoutShape::DashDotDot);
3739        assert!(StrikeoutShape::try_from(6u8).is_err());
3740    }
3741
3742    #[test]
3743    fn strikeout_shape_str_roundtrip() {
3744        for v in &[
3745            StrikeoutShape::None,
3746            StrikeoutShape::Continuous,
3747            StrikeoutShape::Dash,
3748            StrikeoutShape::Dot,
3749            StrikeoutShape::DashDot,
3750            StrikeoutShape::DashDotDot,
3751        ] {
3752            let s = v.to_string();
3753            let back = StrikeoutShape::from_str(&s).unwrap();
3754            assert_eq!(&back, v);
3755        }
3756    }
3757
3758    // ===================================================================
3759    // OutlineType
3760    // ===================================================================
3761
3762    #[test]
3763    fn outline_type_default_is_none() {
3764        assert_eq!(OutlineType::default(), OutlineType::None);
3765    }
3766
3767    #[test]
3768    fn outline_type_display() {
3769        assert_eq!(OutlineType::None.to_string(), "None");
3770        assert_eq!(OutlineType::Solid.to_string(), "Solid");
3771    }
3772
3773    #[test]
3774    fn outline_type_from_str() {
3775        assert_eq!(OutlineType::from_str("None").unwrap(), OutlineType::None);
3776        assert_eq!(OutlineType::from_str("solid").unwrap(), OutlineType::Solid);
3777        assert!(OutlineType::from_str("dashed").is_err());
3778    }
3779
3780    #[test]
3781    fn outline_type_try_from_u8() {
3782        assert_eq!(OutlineType::try_from(0u8).unwrap(), OutlineType::None);
3783        assert_eq!(OutlineType::try_from(1u8).unwrap(), OutlineType::Solid);
3784        assert!(OutlineType::try_from(2u8).is_err());
3785    }
3786
3787    // ===================================================================
3788    // ShadowType
3789    // ===================================================================
3790
3791    #[test]
3792    fn shadow_type_default_is_none() {
3793        assert_eq!(ShadowType::default(), ShadowType::None);
3794    }
3795
3796    #[test]
3797    fn shadow_type_display() {
3798        assert_eq!(ShadowType::None.to_string(), "None");
3799        assert_eq!(ShadowType::Drop.to_string(), "Drop");
3800    }
3801
3802    #[test]
3803    fn shadow_type_from_str() {
3804        assert_eq!(ShadowType::from_str("None").unwrap(), ShadowType::None);
3805        assert_eq!(ShadowType::from_str("drop").unwrap(), ShadowType::Drop);
3806        assert!(ShadowType::from_str("shadow").is_err());
3807    }
3808
3809    #[test]
3810    fn shadow_type_try_from_u8() {
3811        assert_eq!(ShadowType::try_from(0u8).unwrap(), ShadowType::None);
3812        assert_eq!(ShadowType::try_from(1u8).unwrap(), ShadowType::Drop);
3813        assert!(ShadowType::try_from(2u8).is_err());
3814    }
3815
3816    // ===================================================================
3817    // EmbossType
3818    // ===================================================================
3819
3820    #[test]
3821    fn emboss_type_default_is_none() {
3822        assert_eq!(EmbossType::default(), EmbossType::None);
3823    }
3824
3825    #[test]
3826    fn emboss_type_display() {
3827        assert_eq!(EmbossType::None.to_string(), "None");
3828        assert_eq!(EmbossType::Emboss.to_string(), "Emboss");
3829    }
3830
3831    #[test]
3832    fn emboss_type_from_str() {
3833        assert_eq!(EmbossType::from_str("None").unwrap(), EmbossType::None);
3834        assert_eq!(EmbossType::from_str("emboss").unwrap(), EmbossType::Emboss);
3835        assert!(EmbossType::from_str("raised").is_err());
3836    }
3837
3838    #[test]
3839    fn emboss_type_try_from_u8() {
3840        assert_eq!(EmbossType::try_from(0u8).unwrap(), EmbossType::None);
3841        assert_eq!(EmbossType::try_from(1u8).unwrap(), EmbossType::Emboss);
3842        assert!(EmbossType::try_from(2u8).is_err());
3843    }
3844
3845    // ===================================================================
3846    // EngraveType
3847    // ===================================================================
3848
3849    #[test]
3850    fn engrave_type_default_is_none() {
3851        assert_eq!(EngraveType::default(), EngraveType::None);
3852    }
3853
3854    #[test]
3855    fn engrave_type_display() {
3856        assert_eq!(EngraveType::None.to_string(), "None");
3857        assert_eq!(EngraveType::Engrave.to_string(), "Engrave");
3858    }
3859
3860    #[test]
3861    fn engrave_type_from_str() {
3862        assert_eq!(EngraveType::from_str("None").unwrap(), EngraveType::None);
3863        assert_eq!(EngraveType::from_str("engrave").unwrap(), EngraveType::Engrave);
3864        assert!(EngraveType::from_str("sunken").is_err());
3865    }
3866
3867    #[test]
3868    fn engrave_type_try_from_u8() {
3869        assert_eq!(EngraveType::try_from(0u8).unwrap(), EngraveType::None);
3870        assert_eq!(EngraveType::try_from(1u8).unwrap(), EngraveType::Engrave);
3871        assert!(EngraveType::try_from(2u8).is_err());
3872    }
3873
3874    // ===================================================================
3875    // VerticalPosition
3876    // ===================================================================
3877
3878    #[test]
3879    fn vertical_position_default_is_normal() {
3880        assert_eq!(VerticalPosition::default(), VerticalPosition::Normal);
3881    }
3882
3883    #[test]
3884    fn vertical_position_display() {
3885        assert_eq!(VerticalPosition::Normal.to_string(), "Normal");
3886        assert_eq!(VerticalPosition::Superscript.to_string(), "Superscript");
3887        assert_eq!(VerticalPosition::Subscript.to_string(), "Subscript");
3888    }
3889
3890    #[test]
3891    fn vertical_position_from_str() {
3892        assert_eq!(VerticalPosition::from_str("Normal").unwrap(), VerticalPosition::Normal);
3893        assert_eq!(
3894            VerticalPosition::from_str("superscript").unwrap(),
3895            VerticalPosition::Superscript
3896        );
3897        assert_eq!(VerticalPosition::from_str("sub").unwrap(), VerticalPosition::Subscript);
3898        assert!(VerticalPosition::from_str("middle").is_err());
3899    }
3900
3901    #[test]
3902    fn vertical_position_try_from_u8() {
3903        assert_eq!(VerticalPosition::try_from(0u8).unwrap(), VerticalPosition::Normal);
3904        assert_eq!(VerticalPosition::try_from(1u8).unwrap(), VerticalPosition::Superscript);
3905        assert_eq!(VerticalPosition::try_from(2u8).unwrap(), VerticalPosition::Subscript);
3906        assert!(VerticalPosition::try_from(3u8).is_err());
3907    }
3908
3909    #[test]
3910    fn vertical_position_str_roundtrip() {
3911        for v in
3912            &[VerticalPosition::Normal, VerticalPosition::Superscript, VerticalPosition::Subscript]
3913        {
3914            let s = v.to_string();
3915            let back = VerticalPosition::from_str(&s).unwrap();
3916            assert_eq!(&back, v);
3917        }
3918    }
3919
3920    // ===================================================================
3921    // BorderLineType
3922    // ===================================================================
3923
3924    #[test]
3925    fn border_line_type_default_is_none() {
3926        assert_eq!(BorderLineType::default(), BorderLineType::None);
3927    }
3928
3929    #[test]
3930    fn border_line_type_display() {
3931        assert_eq!(BorderLineType::None.to_string(), "None");
3932        assert_eq!(BorderLineType::Solid.to_string(), "Solid");
3933        assert_eq!(BorderLineType::DashDot.to_string(), "DashDot");
3934        assert_eq!(BorderLineType::ThickBetweenSlim.to_string(), "ThickBetweenSlim");
3935    }
3936
3937    #[test]
3938    fn border_line_type_from_str() {
3939        assert_eq!(BorderLineType::from_str("None").unwrap(), BorderLineType::None);
3940        assert_eq!(BorderLineType::from_str("solid").unwrap(), BorderLineType::Solid);
3941        assert_eq!(BorderLineType::from_str("dash_dot").unwrap(), BorderLineType::DashDot);
3942        assert_eq!(BorderLineType::from_str("double").unwrap(), BorderLineType::Double);
3943        assert!(BorderLineType::from_str("wavy").is_err());
3944    }
3945
3946    #[test]
3947    fn border_line_type_try_from_u8() {
3948        assert_eq!(BorderLineType::try_from(0u8).unwrap(), BorderLineType::None);
3949        assert_eq!(BorderLineType::try_from(1u8).unwrap(), BorderLineType::Solid);
3950        assert_eq!(BorderLineType::try_from(10u8).unwrap(), BorderLineType::ThickBetweenSlim);
3951        assert!(BorderLineType::try_from(11u8).is_err());
3952    }
3953
3954    #[test]
3955    fn border_line_type_str_roundtrip() {
3956        for v in &[
3957            BorderLineType::None,
3958            BorderLineType::Solid,
3959            BorderLineType::Dash,
3960            BorderLineType::Dot,
3961            BorderLineType::DashDot,
3962            BorderLineType::DashDotDot,
3963            BorderLineType::LongDash,
3964            BorderLineType::TripleDot,
3965            BorderLineType::Double,
3966            BorderLineType::DoubleSlim,
3967            BorderLineType::ThickBetweenSlim,
3968        ] {
3969            let s = v.to_string();
3970            let back = BorderLineType::from_str(&s).unwrap();
3971            assert_eq!(&back, v);
3972        }
3973    }
3974
3975    // ===================================================================
3976    // FillBrushType
3977    // ===================================================================
3978
3979    #[test]
3980    fn fill_brush_type_default_is_none() {
3981        assert_eq!(FillBrushType::default(), FillBrushType::None);
3982    }
3983
3984    #[test]
3985    fn fill_brush_type_display() {
3986        assert_eq!(FillBrushType::None.to_string(), "None");
3987        assert_eq!(FillBrushType::Solid.to_string(), "Solid");
3988        assert_eq!(FillBrushType::Gradient.to_string(), "Gradient");
3989        assert_eq!(FillBrushType::Pattern.to_string(), "Pattern");
3990    }
3991
3992    #[test]
3993    fn fill_brush_type_from_str() {
3994        assert_eq!(FillBrushType::from_str("None").unwrap(), FillBrushType::None);
3995        assert_eq!(FillBrushType::from_str("solid").unwrap(), FillBrushType::Solid);
3996        assert_eq!(FillBrushType::from_str("gradient").unwrap(), FillBrushType::Gradient);
3997        assert!(FillBrushType::from_str("texture").is_err());
3998    }
3999
4000    #[test]
4001    fn fill_brush_type_try_from_u8() {
4002        assert_eq!(FillBrushType::try_from(0u8).unwrap(), FillBrushType::None);
4003        assert_eq!(FillBrushType::try_from(1u8).unwrap(), FillBrushType::Solid);
4004        assert_eq!(FillBrushType::try_from(2u8).unwrap(), FillBrushType::Gradient);
4005        assert_eq!(FillBrushType::try_from(3u8).unwrap(), FillBrushType::Pattern);
4006        assert!(FillBrushType::try_from(4u8).is_err());
4007    }
4008
4009    #[test]
4010    fn fill_brush_type_str_roundtrip() {
4011        for v in &[
4012            FillBrushType::None,
4013            FillBrushType::Solid,
4014            FillBrushType::Gradient,
4015            FillBrushType::Pattern,
4016        ] {
4017            let s = v.to_string();
4018            let back = FillBrushType::from_str(&s).unwrap();
4019            assert_eq!(&back, v);
4020        }
4021    }
4022
4023    // ===================================================================
4024    // Cross-enum size assertions (compile-time already, but test at runtime too)
4025    // ===================================================================
4026
4027    #[test]
4028    fn all_enums_are_one_byte() {
4029        assert_eq!(std::mem::size_of::<Alignment>(), 1);
4030        assert_eq!(std::mem::size_of::<LineSpacingType>(), 1);
4031        assert_eq!(std::mem::size_of::<BreakType>(), 1);
4032        assert_eq!(std::mem::size_of::<Language>(), 1);
4033        assert_eq!(std::mem::size_of::<UnderlineType>(), 1);
4034        assert_eq!(std::mem::size_of::<StrikeoutShape>(), 1);
4035        assert_eq!(std::mem::size_of::<OutlineType>(), 1);
4036        assert_eq!(std::mem::size_of::<ShadowType>(), 1);
4037        assert_eq!(std::mem::size_of::<EmbossType>(), 1);
4038        assert_eq!(std::mem::size_of::<EngraveType>(), 1);
4039        assert_eq!(std::mem::size_of::<VerticalPosition>(), 1);
4040        assert_eq!(std::mem::size_of::<BorderLineType>(), 1);
4041        assert_eq!(std::mem::size_of::<FillBrushType>(), 1);
4042        assert_eq!(std::mem::size_of::<ApplyPageType>(), 1);
4043        assert_eq!(std::mem::size_of::<NumberFormatType>(), 1);
4044        assert_eq!(std::mem::size_of::<PageNumberPosition>(), 1);
4045    }
4046
4047    // ===================================================================
4048    // ApplyPageType
4049    // ===================================================================
4050
4051    #[test]
4052    fn apply_page_type_default_is_both() {
4053        assert_eq!(ApplyPageType::default(), ApplyPageType::Both);
4054    }
4055
4056    #[test]
4057    fn apply_page_type_display() {
4058        assert_eq!(ApplyPageType::Both.to_string(), "Both");
4059        assert_eq!(ApplyPageType::Even.to_string(), "Even");
4060        assert_eq!(ApplyPageType::Odd.to_string(), "Odd");
4061    }
4062
4063    #[test]
4064    fn apply_page_type_from_str() {
4065        assert_eq!(ApplyPageType::from_str("Both").unwrap(), ApplyPageType::Both);
4066        assert_eq!(ApplyPageType::from_str("BOTH").unwrap(), ApplyPageType::Both);
4067        assert_eq!(ApplyPageType::from_str("even").unwrap(), ApplyPageType::Even);
4068        assert_eq!(ApplyPageType::from_str("ODD").unwrap(), ApplyPageType::Odd);
4069        assert!(ApplyPageType::from_str("invalid").is_err());
4070    }
4071
4072    #[test]
4073    fn apply_page_type_try_from_u8() {
4074        assert_eq!(ApplyPageType::try_from(0u8).unwrap(), ApplyPageType::Both);
4075        assert_eq!(ApplyPageType::try_from(1u8).unwrap(), ApplyPageType::Even);
4076        assert_eq!(ApplyPageType::try_from(2u8).unwrap(), ApplyPageType::Odd);
4077        assert!(ApplyPageType::try_from(3u8).is_err());
4078    }
4079
4080    #[test]
4081    fn apply_page_type_str_roundtrip() {
4082        for v in &[ApplyPageType::Both, ApplyPageType::Even, ApplyPageType::Odd] {
4083            let s = v.to_string();
4084            let back = ApplyPageType::from_str(&s).unwrap();
4085            assert_eq!(&back, v);
4086        }
4087    }
4088
4089    // ===================================================================
4090    // NumberFormatType
4091    // ===================================================================
4092
4093    #[test]
4094    fn number_format_type_default_is_digit() {
4095        assert_eq!(NumberFormatType::default(), NumberFormatType::Digit);
4096    }
4097
4098    #[test]
4099    fn number_format_type_display() {
4100        assert_eq!(NumberFormatType::Digit.to_string(), "Digit");
4101        assert_eq!(NumberFormatType::CircledDigit.to_string(), "CircledDigit");
4102        assert_eq!(NumberFormatType::RomanCapital.to_string(), "RomanCapital");
4103        assert_eq!(NumberFormatType::HanjaDigit.to_string(), "HanjaDigit");
4104    }
4105
4106    #[test]
4107    fn number_format_type_from_str() {
4108        assert_eq!(NumberFormatType::from_str("Digit").unwrap(), NumberFormatType::Digit);
4109        assert_eq!(NumberFormatType::from_str("DIGIT").unwrap(), NumberFormatType::Digit);
4110        assert_eq!(
4111            NumberFormatType::from_str("CircledDigit").unwrap(),
4112            NumberFormatType::CircledDigit
4113        );
4114        assert_eq!(
4115            NumberFormatType::from_str("ROMAN_CAPITAL").unwrap(),
4116            NumberFormatType::RomanCapital
4117        );
4118        assert!(NumberFormatType::from_str("invalid").is_err());
4119    }
4120
4121    #[test]
4122    fn number_format_type_try_from_u8() {
4123        assert_eq!(NumberFormatType::try_from(0u8).unwrap(), NumberFormatType::Digit);
4124        assert_eq!(NumberFormatType::try_from(1u8).unwrap(), NumberFormatType::CircledDigit);
4125        assert_eq!(NumberFormatType::try_from(8u8).unwrap(), NumberFormatType::HanjaDigit);
4126        assert_eq!(
4127            NumberFormatType::try_from(9u8).unwrap(),
4128            NumberFormatType::CircledHangulSyllable
4129        );
4130        assert!(NumberFormatType::try_from(10u8).is_err());
4131    }
4132
4133    #[test]
4134    fn number_format_type_circled_hangul_syllable() {
4135        assert_eq!(NumberFormatType::CircledHangulSyllable.to_string(), "CircledHangulSyllable");
4136        assert_eq!(
4137            NumberFormatType::from_str("CircledHangulSyllable").unwrap(),
4138            NumberFormatType::CircledHangulSyllable
4139        );
4140        assert_eq!(
4141            NumberFormatType::from_str("CIRCLED_HANGUL_SYLLABLE").unwrap(),
4142            NumberFormatType::CircledHangulSyllable
4143        );
4144    }
4145
4146    #[test]
4147    fn number_format_type_str_roundtrip() {
4148        for v in &[
4149            NumberFormatType::Digit,
4150            NumberFormatType::CircledDigit,
4151            NumberFormatType::RomanCapital,
4152            NumberFormatType::RomanSmall,
4153            NumberFormatType::LatinCapital,
4154            NumberFormatType::LatinSmall,
4155            NumberFormatType::HangulSyllable,
4156            NumberFormatType::HangulJamo,
4157            NumberFormatType::HanjaDigit,
4158            NumberFormatType::CircledHangulSyllable,
4159        ] {
4160            let s = v.to_string();
4161            let back = NumberFormatType::from_str(&s).unwrap();
4162            assert_eq!(&back, v);
4163        }
4164    }
4165
4166    // ===================================================================
4167    // PageNumberPosition
4168    // ===================================================================
4169
4170    #[test]
4171    fn page_number_position_default_is_top_center() {
4172        assert_eq!(PageNumberPosition::default(), PageNumberPosition::TopCenter);
4173    }
4174
4175    #[test]
4176    fn page_number_position_display() {
4177        assert_eq!(PageNumberPosition::None.to_string(), "None");
4178        assert_eq!(PageNumberPosition::TopCenter.to_string(), "TopCenter");
4179        assert_eq!(PageNumberPosition::BottomCenter.to_string(), "BottomCenter");
4180        assert_eq!(PageNumberPosition::InsideBottom.to_string(), "InsideBottom");
4181    }
4182
4183    #[test]
4184    fn page_number_position_from_str() {
4185        assert_eq!(PageNumberPosition::from_str("None").unwrap(), PageNumberPosition::None);
4186        assert_eq!(
4187            PageNumberPosition::from_str("BOTTOM_CENTER").unwrap(),
4188            PageNumberPosition::BottomCenter
4189        );
4190        assert_eq!(
4191            PageNumberPosition::from_str("bottom-center").unwrap(),
4192            PageNumberPosition::BottomCenter
4193        );
4194        assert_eq!(PageNumberPosition::from_str("TopLeft").unwrap(), PageNumberPosition::TopLeft);
4195        assert!(PageNumberPosition::from_str("invalid").is_err());
4196    }
4197
4198    #[test]
4199    fn page_number_position_try_from_u8() {
4200        assert_eq!(PageNumberPosition::try_from(0u8).unwrap(), PageNumberPosition::None);
4201        assert_eq!(PageNumberPosition::try_from(2u8).unwrap(), PageNumberPosition::TopCenter);
4202        assert_eq!(PageNumberPosition::try_from(5u8).unwrap(), PageNumberPosition::BottomCenter);
4203        assert_eq!(PageNumberPosition::try_from(10u8).unwrap(), PageNumberPosition::InsideBottom);
4204        assert!(PageNumberPosition::try_from(11u8).is_err());
4205    }
4206
4207    #[test]
4208    fn page_number_position_str_roundtrip() {
4209        for v in &[
4210            PageNumberPosition::None,
4211            PageNumberPosition::TopLeft,
4212            PageNumberPosition::TopCenter,
4213            PageNumberPosition::TopRight,
4214            PageNumberPosition::BottomLeft,
4215            PageNumberPosition::BottomCenter,
4216            PageNumberPosition::BottomRight,
4217            PageNumberPosition::OutsideTop,
4218            PageNumberPosition::OutsideBottom,
4219            PageNumberPosition::InsideTop,
4220            PageNumberPosition::InsideBottom,
4221        ] {
4222            let s = v.to_string();
4223            let back = PageNumberPosition::from_str(&s).unwrap();
4224            assert_eq!(&back, v);
4225        }
4226    }
4227
4228    // ===================================================================
4229    // WordBreakType
4230    // ===================================================================
4231
4232    #[test]
4233    fn word_break_type_default_is_keep_word() {
4234        assert_eq!(WordBreakType::default(), WordBreakType::KeepWord);
4235    }
4236
4237    #[test]
4238    fn word_break_type_display() {
4239        assert_eq!(WordBreakType::KeepWord.to_string(), "KEEP_WORD");
4240        assert_eq!(WordBreakType::BreakWord.to_string(), "BREAK_WORD");
4241    }
4242
4243    #[test]
4244    fn word_break_type_from_str() {
4245        assert_eq!(WordBreakType::from_str("KEEP_WORD").unwrap(), WordBreakType::KeepWord);
4246        assert_eq!(WordBreakType::from_str("KeepWord").unwrap(), WordBreakType::KeepWord);
4247        assert_eq!(WordBreakType::from_str("keep_word").unwrap(), WordBreakType::KeepWord);
4248        assert_eq!(WordBreakType::from_str("BREAK_WORD").unwrap(), WordBreakType::BreakWord);
4249        assert_eq!(WordBreakType::from_str("BreakWord").unwrap(), WordBreakType::BreakWord);
4250        assert_eq!(WordBreakType::from_str("break_word").unwrap(), WordBreakType::BreakWord);
4251        assert!(WordBreakType::from_str("invalid").is_err());
4252    }
4253
4254    #[test]
4255    fn word_break_type_try_from_u8() {
4256        assert_eq!(WordBreakType::try_from(0u8).unwrap(), WordBreakType::KeepWord);
4257        assert_eq!(WordBreakType::try_from(1u8).unwrap(), WordBreakType::BreakWord);
4258        assert!(WordBreakType::try_from(2u8).is_err());
4259    }
4260
4261    #[test]
4262    fn word_break_type_serde_roundtrip() {
4263        for v in &[WordBreakType::KeepWord, WordBreakType::BreakWord] {
4264            let json = serde_json::to_string(v).unwrap();
4265            let back: WordBreakType = serde_json::from_str(&json).unwrap();
4266            assert_eq!(&back, v);
4267        }
4268    }
4269
4270    #[test]
4271    fn word_break_type_str_roundtrip() {
4272        for v in &[WordBreakType::KeepWord, WordBreakType::BreakWord] {
4273            let s = v.to_string();
4274            let back = WordBreakType::from_str(&s).unwrap();
4275            assert_eq!(&back, v);
4276        }
4277    }
4278
4279    // ===================================================================
4280    // EmphasisType
4281    // ===================================================================
4282
4283    #[test]
4284    fn emphasis_type_default_is_none() {
4285        assert_eq!(EmphasisType::default(), EmphasisType::None);
4286    }
4287
4288    #[test]
4289    fn emphasis_type_display_pascal_case() {
4290        assert_eq!(EmphasisType::None.to_string(), "None");
4291        assert_eq!(EmphasisType::DotAbove.to_string(), "DotAbove");
4292        assert_eq!(EmphasisType::RingAbove.to_string(), "RingAbove");
4293        assert_eq!(EmphasisType::Tilde.to_string(), "Tilde");
4294        assert_eq!(EmphasisType::Caron.to_string(), "Caron");
4295        assert_eq!(EmphasisType::Side.to_string(), "Side");
4296        assert_eq!(EmphasisType::Colon.to_string(), "Colon");
4297        assert_eq!(EmphasisType::GraveAccent.to_string(), "GraveAccent");
4298        assert_eq!(EmphasisType::AcuteAccent.to_string(), "AcuteAccent");
4299        assert_eq!(EmphasisType::Circumflex.to_string(), "Circumflex");
4300        assert_eq!(EmphasisType::Macron.to_string(), "Macron");
4301        assert_eq!(EmphasisType::HookAbove.to_string(), "HookAbove");
4302        assert_eq!(EmphasisType::DotBelow.to_string(), "DotBelow");
4303    }
4304
4305    #[test]
4306    fn emphasis_type_from_str_screaming_snake_case() {
4307        assert_eq!(EmphasisType::from_str("NONE").unwrap(), EmphasisType::None);
4308        assert_eq!(EmphasisType::from_str("DOT_ABOVE").unwrap(), EmphasisType::DotAbove);
4309        assert_eq!(EmphasisType::from_str("RING_ABOVE").unwrap(), EmphasisType::RingAbove);
4310        assert_eq!(EmphasisType::from_str("GRAVE_ACCENT").unwrap(), EmphasisType::GraveAccent);
4311        assert_eq!(EmphasisType::from_str("DOT_BELOW").unwrap(), EmphasisType::DotBelow);
4312    }
4313
4314    #[test]
4315    fn emphasis_type_from_str_pascal_case() {
4316        assert_eq!(EmphasisType::from_str("None").unwrap(), EmphasisType::None);
4317        assert_eq!(EmphasisType::from_str("DotAbove").unwrap(), EmphasisType::DotAbove);
4318        assert_eq!(EmphasisType::from_str("HookAbove").unwrap(), EmphasisType::HookAbove);
4319    }
4320
4321    #[test]
4322    fn emphasis_type_from_str_invalid() {
4323        let err = EmphasisType::from_str("INVALID").unwrap_err();
4324        match err {
4325            FoundationError::ParseError { ref type_name, ref value, .. } => {
4326                assert_eq!(type_name, "EmphasisType");
4327                assert_eq!(value, "INVALID");
4328            }
4329            other => panic!("unexpected: {other}"),
4330        }
4331    }
4332
4333    #[test]
4334    fn emphasis_type_try_from_u8() {
4335        assert_eq!(EmphasisType::try_from(0u8).unwrap(), EmphasisType::None);
4336        assert_eq!(EmphasisType::try_from(1u8).unwrap(), EmphasisType::DotAbove);
4337        assert_eq!(EmphasisType::try_from(12u8).unwrap(), EmphasisType::DotBelow);
4338        assert!(EmphasisType::try_from(13u8).is_err());
4339        assert!(EmphasisType::try_from(255u8).is_err());
4340    }
4341
4342    #[test]
4343    fn emphasis_type_repr_values() {
4344        assert_eq!(EmphasisType::None as u8, 0);
4345        assert_eq!(EmphasisType::DotAbove as u8, 1);
4346        assert_eq!(EmphasisType::DotBelow as u8, 12);
4347    }
4348
4349    #[test]
4350    fn emphasis_type_serde_roundtrip() {
4351        for variant in &[
4352            EmphasisType::None,
4353            EmphasisType::DotAbove,
4354            EmphasisType::RingAbove,
4355            EmphasisType::DotBelow,
4356        ] {
4357            let json = serde_json::to_string(variant).unwrap();
4358            let back: EmphasisType = serde_json::from_str(&json).unwrap();
4359            assert_eq!(&back, variant);
4360        }
4361    }
4362
4363    #[test]
4364    fn emphasis_type_str_roundtrip() {
4365        for variant in &[
4366            EmphasisType::None,
4367            EmphasisType::DotAbove,
4368            EmphasisType::GraveAccent,
4369            EmphasisType::DotBelow,
4370        ] {
4371            let s = variant.to_string();
4372            let back = EmphasisType::from_str(&s).unwrap();
4373            assert_eq!(&back, variant);
4374        }
4375    }
4376}