const HANGUL_BASE: u32 = 0xAC00;
const JONGSEONG_COUNT: u32 = 28;
const JUNGSEONG_COUNT: u32 = 21;
static CHOSEONG: &[char] = &[
'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ',
'ㅌ', 'ㅍ', 'ㅎ',
];
static JUNGSEONG: &[char] = &[
'ㅏ', 'ㅐ', 'ㅑ', 'ㅒ', 'ㅓ', 'ㅔ', 'ㅕ', 'ㅖ', 'ㅗ', 'ㅘ', 'ㅙ', 'ㅚ', 'ㅛ', 'ㅜ', 'ㅝ', 'ㅞ',
'ㅟ', 'ㅠ', 'ㅡ', 'ㅢ', 'ㅣ',
];
static JONGSEONG: &[Option<char>] = &[
None,
Some('ㄱ'),
Some('ㄲ'),
Some('ㄳ'),
Some('ㄴ'),
Some('ㄵ'),
Some('ㄶ'),
Some('ㄷ'),
Some('ㄹ'),
Some('ㄺ'),
Some('ㄻ'),
Some('ㄼ'),
Some('ㄽ'),
Some('ㄾ'),
Some('ㄿ'),
Some('ㅀ'),
Some('ㅁ'),
Some('ㅂ'),
Some('ㅄ'),
Some('ㅅ'),
Some('ㅆ'),
Some('ㅇ'),
Some('ㅈ'),
Some('ㅊ'),
Some('ㅋ'),
Some('ㅌ'),
Some('ㅍ'),
Some('ㅎ'),
];
pub fn decompose(c: char) -> Option<(char, char, Option<char>)> {
let code = c as u32;
if !(0xAC00..=0xD7AF).contains(&code) {
return None;
}
let offset = code - HANGUL_BASE;
let choseong_idx = offset / (JUNGSEONG_COUNT * JONGSEONG_COUNT);
let jungseong_idx = (offset % (JUNGSEONG_COUNT * JONGSEONG_COUNT)) / JONGSEONG_COUNT;
let jongseong_idx = offset % JONGSEONG_COUNT;
let leading = *CHOSEONG.get(choseong_idx as usize)?;
let vowel = *JUNGSEONG.get(jungseong_idx as usize)?;
let trailing = JONGSEONG.get(jongseong_idx as usize).copied().flatten();
Some((leading, vowel, trailing))
}
pub fn decompose_string(text: &str) -> String {
let mut result = String::with_capacity(text.len() * 3);
for c in text.chars() {
if let Some((leading, vowel, trailing)) = decompose(c) {
result.push(leading);
result.push(vowel);
if let Some(t) = trailing {
result.push(t);
}
} else {
result.push(c);
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decompose_basic() {
let (l, v, t) = decompose('한').unwrap();
assert_eq!(l, 'ㅎ');
assert_eq!(v, 'ㅏ');
assert_eq!(t, Some('ㄴ'));
}
#[test]
fn decompose_no_trailing() {
let (l, v, t) = decompose('가').unwrap();
assert_eq!(l, 'ㄱ');
assert_eq!(v, 'ㅏ');
assert_eq!(t, None);
}
#[test]
fn decompose_non_hangul() {
assert!(decompose('a').is_none());
assert!(decompose('中').is_none());
}
#[test]
fn decompose_string_test() {
let result = decompose_string("한글");
assert_eq!(result, "ㅎㅏㄴㄱㅡㄹ");
}
#[test]
fn decompose_string_mixed() {
let result = decompose_string("hello한글world");
assert_eq!(result, "helloㅎㅏㄴㄱㅡㄹworld");
}
}