use byteorder::{LittleEndian, ReadBytesExt};
use encoding_rs::WINDOWS_1252;
use flate2::read::ZlibDecoder;
use std::io::{Cursor, Read, Seek, SeekFrom};
use crate::error::{AltiumError, Result};
use crate::format::{CFB_COMPRESSED_TAG, SIZE_FLAG_MASK};
use crate::types::{Coord, CoordPoint, ParameterCollection};
pub fn read_block<R: Read>(reader: &mut R) -> Result<Vec<u8>> {
let size = reader.read_i32::<LittleEndian>()?;
let clean_size = (size & SIZE_FLAG_MASK as i32) as usize;
if clean_size == 0 {
return Ok(Vec::new());
}
let mut buffer = vec![0u8; clean_size];
reader.read_exact(&mut buffer)?;
Ok(buffer)
}
pub fn read_block_with<R: Read, T, F>(reader: &mut R, interpreter: F) -> Result<T>
where
F: FnOnce(&[u8]) -> Result<T>,
{
let data = read_block(reader)?;
interpreter(&data)
}
pub fn decompress_zlib(data: &[u8]) -> Result<Vec<u8>> {
if data.len() < 2 {
return Err(AltiumError::Decompression(
"Data too short for zlib".to_string(),
));
}
let mut decoder = ZlibDecoder::new(&data[2..]);
let mut output = Vec::new();
decoder
.read_to_end(&mut output)
.map_err(|e| AltiumError::Decompression(format!("Zlib decompression failed: {}", e)))?;
Ok(output)
}
pub fn decode_windows_1252(data: &[u8]) -> String {
let (cow, _, _) = WINDOWS_1252.decode(data);
cow.into_owned()
}
pub fn read_raw_string<R: Read>(reader: &mut R, size: usize) -> Result<String> {
if size == 0 {
return Ok(String::new());
}
let mut buffer = vec![0u8; size];
reader.read_exact(&mut buffer)?;
Ok(decode_windows_1252(&buffer))
}
pub fn read_c_string<R: Read>(reader: &mut R, size: usize) -> Result<String> {
if size == 0 {
return Ok(String::new());
}
let mut buffer = vec![0u8; size];
reader.read_exact(&mut buffer)?;
let end = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len());
Ok(decode_windows_1252(&buffer[..end]))
}
pub fn read_pascal_string<R: Read>(reader: &mut R) -> Result<String> {
let data = read_block(reader)?;
if data.is_empty() {
return Ok(String::new());
}
let end = data.iter().position(|&b| b == 0).unwrap_or(data.len());
Ok(decode_windows_1252(&data[..end]))
}
pub fn read_pascal_short_string<R: Read>(reader: &mut R) -> Result<String> {
let size = reader.read_u8()? as usize;
read_raw_string(reader, size)
}
pub fn read_font_name<R: Read + Seek>(reader: &mut R) -> Result<String> {
let start_pos = reader.stream_position()?;
let mut chars = Vec::new();
for _ in 0..16 {
let code = reader.read_u16::<LittleEndian>()?;
if code == 0 {
break;
}
chars.push(code);
}
reader.seek(SeekFrom::Start(start_pos + 32))?;
String::from_utf16(&chars)
.map_err(|e| AltiumError::Encoding(format!("Invalid UTF-16 font name: {}", e)))
}
pub fn read_string_block<R: Read>(reader: &mut R) -> Result<String> {
let data = read_block(reader)?;
if data.is_empty() {
return Ok(String::new());
}
let mut cursor = Cursor::new(data);
read_pascal_short_string(&mut cursor)
}
pub fn read_parameters<R: Read>(
reader: &mut R,
size: usize,
raw: bool,
) -> Result<ParameterCollection> {
let data = if raw {
read_raw_string(reader, size)?
} else {
read_c_string(reader, size)?
};
Ok(ParameterCollection::from_string(&data))
}
pub fn read_parameters_block<R: Read>(reader: &mut R) -> Result<ParameterCollection> {
let data = read_block(reader)?;
if data.is_empty() {
return Ok(ParameterCollection::new());
}
let end = data.iter().position(|&b| b == 0).unwrap_or(data.len());
let s = decode_windows_1252(&data[..end]);
Ok(ParameterCollection::from_string(&s))
}
pub fn read_coord_point<R: Read>(reader: &mut R) -> Result<CoordPoint> {
let x = reader.read_i32::<LittleEndian>()?;
let y = reader.read_i32::<LittleEndian>()?;
Ok(CoordPoint::from_raw(x, y))
}
pub fn read_header<R: Read>(reader: &mut R) -> Result<u32> {
Ok(reader.read_u32::<LittleEndian>()?)
}
pub fn read_compressed_storage<R: Read>(reader: &mut R) -> Result<(String, Vec<u8>)> {
let block_data = read_block(reader)?;
if block_data.is_empty() {
return Ok((String::new(), Vec::new()));
}
let mut cursor = Cursor::new(block_data);
let tag = cursor.read_u8()?;
if tag != CFB_COMPRESSED_TAG {
return Err(AltiumError::Parse(
"Expected 0xD0 tag in compressed storage".to_string(),
));
}
let id = read_pascal_short_string(&mut cursor)?;
let compressed = read_block(&mut cursor)?;
let data = decompress_zlib(&compressed)?;
Ok((id, data))
}
pub trait ReadExt: Read {
fn read_coord(&mut self) -> Result<Coord>
where
Self: Sized,
{
let value = self.read_i32::<LittleEndian>()?;
Ok(Coord::from_raw(value))
}
fn read_bool8(&mut self) -> Result<bool>
where
Self: Sized,
{
let value = self.read_u8()?;
Ok(value != 0)
}
}
impl<R: Read> ReadExt for R {}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_read_block() {
let data = [5, 0, 0, 0, b'h', b'e', b'l', b'l', b'o'];
let mut cursor = Cursor::new(&data);
let result = read_block(&mut cursor).unwrap();
assert_eq!(result, b"hello");
}
#[test]
fn test_read_pascal_short_string() {
let data = [5, b'h', b'e', b'l', b'l', b'o'];
let mut cursor = Cursor::new(&data);
let result = read_pascal_short_string(&mut cursor).unwrap();
assert_eq!(result, "hello");
}
#[test]
fn test_read_coord_point() {
let data = [0, 0, 1, 0, 0, 0, 2, 0]; let mut cursor = Cursor::new(&data);
let point = read_coord_point(&mut cursor).unwrap();
assert_eq!(point.x.to_raw(), 65536);
assert_eq!(point.y.to_raw(), 131072);
}
#[test]
fn test_decode_windows_1252() {
let data = b"Hello World";
let result = decode_windows_1252(data);
assert_eq!(result, "Hello World");
}
}