1use crate::cjk::{big5, euc_kr, gb18030};
9use crate::jis::{euc_jp, iso2022jp, shift_jis};
10use crate::label::Encoding;
11use crate::tables::{
12 LATIN1_CODEPOINTS, WINDOWS_1252_CODEPOINTS, encode_single_byte, encode_single_byte_upper,
13 legacy_single_byte_table,
14};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct EncodeError {
20 pub character: char,
22 pub position: usize,
25}
26
27impl std::fmt::Display for EncodeError {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 write!(
30 f,
31 "character {:?} (U+{:04X}) at byte offset {} has no representation in the target encoding",
32 self.character, self.character as u32, self.position
33 )
34 }
35}
36
37impl std::error::Error for EncodeError {}
38
39pub fn encode_from_utf8(
72 input: &str,
73 encoding: Encoding,
74 dst: &mut Vec<u8>,
75) -> Result<(), EncodeError> {
76 match encoding {
77 Encoding::Utf8 => {
78 dst.extend_from_slice(input.as_bytes());
79 Ok(())
80 }
81 Encoding::Utf16Le => {
82 encode_utf16(input, dst, false);
83 Ok(())
84 }
85 Encoding::Utf16Be => {
86 encode_utf16(input, dst, true);
87 Ok(())
88 }
89 Encoding::Latin1 => encode_single_byte_str(input, dst, &LATIN1_CODEPOINTS),
90 Encoding::Windows1252 => encode_single_byte_str(input, dst, &WINDOWS_1252_CODEPOINTS),
91 Encoding::Ascii => encode_ascii(input, dst),
92 Encoding::ShiftJis => shift_jis::encode(input, dst),
93 Encoding::EucJp => euc_jp::encode(input, dst),
94 Encoding::Iso2022Jp => iso2022jp::encode(input, dst),
95 Encoding::Gbk => gb18030::encode(input, dst, true),
96 Encoding::Gb18030 => gb18030::encode(input, dst, false),
97 Encoding::Big5 => big5::encode(input, dst),
98 Encoding::EucKr => euc_kr::encode(input, dst),
99 other => encode_legacy_single_byte_str(input, dst, other),
101 }
102}
103
104fn encode_legacy_single_byte_str(
107 input: &str,
108 dst: &mut Vec<u8>,
109 encoding: Encoding,
110) -> Result<(), EncodeError> {
111 let table = legacy_single_byte_table(encoding);
112 dst.reserve(input.len());
113 for (position, character) in input.char_indices() {
114 match encode_single_byte_upper(table, character as u32) {
115 Some(byte) => dst.push(byte),
116 None => {
117 return Err(EncodeError {
118 character,
119 position,
120 });
121 }
122 }
123 }
124 Ok(())
125}
126
127fn encode_utf16(input: &str, dst: &mut Vec<u8>, big_endian: bool) {
130 dst.reserve(input.len() * 2);
131 for unit in input.encode_utf16() {
132 let bytes = if big_endian {
133 unit.to_be_bytes()
134 } else {
135 unit.to_le_bytes()
136 };
137 dst.extend_from_slice(&bytes);
138 }
139}
140
141fn encode_single_byte_str(
142 input: &str,
143 dst: &mut Vec<u8>,
144 table: &[u32; 256],
145) -> Result<(), EncodeError> {
146 dst.reserve(input.len());
147 for (position, character) in input.char_indices() {
148 match encode_single_byte(table, character as u32) {
149 Some(byte) => dst.push(byte),
150 None => {
151 return Err(EncodeError {
152 character,
153 position,
154 });
155 }
156 }
157 }
158 Ok(())
159}
160
161fn encode_ascii(input: &str, dst: &mut Vec<u8>) -> Result<(), EncodeError> {
162 dst.reserve(input.len());
163 for (position, character) in input.char_indices() {
164 if character.is_ascii() {
165 dst.push(character as u8);
166 } else {
167 return Err(EncodeError {
168 character,
169 position,
170 });
171 }
172 }
173 Ok(())
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn utf8_target_is_a_plain_copy() -> Result<(), EncodeError> {
182 let mut dst = Vec::new();
183 encode_from_utf8("héllo→🎉", Encoding::Utf8, &mut dst)?;
184 assert_eq!(dst, "héllo→🎉".as_bytes());
185 Ok(())
186 }
187
188 #[test]
189 fn utf16_targets_never_fail_even_for_astral_characters() -> Result<(), EncodeError> {
190 let mut le = Vec::new();
191 encode_from_utf8("🎉", Encoding::Utf16Le, &mut le)?;
192 assert_eq!(
193 le,
194 "🎉"
195 .encode_utf16()
196 .flat_map(|u| u.to_le_bytes())
197 .collect::<Vec<_>>()
198 );
199
200 let mut be = Vec::new();
201 encode_from_utf8("🎉", Encoding::Utf16Be, &mut be)?;
202 assert_eq!(
203 be,
204 "🎉"
205 .encode_utf16()
206 .flat_map(|u| u.to_be_bytes())
207 .collect::<Vec<_>>()
208 );
209 Ok(())
210 }
211
212 #[test]
213 fn latin1_round_trips_within_0x00_to_0xff() -> Result<(), EncodeError> {
214 let mut dst = Vec::new();
215 encode_from_utf8("café", Encoding::Latin1, &mut dst)?;
216 assert_eq!(dst, b"caf\xE9");
217 Ok(())
218 }
219
220 #[test]
221 fn latin1_rejects_out_of_range_character() {
222 let mut dst = Vec::new();
223 let err = encode_from_utf8("→", Encoding::Latin1, &mut dst).expect_err("must fail");
224 assert_eq!(err.character, '→');
225 assert_eq!(err.position, 0);
226 }
227
228 #[test]
229 fn windows1252_encodes_smart_quotes() -> Result<(), EncodeError> {
230 let mut dst = Vec::new();
231 encode_from_utf8("\u{2018}hi\u{2019}", Encoding::Windows1252, &mut dst)?;
232 assert_eq!(dst, [0x91, b'h', b'i', 0x92]);
233 Ok(())
234 }
235
236 #[test]
237 fn windows1252_rejects_character_outside_the_repertoire() {
238 let mut dst = Vec::new();
239 let err = encode_from_utf8("あ", Encoding::Windows1252, &mut dst).expect_err("must fail");
240 assert_eq!(err.character, 'あ');
241 }
242
243 #[test]
244 fn ascii_rejects_first_non_ascii_character_with_byte_position() {
245 let mut dst = Vec::new();
246 let err = encode_from_utf8("ok日本語", Encoding::Ascii, &mut dst).expect_err("must fail");
247 assert_eq!(dst, b"ok");
248 assert_eq!(err.character, '日');
249 assert_eq!(err.position, 2);
250 }
251
252 #[test]
253 fn error_display_is_human_readable() {
254 let err = EncodeError {
255 character: 'あ',
256 position: 5,
257 };
258 assert_eq!(
259 err.to_string(),
260 "character 'あ' (U+3042) at byte offset 5 has no representation in the target encoding"
261 );
262 }
263
264 #[test]
265 fn legacy_single_byte_encodes_within_repertoire() -> Result<(), EncodeError> {
266 let mut dst = Vec::new();
268 encode_from_utf8("\u{0104}", Encoding::Windows1250, &mut dst)?;
269 assert_eq!(dst, vec![0xA5]);
270 Ok(())
271 }
272
273 #[test]
274 fn legacy_single_byte_rejects_character_outside_the_repertoire() {
275 let mut dst = Vec::new();
276 let err = encode_from_utf8("日", Encoding::Iso8859_2, &mut dst).expect_err("must fail");
277 assert_eq!(err.character, '日');
278 assert_eq!(err.position, 0);
279 }
280
281 #[test]
282 fn every_legacy_single_byte_encoding_round_trips_its_own_mapped_bytes() {
283 for &encoding in Encoding::ALL {
284 if !encoding.is_legacy_single_byte_table() {
285 continue;
286 }
287 let table = legacy_single_byte_table(encoding);
288 for pointer in 0u8..128 {
289 let codepoint = table[pointer as usize];
290 if codepoint == crate::tables::UNMAPPED {
291 continue;
292 }
293 let Some(c) = char::from_u32(codepoint) else {
294 continue;
295 };
296 let mut dst = Vec::new();
297 let text = c.to_string();
298 encode_from_utf8(&text, encoding, &mut dst)
299 .unwrap_or_else(|e| panic!("{encoding}: {text:?}: {e}"));
300 assert_eq!(dst, vec![0x80 + pointer], "{encoding}: {text:?}");
301 }
302 }
303 }
304
305 #[test]
306 fn shift_jis_euc_jp_iso2022jp_are_routed_to_the_jis_module() -> Result<(), EncodeError> {
307 let mut dst = Vec::new();
308 encode_from_utf8("日", Encoding::ShiftJis, &mut dst)?;
309 assert_eq!(dst, vec![0x93, 0xFA]);
310
311 let mut dst = Vec::new();
312 encode_from_utf8("日", Encoding::EucJp, &mut dst)?;
313 assert_eq!(dst, vec![0xC6, 0xFC]);
314
315 let mut dst = Vec::new();
316 encode_from_utf8("日", Encoding::Iso2022Jp, &mut dst)?;
317 assert_eq!(dst, vec![0x1B, 0x24, 0x42, 0x46, 0x7C, 0x1B, 0x28, 0x42]);
318 Ok(())
319 }
320
321 #[test]
322 fn gbk_gb18030_big5_euc_kr_are_routed_to_the_cjk_module() -> Result<(), EncodeError> {
323 let mut dst = Vec::new();
324 encode_from_utf8("中", Encoding::Gbk, &mut dst)?;
325 assert_eq!(dst, vec![0xD6, 0xD0]);
326
327 let mut dst = Vec::new();
328 encode_from_utf8("中", Encoding::Gb18030, &mut dst)?;
329 assert_eq!(dst, vec![0xD6, 0xD0]);
330
331 let mut dst = Vec::new();
332 encode_from_utf8("中", Encoding::Big5, &mut dst)?;
333 assert_eq!(dst, vec![0xA4, 0xA4]);
334
335 let mut dst = Vec::new();
336 encode_from_utf8("한", Encoding::EucKr, &mut dst)?;
337 assert_eq!(dst, vec![0xC7, 0xD1]);
338 Ok(())
339 }
340}