Skip to main content

hanmo/
compat.rs

1//! 호환 자모(U+3130) ↔ 조합용 자모(U+1100) 다리, 그리고 초성↔종성 변환.
2//! Compatibility-jamo (U+3130) <-> conjoining-jamo (U+1100) bridge, and
3//! cho<->jong consonant conversion. Pure Unicode data, no IME logic.
4
5use crate::syllable::{self, CHO, JONG, JUNG};
6
7/// 현대 초성 인덱스 → 호환 자모.
8pub const CHO_COMPAT: [u32; 19] = [
9    0x3131, 0x3132, 0x3134, 0x3137, 0x3138, 0x3139, 0x3141, 0x3142, 0x3143, 0x3145, 0x3146, 0x3147,
10    0x3148, 0x3149, 0x314A, 0x314B, 0x314C, 0x314D, 0x314E,
11];
12
13/// 현대 중성 인덱스 → 호환 자모(U+314F..=U+3163, 연속).
14pub const JUNG_COMPAT: [u32; 21] = [
15    0x314F, 0x3150, 0x3151, 0x3152, 0x3153, 0x3154, 0x3155, 0x3156, 0x3157, 0x3158, 0x3159, 0x315A,
16    0x315B, 0x315C, 0x315D, 0x315E, 0x315F, 0x3160, 0x3161, 0x3162, 0x3163,
17];
18
19/// 종성 인덱스(0=없음) → 호환 자모. 0 자리는 0.
20pub const JONG_COMPAT: [u32; 28] = [
21    0x0000, 0x3131, 0x3132, 0x3133, 0x3134, 0x3135, 0x3136, 0x3137, 0x3139, 0x313A, 0x313B, 0x313C,
22    0x313D, 0x313E, 0x313F, 0x3140, 0x3141, 0x3142, 0x3144, 0x3145, 0x3146, 0x3147, 0x3148, 0x314A,
23    0x314B, 0x314C, 0x314D, 0x314E,
24];
25
26/// 호환 자모 → 조합용 초성 코드포인트(현대 초성에 대응할 때만).
27pub fn cho_cp_for_compat(compat: u32) -> Option<u32> {
28    CHO_COMPAT.iter().position(|&c| c == compat).map(|i| CHO[i])
29}
30
31/// 호환 자모 → 조합용 중성 코드포인트(현대 중성에 대응할 때만).
32pub fn jung_cp_for_compat(compat: u32) -> Option<u32> {
33    JUNG_COMPAT
34        .iter()
35        .position(|&c| c == compat)
36        .map(|i| JUNG[i])
37}
38
39/// 호환 자모 → 조합용 종성 코드포인트(받침에 대응할 때만; 인덱스 0=받침 없음 제외).
40pub fn jong_cp_for_compat(compat: u32) -> Option<u32> {
41    JONG_COMPAT
42        .iter()
43        .enumerate()
44        .skip(1)
45        .find(|(_, &c)| c == compat)
46        .map(|(i, _)| JONG[i])
47}
48
49/// 호환 자모가 모음(U+314F..=U+3163)인가.
50pub fn is_vowel_compat(compat: u32) -> bool {
51    (0x314F..=0x3163).contains(&compat)
52}
53
54/// 초성 코드포인트를 같은 자음의 종성 코드포인트로 바꾼다(대응 없으면 None).
55/// 예: ㄱ초성(U+1100) → ㄱ종성(U+11A8).
56pub fn cho_to_jong(cho_cp: u32) -> Option<u32> {
57    let compat = syllable::cho_index(cho_cp).map(|i| CHO_COMPAT[i as usize])?;
58    jong_cp_for_compat(compat)
59}
60
61/// 종성 코드포인트를 같은 자음의 초성 코드포인트로 바꾼다(대응 없으면 None).
62/// 예: ㅇ종성(U+11BC) → ㅇ초성(U+110B).
63pub fn jong_to_cho(jong_cp: u32) -> Option<u32> {
64    let compat = syllable::jong_index(jong_cp).map(|i| JONG_COMPAT[i as usize])?;
65    cho_cp_for_compat(compat)
66}
67
68/// 조합용 자모 코드포인트의 호환 자모(현대 집합일 때만). 위치(초/중/종)별로 본다.
69/// `cho_compat`/`jung_compat`/`jong_compat` 의 통합 진입점이 필요할 때 사용.
70pub fn cho_compat(cho_cp: u32) -> Option<u32> {
71    syllable::cho_index(cho_cp).map(|i| CHO_COMPAT[i as usize])
72}
73/// 중성 코드포인트 → 호환 자모.
74pub fn jung_compat(jung_cp: u32) -> Option<u32> {
75    syllable::jung_index(jung_cp).map(|i| JUNG_COMPAT[i as usize])
76}
77/// 종성 코드포인트 → 호환 자모.
78pub fn jong_compat(jong_cp: u32) -> Option<u32> {
79    syllable::jong_index(jong_cp).map(|i| JONG_COMPAT[i as usize])
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn compat_bridge_roundtrip() {
88        // ㄱ초성 U+1100 → 호환 ㄱ U+3131 → 다시 초성 U+1100
89        assert_eq!(cho_compat(0x1100), Some(0x3131));
90        assert_eq!(cho_cp_for_compat(0x3131), Some(0x1100));
91        // ㄱ종성 U+11A8 → 호환 ㄱ U+3131
92        assert_eq!(jong_compat(0x11A8), Some(0x3131));
93        assert_eq!(jong_cp_for_compat(0x3131), Some(0x11A8));
94        // ㅏ중성 U+1161 → 호환 ㅏ U+314F
95        assert_eq!(jung_compat(0x1161), Some(0x314F));
96        assert_eq!(jung_cp_for_compat(0x314F), Some(0x1161));
97    }
98
99    #[test]
100    fn cho_jong_conversion() {
101        // ㄱ초성 ↔ ㄱ종성
102        assert_eq!(cho_to_jong(0x1100), Some(0x11A8));
103        assert_eq!(jong_to_cho(0x11A8), Some(0x1100));
104        // ㅇ초성 U+110B ↔ ㅇ종성 U+11BC
105        assert_eq!(cho_to_jong(0x110B), Some(0x11BC));
106        assert_eq!(jong_to_cho(0x11BC), Some(0x110B));
107    }
108
109    #[test]
110    fn is_vowel() {
111        assert!(is_vowel_compat(0x314F)); // ㅏ
112        assert!(is_vowel_compat(0x3163)); // ㅣ
113        assert!(!is_vowel_compat(0x3131)); // ㄱ
114    }
115}