use crate::char_struct::CharType;
use crate::rules::RuleMeta;
use crate::rules::context::RuleContext;
use crate::rules::traits::{BrailleRule, Phase, RuleResult};
use crate::utils;
pub static META: RuleMeta = RuleMeta {
section: "57",
subsection: None,
name: "symbol_grouping",
standard_ref: "2024 Korean Braille Standard, Ch.6 Sec.13 Art.57",
description: "Group repeated placeholder symbols with ⠸ ... ⠇",
};
const PREFIX: u8 = 56; const SUFFIX: u8 = 7;
fn placeholder_mark(ch: char) -> Option<u8> {
match ch {
'○' => Some(52), '×' => Some(45), '△' => Some(44), '☆' => Some(20), '◇' => Some(34), '◆' => Some(21), _ => None,
}
}
fn is_math_times_context(ctx: &RuleContext) -> bool {
if ctx.current_char() != '×' {
return false;
}
let prev = ctx.prev_char();
let next = ctx.next_char();
(prev.is_some_and(|c| c.is_ascii_digit()) && next.is_some_and(|c| c.is_ascii_digit()))
|| (prev.is_some_and(utils::is_korean_char) && next.is_some_and(utils::is_korean_char))
}
fn is_placeholder_times_context(ctx: &RuleContext) -> bool {
if ctx.current_char() != '×' {
return false;
}
if is_math_times_context(ctx) {
return false;
}
ctx.prev_char().is_some_and(|c| c == '×')
|| ctx.next_char().is_some_and(|c| c == '×')
|| (ctx.prev_char().is_none() && ctx.next_char().is_some_and(utils::is_korean_char))
}
pub struct Rule57;
impl BrailleRule for Rule57 {
fn meta(&self) -> &'static RuleMeta {
&META
}
fn phase(&self) -> Phase {
Phase::CoreEncoding
}
fn priority(&self) -> u16 {
90 }
fn matches(&self, ctx: &RuleContext) -> bool {
match ctx.char_type {
CharType::Symbol(c) => placeholder_mark(*c).is_some(),
CharType::MathSymbol('×') => is_placeholder_times_context(ctx),
_ => false,
}
}
fn apply(&self, ctx: &mut RuleContext) -> Result<RuleResult, String> {
let current = ctx.current_char();
if current == '×' && !is_placeholder_times_context(ctx) {
return Ok(RuleResult::Skip);
}
let Some(mark) = placeholder_mark(current) else {
return Ok(RuleResult::Skip);
};
let count = ctx.word_chars[ctx.index..]
.iter()
.take_while(|&&c| c == current)
.count();
ctx.emit(PREFIX);
for _ in 0..count {
ctx.emit(mark);
}
ctx.emit(SUFFIX);
if count > 1 {
*ctx.skip_count = count - 1;
}
Ok(RuleResult::Consumed)
}
}
#[cfg(test)]
mod tests {
#[test]
fn groups_repeated_symbols() {
assert_eq!(crate::encode_to_unicode("김○○ 씨").unwrap(), "⠈⠕⠢⠸⠴⠴⠇⠀⠠⠠⠕");
assert_eq!(crate::encode_to_unicode("△△도서관").unwrap(), "⠸⠬⠬⠇⠊⠥⠠⠎⠈⠧⠒");
}
#[test]
fn handles_times_dual_context() {
assert_eq!(crate::encode_to_unicode("5×3").unwrap(), "⠼⠑⠡⠼⠉");
assert_eq!(crate::encode_to_unicode("×란").unwrap(), "⠸⠭⠇⠐⠣⠒");
}
}