1#[derive(Debug, Clone, Copy)]
8pub struct Piece {
9 pub cp_start: u32,
10 pub cp_end: u32,
11 pub fc: u32,
13 pub compressed: bool,
14 pub prm: u16,
15}
16
17#[derive(Debug)]
18pub struct PieceTable {
19 pub pieces: Vec<Piece>,
20 pub grpprls: Vec<Vec<u8>>,
23}
24
25const CP1252_HIGH: [u16; 32] = [
28 0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160,
29 0x2039, 0x0152, 0x008D, 0x017D, 0x008F, 0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022,
30 0x2013, 0x2014, 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178,
31];
32
33pub fn cp1252_to_utf16(byte: u8) -> u16 {
34 if (0x80..0xA0).contains(&byte) {
35 CP1252_HIGH[(byte - 0x80) as usize]
36 } else {
37 byte as u16
38 }
39}
40
41fn read_u16(bytes: &[u8], offset: usize) -> Result<u16, String> {
42 bytes
43 .get(offset..offset + 2)
44 .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
45 .ok_or_else(|| "Clx: unexpected end of data".to_string())
46}
47
48fn read_u32(bytes: &[u8], offset: usize) -> Result<u32, String> {
49 bytes
50 .get(offset..offset + 4)
51 .map(|chunk| u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
52 .ok_or_else(|| "Clx: unexpected end of data".to_string())
53}
54
55impl PieceTable {
56 pub fn parse(clx: &[u8]) -> Result<PieceTable, String> {
58 let mut grpprls = Vec::new();
59 let mut pos = 0usize;
60 while pos < clx.len() {
61 match clx[pos] {
62 0x01 => {
63 let cb = read_u16(clx, pos + 1)? as usize;
64 let grpprl = clx
65 .get(pos + 3..pos + 3 + cb)
66 .ok_or_else(|| "Clx: Prc grpprl out of bounds".to_string())?;
67 grpprls.push(grpprl.to_vec());
68 pos += 3 + cb;
69 }
70 0x02 => {
71 let lcb = read_u32(clx, pos + 1)? as usize;
72 let plc = clx
73 .get(pos + 5..pos + 5 + lcb)
74 .ok_or_else(|| "Clx: PlcPcd out of bounds".to_string())?;
75 let pieces = parse_plc_pcd(plc)?;
76 return Ok(PieceTable { pieces, grpprls });
77 }
78 tag => return Err(format!("Clx: unexpected tag 0x{tag:02X}")),
79 }
80 }
81 Err("Clx: missing Pcdt (piece table)".to_string())
82 }
83
84 pub fn cp_max(&self) -> u32 {
85 self.pieces.last().map(|piece| piece.cp_end).unwrap_or(0)
86 }
87
88 fn piece_index_for(&self, cp: u32) -> Option<usize> {
89 let mut low = 0usize;
91 let mut high = self.pieces.len();
92 while low < high {
93 let mid = (low + high) / 2;
94 let piece = &self.pieces[mid];
95 if cp < piece.cp_start {
96 high = mid;
97 } else if cp >= piece.cp_end {
98 low = mid + 1;
99 } else {
100 return Some(mid);
101 }
102 }
103 None
104 }
105
106 pub fn piece_for(&self, cp: u32) -> Option<&Piece> {
107 self.piece_index_for(cp).map(|index| &self.pieces[index])
108 }
109
110 pub fn cp_to_fc(&self, cp: u32) -> Option<u32> {
112 let piece = self.piece_for(cp)?;
113 let delta = cp - piece.cp_start;
114 Some(if piece.compressed {
115 piece.fc + delta
116 } else {
117 piece.fc + delta * 2
118 })
119 }
120
121 pub fn text(&self, word_stream: &[u8], cp_start: u32, cp_end: u32) -> Vec<u16> {
125 let mut out = Vec::with_capacity((cp_end.saturating_sub(cp_start)) as usize);
126 let mut cp = cp_start;
127 while cp < cp_end {
128 let Some(piece) = self.piece_for(cp) else {
129 out.push(0xFFFD);
130 cp += 1;
131 continue;
132 };
133 let take_end = piece.cp_end.min(cp_end);
134 let delta = (cp - piece.cp_start) as usize;
135 let count = (take_end - cp) as usize;
136 if piece.compressed {
137 let start = piece.fc as usize + delta;
138 for index in 0..count {
139 out.push(
140 word_stream
141 .get(start + index)
142 .map(|byte| cp1252_to_utf16(*byte))
143 .unwrap_or(0xFFFD),
144 );
145 }
146 } else {
147 let start = piece.fc as usize + delta * 2;
148 for index in 0..count {
149 out.push(
150 word_stream
151 .get(start + index * 2..start + index * 2 + 2)
152 .map(|pair| u16::from_le_bytes([pair[0], pair[1]]))
153 .unwrap_or(0xFFFD),
154 );
155 }
156 }
157 cp = take_end;
158 }
159 out
160 }
161}
162
163fn parse_plc_pcd(plc: &[u8]) -> Result<Vec<Piece>, String> {
164 if plc.len() < 4 || !(plc.len() - 4).is_multiple_of(12) {
165 return Err(format!("PlcPcd: invalid length {}", plc.len()));
166 }
167 let count = (plc.len() - 4) / 12;
168 let mut pieces = Vec::with_capacity(count);
169 for index in 0..count {
170 let cp_start = read_u32(plc, index * 4)?;
171 let cp_end = read_u32(plc, (index + 1) * 4)?;
172 let pcd_offset = (count + 1) * 4 + index * 8;
173 let raw_fc = read_u32(plc, pcd_offset + 2)?;
174 let prm = read_u16(plc, pcd_offset + 6)?;
175 let compressed = raw_fc & 0x4000_0000 != 0;
176 let fc_value = raw_fc & 0x3FFF_FFFF;
177 let fc = if compressed { fc_value / 2 } else { fc_value };
178 if cp_end < cp_start {
179 return Err("PlcPcd: descending CP entries".to_string());
180 }
181 pieces.push(Piece {
182 cp_start,
183 cp_end,
184 fc,
185 compressed,
186 prm,
187 });
188 }
189 Ok(pieces)
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 fn build_plc_pcd(entries: &[(u32, u32, u32, u16)]) -> Vec<u8> {
197 let mut clx = vec![0x02u8];
199 let lcb = (entries.len() * 12 + 4) as u32;
200 clx.extend_from_slice(&lcb.to_le_bytes());
201 for entry in entries {
202 clx.extend_from_slice(&entry.0.to_le_bytes());
203 }
204 clx.extend_from_slice(&entries.last().unwrap().1.to_le_bytes());
205 for entry in entries {
206 clx.extend_from_slice(&0u16.to_le_bytes());
207 clx.extend_from_slice(&entry.2.to_le_bytes());
208 clx.extend_from_slice(&entry.3.to_le_bytes());
209 }
210 clx
211 }
212
213 #[test]
214 fn parses_mixed_encoding_pieces() {
215 let mut stream = Vec::new();
218 stream.extend_from_slice(b"Hi\x93\x00");
219 for unit in "W\u{F6}rld".encode_utf16() {
220 stream.extend_from_slice(&unit.to_le_bytes());
221 }
222
223 let clx = build_plc_pcd(&[(0, 3, 0x4000_0000, 0), (3, 8, 4, 0)]);
224 let table = PieceTable::parse(&clx).expect("parse clx");
225 assert_eq!(table.cp_max(), 8);
226
227 let text = String::from_utf16_lossy(&table.text(&stream, 0, 8));
228 assert_eq!(text, "Hi\u{201C}W\u{F6}rld");
229
230 assert_eq!(table.cp_to_fc(1), Some(1));
232 assert_eq!(table.cp_to_fc(4), Some(6));
234 }
235
236 #[test]
237 fn parses_prc_entries_before_pcdt() {
238 let mut clx = vec![0x01u8];
239 clx.extend_from_slice(&2u16.to_le_bytes());
240 clx.extend_from_slice(&[0xAA, 0xBB]);
241 let pcdt = build_plc_pcd(&[(0, 1, 0x4000_0000, 0x0003)]);
242 clx.extend_from_slice(&pcdt);
243
244 let table = PieceTable::parse(&clx).expect("parse clx");
245 assert_eq!(table.grpprls.len(), 1);
246 assert_eq!(table.grpprls[0], vec![0xAA, 0xBB]);
247 assert_eq!(table.pieces[0].prm, 0x0003);
248 }
249}