baken_export/pdb/
string.rs1pub fn encode(s: &str) -> Vec<u8> {
10 if s.is_ascii() && s.len() < 0x7f {
11 let mut out = Vec::with_capacity(s.len() + 1);
12 out.push((((s.len() + 1) as u8) << 1) | 1);
13 out.extend_from_slice(s.as_bytes());
14 out
15 } else {
16 let data: Vec<u8> = s.encode_utf16().flat_map(u16::to_le_bytes).collect();
17 let total = (data.len() + 4) as u16;
18 let mut out = Vec::with_capacity(data.len() + 4);
19 out.push(0x90);
20 out.extend_from_slice(&total.to_le_bytes());
21 out.push(0);
22 out.extend_from_slice(&data);
23 out
24 }
25}
26
27pub fn decode(buf: &[u8], off: usize) -> Option<(String, usize)> {
29 let flag = *buf.get(off)?;
30 if flag & 1 == 1 {
31 let n = (flag >> 1) as usize;
32 let body = buf.get(off + 1..off + n)?;
33 return Some((String::from_utf8_lossy(body).into_owned(), n));
34 }
35 let n = u16::from_le_bytes([*buf.get(off + 1)?, *buf.get(off + 2)?]) as usize;
36 let body = buf.get(off + 4..off + n)?;
37 let s = match flag {
38 0x40 => String::from_utf8_lossy(body).into_owned(),
39 0x90 => {
40 let units: Vec<u16> = body
41 .chunks_exact(2)
42 .map(|c| u16::from_le_bytes([c[0], c[1]]))
43 .collect();
44 String::from_utf16_lossy(&units)
45 }
46 _ => return None,
47 };
48 Some((s, n))
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54
55 #[test]
56 fn short_ascii_matches_fixture_bytes() {
57 assert_eq!(encode("1A"), vec![0x07, b'1', b'A']);
59 assert_eq!(encode(""), vec![0x03]);
61 assert_eq!(encode("Pink"), vec![0x0b, b'P', b'i', b'n', b'k']);
62 }
63
64 #[test]
65 fn utf16_matches_columns_row() {
66 let s = "\u{fffa}GENRE\u{fffb}";
68 let enc = encode(s);
69 assert_eq!(&enc[..4], &[0x90, 0x12, 0x00, 0x00]);
70 assert_eq!(enc.len(), 0x12);
71 assert_eq!(decode(&enc, 0).unwrap(), (s.to_string(), 0x12));
72 }
73
74 #[test]
75 fn round_trip() {
76 for s in [
77 "",
78 "a",
79 "Techno",
80 "22. 盾",
81 "Jesse Pinkman's Hydrofluoric Acid / JPHFA",
82 ] {
83 let enc = encode(s);
84 assert_eq!(decode(&enc, 0).unwrap(), (s.to_string(), enc.len()));
85 }
86 }
87}