1use crate::types::*;
2use unicode_segmentation::UnicodeSegmentation;
3
4const SPACE_STR_LIST: [&str; 2] = [" ", " "];
5
6pub struct FixedFormatter {
7 length: usize,
8 keep_original: bool,
9 #[allow(unused)]
10 typ: Option<ScriptType>,
11}
12
13impl FixedFormatter {
14 pub fn new(length: usize, keep_original: bool, typ: Option<ScriptType>) -> Self {
15 FixedFormatter {
16 length,
17 keep_original,
18 typ,
19 }
20 }
21
22 #[cfg(feature = "circus")]
23 fn is_circus(&self) -> bool {
24 matches!(self.typ, Some(ScriptType::Circus))
25 }
26
27 #[cfg(not(feature = "circus"))]
28 fn is_circus(&self) -> bool {
29 false
30 }
31
32 pub fn format(&self, message: &str) -> String {
33 let mut result = String::new();
34 let vec: Vec<_> = UnicodeSegmentation::graphemes(message, true).collect();
35 let mut current_length = 0;
36 let mut is_command = false;
37 let mut pre_is_lf = false;
38 let mut is_ruby = false;
39 let mut is_ruby_rt = false;
40 let mut last_command = None;
41 for grapheme in vec {
42 if grapheme == "\n" {
43 if self.keep_original
44 || (self.is_circus() && last_command.as_ref().is_some_and(|cmd| cmd == "@n"))
45 {
46 result.push('\n');
47 current_length = 0;
48 }
49 pre_is_lf = true;
50 continue;
51 }
52 if current_length >= self.length {
53 result.push('\n');
54 current_length = 0;
55 }
56 if (current_length == 0 || pre_is_lf) && SPACE_STR_LIST.contains(&grapheme) {
57 continue;
58 }
59 result.push_str(grapheme);
60 if self.is_circus() {
61 if grapheme == "@" {
62 is_command = true;
63 last_command = Some(String::new());
64 } else if is_command && grapheme.len() != 1
65 || !grapheme
66 .chars()
67 .next()
68 .unwrap_or(' ')
69 .is_ascii_alphanumeric()
70 {
71 is_command = false;
72 }
73 if grapheme == "{" {
74 is_ruby = true;
75 is_ruby_rt = true;
76 } else if is_ruby && grapheme == "/" {
77 is_ruby_rt = false;
78 continue;
79 } else if is_ruby && grapheme == "}" {
80 is_ruby = false;
81 continue;
82 }
83 }
84 if is_command {
85 if let Some(ref mut cmd) = last_command {
86 cmd.push_str(grapheme);
87 }
88 }
89 if !is_command && !is_ruby_rt {
90 current_length += 1;
91 }
92 pre_is_lf = false;
93 }
94 return result;
95 }
96}
97
98#[test]
99fn test_format() {
100 let formatter = FixedFormatter::new(10, false, None);
101 let message = "This is a test message.\nThis is another line.";
102 let formatted_message = formatter.format(message);
103 assert_eq!(
104 formatted_message,
105 "This is a \ntest messa\nge.This is\nanother li\nne."
106 );
107 assert_eq!(formatter.format("● This is a test."), "● This is \na test.");
108 assert_eq!(
109 formatter.format("● This is a test."),
110 "● This is \na test."
111 );
112 let fommater2 = FixedFormatter::new(10, true, None);
113 assert_eq!(
114 fommater2.format("● Th\n is is a te st."),
115 "● Th\nis is a te\nst."
116 );
117 #[cfg(feature = "circus")]
118 {
119 let circus_formatter = FixedFormatter::new(10, false, Some(ScriptType::Circus));
120 assert_eq!(
121 circus_formatter.format("● @cmd1@cmd2@cmd3中文字数是一\n 二三 四五六七八九十"),
122 "● @cmd1@cmd2@cmd3中文字数是一二三\n四五六七八九十"
123 );
124 assert_eq!(
125 circus_formatter
126 .format("● @cmd1@cmd2@cmd3{rubyText/中文}字数是一\n 二三 四五六七八九十"),
127 "● @cmd1@cmd2@cmd3{rubyText/中文}字数是一二三\n四五六七八九十"
128 );
129 let circus_formatter2 = FixedFormatter::new(32, false, Some(ScriptType::Circus));
130 assert_eq!(
131 circus_formatter2.format("@re1@re2@b1@t30@w1「当然现在我很幸福哦?\n 因为有你在身边」@n\n「@b1@t38@w1当然现在我很幸福哦?\n 因为有敦也君在身边」"),
132 "@re1@re2@b1@t30@w1「当然现在我很幸福哦?因为有你在身边」@n\n「@b1@t38@w1当然现在我很幸福哦?因为有敦也君在身边」"
133 );
134 }
135}