altium_format/io/
reader.rs1use byteorder::{LittleEndian, ReadBytesExt};
6use encoding_rs::WINDOWS_1252;
7use flate2::read::ZlibDecoder;
8use std::io::{Cursor, Read, Seek, SeekFrom};
9
10use crate::error::{AltiumError, Result};
11use crate::format::{CFB_COMPRESSED_TAG, SIZE_FLAG_MASK};
12use crate::types::{Coord, CoordPoint, ParameterCollection};
13
14pub fn read_block<R: Read>(reader: &mut R) -> Result<Vec<u8>> {
18 let size = reader.read_i32::<LittleEndian>()?;
19 let clean_size = (size & SIZE_FLAG_MASK as i32) as usize;
21
22 if clean_size == 0 {
23 return Ok(Vec::new());
24 }
25
26 let mut buffer = vec![0u8; clean_size];
27 reader.read_exact(&mut buffer)?;
28 Ok(buffer)
29}
30
31pub fn read_block_with<R: Read, T, F>(reader: &mut R, interpreter: F) -> Result<T>
33where
34 F: FnOnce(&[u8]) -> Result<T>,
35{
36 let data = read_block(reader)?;
37 interpreter(&data)
38}
39
40pub fn decompress_zlib(data: &[u8]) -> Result<Vec<u8>> {
44 if data.len() < 2 {
45 return Err(AltiumError::Decompression(
46 "Data too short for zlib".to_string(),
47 ));
48 }
49
50 let mut decoder = ZlibDecoder::new(&data[2..]);
52 let mut output = Vec::new();
53 decoder
54 .read_to_end(&mut output)
55 .map_err(|e| AltiumError::Decompression(format!("Zlib decompression failed: {}", e)))?;
56 Ok(output)
57}
58
59pub fn decode_windows_1252(data: &[u8]) -> String {
61 let (cow, _, _) = WINDOWS_1252.decode(data);
62 cow.into_owned()
63}
64
65pub fn read_raw_string<R: Read>(reader: &mut R, size: usize) -> Result<String> {
67 if size == 0 {
68 return Ok(String::new());
69 }
70 let mut buffer = vec![0u8; size];
71 reader.read_exact(&mut buffer)?;
72 Ok(decode_windows_1252(&buffer))
73}
74
75pub fn read_c_string<R: Read>(reader: &mut R, size: usize) -> Result<String> {
79 if size == 0 {
80 return Ok(String::new());
81 }
82 let mut buffer = vec![0u8; size];
83 reader.read_exact(&mut buffer)?;
84
85 let end = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
87 Ok(decode_windows_1252(&buffer[..end]))
88}
89
90pub fn read_pascal_string<R: Read>(reader: &mut R) -> Result<String> {
92 let data = read_block(reader)?;
93 if data.is_empty() {
94 return Ok(String::new());
95 }
96 let end = data.iter().position(|&b| b == 0).unwrap_or(data.len());
98 Ok(decode_windows_1252(&data[..end]))
99}
100
101pub fn read_pascal_short_string<R: Read>(reader: &mut R) -> Result<String> {
103 let size = reader.read_u8()? as usize;
104 read_raw_string(reader, size)
105}
106
107pub fn read_font_name<R: Read + Seek>(reader: &mut R) -> Result<String> {
109 let start_pos = reader.stream_position()?;
110 let mut chars = Vec::new();
111
112 for _ in 0..16 {
113 let code = reader.read_u16::<LittleEndian>()?;
114 if code == 0 {
115 break;
116 }
117 chars.push(code);
118 }
119
120 reader.seek(SeekFrom::Start(start_pos + 32))?;
122
123 String::from_utf16(&chars)
124 .map_err(|e| AltiumError::Encoding(format!("Invalid UTF-16 font name: {}", e)))
125}
126
127pub fn read_string_block<R: Read>(reader: &mut R) -> Result<String> {
129 let data = read_block(reader)?;
130 if data.is_empty() {
131 return Ok(String::new());
132 }
133 let mut cursor = Cursor::new(data);
134 read_pascal_short_string(&mut cursor)
135}
136
137pub fn read_parameters<R: Read>(
141 reader: &mut R,
142 size: usize,
143 raw: bool,
144) -> Result<ParameterCollection> {
145 let data = if raw {
146 read_raw_string(reader, size)?
147 } else {
148 read_c_string(reader, size)?
149 };
150 Ok(ParameterCollection::from_string(&data))
151}
152
153pub fn read_parameters_block<R: Read>(reader: &mut R) -> Result<ParameterCollection> {
155 let data = read_block(reader)?;
156 if data.is_empty() {
157 return Ok(ParameterCollection::new());
158 }
159 let end = data.iter().position(|&b| b == 0).unwrap_or(data.len());
161 let s = decode_windows_1252(&data[..end]);
162 Ok(ParameterCollection::from_string(&s))
163}
164
165pub fn read_coord_point<R: Read>(reader: &mut R) -> Result<CoordPoint> {
167 let x = reader.read_i32::<LittleEndian>()?;
168 let y = reader.read_i32::<LittleEndian>()?;
169 Ok(CoordPoint::from_raw(x, y))
170}
171
172pub fn read_header<R: Read>(reader: &mut R) -> Result<u32> {
174 Ok(reader.read_u32::<LittleEndian>()?)
175}
176
177pub fn read_compressed_storage<R: Read>(reader: &mut R) -> Result<(String, Vec<u8>)> {
179 let block_data = read_block(reader)?;
180 if block_data.is_empty() {
181 return Ok((String::new(), Vec::new()));
182 }
183
184 let mut cursor = Cursor::new(block_data);
185
186 let tag = cursor.read_u8()?;
188 if tag != CFB_COMPRESSED_TAG {
189 return Err(AltiumError::Parse(
190 "Expected 0xD0 tag in compressed storage".to_string(),
191 ));
192 }
193
194 let id = read_pascal_short_string(&mut cursor)?;
196
197 let compressed = read_block(&mut cursor)?;
199
200 let data = decompress_zlib(&compressed)?;
202
203 Ok((id, data))
204}
205
206pub trait ReadExt: Read {
208 fn read_coord(&mut self) -> Result<Coord>
210 where
211 Self: Sized,
212 {
213 let value = self.read_i32::<LittleEndian>()?;
214 Ok(Coord::from_raw(value))
215 }
216
217 fn read_bool8(&mut self) -> Result<bool>
219 where
220 Self: Sized,
221 {
222 let value = self.read_u8()?;
223 Ok(value != 0)
224 }
225}
226
227impl<R: Read> ReadExt for R {}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232 use std::io::Cursor;
233
234 #[test]
235 fn test_read_block() {
236 let data = [5, 0, 0, 0, b'h', b'e', b'l', b'l', b'o'];
238 let mut cursor = Cursor::new(&data);
239
240 let result = read_block(&mut cursor).unwrap();
241 assert_eq!(result, b"hello");
242 }
243
244 #[test]
245 fn test_read_pascal_short_string() {
246 let data = [5, b'h', b'e', b'l', b'l', b'o'];
248 let mut cursor = Cursor::new(&data);
249
250 let result = read_pascal_short_string(&mut cursor).unwrap();
251 assert_eq!(result, "hello");
252 }
253
254 #[test]
255 fn test_read_coord_point() {
256 let data = [0, 0, 1, 0, 0, 0, 2, 0]; let mut cursor = Cursor::new(&data);
259
260 let point = read_coord_point(&mut cursor).unwrap();
261 assert_eq!(point.x.to_raw(), 65536);
262 assert_eq!(point.y.to_raw(), 131072);
263 }
264
265 #[test]
266 fn test_decode_windows_1252() {
267 let data = b"Hello World";
268 let result = decode_windows_1252(data);
269 assert_eq!(result, "Hello World");
270 }
271}