use core::fmt;
use crate::codec::{EncodeError, encode_with_lookup};
use crate::decomp::Decomp;
use crate::zigen;
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct EncodedCode {
bytes: [u8; 4],
len: u8,
}
impl EncodedCode {
#[inline]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..self.len as usize]
}
#[inline]
pub fn as_str(&self) -> &str {
unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
}
#[inline]
pub fn len(&self) -> usize {
self.len as usize
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
impl fmt::Debug for EncodedCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "EncodedCode({:?})", self.as_str())
}
}
impl fmt::Display for EncodedCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[inline]
pub fn encode_into(decomp: &Decomp, out: &mut [u8; 4]) -> Result<usize, EncodeError> {
encode_with_lookup(&decomp.as_ref(), zigen::lookup, out)
}
#[inline]
pub fn encode(decomp: &Decomp) -> Result<EncodedCode, EncodeError> {
let mut out = [0u8; 4];
let n = encode_into(decomp, &mut out)?;
Ok(EncodedCode {
bytes: out,
len: n as u8,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codec::{Shape, Stroke};
use crate::decomp::embedded_seed;
#[test]
fn encodes_jianming_as_letter_x4() {
let mut count = 0;
for (ch, decomp) in embedded_seed() {
if !crate::codec::JIANMING_ZIGEN.contains(ch) {
continue;
}
let code = encode(&decomp).expect("encode failed");
assert_eq!(code.len(), 4, "{ch}");
let bytes = code.as_bytes();
assert!(
bytes.iter().all(|b| *b == bytes[0]),
"{ch} → {} should be letter ×4",
code
);
count += 1;
}
assert_eq!(count, 25);
}
#[test]
fn encodes_dan_bi_hua() {
let cases: &[(char, &str, Stroke)] = &[
('一', "ggll", Stroke::Heng),
('丨', "hhll", Stroke::Shu),
('丿', "ttll", Stroke::Pie),
('丶', "yyll", Stroke::Na),
('乙', "nnll", Stroke::Zhe),
];
for (ch, expected, stroke) in cases {
let d = Decomp {
zigen: vec![*ch],
strokes: vec![*stroke],
shape: Shape::Whole,
};
let code = encode(&d).unwrap();
assert_eq!(code.as_str(), *expected, "{ch} mismatch");
}
}
#[test]
fn encodes_specific_jianming() {
let seed: std::collections::HashMap<char, _> = embedded_seed().into_iter().collect();
for (ch, expected) in &[
('王', "gggg"),
('土', "ffff"),
('大', "dddd"),
('禾', "tttt"),
('纟', "xxxx"),
] {
let d = seed.get(ch).unwrap();
let code = encode(d).unwrap();
assert_eq!(code.as_str(), *expected);
}
}
#[test]
fn encoder_does_not_allocate_on_zero_alloc_path() {
let d = Decomp {
zigen: vec!['王'],
strokes: vec![Stroke::Heng],
shape: Shape::Whole,
};
let mut buf = [0u8; 4];
let n = encode_into(&d, &mut buf).unwrap();
assert_eq!(&buf[..n], b"gggg");
}
}