use crate::*;
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct Settings {
pub word_len: Option<u8>,
pub checksum: Checksum,
pub decorate: bool,
}
impl Default for Settings {
fn default() -> Self {
Settings {
word_len: Some(3),
checksum: Checksum::default(),
decorate: false,
}
}
}
pub fn encode(data: impl AsRef<[u8]>) -> String {
encode_with_settings(data, Settings::default())
}
pub fn encode_with_settings(data: impl AsRef<[u8]>, settings: Settings) -> String {
encode_mono(data.as_ref(), settings)
}
#[inline(never)]
fn encode_mono(data: &[u8], settings: Settings) -> String {
let Settings{ word_len: max_word, checksum, decorate } = settings;
let mut sentence = Sentence {
buffer: Vec::with_capacity(3 * (data.len() + checksum.len())),
previous: None,
word_len: 0,
max_word: max_word.unwrap_or(u8::MAX),
decorate,
};
let mut hash = Fnv1a::new();
for (i, &byte) in data.iter().enumerate() {
hash.update(byte);
let encoded = running_code(byte, i);
sentence.push(encoded, hash);
}
let checksum_len = checksum.len();
let checksum_bytes = hash.bytes();
for &byte in &checksum_bytes[..checksum_len] {
hash.update(byte);
sentence.push(byte, hash);
}
let buffer = sentence.finalise();
String::from_utf8(buffer).expect("All syllables are valid UTF-8")
}
struct Sentence {
buffer: Vec<u8>,
previous: Option<&'static [u8]>,
word_len: u8,
max_word: u8,
decorate: bool,
}
impl Sentence {
fn push(&mut self, byte: u8, seed: Fnv1a) {
let syllable = syllables::get(byte);
let ambiguous = |preceding| {
let next = syllable[0];
syllables::char_follows(next, preceding)
};
let word_break = self.word_len >= self.max_word || self.previous.is_some_and(ambiguous);
let seed = seed.0.count_ones();
let (capitalise, delim): (bool, Option<&[u8]>) = match (word_break, self.decorate) {
(true, true) if seed > 19 => (true, Some(b". ")),
(true, true) if seed < 14 => (false, Some(b", ")),
(true, _) => (false, Some(b" ")),
(false, true) => (self.buffer.is_empty(), None),
(false, false) => (false, None),
};
if let Some(delim) = delim {
self.word_len = 0;
self.previous = None;
self.buffer.reserve(delim.len() + syllable.len());
self.buffer.extend_from_slice(delim);
}
self.buffer.extend_from_slice(syllable);
self.previous = Some(syllable);
self.word_len += 1;
if capitalise {
let first = self.buffer.len() - syllable.len();
self.buffer[first] = self.buffer[first].to_ascii_uppercase();
}
}
fn finalise(mut self) -> Vec<u8> {
if self.decorate && !self.buffer.is_empty() {
self.buffer.push(b'.');
}
self.buffer
}
}