use crate::loader::TalkConfig;
use super::tokenizer::{Token, TokenKind};
#[derive(Debug, Clone, Default)]
pub struct WaitValues {
pub normal: i64,
pub period: i64,
pub comma: i64,
pub strong: i64,
pub leader: i64,
}
impl WaitValues {
pub fn from_config(config: &TalkConfig) -> Self {
Self {
normal: config.script_wait_normal,
period: config.script_wait_period,
comma: config.script_wait_comma,
strong: config.script_wait_strong,
leader: config.script_wait_leader,
}
}
pub fn get_wait(&self, kind: &TokenKind) -> Option<i64> {
match kind {
TokenKind::SakuraScript => None,
TokenKind::LineEndProhibited => None,
TokenKind::General => Some(self.normal),
TokenKind::Period => Some(self.period),
TokenKind::Comma => Some(self.comma),
TokenKind::Strong => Some(self.strong),
TokenKind::Leader => Some(self.leader),
TokenKind::LineStartProhibited => None, }
}
}
pub fn insert_waits(tokens: &[Token], wait_values: &WaitValues) -> String {
let mut result = String::new();
let mut pending_max_wait: Option<i64> = None;
let mut pending_text = String::new();
for token in tokens {
match &token.kind {
TokenKind::SakuraScript => {
flush_pending(&mut result, &mut pending_text, &mut pending_max_wait);
result.push_str(&token.text);
}
TokenKind::LineEndProhibited => {
flush_pending(&mut result, &mut pending_text, &mut pending_max_wait);
result.push_str(&token.text);
}
TokenKind::General => {
flush_pending(&mut result, &mut pending_text, &mut pending_max_wait);
append_with_per_char_wait(&mut result, &token.text, wait_values.normal);
}
TokenKind::Leader => {
flush_pending(&mut result, &mut pending_text, &mut pending_max_wait);
append_with_per_char_wait(&mut result, &token.text, wait_values.leader);
}
TokenKind::Period | TokenKind::Comma | TokenKind::Strong | TokenKind::LineStartProhibited => {
let wait = match &token.kind {
TokenKind::Period => wait_values.period,
TokenKind::Comma => wait_values.comma,
TokenKind::Strong => wait_values.strong,
TokenKind::LineStartProhibited => {
pending_max_wait.unwrap_or(0)
}
_ => unreachable!(),
};
pending_text.push_str(&token.text);
pending_max_wait = Some(pending_max_wait.map_or(wait, |m| m.max(wait)));
}
}
}
flush_pending(&mut result, &mut pending_text, &mut pending_max_wait);
result
}
fn flush_pending(result: &mut String, pending_text: &mut String, pending_max_wait: &mut Option<i64>) {
if !pending_text.is_empty() {
result.push_str(pending_text);
if let Some(max_wait) = pending_max_wait.take() {
let effective_wait = max_wait - 50;
if effective_wait > 0 {
result.push_str(&format!(r"\_w[{}]", effective_wait));
}
}
pending_text.clear();
}
*pending_max_wait = None;
}
fn append_with_per_char_wait(result: &mut String, text: &str, wait_ms: i64) {
let effective_wait = wait_ms - 50;
for c in text.chars() {
result.push(c);
if effective_wait > 0 {
result.push_str(&format!(r"\_w[{}]", effective_wait));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_token(kind: TokenKind, text: &str) -> Token {
Token::new(kind, text)
}
fn default_wait_values() -> WaitValues {
WaitValues {
normal: 100, period: 1000, comma: 500, strong: 500, leader: 200, }
}
#[test]
fn test_general_text_wait() {
let tokens = vec![
make_token(TokenKind::General, "こ"),
make_token(TokenKind::General, "ん"),
make_token(TokenKind::General, "に"),
make_token(TokenKind::General, "ち"),
make_token(TokenKind::General, "は"),
];
let wait_values = default_wait_values();
let result = insert_waits(&tokens, &wait_values);
assert_eq!(result, r"こ\_w[50]ん\_w[50]に\_w[50]ち\_w[50]は\_w[50]");
}
#[test]
fn test_sakura_script_no_wait() {
let tokens = vec![
make_token(TokenKind::SakuraScript, r"\h"),
make_token(TokenKind::SakuraScript, r"\s[0]"),
make_token(TokenKind::General, "あ"),
];
let wait_values = default_wait_values();
let result = insert_waits(&tokens, &wait_values);
assert_eq!(result, r"\h\s[0]あ\_w[50]");
}
#[test]
fn test_period_wait() {
let tokens = vec![
make_token(TokenKind::General, "あ"),
make_token(TokenKind::Period, "。"),
];
let wait_values = default_wait_values();
let result = insert_waits(&tokens, &wait_values);
assert_eq!(result, r"あ\_w[50]。\_w[950]");
}
#[test]
fn test_consecutive_punctuation_max_wait() {
let tokens = vec![
make_token(TokenKind::LineStartProhibited, "」"),
make_token(TokenKind::LineStartProhibited, "」"),
make_token(TokenKind::LineStartProhibited, "」"),
make_token(TokenKind::Strong, "!"),
make_token(TokenKind::Strong, "?"),
make_token(TokenKind::Period, "。"),
make_token(TokenKind::Comma, "、"),
];
let wait_values = default_wait_values();
let result = insert_waits(&tokens, &wait_values);
assert_eq!(result, r"」」」!?。、\_w[950]");
}
#[test]
fn test_leader_per_char_wait() {
let tokens = vec![
make_token(TokenKind::Leader, "…"),
make_token(TokenKind::Leader, "…"),
];
let wait_values = default_wait_values();
let result = insert_waits(&tokens, &wait_values);
assert_eq!(result, r"…\_w[150]…\_w[150]");
}
#[test]
fn test_line_end_prohibited_no_wait() {
let tokens = vec![
make_token(TokenKind::LineEndProhibited, "「"),
make_token(TokenKind::General, "あ"),
];
let wait_values = default_wait_values();
let result = insert_waits(&tokens, &wait_values);
assert_eq!(result, r"「あ\_w[50]");
}
#[test]
fn test_zero_wait_skip() {
let tokens = vec![make_token(TokenKind::General, "あ")];
let wait_values = WaitValues {
normal: 50, ..Default::default()
};
let result = insert_waits(&tokens, &wait_values);
assert_eq!(result, "あ");
}
#[test]
fn test_negative_wait_skip() {
let tokens = vec![make_token(TokenKind::General, "あ")];
let wait_values = WaitValues {
normal: 30, ..Default::default()
};
let result = insert_waits(&tokens, &wait_values);
assert_eq!(result, "あ");
}
#[test]
fn test_mixed_text() {
let tokens = vec![
make_token(TokenKind::SakuraScript, r"\h"),
make_token(TokenKind::General, "あ"),
make_token(TokenKind::Period, "。"),
make_token(TokenKind::General, "い"),
];
let wait_values = default_wait_values();
let result = insert_waits(&tokens, &wait_values);
assert_eq!(result, r"\hあ\_w[50]。\_w[950]い\_w[50]");
}
#[test]
fn test_empty_tokens() {
let tokens: Vec<Token> = vec![];
let wait_values = default_wait_values();
let result = insert_waits(&tokens, &wait_values);
assert_eq!(result, "");
}
#[test]
fn test_requirement_7_1_example() {
let tokens = vec![
make_token(TokenKind::LineStartProhibited, "」"),
make_token(TokenKind::LineStartProhibited, "」"),
make_token(TokenKind::LineStartProhibited, "」"),
make_token(TokenKind::Strong, "!"),
make_token(TokenKind::Strong, "?"),
make_token(TokenKind::Period, "。"),
make_token(TokenKind::Comma, "、"),
];
let wait_values = WaitValues {
period: 1000,
comma: 500,
strong: 500,
..Default::default()
};
let result = insert_waits(&tokens, &wait_values);
assert_eq!(result, r"」」」!?。、\_w[950]");
}
#[test]
fn test_requirement_7_2_example() {
let tokens = vec![
make_token(TokenKind::General, "こ"),
make_token(TokenKind::General, "ん"),
make_token(TokenKind::General, "に"),
make_token(TokenKind::General, "ち"),
make_token(TokenKind::General, "は"),
];
let wait_values = WaitValues {
normal: 100,
..Default::default()
};
let result = insert_waits(&tokens, &wait_values);
assert_eq!(result, r"こ\_w[50]ん\_w[50]に\_w[50]ち\_w[50]は\_w[50]");
}
#[test]
fn test_requirement_7_3_example() {
let tokens = vec![
make_token(TokenKind::SakuraScript, r"\h"),
make_token(TokenKind::SakuraScript, r"\s[0]"),
make_token(TokenKind::General, "こ"),
make_token(TokenKind::General, "ん"),
make_token(TokenKind::General, "に"),
make_token(TokenKind::General, "ち"),
make_token(TokenKind::General, "は"),
];
let wait_values = WaitValues {
normal: 100,
..Default::default()
};
let result = insert_waits(&tokens, &wait_values);
assert_eq!(result, r"\h\s[0]こ\_w[50]ん\_w[50]に\_w[50]ち\_w[50]は\_w[50]");
}
}