Skip to main content

hangul_cd/
jamo.rs

1use thiserror::Error;
2
3/// An error enum for Jamo-related errors.
4#[derive(Error, Debug, PartialEq, Eq)]
5pub enum JamoError {
6    /// Character could not be converted to Jamo
7    #[error("Could not convert character '{0}' to Jamo")]
8    FromCharError(char),
9}
10
11/// An enum for the Unicode type of a Jamo character. Types include
12/// modern, compatibility, non-standard modern, non-standard compatibility,
13/// and non-Hangul.
14#[derive(Debug, PartialEq, Eq)]
15pub enum JamoUnicodeType {
16    /// Modern Jamo; these are used to construct standard modern pre-composed
17    /// Hangul syllable blocks.
18    Modern,
19
20    /// Compatibility Jamo; these are included in Unicode for compatibility
21    /// with older standards and encodings. These are not used for composing
22    /// standard modern Hangul syllables, but can be converted to modern Jamo.
23    /// Some Korean IMEs produce compatibility Jamo characters if not
24    /// converted to syllable blocks.
25    Compatibility,
26
27    /// Non-standard modern Jamo; these are not used for composing standard modern
28    /// Hangul syllables and fall outside the typical modern Jamo range.
29    /// These are typically archaic or obsolete jamo characters.
30    NonStandardModern,
31
32    /// Non-standard compatibility Jamo; these are not used for composing
33    /// standard modern Hangul syllables and fall outside the typical
34    /// compatibility Jamo range. These are typically archaic or obsolete
35    /// jamo characters.
36    NonStandardCompatibility,
37
38    /// Non-Hangul character; this is not a Hangul jamo character.
39    NonHangul,
40}
41
42impl JamoUnicodeType {
43    /// Evaluates a character and determines its Jamo Unicode type
44    /// as being modern, compatibility, non-standard modern,
45    /// non-standard compatibility, or non-Hangul.
46    pub fn evaluate(c: char) -> JamoUnicodeType {
47        match c as u32 {
48            0x1100..=0x1112 | 0x1161..=0x1175 | 0x11A8..=0x11C2 => JamoUnicodeType::Modern,
49            0x3130..=0x3163 => JamoUnicodeType::Compatibility,
50            0x1113..=0x1160 | 0x1176..=0x11A7 | 0x11C3..=0x11FF => {
51                JamoUnicodeType::NonStandardModern
52            }
53            0x3164..=0x318F => JamoUnicodeType::NonStandardCompatibility,
54            _ => JamoUnicodeType::NonHangul,
55        }
56    }
57}
58
59// Jamo arithmetic
60pub(crate) const S_BASE: u32 = 0xAC00;
61pub(crate) const L_BASE: u32 = 0x1100;
62pub(crate) const V_BASE: u32 = 0x1161;
63pub(crate) const T_BASE: u32 = 0x11A7;
64pub(crate) const V_COUNT: u32 = 21;
65pub(crate) const T_COUNT: u32 = 28;
66pub(crate) const N_COUNT: u32 = V_COUNT * T_COUNT;
67pub(crate) const S_COUNT: u32 = 11172;
68
69/// Converts compatibility jamo to modern jamo, specifically for
70/// initial consonants or initial composite consonants.
71///
72/// What are compatibility and modern jamo? In Unicode, Hangul jamo characteres
73/// are represented in two different blocks: the "Hangul Jamo" block (U+1100 to U+11FF)
74/// and the "Hangul Compatibility Jamo" block (U+3130 to U+318F).
75/// The former contains the modern jamo used for composing syllables, while the latter
76/// includes characters for compatibility with older standards and encodings.
77///
78/// As an example, both U+1100 and U+3131 represent the Hangul consonant "Giyeok" (ㄱ),
79/// but U+1100 is the modern jamo used in syllable composition, while
80/// U+3131 is the compatibility jamo.
81///
82/// In order to properly compose Hangul syllables as Unicode characters, it is
83/// necessary to convert any compatibility jamo into their modern equivalents.
84/// The math done to compose syllables in this crate relies on the use of
85/// modern Jamo. The APIs for converting `HangulBlock`s or composing blocks
86/// or words or strings in this crate automatically handle this conversion,
87/// but the function is provided for users who want to manually work with Jamo
88/// characters.
89///
90/// This function maps compatibility jamo characters to their modern equivalents.
91/// If the input character is not a compatibility jamo, it is returned unchanged
92/// (including if it is not a Hangul jamo at all).
93pub fn modernized_jamo_initial(c: char) -> char {
94    match c {
95        '\u{3131}' => '\u{1100}', // ㄱ
96        '\u{3132}' => '\u{1101}', // ㄲ
97        '\u{3134}' => '\u{1102}', // ㄴ
98        '\u{3137}' => '\u{1103}', // ㄷ
99        '\u{3138}' => '\u{1104}', // ㄸ
100        '\u{3139}' => '\u{1105}', // ㄹ
101        '\u{3141}' => '\u{1106}', // ㅁ
102        '\u{3142}' => '\u{1107}', // ㅂ
103        '\u{3143}' => '\u{1108}', // ㅃ
104        '\u{3145}' => '\u{1109}', // ㅅ
105        '\u{3146}' => '\u{110A}', // ㅆ
106        '\u{3147}' => '\u{110B}', // ㅇ
107        '\u{3148}' => '\u{110C}', // ㅈ
108        '\u{3149}' => '\u{110D}', // ㅉ
109        '\u{314A}' => '\u{110E}', // ㅊ
110        '\u{314B}' => '\u{110F}', // ㅋ
111        '\u{314C}' => '\u{1110}', // ㅌ
112        '\u{314D}' => '\u{1111}', // ㅍ
113        '\u{314E}' => '\u{1112}', // ㅎ
114        other => other,
115    }
116}
117
118/// Converts compatibility jamo to modern jamo, specifically for
119/// vowels or composite vowels.
120///
121/// What are compatibility and modern jamo? In Unicode, Hangul jamo characteres
122/// are represented in two different blocks: the "Hangul Jamo" block (U+1100 to U+11FF)
123/// and the "Hangul Compatibility Jamo" block (U+3130 to U+318F).
124/// The former contains the modern jamo used for composing syllables, while the latter
125/// includes characters for compatibility with older standards and encodings.
126///
127/// As an example, both U+1100 and U+3131 represent the Hangul consonant "Giyeok" (ㄱ),
128/// but U+1100 is the modern jamo used in syllable composition, while
129/// U+3131 is the compatibility jamo.
130///
131/// In order to properly compose Hangul syllables as Unicode characters, it is
132/// necessary to convert any compatibility jamo into their modern equivalents.
133/// The math done to compose syllables in this crate relies on the use of
134/// modern Jamo. The APIs for converting `HangulBlock`s or composing blocks
135/// or words or strings in this crate automatically handle this conversion,
136/// but the function is provided for users who want to manually work with Jamo
137/// characters.
138///
139/// This function maps compatibility jamo characters to their modern equivalents.
140/// If the input character is not a compatibility jamo, it is returned unchanged
141/// (including if it is not a Hangul jamo at all).
142pub fn modernized_jamo_vowel(c: char) -> char {
143    match c {
144        '\u{314F}' => '\u{1161}', // ㅏ
145        '\u{3150}' => '\u{1162}', // ㅐ
146        '\u{3151}' => '\u{1163}', // ㅑ
147        '\u{3152}' => '\u{1164}', // ㅒ
148        '\u{3153}' => '\u{1165}', // ㅓ
149        '\u{3154}' => '\u{1166}', // ㅔ
150        '\u{3155}' => '\u{1167}', // ㅕ
151        '\u{3156}' => '\u{1168}', // ㅖ
152        '\u{3157}' => '\u{1169}', // ㅗ
153        '\u{3158}' => '\u{116A}', // ㅘ
154        '\u{3159}' => '\u{116B}', // ㅙ
155        '\u{315A}' => '\u{116C}', // ㅚ
156        '\u{315B}' => '\u{116D}', // ㅛ
157        '\u{315C}' => '\u{116E}', // ㅜ
158        '\u{315D}' => '\u{116F}', // ㅝ
159        '\u{315E}' => '\u{1170}', // ㅞ
160        '\u{315F}' => '\u{1171}', // ㅟ
161        '\u{3160}' => '\u{1172}', // ㅠ
162        '\u{3161}' => '\u{1173}', // ㅡ
163        '\u{3162}' => '\u{1174}', // ㅢ
164        '\u{3163}' => '\u{1175}', // ㅣ
165        other => other,
166    }
167}
168
169/// Converts compatibility jamo to modern jamo, specifically for
170/// final consonants or final composite consonants.
171///
172/// What are compatibility and modern jamo? In Unicode, Hangul jamo characteres
173/// are represented in two different blocks: the "Hangul Jamo" block (U+1100 to U+11FF)
174/// and the "Hangul Compatibility Jamo" block (U+3130 to U+318F).
175/// The former contains the modern jamo used for composing syllables, while the latter
176/// includes characters for compatibility with older standards and encodings.
177///
178/// As an example, both U+1100 and U+3131 represent the Hangul consonant "Giyeok" (ㄱ),
179/// but U+1100 is the modern jamo used in syllable composition, while
180/// U+3131 is the compatibility jamo.
181///
182/// In order to properly compose Hangul syllables as Unicode characters, it is
183/// necessary to convert any compatibility jamo into their modern equivalents.
184/// The math done to compose syllables in this crate relies on the use of
185/// modern Jamo. The APIs for converting `HangulBlock`s or composing blocks
186/// or words or strings in this crate automatically handle this conversion,
187/// but the function is provided for users who want to manually work with Jamo
188/// characters.
189///
190/// This function maps compatibility jamo characters to their modern equivalents.
191/// If the input character is not a compatibility jamo, it is returned unchanged
192/// (including if it is not a Hangul jamo at all).
193pub fn modernized_jamo_final(c: char) -> char {
194    match c {
195        '\u{3131}' => '\u{11A8}', // ㄱ
196        '\u{3132}' => '\u{11A9}', // ㄲ
197        '\u{3133}' => '\u{11AA}', // ㄳ
198        '\u{3134}' => '\u{11AB}', // ㄴ
199        '\u{3135}' => '\u{11AC}', // ㄵ
200        '\u{3136}' => '\u{11AD}', // ㄶ
201        '\u{3137}' => '\u{11AE}', // ㄷ
202        '\u{3139}' => '\u{11AF}', // ㄹ
203        '\u{313A}' => '\u{11B0}', // ㄺ
204        '\u{313B}' => '\u{11B1}', // ㄻ
205        '\u{313C}' => '\u{11B2}', // ㄼ
206        '\u{313D}' => '\u{11B3}', // ㄽ
207        '\u{313E}' => '\u{11B4}', // ㄾ
208        '\u{313F}' => '\u{11B5}', // ㄿ
209        '\u{3140}' => '\u{11B6}', // ㅀ
210        '\u{3141}' => '\u{11B7}', // ㅁ
211        '\u{3142}' => '\u{11B8}', // ㅂ
212        '\u{3144}' => '\u{11B9}', // ㅄ
213        '\u{3145}' => '\u{11BA}', // ㅅ
214        '\u{3146}' => '\u{11BB}', // ㅆ
215        '\u{3147}' => '\u{11BC}', // ㅇ
216        '\u{3148}' => '\u{11BD}', // ㅈ
217        '\u{314A}' => '\u{11BE}', // ㅊ
218        '\u{314B}' => '\u{11BF}', // ㅋ
219        '\u{314C}' => '\u{11C0}', // ㅌ
220        '\u{314D}' => '\u{11C1}', // ㅍ
221        '\u{314E}' => '\u{11C2}', // ㅎ
222        other => other,
223    }
224}
225
226/// Converts a modern jamo character to its compatibility jamo equivalent.
227/// If the input character is not a modern jamo, it is returned unchanged
228/// (including if it is not a Hangul jamo at all).
229///
230/// For more info on modern and compatibility jamo, see the documentation
231/// for `modernized_jamo_initial`, `modernized_jamo_vowel`,
232/// or `modernized_jamo_final`.
233pub fn modern_to_compatibility_jamo(c: char) -> char {
234    match c {
235        // Initial consonants
236        '\u{1100}' => '\u{3131}', // ㄱ
237        '\u{1101}' => '\u{3132}', // ㄲ
238        '\u{1102}' => '\u{3134}', // ㄴ
239        '\u{1103}' => '\u{3137}', // ㄷ
240        '\u{1104}' => '\u{3138}', // ㄸ
241        '\u{1105}' => '\u{3139}', // ㄹ
242        '\u{1106}' => '\u{3141}', // ㅁ
243        '\u{1107}' => '\u{3142}', // ㅂ
244        '\u{1108}' => '\u{3143}', // ㅃ
245        '\u{1109}' => '\u{3145}', // ㅅ
246        '\u{110A}' => '\u{3146}', // ㅆ
247        '\u{110B}' => '\u{3147}', // ㅇ
248        '\u{110C}' => '\u{3148}', // ㅈ
249        '\u{110D}' => '\u{3149}', // ㅉ
250        '\u{110E}' => '\u{314A}', // ㅊ
251        '\u{110F}' => '\u{314B}', // ㅋ
252        '\u{1110}' => '\u{314C}', // ㅌ
253        '\u{1111}' => '\u{314D}', // ㅍ
254        '\u{1112}' => '\u{314E}', // ㅎ
255
256        // Vowels
257        '\u{1161}' => '\u{314F}', // ㅏ
258        '\u{1162}' => '\u{3150}', // ㅐ
259        '\u{1163}' => '\u{3151}', // ㅑ
260        '\u{1164}' => '\u{3152}', // ㅒ
261        '\u{1165}' => '\u{3153}', // ㅓ
262        '\u{1166}' => '\u{3154}', // ㅔ
263        '\u{1167}' => '\u{3155}', // ㅕ
264        '\u{1168}' => '\u{3156}', // ㅖ
265        '\u{1169}' => '\u{3157}', // ㅗ
266        '\u{116A}' => '\u{3158}', // ㅘ
267        '\u{116B}' => '\u{3159}', // ㅙ
268        '\u{116C}' => '\u{315A}', // ㅚ
269        '\u{116D}' => '\u{315B}', // ㅛ
270        '\u{116E}' => '\u{315C}', // ㅜ
271        '\u{116F}' => '\u{315D}', // ㅝ
272        '\u{1170}' => '\u{315E}', // ㅞ
273        '\u{1171}' => '\u{315F}', // ㅟ
274        '\u{1172}' => '\u{3160}', // ㅠ
275        '\u{1173}' => '\u{3161}', // ㅡ
276        '\u{1174}' => '\u{3162}', // ㅢ
277        '\u{1175}' => '\u{3163}', // ㅣ
278
279        // Final consonants
280        '\u{11A8}' => '\u{3131}', // ㄱ
281        '\u{11A9}' => '\u{3132}', // ㄲ
282        '\u{11AA}' => '\u{3133}', // ㄳ
283        '\u{11AB}' => '\u{3134}', // ㄴ
284        '\u{11AC}' => '\u{3135}', // ㄵ
285        '\u{11AD}' => '\u{3136}', // ㄶ
286        '\u{11AE}' => '\u{3137}', // ㄷ
287        '\u{11AF}' => '\u{3139}', // ㄹ
288        '\u{11B0}' => '\u{313A}', // ㄺ
289        '\u{11B1}' => '\u{313B}', // ㄻ
290        '\u{11B2}' => '\u{313C}', // ㄼ
291        '\u{11B3}' => '\u{313D}', // ㄽ
292        '\u{11B4}' => '\u{313E}', // ㄾ
293        '\u{11B5}' => '\u{313F}', // ㄿ
294        '\u{11B6}' => '\u{3140}', // ㅀ
295        '\u{11B7}' => '\u{3141}', // ㅁ
296        '\u{11B8}' => '\u{3142}', // ㅂ
297        '\u{11B9}' => '\u{3144}', // ㅄ
298        '\u{11BA}' => '\u{3145}', // ㅅ
299        '\u{11BB}' => '\u{3146}', // ㅆ
300        '\u{11BC}' => '\u{3147}', // ㅇ
301        '\u{11BD}' => '\u{3148}', // ㅈ
302        '\u{11BE}' => '\u{314A}', // ㅊ
303        '\u{11BF}' => '\u{314B}', // ㅋ
304        '\u{11C0}' => '\u{314C}', // ㅌ
305        '\u{11C1}' => '\u{314D}', // ㅍ
306        '\u{11C2}' => '\u{314E}', // ㅎ
307
308        other => other,
309    }
310}
311
312/// An enum representing either a Hangul Jamo character or a non-Hangul
313/// character. Archaic or non-standard jamo like ᅀ will be classified as NonHangul
314/// because they are not used in standard modern Hangul syllable composition.
315#[derive(Debug, PartialEq, Eq, Clone)]
316pub enum Character {
317    NonHangul(char),
318    Hangul(Jamo),
319}
320
321impl Character {
322    /// Determines the type of Hangul letter for a given character.
323    /// Archaic or non-standard jamo like ᅀ will be classified as NonHangul
324    /// because they are not used in standard modern Hangul syllable composition.
325    /// Classifies a character as Hangul jamo or non-Hangul and
326    /// returns the appropriate `Character` enum variant.
327    ///
328    /// **Example:**
329    /// ```rust
330    /// use hangul_cd::jamo::{
331    ///     Character,
332    ///     Jamo,
333    ///     JamoConsonantSingular,
334    ///     JamoVowelSingular,
335    ///     JamoVowelComposite,
336    ///     JamoConsonantComposite,
337    /// };
338    ///
339    /// // Valid Hangul consonant
340    /// assert_eq!(
341    ///     Character::from_char('ㄱ').unwrap(),
342    ///     Character::Hangul(Jamo::Consonant(JamoConsonantSingular::Giyeok))
343    /// );
344    ///
345    /// // Valid Hangul vowel
346    /// assert_eq!(
347    ///     Character::from_char('ㅏ').unwrap(),
348    ///     Character::Hangul(Jamo::Vowel(JamoVowelSingular::A))
349    /// );
350    ///
351    /// // Valid composite consonant
352    /// assert_eq!(
353    ///     Character::from_char('ㄲ').unwrap(),
354    ///     Character::Hangul(Jamo::CompositeConsonant(JamoConsonantComposite::SsangGiyeok))
355    /// );
356    ///
357    /// // Valid composite vowel
358    /// assert_eq!(
359    ///     Character::from_char('ㅘ').unwrap(),
360    ///     Character::Hangul(Jamo::CompositeVowel(JamoVowelComposite::Wa))
361    /// );
362    ///
363    /// // Non-Hangul character
364    /// assert_eq!(
365    ///     Character::from_char('A').unwrap(),
366    ///     Character::NonHangul('A')
367    /// );
368    /// ```
369    pub fn from_char(c: char) -> Result<Self, JamoError> {
370        match JamoUnicodeType::evaluate(c) {
371            JamoUnicodeType::Modern => {
372                let cc = modern_to_compatibility_jamo(c);
373                Self::from_compatibility_jamo(cc)
374            }
375            JamoUnicodeType::Compatibility => Self::from_compatibility_jamo(c),
376            _ => Ok(Character::NonHangul(c)),
377        }
378    }
379
380    fn from_compatibility_jamo(c: char) -> Result<Self, JamoError> {
381        Ok(Self::Hangul(Jamo::from_compatibility_jamo(c)?))
382    }
383
384    /// Returns the Jamo if the `Character` is a Hangul jamo,
385    /// or `None` otherwise.
386    pub fn jamo(&self) -> Option<&Jamo> {
387        match self {
388            Character::Hangul(jamo) => Some(jamo),
389            Character::NonHangul(_) => None,
390        }
391    }
392}
393
394/// An enum representing the different types of Hangul Jamo characters:
395/// consonants, composite consonants, vowels, and composite vowels.
396#[derive(Debug, PartialEq, Eq, Clone)]
397pub enum Jamo {
398    Consonant(JamoConsonantSingular),
399    CompositeConsonant(JamoConsonantComposite),
400    Vowel(JamoVowelSingular),
401    CompositeVowel(JamoVowelComposite),
402}
403
404/// An enum representing singular Hangul consonant jamo.
405#[derive(Debug, PartialEq, Eq, Clone)]
406pub enum JamoConsonantSingular {
407    /// ㄱ
408    Giyeok,
409    /// ㄴ
410    Nieun,
411    /// ㄷ
412    Digeut,
413    /// ㄹ
414    Rieul,
415    /// ㅁ
416    Mieum,
417    /// ㅂ
418    Bieup,
419    /// ㅅ
420    Siot,
421    /// ㅇ
422    Ieung,
423    /// ㅈ
424    Jieut,
425    /// ㅊ
426    Chieut,
427    /// ㅋ
428    Kieuk,
429    /// ㅌ
430    Tieut,
431    /// ㅍ
432    Pieup,
433    /// ㅎ
434    Hieut,
435}
436
437impl JamoConsonantSingular {
438    /// Returns the modern jamo character for the given position
439    /// (initial or final). Returns `None` for positions that
440    /// are not applicable to consonants (i.e., medial).
441    ///
442    /// A position must be specified because consonants have multiple
443    /// encodings in the modern Jamo Unicode block depending on whether
444    /// they are used as initial or final consonants in a syllable.
445    ///
446    /// **Example:**
447    /// ```rust
448    /// use hangul_cd::jamo::{
449    ///     JamoConsonantSingular,
450    ///     JamoPosition,
451    /// };
452    ///
453    /// let giyeok = JamoConsonantSingular::Giyeok;
454    /// assert_eq!(giyeok.char_modern(JamoPosition::Initial), Some('\u{1100}')); // Initial ㄱ
455    /// assert_eq!(giyeok.char_modern(JamoPosition::Final), Some('\u{11A8}'));   // Final ㄱ
456    /// assert_eq!(giyeok.char_modern(JamoPosition::Vowel), None);              // Medial is not applicable
457    /// ```
458    pub fn char_modern(&self, position: JamoPosition) -> Option<char> {
459        match position {
460            JamoPosition::Initial => Some(self.char_modern_initial()),
461            JamoPosition::Final => Some(self.char_modern_final()),
462            _ => None,
463        }
464    }
465
466    fn char_modern_initial(&self) -> char {
467        match self {
468            JamoConsonantSingular::Giyeok => '\u{1100}',
469            JamoConsonantSingular::Nieun => '\u{1102}',
470            JamoConsonantSingular::Digeut => '\u{1103}',
471            JamoConsonantSingular::Rieul => '\u{1105}',
472            JamoConsonantSingular::Mieum => '\u{1106}',
473            JamoConsonantSingular::Bieup => '\u{1107}',
474            JamoConsonantSingular::Siot => '\u{1109}',
475            JamoConsonantSingular::Ieung => '\u{110B}',
476            JamoConsonantSingular::Jieut => '\u{110C}',
477            JamoConsonantSingular::Chieut => '\u{110E}',
478            JamoConsonantSingular::Kieuk => '\u{110F}',
479            JamoConsonantSingular::Tieut => '\u{1110}',
480            JamoConsonantSingular::Pieup => '\u{1111}',
481            JamoConsonantSingular::Hieut => '\u{1112}',
482        }
483    }
484
485    fn char_modern_final(&self) -> char {
486        match self {
487            JamoConsonantSingular::Giyeok => '\u{11A8}',
488            JamoConsonantSingular::Nieun => '\u{11AB}',
489            JamoConsonantSingular::Digeut => '\u{11AE}',
490            JamoConsonantSingular::Rieul => '\u{11AF}',
491            JamoConsonantSingular::Mieum => '\u{11B7}',
492            JamoConsonantSingular::Bieup => '\u{11B8}',
493            JamoConsonantSingular::Siot => '\u{11BA}',
494            JamoConsonantSingular::Ieung => '\u{11BC}',
495            JamoConsonantSingular::Jieut => '\u{11BD}',
496            JamoConsonantSingular::Chieut => '\u{11BE}',
497            JamoConsonantSingular::Kieuk => '\u{11BF}',
498            JamoConsonantSingular::Tieut => '\u{11C0}',
499            JamoConsonantSingular::Pieup => '\u{11C1}',
500            JamoConsonantSingular::Hieut => '\u{11C2}',
501        }
502    }
503
504    /// Returns the compatibility jamo character for this singular consonant.
505    ///
506    /// **Example:**
507    /// ```rust
508    /// use hangul_cd::jamo::JamoConsonantSingular;
509    ///
510    /// let siot = JamoConsonantSingular::Siot;
511    /// assert_eq!(siot.char_compatibility(), 'ㅅ');
512    /// ```
513    pub fn char_compatibility(&self) -> char {
514        match self {
515            JamoConsonantSingular::Giyeok => 'ㄱ',
516            JamoConsonantSingular::Nieun => 'ㄴ',
517            JamoConsonantSingular::Digeut => 'ㄷ',
518            JamoConsonantSingular::Rieul => 'ㄹ',
519            JamoConsonantSingular::Mieum => 'ㅁ',
520            JamoConsonantSingular::Bieup => 'ㅂ',
521            JamoConsonantSingular::Siot => 'ㅅ',
522            JamoConsonantSingular::Ieung => 'ㅇ',
523            JamoConsonantSingular::Jieut => 'ㅈ',
524            JamoConsonantSingular::Chieut => 'ㅊ',
525            JamoConsonantSingular::Kieuk => 'ㅋ',
526            JamoConsonantSingular::Tieut => 'ㅌ',
527            JamoConsonantSingular::Pieup => 'ㅍ',
528            JamoConsonantSingular::Hieut => 'ㅎ',
529        }
530    }
531
532    /// Combines this singular consonant with another singular consonant
533    /// to form a composite consonant for use in the initial position
534    /// of a Hangul syllable. Returns `None` if the combination is not valid.
535    ///
536    /// Only the following combinations are valid for initial position:
537    /// - ㄱ + ㄱ = ㄲ
538    /// - ㄷ + ㄷ = ㄸ
539    /// - ㅂ + ㅂ = ㅃ
540    /// - ㅅ + ㅅ = ㅆ
541    /// - ㅈ + ㅈ = ㅉ
542    ///
543    /// **Example:**
544    /// ```rust
545    /// use hangul_cd::jamo::{
546    ///     JamoConsonantSingular,
547    ///     JamoConsonantComposite,
548    /// };
549    ///
550    /// let bieup = JamoConsonantSingular::Bieup;
551    /// let composite = bieup.combine_for_initial(&JamoConsonantSingular::Bieup);
552    /// assert_eq!(composite, Some(JamoConsonantComposite::SsangBieup));
553    /// ```
554    pub fn combine_for_initial(
555        &self,
556        other: &JamoConsonantSingular,
557    ) -> Option<JamoConsonantComposite> {
558        match (self, other) {
559            (JamoConsonantSingular::Giyeok, JamoConsonantSingular::Giyeok) => {
560                Some(JamoConsonantComposite::SsangGiyeok)
561            }
562            (JamoConsonantSingular::Digeut, JamoConsonantSingular::Digeut) => {
563                Some(JamoConsonantComposite::SsangDigeut)
564            }
565            (JamoConsonantSingular::Bieup, JamoConsonantSingular::Bieup) => {
566                Some(JamoConsonantComposite::SsangBieup)
567            }
568            (JamoConsonantSingular::Siot, JamoConsonantSingular::Siot) => {
569                Some(JamoConsonantComposite::SsangSiot)
570            }
571            (JamoConsonantSingular::Jieut, JamoConsonantSingular::Jieut) => {
572                Some(JamoConsonantComposite::SsangJieut)
573            }
574            _ => None,
575        }
576    }
577
578    /// Combines this singular consonant with another singular consonant
579    /// to form a composite consonant for use in the final position
580    /// of a Hangul syllable. Returns `None` if the combination is not valid.
581    ///
582    /// Only the following combinations are valid for final position:
583    /// - ㄱ + ㅅ = ㄳ
584    /// - ㄴ + ㅈ = ㄵ
585    /// - ㄴ + ㅎ = ㄶ
586    /// - ㄹ + ㄱ = ㄺ
587    /// - ㄹ + ㅁ = ㄻ
588    /// - ㄹ + ㅂ = ㄼ
589    /// - ㄹ + ㅅ = ㄽ
590    /// - ㄹ + ㅌ = ㄾ
591    /// - ㄹ + ㅍ = ㄿ
592    /// - ㄹ + ㅎ = ㅀ
593    /// - ㅂ + ㅅ = ㅄ
594    /// - ㄱ + ㄱ = ㄲ
595    /// - ㅅ + ㅅ = ㅆ
596    ///
597    /// **Example:**
598    /// ```rust
599    /// use hangul_cd::jamo::{
600    ///     JamoConsonantSingular,
601    ///     JamoConsonantComposite,
602    /// };
603    ///
604    /// let rieul = JamoConsonantSingular::Rieul;
605    /// let composite = rieul.combine_for_final(&JamoConsonantSingular::Mieum);
606    /// assert_eq!(composite, Some(JamoConsonantComposite::RieulMieum));
607    /// ```
608    pub fn combine_for_final(
609        &self,
610        other: &JamoConsonantSingular,
611    ) -> Option<JamoConsonantComposite> {
612        match (self, other) {
613            (JamoConsonantSingular::Giyeok, JamoConsonantSingular::Siot) => {
614                Some(JamoConsonantComposite::GiyeokSiot)
615            }
616            (JamoConsonantSingular::Nieun, JamoConsonantSingular::Jieut) => {
617                Some(JamoConsonantComposite::NieunJieut)
618            }
619            (JamoConsonantSingular::Nieun, JamoConsonantSingular::Hieut) => {
620                Some(JamoConsonantComposite::NieunHieut)
621            }
622            (JamoConsonantSingular::Rieul, JamoConsonantSingular::Giyeok) => {
623                Some(JamoConsonantComposite::RieulGiyeok)
624            }
625            (JamoConsonantSingular::Rieul, JamoConsonantSingular::Mieum) => {
626                Some(JamoConsonantComposite::RieulMieum)
627            }
628            (JamoConsonantSingular::Rieul, JamoConsonantSingular::Bieup) => {
629                Some(JamoConsonantComposite::RieulBieup)
630            }
631            (JamoConsonantSingular::Rieul, JamoConsonantSingular::Siot) => {
632                Some(JamoConsonantComposite::RieulSiot)
633            }
634            (JamoConsonantSingular::Rieul, JamoConsonantSingular::Tieut) => {
635                Some(JamoConsonantComposite::RieulTieut)
636            }
637            (JamoConsonantSingular::Rieul, JamoConsonantSingular::Pieup) => {
638                Some(JamoConsonantComposite::RieulPieup)
639            }
640            (JamoConsonantSingular::Rieul, JamoConsonantSingular::Hieut) => {
641                Some(JamoConsonantComposite::RieulHieut)
642            }
643            (JamoConsonantSingular::Giyeok, JamoConsonantSingular::Giyeok) => {
644                Some(JamoConsonantComposite::SsangGiyeok)
645            }
646            (JamoConsonantSingular::Siot, JamoConsonantSingular::Siot) => {
647                Some(JamoConsonantComposite::SsangSiot)
648            }
649            (JamoConsonantSingular::Bieup, JamoConsonantSingular::Siot) => {
650                Some(JamoConsonantComposite::BieupSiot)
651            }
652            _ => None,
653        }
654    }
655}
656
657/// An enum representing composite Hangul consonant jamo.
658#[derive(Debug, PartialEq, Eq, Clone)]
659pub enum JamoConsonantComposite {
660    /// ㄳ
661    GiyeokSiot,
662    /// ㄵ
663    NieunJieut,
664    /// ㄶ
665    NieunHieut,
666    /// ㄺ
667    RieulGiyeok,
668    /// ㄻ
669    RieulMieum,
670    /// ㄼ
671    RieulBieup,
672    /// ㄽ
673    RieulSiot,
674    /// ㄾ
675    RieulTieut,
676    /// ㄿ
677    RieulPieup,
678    /// ㅀ
679    RieulHieut,
680    /// ㄲ
681    SsangGiyeok,
682    /// ㄸ
683    SsangDigeut,
684    /// ㅃ
685    SsangBieup,
686    /// ㅆ
687    SsangSiot,
688    /// ㅉ
689    SsangJieut,
690    /// ㅄ
691    BieupSiot,
692}
693
694impl JamoConsonantComposite {
695    /// Returns the modern jamo character for the given position
696    /// (initial or final). Returns `None` for positions that
697    /// are not applicable to composite consonants (i.e., medial).
698    ///
699    /// A position must be specified because composite consonants have multiple
700    /// encodings in the modern Jamo Unicode block depending on whether
701    /// they are used as initial or final consonants in a syllable.
702    ///
703    /// **Example:**
704    /// ```rust
705    /// use hangul_cd::jamo::{
706    ///     JamoConsonantComposite,
707    ///     JamoPosition,
708    /// };
709    ///
710    /// let ssang_giyeok = JamoConsonantComposite::SsangGiyeok;
711    /// assert_eq!(ssang_giyeok.char_modern(JamoPosition::Initial), Some('\u{1101}')); // Initial ㄲ
712    /// assert_eq!(ssang_giyeok.char_modern(JamoPosition::Final), Some('\u{11A9}'));   // Final ㄲ
713    /// assert_eq!(ssang_giyeok.char_modern(JamoPosition::Vowel), None);              // Medial is not applicable
714    /// ```
715    pub fn char_modern(&self, position: JamoPosition) -> Option<char> {
716        match position {
717            JamoPosition::Initial => self.char_modern_initial(),
718            JamoPosition::Final => self.char_modern_final(),
719            _ => None,
720        }
721    }
722
723    fn char_modern_initial(&self) -> Option<char> {
724        match self {
725            JamoConsonantComposite::SsangGiyeok => Some('\u{1101}'),
726            JamoConsonantComposite::SsangDigeut => Some('\u{1104}'),
727            JamoConsonantComposite::SsangBieup => Some('\u{1108}'),
728            JamoConsonantComposite::SsangSiot => Some('\u{110A}'),
729            JamoConsonantComposite::SsangJieut => Some('\u{110D}'),
730            _ => None,
731        }
732    }
733
734    fn char_modern_final(&self) -> Option<char> {
735        match self {
736            JamoConsonantComposite::GiyeokSiot => Some('\u{11AA}'),
737            JamoConsonantComposite::NieunJieut => Some('\u{11AC}'),
738            JamoConsonantComposite::NieunHieut => Some('\u{11AD}'),
739            JamoConsonantComposite::RieulGiyeok => Some('\u{11B0}'),
740            JamoConsonantComposite::RieulMieum => Some('\u{11B1}'),
741            JamoConsonantComposite::RieulBieup => Some('\u{11B2}'),
742            JamoConsonantComposite::RieulSiot => Some('\u{11B3}'),
743            JamoConsonantComposite::RieulTieut => Some('\u{11B4}'),
744            JamoConsonantComposite::RieulPieup => Some('\u{11B5}'),
745            JamoConsonantComposite::RieulHieut => Some('\u{11B6}'),
746            JamoConsonantComposite::SsangGiyeok => Some('\u{11A9}'),
747            JamoConsonantComposite::SsangSiot => Some('\u{11BB}'),
748            JamoConsonantComposite::BieupSiot => Some('\u{11B9}'),
749            _ => None,
750        }
751    }
752
753    /// Returns the compatibility jamo character for this composite consonant.
754    ///
755    /// **Example:**
756    /// ```rust
757    /// use hangul_cd::jamo::JamoConsonantComposite;
758    ///
759    /// let gieok_siot = JamoConsonantComposite::GiyeokSiot;
760    /// assert_eq!(gieok_siot.char_compatibility(), 'ㄳ');
761    /// ```
762    pub fn char_compatibility(&self) -> char {
763        match self {
764            JamoConsonantComposite::GiyeokSiot => 'ㄳ',
765            JamoConsonantComposite::NieunJieut => 'ㄵ',
766            JamoConsonantComposite::NieunHieut => 'ㄶ',
767            JamoConsonantComposite::RieulGiyeok => 'ㄺ',
768            JamoConsonantComposite::RieulMieum => 'ㄻ',
769            JamoConsonantComposite::RieulBieup => 'ㄼ',
770            JamoConsonantComposite::RieulSiot => 'ㄽ',
771            JamoConsonantComposite::RieulTieut => 'ㄾ',
772            JamoConsonantComposite::RieulPieup => 'ㄿ',
773            JamoConsonantComposite::RieulHieut => 'ㅀ',
774            JamoConsonantComposite::SsangGiyeok => 'ㄲ',
775            JamoConsonantComposite::SsangDigeut => 'ㄸ',
776            JamoConsonantComposite::SsangBieup => 'ㅃ',
777            JamoConsonantComposite::SsangSiot => 'ㅆ',
778            JamoConsonantComposite::SsangJieut => 'ㅉ',
779            JamoConsonantComposite::BieupSiot => 'ㅄ',
780        }
781    }
782
783    /// Decomposes the composite consonant into its two constituent singular consonants.
784    ///
785    /// **Example:**
786    /// ```rust
787    /// use hangul_cd::jamo::{
788    ///     Jamo,
789    ///     JamoConsonantSingular,
790    ///     JamoConsonantComposite,
791    /// };
792    ///
793    /// let composite = JamoConsonantComposite::RieulMieum;
794    /// let (first, second) = composite.decompose();
795    /// assert_eq!(first, Jamo::Consonant(JamoConsonantSingular::Rieul));
796    /// assert_eq!(second, Jamo::Consonant(JamoConsonantSingular::Mieum));
797    /// ```
798    pub fn decompose(&self) -> (Jamo, Jamo) {
799        match self {
800            JamoConsonantComposite::GiyeokSiot => (
801                Jamo::Consonant(JamoConsonantSingular::Giyeok),
802                Jamo::Consonant(JamoConsonantSingular::Siot),
803            ),
804            JamoConsonantComposite::NieunJieut => (
805                Jamo::Consonant(JamoConsonantSingular::Nieun),
806                Jamo::Consonant(JamoConsonantSingular::Jieut),
807            ),
808            JamoConsonantComposite::NieunHieut => (
809                Jamo::Consonant(JamoConsonantSingular::Nieun),
810                Jamo::Consonant(JamoConsonantSingular::Hieut),
811            ),
812            JamoConsonantComposite::RieulGiyeok => (
813                Jamo::Consonant(JamoConsonantSingular::Rieul),
814                Jamo::Consonant(JamoConsonantSingular::Giyeok),
815            ),
816            JamoConsonantComposite::RieulMieum => (
817                Jamo::Consonant(JamoConsonantSingular::Rieul),
818                Jamo::Consonant(JamoConsonantSingular::Mieum),
819            ),
820            JamoConsonantComposite::RieulBieup => (
821                Jamo::Consonant(JamoConsonantSingular::Rieul),
822                Jamo::Consonant(JamoConsonantSingular::Bieup),
823            ),
824            JamoConsonantComposite::RieulSiot => (
825                Jamo::Consonant(JamoConsonantSingular::Rieul),
826                Jamo::Consonant(JamoConsonantSingular::Siot),
827            ),
828            JamoConsonantComposite::RieulTieut => (
829                Jamo::Consonant(JamoConsonantSingular::Rieul),
830                Jamo::Consonant(JamoConsonantSingular::Tieut),
831            ),
832            JamoConsonantComposite::RieulPieup => (
833                Jamo::Consonant(JamoConsonantSingular::Rieul),
834                Jamo::Consonant(JamoConsonantSingular::Pieup),
835            ),
836            JamoConsonantComposite::RieulHieut => (
837                Jamo::Consonant(JamoConsonantSingular::Rieul),
838                Jamo::Consonant(JamoConsonantSingular::Hieut),
839            ),
840            JamoConsonantComposite::SsangGiyeok => (
841                Jamo::Consonant(JamoConsonantSingular::Giyeok),
842                Jamo::Consonant(JamoConsonantSingular::Giyeok),
843            ),
844            JamoConsonantComposite::SsangDigeut => (
845                Jamo::Consonant(JamoConsonantSingular::Digeut),
846                Jamo::Consonant(JamoConsonantSingular::Digeut),
847            ),
848            JamoConsonantComposite::SsangBieup => (
849                Jamo::Consonant(JamoConsonantSingular::Bieup),
850                Jamo::Consonant(JamoConsonantSingular::Bieup),
851            ),
852            JamoConsonantComposite::SsangSiot => (
853                Jamo::Consonant(JamoConsonantSingular::Siot),
854                Jamo::Consonant(JamoConsonantSingular::Siot),
855            ),
856            JamoConsonantComposite::SsangJieut => (
857                Jamo::Consonant(JamoConsonantSingular::Jieut),
858                Jamo::Consonant(JamoConsonantSingular::Jieut),
859            ),
860            JamoConsonantComposite::BieupSiot => (
861                Jamo::Consonant(JamoConsonantSingular::Bieup),
862                Jamo::Consonant(JamoConsonantSingular::Siot),
863            ),
864        }
865    }
866
867    /// Checks if the composite consonant is valid for use in the initial position
868    /// of a Hangul syllable.
869    ///
870    /// **Example:**
871    /// ```rust
872    /// use hangul_cd::jamo::JamoConsonantComposite;
873    ///
874    /// let ssang_giyeok = JamoConsonantComposite::SsangGiyeok;
875    /// assert!(ssang_giyeok.is_valid_initial());
876    ///
877    /// let gieok_siot = JamoConsonantComposite::GiyeokSiot;
878    /// assert!(!gieok_siot.is_valid_initial());
879    /// ```
880    pub fn is_valid_initial(&self) -> bool {
881        matches!(
882            self,
883            JamoConsonantComposite::SsangGiyeok
884                | JamoConsonantComposite::SsangDigeut
885                | JamoConsonantComposite::SsangBieup
886                | JamoConsonantComposite::SsangSiot
887                | JamoConsonantComposite::SsangJieut
888        )
889    }
890
891    /// Checks if the composite consonant is valid for use in the final position
892    /// of a Hangul syllable.
893    ///
894    /// **Example:**
895    /// ```rust
896    /// use hangul_cd::jamo::JamoConsonantComposite;
897    ///
898    /// let rieul_mieum = JamoConsonantComposite::RieulMieum;
899    /// assert!(rieul_mieum.is_valid_final());
900    ///
901    /// let ssang_giyeok = JamoConsonantComposite::SsangGiyeok;
902    /// assert!(ssang_giyeok.is_valid_final());
903    /// ```
904    pub fn is_valid_final(&self) -> bool {
905        matches!(
906            self,
907            JamoConsonantComposite::GiyeokSiot
908                | JamoConsonantComposite::NieunJieut
909                | JamoConsonantComposite::NieunHieut
910                | JamoConsonantComposite::RieulGiyeok
911                | JamoConsonantComposite::RieulMieum
912                | JamoConsonantComposite::RieulBieup
913                | JamoConsonantComposite::RieulSiot
914                | JamoConsonantComposite::RieulTieut
915                | JamoConsonantComposite::RieulPieup
916                | JamoConsonantComposite::RieulHieut
917                | JamoConsonantComposite::SsangGiyeok
918                | JamoConsonantComposite::SsangSiot
919                | JamoConsonantComposite::BieupSiot
920        )
921    }
922}
923
924/// An enum representing singular Hangul vowel jamo.
925#[derive(Debug, PartialEq, Eq, Clone)]
926pub enum JamoVowelSingular {
927    /// ㅏ
928    A,
929    /// ㅐ
930    Ae,
931    /// ㅑ
932    Ya,
933    /// ㅒ
934    Yae,
935    /// ㅓ
936    Eo,
937    /// ㅔ
938    E,
939    /// ㅕ
940    Yeo,
941    /// ㅖ
942    Ye,
943    /// ㅗ
944    O,
945    /// ㅛ
946    Yo,
947    /// ㅜ
948    U,
949    /// ㅠ
950    Yu,
951    /// ㅡ
952    Eu,
953    /// ㅣ
954    I,
955}
956
957impl JamoVowelSingular {
958    /// Returns the modern jamo character for this singular vowel.
959    /// No position is needed since vowels only have one encoding
960    /// in the modern Jamo Unicode block.
961    ///
962    /// **Example:**
963    /// ```rust
964    /// use hangul_cd::jamo::JamoVowelSingular;
965    ///
966    /// let eo = JamoVowelSingular::Eo;
967    /// assert_eq!(eo.char_modern(), '\u{1165}'); // Modern ㅓ
968    /// ```
969    pub fn char_modern(&self) -> char {
970        match self {
971            JamoVowelSingular::A => '\u{1161}',
972            JamoVowelSingular::Ae => '\u{1162}',
973            JamoVowelSingular::Ya => '\u{1163}',
974            JamoVowelSingular::Yae => '\u{1164}',
975            JamoVowelSingular::Eo => '\u{1165}',
976            JamoVowelSingular::E => '\u{1166}',
977            JamoVowelSingular::Yeo => '\u{1167}',
978            JamoVowelSingular::Ye => '\u{1168}',
979            JamoVowelSingular::O => '\u{1169}',
980            JamoVowelSingular::Yo => '\u{116D}',
981            JamoVowelSingular::U => '\u{116E}',
982            JamoVowelSingular::Yu => '\u{1172}',
983            JamoVowelSingular::Eu => '\u{1173}',
984            JamoVowelSingular::I => '\u{1175}',
985        }
986    }
987
988    /// Returns the compatibility jamo character for this singular vowel.
989    ///
990    /// **Example:**
991    /// ```rust
992    /// use hangul_cd::jamo::JamoVowelSingular;
993    ///
994    /// let yo = JamoVowelSingular::Yo;
995    /// assert_eq!(yo.char_compatibility(), 'ㅛ');
996    /// ```
997    pub fn char_compatibility(&self) -> char {
998        match self {
999            JamoVowelSingular::A => 'ㅏ',
1000            JamoVowelSingular::Ae => 'ㅐ',
1001            JamoVowelSingular::Ya => 'ㅑ',
1002            JamoVowelSingular::Yae => 'ㅒ',
1003            JamoVowelSingular::Eo => 'ㅓ',
1004            JamoVowelSingular::E => 'ㅔ',
1005            JamoVowelSingular::Yeo => 'ㅕ',
1006            JamoVowelSingular::Ye => 'ㅖ',
1007            JamoVowelSingular::O => 'ㅗ',
1008            JamoVowelSingular::Yo => 'ㅛ',
1009            JamoVowelSingular::U => 'ㅜ',
1010            JamoVowelSingular::Yu => 'ㅠ',
1011            JamoVowelSingular::Eu => 'ㅡ',
1012            JamoVowelSingular::I => 'ㅣ',
1013        }
1014    }
1015
1016    /// Combines this singular vowel with another singular vowel
1017    /// to form a composite vowel. Returns `None` if the combination is not valid.
1018    ///
1019    /// Only the following combinations are valid:
1020    /// - ㅗ + ㅏ = ㅘ
1021    /// - ㅗ + ㅐ = ㅙ
1022    /// - ㅗ + ㅣ = ㅚ
1023    /// - ㅜ + ㅓ = ㅝ
1024    /// - ㅜ + ㅔ = ㅞ
1025    /// - ㅜ + ㅣ = ㅟ
1026    /// - ㅡ + ㅣ = ㅢ
1027    ///
1028    /// **Example:**
1029    /// ```rust
1030    /// use hangul_cd::jamo::{
1031    ///     JamoVowelSingular,
1032    ///     JamoVowelComposite,
1033    /// };
1034    ///
1035    /// let o = JamoVowelSingular::O;                           // ㅗ
1036    /// let composite = o.combine(&JamoVowelSingular::A);       // ㅏ
1037    /// assert_eq!(composite, Some(JamoVowelComposite::Wa));    // ㅘ
1038    /// ```
1039    pub fn combine(&self, other: &JamoVowelSingular) -> Option<JamoVowelComposite> {
1040        match (self, other) {
1041            (JamoVowelSingular::O, JamoVowelSingular::A) => Some(JamoVowelComposite::Wa),
1042            (JamoVowelSingular::O, JamoVowelSingular::Ae) => Some(JamoVowelComposite::Wae),
1043            (JamoVowelSingular::O, JamoVowelSingular::I) => Some(JamoVowelComposite::Oe),
1044            (JamoVowelSingular::U, JamoVowelSingular::Eo) => Some(JamoVowelComposite::Wo),
1045            (JamoVowelSingular::U, JamoVowelSingular::E) => Some(JamoVowelComposite::We),
1046            (JamoVowelSingular::U, JamoVowelSingular::I) => Some(JamoVowelComposite::Wi),
1047            (JamoVowelSingular::Eu, JamoVowelSingular::I) => Some(JamoVowelComposite::Ui),
1048            _ => None,
1049        }
1050    }
1051}
1052
1053/// An enum representing composite Hangul vowel jamo.
1054#[derive(Debug, PartialEq, Eq, Clone)]
1055pub enum JamoVowelComposite {
1056    /// ㅘ
1057    Wa,
1058    /// ㅙ
1059    Wae,
1060    /// ㅚ
1061    Oe,
1062    /// ㅝ
1063    Wo,
1064    /// ㅞ
1065    We,
1066    /// ㅟ
1067    Wi,
1068    /// ㅢ
1069    Ui,
1070}
1071
1072impl JamoVowelComposite {
1073    /// Returns the modern jamo character for this composite vowel.
1074    /// No position is needed since vowels only have one encoding
1075    /// in the modern Jamo Unicode block.
1076    ///
1077    /// **Example:**
1078    /// ```rust
1079    /// use hangul_cd::jamo::JamoVowelComposite;
1080    ///
1081    /// let wae = JamoVowelComposite::Wae;
1082    /// assert_eq!(wae.char_modern(), '\u{116B}'); // Modern ㅙ
1083    /// ```
1084    pub fn char_modern(&self) -> char {
1085        match self {
1086            JamoVowelComposite::Wa => '\u{116A}',
1087            JamoVowelComposite::Wae => '\u{116B}',
1088            JamoVowelComposite::Oe => '\u{116C}',
1089            JamoVowelComposite::Wo => '\u{116F}',
1090            JamoVowelComposite::We => '\u{1170}',
1091            JamoVowelComposite::Wi => '\u{1171}',
1092            JamoVowelComposite::Ui => '\u{1174}',
1093        }
1094    }
1095
1096    /// Returns the compatibility jamo character for this composite vowel.
1097    ///
1098    /// **Example:**
1099    /// ```rust
1100    /// use hangul_cd::jamo::JamoVowelComposite;
1101    ///
1102    /// let wae = JamoVowelComposite::Wae;
1103    /// assert_eq!(wae.char_compatibility(), 'ㅙ');
1104    /// ```
1105    pub fn char_compatibility(&self) -> char {
1106        match self {
1107            JamoVowelComposite::Wa => 'ㅘ',
1108            JamoVowelComposite::Wae => 'ㅙ',
1109            JamoVowelComposite::Oe => 'ㅚ',
1110            JamoVowelComposite::Wo => 'ㅝ',
1111            JamoVowelComposite::We => 'ㅞ',
1112            JamoVowelComposite::Wi => 'ㅟ',
1113            JamoVowelComposite::Ui => 'ㅢ',
1114        }
1115    }
1116
1117    /// Decomposes the composite vowel into its two constituent singular vowels.
1118    ///
1119    /// **Example:**
1120    /// ```rust
1121    /// use hangul_cd::jamo::{
1122    ///     Jamo,
1123    ///     JamoVowelSingular,
1124    ///     JamoVowelComposite,
1125    /// };
1126    ///
1127    /// let composite = JamoVowelComposite::Wae;
1128    /// let (first, second) = composite.decompose();
1129    /// assert_eq!(first, Jamo::Vowel(JamoVowelSingular::O));
1130    /// assert_eq!(second, Jamo::Vowel(JamoVowelSingular::Ae));
1131    /// ```
1132    pub fn decompose(&self) -> (Jamo, Jamo) {
1133        match self {
1134            JamoVowelComposite::Wa => (
1135                Jamo::Vowel(JamoVowelSingular::O),
1136                Jamo::Vowel(JamoVowelSingular::A),
1137            ),
1138            JamoVowelComposite::Wae => (
1139                Jamo::Vowel(JamoVowelSingular::O),
1140                Jamo::Vowel(JamoVowelSingular::Ae),
1141            ),
1142            JamoVowelComposite::Oe => (
1143                Jamo::Vowel(JamoVowelSingular::O),
1144                Jamo::Vowel(JamoVowelSingular::I),
1145            ),
1146            JamoVowelComposite::Wo => (
1147                Jamo::Vowel(JamoVowelSingular::U),
1148                Jamo::Vowel(JamoVowelSingular::Eo),
1149            ),
1150            JamoVowelComposite::We => (
1151                Jamo::Vowel(JamoVowelSingular::U),
1152                Jamo::Vowel(JamoVowelSingular::E),
1153            ),
1154            JamoVowelComposite::Wi => (
1155                Jamo::Vowel(JamoVowelSingular::U),
1156                Jamo::Vowel(JamoVowelSingular::I),
1157            ),
1158            JamoVowelComposite::Ui => (
1159                Jamo::Vowel(JamoVowelSingular::Eu),
1160                Jamo::Vowel(JamoVowelSingular::I),
1161            ),
1162        }
1163    }
1164}
1165
1166/// An enum representing Hangul jamo, including both consonants and vowels,
1167/// as well as singular and composite forms.
1168#[derive(Debug, PartialEq, Eq, Clone)]
1169pub enum JamoPosition {
1170    Initial,
1171    Vowel,
1172    Final,
1173}
1174
1175impl Jamo {
1176    /// Returns the compatibility jamo character for this Jamo.
1177    /// This is a different Unicode codepoint than the modernized version.
1178    ///
1179    /// **Example:**
1180    /// ```rust
1181    /// use hangul_cd::jamo::{Jamo, JamoConsonantSingular};
1182    /// let jamo = Jamo::Consonant(JamoConsonantSingular::Giyeok);
1183    /// assert_eq!(jamo.char_compatibility(), 'ㄱ');
1184    /// ```
1185    pub fn char_compatibility(&self) -> char {
1186        match self {
1187            Jamo::Consonant(c) => c.char_compatibility(),
1188            Jamo::CompositeConsonant(c) => c.char_compatibility(),
1189            Jamo::Vowel(c) => c.char_compatibility(),
1190            Jamo::CompositeVowel(c) => c.char_compatibility(),
1191        }
1192    }
1193
1194    /// Returns the modern jamo character for this Jamo.
1195    /// This is a different Unicode codepoint than the compatibility version.
1196    /// A position must be specified because some jamo have multiple forms;
1197    /// for example, consonants can have different modern block
1198    /// encodings depending on whether
1199    /// they appear at the beginning or end of a syllable.
1200    ///
1201    /// **Example:**
1202    /// ```rust
1203    /// use hangul_cd::jamo::{Jamo, JamoConsonantSingular, JamoPosition};
1204    /// let jamo = Jamo::Consonant(JamoConsonantSingular::Giyeok);
1205    /// assert_eq!(jamo.char_modern(JamoPosition::Initial), Some('ᄀ'));
1206    /// ```
1207    pub fn char_modern(&self, position: JamoPosition) -> Option<char> {
1208        match self {
1209            Jamo::Consonant(c) => c.char_modern(position),
1210            Jamo::CompositeConsonant(c) => match position {
1211                JamoPosition::Initial => c.char_modern_initial(),
1212                JamoPosition::Final => c.char_modern_final(),
1213                JamoPosition::Vowel => None,
1214            },
1215            Jamo::Vowel(c) => match position {
1216                JamoPosition::Vowel => Some(c.char_modern()),
1217                _ => None,
1218            },
1219            Jamo::CompositeVowel(c) => match position {
1220                JamoPosition::Vowel => Some(c.char_modern()),
1221                _ => None,
1222            },
1223        }
1224    }
1225
1226    /// Creates a Jamo from a modern jamo character. There is no need
1227    /// to specify the position (initial, vowel, final) of the jamo character.
1228    ///
1229    /// **Example:**
1230    /// ```rust
1231    /// use hangul_cd::jamo::{Jamo, JamoConsonantSingular};
1232    /// let jamo = Jamo::from_modern_jamo('ᄀ').unwrap();
1233    /// assert_eq!(jamo, Jamo::Consonant(JamoConsonantSingular::Giyeok));
1234    /// ```
1235    pub fn from_modern_jamo(c: char) -> Result<Self, JamoError> {
1236        let cc = modern_to_compatibility_jamo(c);
1237        Self::from_compatibility_jamo(cc)
1238    }
1239
1240    /// Creates a Jamo from a compatibility jamo character.
1241    ///
1242    /// **Example:**
1243    /// ```rust
1244    /// use hangul_cd::jamo::{Jamo, JamoConsonantSingular};
1245    /// let jamo = Jamo::from_compatibility_jamo('ㄱ').unwrap();
1246    /// assert_eq!(jamo, Jamo::Consonant(JamoConsonantSingular::Giyeok));
1247    /// ```
1248    pub fn from_compatibility_jamo(c: char) -> Result<Self, JamoError> {
1249        match c {
1250            // Singular consonants
1251            'ㄱ' => Ok(Jamo::Consonant(JamoConsonantSingular::Giyeok)),
1252            'ㄴ' => Ok(Jamo::Consonant(JamoConsonantSingular::Nieun)),
1253            'ㄷ' => Ok(Jamo::Consonant(JamoConsonantSingular::Digeut)),
1254            'ㄹ' => Ok(Jamo::Consonant(JamoConsonantSingular::Rieul)),
1255            'ㅁ' => Ok(Jamo::Consonant(JamoConsonantSingular::Mieum)),
1256            'ㅂ' => Ok(Jamo::Consonant(JamoConsonantSingular::Bieup)),
1257            'ㅅ' => Ok(Jamo::Consonant(JamoConsonantSingular::Siot)),
1258            'ㅇ' => Ok(Jamo::Consonant(JamoConsonantSingular::Ieung)),
1259            'ㅈ' => Ok(Jamo::Consonant(JamoConsonantSingular::Jieut)),
1260            'ㅊ' => Ok(Jamo::Consonant(JamoConsonantSingular::Chieut)),
1261            'ㅋ' => Ok(Jamo::Consonant(JamoConsonantSingular::Kieuk)),
1262            'ㅌ' => Ok(Jamo::Consonant(JamoConsonantSingular::Tieut)),
1263            'ㅍ' => Ok(Jamo::Consonant(JamoConsonantSingular::Pieup)),
1264            'ㅎ' => Ok(Jamo::Consonant(JamoConsonantSingular::Hieut)),
1265
1266            // Composite consonants
1267            'ㄳ' => Ok(Jamo::CompositeConsonant(JamoConsonantComposite::GiyeokSiot)),
1268            'ㄵ' => Ok(Jamo::CompositeConsonant(JamoConsonantComposite::NieunJieut)),
1269            'ㄶ' => Ok(Jamo::CompositeConsonant(JamoConsonantComposite::NieunHieut)),
1270            'ㄺ' => Ok(Jamo::CompositeConsonant(
1271                JamoConsonantComposite::RieulGiyeok,
1272            )),
1273            'ㄻ' => Ok(Jamo::CompositeConsonant(JamoConsonantComposite::RieulMieum)),
1274            'ㄼ' => Ok(Jamo::CompositeConsonant(JamoConsonantComposite::RieulBieup)),
1275            'ㄽ' => Ok(Jamo::CompositeConsonant(JamoConsonantComposite::RieulSiot)),
1276            'ㄾ' => Ok(Jamo::CompositeConsonant(JamoConsonantComposite::RieulTieut)),
1277            'ㄿ' => Ok(Jamo::CompositeConsonant(JamoConsonantComposite::RieulPieup)),
1278            'ㅀ' => Ok(Jamo::CompositeConsonant(JamoConsonantComposite::RieulHieut)),
1279            'ㄲ' => Ok(Jamo::CompositeConsonant(
1280                JamoConsonantComposite::SsangGiyeok,
1281            )),
1282            'ㄸ' => Ok(Jamo::CompositeConsonant(
1283                JamoConsonantComposite::SsangDigeut,
1284            )),
1285            'ㅃ' => Ok(Jamo::CompositeConsonant(JamoConsonantComposite::SsangBieup)),
1286            'ㅆ' => Ok(Jamo::CompositeConsonant(JamoConsonantComposite::SsangSiot)),
1287            'ㅉ' => Ok(Jamo::CompositeConsonant(JamoConsonantComposite::SsangJieut)),
1288            'ㅄ' => Ok(Jamo::CompositeConsonant(JamoConsonantComposite::BieupSiot)),
1289
1290            // Singular vowels
1291            'ㅏ' => Ok(Jamo::Vowel(JamoVowelSingular::A)),
1292            'ㅐ' => Ok(Jamo::Vowel(JamoVowelSingular::Ae)),
1293            'ㅑ' => Ok(Jamo::Vowel(JamoVowelSingular::Ya)),
1294            'ㅒ' => Ok(Jamo::Vowel(JamoVowelSingular::Yae)),
1295            'ㅓ' => Ok(Jamo::Vowel(JamoVowelSingular::Eo)),
1296            'ㅔ' => Ok(Jamo::Vowel(JamoVowelSingular::E)),
1297            'ㅕ' => Ok(Jamo::Vowel(JamoVowelSingular::Yeo)),
1298            'ㅖ' => Ok(Jamo::Vowel(JamoVowelSingular::Ye)),
1299            'ㅗ' => Ok(Jamo::Vowel(JamoVowelSingular::O)),
1300            'ㅛ' => Ok(Jamo::Vowel(JamoVowelSingular::Yo)),
1301            'ㅜ' => Ok(Jamo::Vowel(JamoVowelSingular::U)),
1302            'ㅠ' => Ok(Jamo::Vowel(JamoVowelSingular::Yu)),
1303            'ㅡ' => Ok(Jamo::Vowel(JamoVowelSingular::Eu)),
1304            'ㅣ' => Ok(Jamo::Vowel(JamoVowelSingular::I)),
1305
1306            // Composite vowels
1307            'ㅘ' => Ok(Jamo::CompositeVowel(JamoVowelComposite::Wa)),
1308            'ㅙ' => Ok(Jamo::CompositeVowel(JamoVowelComposite::Wae)),
1309            'ㅚ' => Ok(Jamo::CompositeVowel(JamoVowelComposite::Oe)),
1310            'ㅝ' => Ok(Jamo::CompositeVowel(JamoVowelComposite::Wo)),
1311            'ㅞ' => Ok(Jamo::CompositeVowel(JamoVowelComposite::We)),
1312            'ㅟ' => Ok(Jamo::CompositeVowel(JamoVowelComposite::Wi)),
1313            'ㅢ' => Ok(Jamo::CompositeVowel(JamoVowelComposite::Ui)),
1314
1315            _ => Err(JamoError::FromCharError(c)),
1316        }
1317    }
1318}
1319
1320#[cfg(test)]
1321mod tests {
1322    use super::*;
1323
1324    #[test]
1325    fn character_from_char_identifies_valid_consonants_compatibility() {
1326        let tests = vec![
1327            ('ㄱ', Jamo::Consonant(JamoConsonantSingular::Giyeok)),
1328            ('ㄴ', Jamo::Consonant(JamoConsonantSingular::Nieun)),
1329            ('ㄷ', Jamo::Consonant(JamoConsonantSingular::Digeut)),
1330            ('ㄹ', Jamo::Consonant(JamoConsonantSingular::Rieul)),
1331            ('ㅁ', Jamo::Consonant(JamoConsonantSingular::Mieum)),
1332            ('ㅂ', Jamo::Consonant(JamoConsonantSingular::Bieup)),
1333            ('ㅅ', Jamo::Consonant(JamoConsonantSingular::Siot)),
1334            ('ㅇ', Jamo::Consonant(JamoConsonantSingular::Ieung)),
1335            ('ㅈ', Jamo::Consonant(JamoConsonantSingular::Jieut)),
1336            ('ㅊ', Jamo::Consonant(JamoConsonantSingular::Chieut)),
1337            ('ㅋ', Jamo::Consonant(JamoConsonantSingular::Kieuk)),
1338            ('ㅌ', Jamo::Consonant(JamoConsonantSingular::Tieut)),
1339            ('ㅍ', Jamo::Consonant(JamoConsonantSingular::Pieup)),
1340            ('ㅎ', Jamo::Consonant(JamoConsonantSingular::Hieut)),
1341        ];
1342        for (c, expected_jamo) in tests {
1343            let result = Character::from_char(c);
1344            assert_eq!(
1345                result,
1346                Ok(Character::Hangul(expected_jamo)),
1347                "Failed on consonant: {}; got result: {:?}",
1348                c,
1349                result
1350            )
1351        }
1352    }
1353
1354    #[test]
1355    fn character_from_char_identifies_valid_consonants_modern() {
1356        let tests = vec![
1357            ('ᄀ', Jamo::Consonant(JamoConsonantSingular::Giyeok)),
1358            ('ᄂ', Jamo::Consonant(JamoConsonantSingular::Nieun)),
1359            ('ᄃ', Jamo::Consonant(JamoConsonantSingular::Digeut)),
1360            ('ᄅ', Jamo::Consonant(JamoConsonantSingular::Rieul)),
1361            ('ᄆ', Jamo::Consonant(JamoConsonantSingular::Mieum)),
1362            ('ᄇ', Jamo::Consonant(JamoConsonantSingular::Bieup)),
1363            ('ᄉ', Jamo::Consonant(JamoConsonantSingular::Siot)),
1364            ('ᄋ', Jamo::Consonant(JamoConsonantSingular::Ieung)),
1365            ('ᄌ', Jamo::Consonant(JamoConsonantSingular::Jieut)),
1366            ('ᄎ', Jamo::Consonant(JamoConsonantSingular::Chieut)),
1367            ('ᄏ', Jamo::Consonant(JamoConsonantSingular::Kieuk)),
1368            ('ᄐ', Jamo::Consonant(JamoConsonantSingular::Tieut)),
1369            ('ᄑ', Jamo::Consonant(JamoConsonantSingular::Pieup)),
1370            ('ᄒ', Jamo::Consonant(JamoConsonantSingular::Hieut)),
1371        ];
1372        for (c, expected_jamo) in tests {
1373            let result = Character::from_char(c);
1374            assert_eq!(
1375                result,
1376                Ok(Character::Hangul(expected_jamo)),
1377                "Failed on consonant: {}; got result: {:?}",
1378                c,
1379                result
1380            )
1381        }
1382    }
1383
1384    #[test]
1385    fn character_from_char_identifies_valid_vowels_compatibility() {
1386        let tests = vec![
1387            ('ㅏ', Jamo::Vowel(JamoVowelSingular::A)),
1388            ('ㅐ', Jamo::Vowel(JamoVowelSingular::Ae)),
1389            ('ㅑ', Jamo::Vowel(JamoVowelSingular::Ya)),
1390            ('ㅒ', Jamo::Vowel(JamoVowelSingular::Yae)),
1391            ('ㅓ', Jamo::Vowel(JamoVowelSingular::Eo)),
1392            ('ㅔ', Jamo::Vowel(JamoVowelSingular::E)),
1393            ('ㅕ', Jamo::Vowel(JamoVowelSingular::Yeo)),
1394            ('ㅖ', Jamo::Vowel(JamoVowelSingular::Ye)),
1395            ('ㅗ', Jamo::Vowel(JamoVowelSingular::O)),
1396            ('ㅛ', Jamo::Vowel(JamoVowelSingular::Yo)),
1397            ('ㅜ', Jamo::Vowel(JamoVowelSingular::U)),
1398            ('ㅠ', Jamo::Vowel(JamoVowelSingular::Yu)),
1399            ('ㅡ', Jamo::Vowel(JamoVowelSingular::Eu)),
1400            ('ㅣ', Jamo::Vowel(JamoVowelSingular::I)),
1401        ];
1402        for (c, expected_jamo) in tests {
1403            let result = Character::from_char(c);
1404            assert_eq!(
1405                result,
1406                Ok(Character::Hangul(expected_jamo)),
1407                "Failed on vowel: {}; got result: {:?}",
1408                c,
1409                result
1410            )
1411        }
1412    }
1413
1414    #[test]
1415    fn character_from_char_identifies_valid_vowels_modern() {
1416        let tests = vec![
1417            ('ᅡ', Jamo::Vowel(JamoVowelSingular::A)),
1418            ('ᅢ', Jamo::Vowel(JamoVowelSingular::Ae)),
1419            ('ᅣ', Jamo::Vowel(JamoVowelSingular::Ya)),
1420            ('ᅤ', Jamo::Vowel(JamoVowelSingular::Yae)),
1421            ('ᅥ', Jamo::Vowel(JamoVowelSingular::Eo)),
1422            ('ᅦ', Jamo::Vowel(JamoVowelSingular::E)),
1423            ('ᅧ', Jamo::Vowel(JamoVowelSingular::Yeo)),
1424            ('ᅨ', Jamo::Vowel(JamoVowelSingular::Ye)),
1425            ('ᅩ', Jamo::Vowel(JamoVowelSingular::O)),
1426            ('ᅭ', Jamo::Vowel(JamoVowelSingular::Yo)),
1427            ('ᅮ', Jamo::Vowel(JamoVowelSingular::U)),
1428            ('ᅲ', Jamo::Vowel(JamoVowelSingular::Yu)),
1429            ('ᅳ', Jamo::Vowel(JamoVowelSingular::Eu)),
1430            ('ᅵ', Jamo::Vowel(JamoVowelSingular::I)),
1431        ];
1432        for (c, expected_jamo) in tests {
1433            let result = Character::from_char(c);
1434            assert_eq!(
1435                result,
1436                Ok(Character::Hangul(expected_jamo)),
1437                "Failed on vowel: {}; got result: {:?}",
1438                c,
1439                result
1440            )
1441        }
1442    }
1443
1444    #[test]
1445    fn character_from_char_identifies_double_initials_compatibility() {
1446        let tests = vec![
1447            (
1448                'ㄲ',
1449                Jamo::CompositeConsonant(JamoConsonantComposite::SsangGiyeok),
1450            ),
1451            (
1452                'ㄸ',
1453                Jamo::CompositeConsonant(JamoConsonantComposite::SsangDigeut),
1454            ),
1455            (
1456                'ㅃ',
1457                Jamo::CompositeConsonant(JamoConsonantComposite::SsangBieup),
1458            ),
1459            (
1460                'ㅆ',
1461                Jamo::CompositeConsonant(JamoConsonantComposite::SsangSiot),
1462            ),
1463            (
1464                'ㅉ',
1465                Jamo::CompositeConsonant(JamoConsonantComposite::SsangJieut),
1466            ),
1467        ];
1468        for (c, expected_jamo) in tests {
1469            let result = Character::from_char(c);
1470            assert_eq!(
1471                result,
1472                Ok(Character::Hangul(expected_jamo)),
1473                "Failed on double initial: {}; got result: {:?}",
1474                c,
1475                result
1476            )
1477        }
1478    }
1479
1480    #[test]
1481    fn character_from_char_identifies_double_initials_modern() {
1482        let tests = vec![
1483            (
1484                'ᄁ',
1485                Jamo::CompositeConsonant(JamoConsonantComposite::SsangGiyeok),
1486            ),
1487            (
1488                'ᄄ',
1489                Jamo::CompositeConsonant(JamoConsonantComposite::SsangDigeut),
1490            ),
1491            (
1492                'ᄈ',
1493                Jamo::CompositeConsonant(JamoConsonantComposite::SsangBieup),
1494            ),
1495            (
1496                'ᄊ',
1497                Jamo::CompositeConsonant(JamoConsonantComposite::SsangSiot),
1498            ),
1499            (
1500                'ᄍ',
1501                Jamo::CompositeConsonant(JamoConsonantComposite::SsangJieut),
1502            ),
1503        ];
1504        for (c, expected_jamo) in tests {
1505            let result = Character::from_char(c);
1506            assert_eq!(
1507                result,
1508                Ok(Character::Hangul(expected_jamo)),
1509                "Failed on double initial: {}; got result: {:?}",
1510                c,
1511                result
1512            )
1513        }
1514    }
1515
1516    #[test]
1517    fn character_from_char_identifies_composite_vowels_compatibility() {
1518        let tests = vec![
1519            ('ㅘ', Jamo::CompositeVowel(JamoVowelComposite::Wa)),
1520            ('ㅙ', Jamo::CompositeVowel(JamoVowelComposite::Wae)),
1521            ('ㅚ', Jamo::CompositeVowel(JamoVowelComposite::Oe)),
1522            ('ㅝ', Jamo::CompositeVowel(JamoVowelComposite::Wo)),
1523            ('ㅞ', Jamo::CompositeVowel(JamoVowelComposite::We)),
1524            ('ㅟ', Jamo::CompositeVowel(JamoVowelComposite::Wi)),
1525            ('ㅢ', Jamo::CompositeVowel(JamoVowelComposite::Ui)),
1526        ];
1527        for (c, expected_jamo) in tests {
1528            let result = Character::from_char(c);
1529            assert_eq!(
1530                result,
1531                Ok(Character::Hangul(expected_jamo)),
1532                "Failed on composite vowel: {}; got result: {:?}",
1533                c,
1534                result
1535            )
1536        }
1537    }
1538
1539    #[test]
1540    fn character_from_char_identifies_composite_vowels_modern() {
1541        let tests = vec![
1542            ('ᅪ', Jamo::CompositeVowel(JamoVowelComposite::Wa)),
1543            ('ᅫ', Jamo::CompositeVowel(JamoVowelComposite::Wae)),
1544            ('ᅬ', Jamo::CompositeVowel(JamoVowelComposite::Oe)),
1545            ('ᅯ', Jamo::CompositeVowel(JamoVowelComposite::Wo)),
1546            ('ᅰ', Jamo::CompositeVowel(JamoVowelComposite::We)),
1547            ('ᅱ', Jamo::CompositeVowel(JamoVowelComposite::Wi)),
1548            ('ᅴ', Jamo::CompositeVowel(JamoVowelComposite::Ui)),
1549        ];
1550        for (c, expected_jamo) in tests {
1551            let result = Character::from_char(c);
1552            assert_eq!(
1553                result,
1554                Ok(Character::Hangul(expected_jamo)),
1555                "Failed on composite vowel: {}; got result: {:?}",
1556                c,
1557                result
1558            )
1559        }
1560    }
1561
1562    #[test]
1563    fn character_from_char_identifies_composite_finals_compatibility() {
1564        let tests = vec![
1565            (
1566                'ㄳ',
1567                Jamo::CompositeConsonant(JamoConsonantComposite::GiyeokSiot),
1568            ),
1569            (
1570                'ㄵ',
1571                Jamo::CompositeConsonant(JamoConsonantComposite::NieunJieut),
1572            ),
1573            (
1574                'ㄶ',
1575                Jamo::CompositeConsonant(JamoConsonantComposite::NieunHieut),
1576            ),
1577            (
1578                'ㄺ',
1579                Jamo::CompositeConsonant(JamoConsonantComposite::RieulGiyeok),
1580            ),
1581            (
1582                'ㄻ',
1583                Jamo::CompositeConsonant(JamoConsonantComposite::RieulMieum),
1584            ),
1585            (
1586                'ㄼ',
1587                Jamo::CompositeConsonant(JamoConsonantComposite::RieulBieup),
1588            ),
1589            (
1590                'ㄽ',
1591                Jamo::CompositeConsonant(JamoConsonantComposite::RieulSiot),
1592            ),
1593            (
1594                'ㄾ',
1595                Jamo::CompositeConsonant(JamoConsonantComposite::RieulTieut),
1596            ),
1597            (
1598                'ㄿ',
1599                Jamo::CompositeConsonant(JamoConsonantComposite::RieulPieup),
1600            ),
1601            (
1602                'ㅀ',
1603                Jamo::CompositeConsonant(JamoConsonantComposite::RieulHieut),
1604            ),
1605        ];
1606        for (c, expected_jamo) in tests {
1607            let result = Character::from_char(c);
1608            assert_eq!(
1609                result,
1610                Ok(Character::Hangul(expected_jamo)),
1611                "Failed on composite final: {}; got result: {:?}",
1612                c,
1613                result
1614            )
1615        }
1616    }
1617
1618    #[test]
1619    fn character_from_char_identifies_composite_finals_modern() {
1620        let tests = vec![
1621            (
1622                'ᆪ',
1623                Jamo::CompositeConsonant(JamoConsonantComposite::GiyeokSiot),
1624            ),
1625            (
1626                'ᆬ',
1627                Jamo::CompositeConsonant(JamoConsonantComposite::NieunJieut),
1628            ),
1629            (
1630                'ᆭ',
1631                Jamo::CompositeConsonant(JamoConsonantComposite::NieunHieut),
1632            ),
1633            (
1634                'ᆰ',
1635                Jamo::CompositeConsonant(JamoConsonantComposite::RieulGiyeok),
1636            ),
1637            (
1638                'ᆱ',
1639                Jamo::CompositeConsonant(JamoConsonantComposite::RieulMieum),
1640            ),
1641            (
1642                'ᆲ',
1643                Jamo::CompositeConsonant(JamoConsonantComposite::RieulBieup),
1644            ),
1645            (
1646                'ᆳ',
1647                Jamo::CompositeConsonant(JamoConsonantComposite::RieulSiot),
1648            ),
1649            (
1650                'ᆴ',
1651                Jamo::CompositeConsonant(JamoConsonantComposite::RieulTieut),
1652            ),
1653            (
1654                'ᆵ',
1655                Jamo::CompositeConsonant(JamoConsonantComposite::RieulPieup),
1656            ),
1657            (
1658                'ᆶ',
1659                Jamo::CompositeConsonant(JamoConsonantComposite::RieulHieut),
1660            ),
1661        ];
1662        for (c, expected_jamo) in tests {
1663            let result = Character::from_char(c);
1664            assert_eq!(
1665                result,
1666                Ok(Character::Hangul(expected_jamo)),
1667                "Failed on composite final: {}; got result: {:?}",
1668                c,
1669                result
1670            )
1671        }
1672    }
1673
1674    #[test]
1675    fn character_from_char_identifies_non_hangul() {
1676        let non_hangul_chars = "ABCxyz123!@# ";
1677        for c in non_hangul_chars.chars() {
1678            let result = Character::from_char(c);
1679            assert!(
1680                result == Ok(Character::NonHangul(c)),
1681                "Failed on non-Hangul char: {}; got result: {:?}",
1682                c,
1683                result
1684            );
1685        }
1686    }
1687}