use cfb::CompoundFile;
use flate2::Compression;
use flate2::read::ZlibDecoder;
use flate2::write::ZlibEncoder;
use std::collections::HashMap;
use std::fs::File;
use std::io::{Cursor, Read, Seek, Write};
use std::path::Path;
use crate::error::{AltiumError, Result};
use crate::io::{PcbLib, SchLib};
use crate::types::ParameterCollection;
#[derive(Debug, Default)]
pub struct IntLib {
pub version: u32,
pub schlib: SchLib,
pub pcblib: PcbLib,
pub cross_refs: Vec<CrossReference>,
pub parameters: Vec<ComponentParameters>,
}
#[derive(Debug, Clone, Default)]
pub struct CrossReference {
pub name: String,
pub schlib_path: String,
pub description: String,
pub source_path: String,
pub footprint: String,
pub pcblib_type: String,
pub pcblib_path: String,
pub pcblib_source_path: String,
}
#[derive(Debug, Clone)]
pub struct ComponentParameters {
pub name: String,
pub params: ParameterCollection,
}
impl IntLib {
pub fn open<R: Read + Seek>(reader: R) -> Result<Self> {
let mut intlib = IntLib::default();
let mut cf = CompoundFile::open(reader).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
e.to_string(),
))
})?;
intlib.read_version(&mut cf)?;
intlib.read_cross_refs(&mut cf)?;
intlib.read_parameters(&mut cf)?;
intlib.read_schlib(&mut cf)?;
intlib.read_pcblib(&mut cf)?;
Ok(intlib)
}
pub fn open_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let file = File::open(path)?;
Self::open(file)
}
pub fn save<W: Read + Write + Seek>(&self, writer: W) -> Result<()> {
let mut cf = CompoundFile::create(writer)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
self.write_version(&mut cf)?;
self.write_cross_refs(&mut cf)?;
self.write_parameters(&mut cf)?;
self.write_schlib(&mut cf)?;
self.write_pcblib(&mut cf)?;
cf.flush()
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
Ok(())
}
pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let file = File::create(path)?;
self.save(file)
}
fn read_version<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
let stream_path = "/Version.Txt";
if cf.entry(stream_path).is_err() {
return Ok(());
}
let mut stream = cf.open_stream(stream_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
let mut data = Vec::new();
stream.read_to_end(&mut data)?;
if data.len() >= 5 {
self.version = data[1] as u32 | ((data[2] as u32) << 8);
}
Ok(())
}
fn write_version<F: Read + Write + Seek>(&self, cf: &mut CompoundFile<F>) -> Result<()> {
let mut data = vec![0u8; 5];
data[1] = (self.version & 0xFF) as u8;
data[2] = ((self.version >> 8) & 0xFF) as u8;
let stream = cf
.create_stream("/Version.Txt")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
let mut stream = stream;
stream.write_all(&data)?;
Ok(())
}
fn read_cross_refs<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
let stream_path = "/LibCrossRef.Txt";
if cf.entry(stream_path).is_err() {
return Ok(());
}
let mut stream = cf.open_stream(stream_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
let mut data = Vec::new();
stream.read_to_end(&mut data)?;
if data.is_empty() {
return Ok(());
}
if data.len() > 1 && data[0] == 0x02 {
let decompressed = decompress_zlib(&data[1..])?;
self.parse_cross_refs(&decompressed)?;
}
Ok(())
}
fn parse_cross_refs(&mut self, data: &[u8]) -> Result<()> {
use byteorder::{LittleEndian, ReadBytesExt};
if data.len() < 4 {
return Ok(());
}
let mut cursor = Cursor::new(data);
let entry_count = cursor.read_u32::<LittleEndian>()? as usize;
for _ in 0..entry_count {
match self.read_cross_ref_entry(&mut cursor) {
Ok(entry) => {
if !entry.name.is_empty() {
self.cross_refs.push(entry);
}
}
Err(_) => break,
}
}
Ok(())
}
fn read_block_string<R: Read>(reader: &mut R) -> Result<String> {
use byteorder::{LittleEndian, ReadBytesExt};
let block_size = reader.read_u32::<LittleEndian>()? as usize;
if block_size <= 1 {
return Ok(String::new());
}
let str_len = reader.read_u8()? as usize;
if str_len == 0 {
let remaining = block_size.saturating_sub(1);
if remaining > 0 {
let mut skip = vec![0u8; remaining];
let _ = reader.read_exact(&mut skip);
}
return Ok(String::new());
}
let mut buf = vec![0u8; str_len];
reader.read_exact(&mut buf)?;
Ok(String::from_utf8_lossy(&buf).to_string())
}
fn read_cross_ref_entry<R: Read>(&self, reader: &mut R) -> Result<CrossReference> {
let name = Self::read_block_string(reader)?;
let schlib_path = Self::read_block_string(reader)?;
let _ = Self::read_block_string(reader)?;
let description = Self::read_block_string(reader)?;
let source_path = Self::read_block_string(reader)?;
let _ = Self::read_block_string(reader)?;
let footprint = Self::read_block_string(reader)?;
let pcblib_type = Self::read_block_string(reader)?;
let _ = Self::read_block_string(reader)?;
let pcblib_path = Self::read_block_string(reader)?;
let pcblib_source_path = Self::read_block_string(reader)?;
Ok(CrossReference {
name,
schlib_path,
description,
source_path,
footprint,
pcblib_type,
pcblib_path,
pcblib_source_path,
})
}
fn write_cross_refs<F: Read + Write + Seek>(&self, cf: &mut CompoundFile<F>) -> Result<()> {
if self.cross_refs.is_empty() {
return Ok(());
}
use byteorder::{LittleEndian, WriteBytesExt};
let mut data = Vec::new();
data.write_u32::<LittleEndian>(self.cross_refs.len() as u32)?;
for entry in &self.cross_refs {
self.write_cross_ref_entry(&mut data, entry)?;
}
let compressed = compress_zlib(&data)?;
let mut final_data = vec![0x02u8];
final_data.extend(compressed);
let stream = cf
.create_stream("/LibCrossRef.Txt")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
let mut stream = stream;
stream.write_all(&final_data)?;
Ok(())
}
fn write_block_string<W: Write>(writer: &mut W, s: &str) -> Result<()> {
use byteorder::{LittleEndian, WriteBytesExt};
if s.is_empty() {
writer.write_u32::<LittleEndian>(1)?;
} else {
let bytes = s.as_bytes();
let block_size = 1 + bytes.len(); writer.write_u32::<LittleEndian>(block_size as u32)?;
writer.write_u8(bytes.len() as u8)?;
writer.write_all(bytes)?;
}
Ok(())
}
fn write_cross_ref_entry<W: Write>(
&self,
writer: &mut W,
entry: &CrossReference,
) -> Result<()> {
Self::write_block_string(writer, &entry.name)?;
Self::write_block_string(writer, &entry.schlib_path)?;
Self::write_block_string(writer, "")?;
Self::write_block_string(writer, &entry.description)?;
Self::write_block_string(writer, &entry.source_path)?;
Self::write_block_string(writer, "")?;
Self::write_block_string(writer, &entry.footprint)?;
Self::write_block_string(writer, &entry.pcblib_type)?;
Self::write_block_string(writer, "")?;
Self::write_block_string(writer, &entry.pcblib_path)?;
Self::write_block_string(writer, &entry.pcblib_source_path)?;
Ok(())
}
fn read_parameters<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
let stream_path = "/Parameters .bin";
if cf.entry(stream_path).is_err() {
return Ok(());
}
let mut stream = cf.open_stream(stream_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
let mut data = Vec::new();
stream.read_to_end(&mut data)?;
if data.is_empty() {
return Ok(());
}
if data.len() > 1 && data[0] == 0x02 {
let decompressed = decompress_zlib(&data[1..])?;
self.parse_parameters(&decompressed)?;
}
Ok(())
}
fn parse_parameters(&mut self, data: &[u8]) -> Result<()> {
let mut cursor = Cursor::new(data);
while (cursor.position() as usize) < data.len() {
match self.read_parameter_entry(&mut cursor) {
Ok(entry) => self.parameters.push(entry),
Err(_) => break,
}
}
Ok(())
}
fn read_parameter_entry<R: Read>(&self, reader: &mut R) -> Result<ComponentParameters> {
use byteorder::{LittleEndian, ReadBytesExt};
let len = reader.read_u16::<LittleEndian>()? as usize;
if len == 0 {
return Err(AltiumError::Parse("Empty parameter block".to_string()));
}
let mut buf = vec![0u8; len];
reader.read_exact(&mut buf)?;
let text = String::from_utf8_lossy(&buf).to_string();
let params = ParameterCollection::from_string(&text);
let name = params
.get("Library Reference")
.or_else(|| params.get("Designator"))
.map(|v| v.as_str().to_string())
.unwrap_or_default();
Ok(ComponentParameters { name, params })
}
fn write_parameters<F: Read + Write + Seek>(&self, cf: &mut CompoundFile<F>) -> Result<()> {
if self.parameters.is_empty() {
return Ok(());
}
use byteorder::{LittleEndian, WriteBytesExt};
let mut data = Vec::new();
for entry in &self.parameters {
let param_str = entry.params.to_string();
let bytes = param_str.as_bytes();
data.write_u16::<LittleEndian>(bytes.len() as u16)?;
data.write_all(bytes)?;
}
let compressed = compress_zlib(&data)?;
let mut final_data = vec![0x02u8];
final_data.extend(compressed);
let stream = cf
.create_stream("/Parameters .bin")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
let mut stream = stream;
stream.write_all(&final_data)?;
Ok(())
}
fn read_schlib<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
let stream_path = "/SchLib/0.schlib";
if cf.entry(stream_path).is_err() {
return Ok(());
}
let mut stream = cf.open_stream(stream_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
let mut data = Vec::new();
stream.read_to_end(&mut data)?;
if data.is_empty() {
return Ok(());
}
if data.len() > 1 && data[0] == 0x02 {
let decompressed = decompress_zlib(&data[1..])?;
let cursor = Cursor::new(decompressed);
self.schlib = SchLib::open(cursor)?;
}
Ok(())
}
fn write_schlib<F: Read + Write + Seek>(&self, cf: &mut CompoundFile<F>) -> Result<()> {
cf.create_storage("/SchLib")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
let mut schlib_buf = Cursor::new(Vec::new());
self.schlib.save(&mut schlib_buf)?;
let compressed = compress_zlib(schlib_buf.get_ref())?;
let mut final_data = vec![0x02u8];
final_data.extend(compressed);
let stream = cf
.create_stream("/SchLib/0.schlib")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
let mut stream = stream;
stream.write_all(&final_data)?;
Ok(())
}
fn read_pcblib<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
let stream_path = "/PCBLib/0.pcblib";
if cf.entry(stream_path).is_err() {
return Ok(());
}
let mut stream = cf.open_stream(stream_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
let mut data = Vec::new();
stream.read_to_end(&mut data)?;
if data.is_empty() {
return Ok(());
}
if data.len() > 1 && data[0] == 0x02 {
let decompressed = decompress_zlib(&data[1..])?;
let cursor = Cursor::new(decompressed);
self.pcblib = PcbLib::open(cursor)?;
}
Ok(())
}
fn write_pcblib<F: Read + Write + Seek>(&self, cf: &mut CompoundFile<F>) -> Result<()> {
cf.create_storage("/PCBLib")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
let mut pcblib_buf = Cursor::new(Vec::new());
self.pcblib.save(&mut pcblib_buf)?;
let compressed = compress_zlib(pcblib_buf.get_ref())?;
let mut final_data = vec![0x02u8];
final_data.extend(compressed);
let stream = cf
.create_stream("/PCBLib/0.pcblib")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
let mut stream = stream;
stream.write_all(&final_data)?;
Ok(())
}
pub fn schematic_component_count(&self) -> usize {
self.schlib.component_count()
}
pub fn footprint_count(&self) -> usize {
self.pcblib.component_count()
}
pub fn get_cross_ref(&self, name: &str) -> Option<&CrossReference> {
self.cross_refs.iter().find(|r| r.name == name)
}
pub fn get_parameters(&self, name: &str) -> Option<&ComponentParameters> {
self.parameters.iter().find(|p| p.name == name)
}
pub fn component_footprint_map(&self) -> HashMap<String, String> {
self.cross_refs
.iter()
.map(|r| (r.name.clone(), r.footprint.clone()))
.collect()
}
}
fn decompress_zlib(data: &[u8]) -> Result<Vec<u8>> {
let mut decoder = ZlibDecoder::new(data);
let mut decompressed = Vec::new();
decoder.read_to_end(&mut decompressed).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("zlib decompress failed: {}", e),
))
})?;
Ok(decompressed)
}
fn compress_zlib(data: &[u8]) -> Result<Vec<u8>> {
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
encoder.write_all(data)?;
encoder.finish().map_err(|e| {
AltiumError::Io(std::io::Error::other(format!(
"zlib compress failed: {}",
e
)))
})
}
use crate::dump::{DumpTree, TreeBuilder};
impl DumpTree for IntLib {
fn dump(&self, tree: &mut TreeBuilder) {
tree.root(&format!(
"IntLib (v{}, {} symbols, {} footprints)",
self.version,
self.schematic_component_count(),
self.footprint_count()
));
tree.push(true);
tree.begin_node(&format!("Cross-References ({})", self.cross_refs.len()));
for (i, xref) in self.cross_refs.iter().enumerate() {
tree.push(i < self.cross_refs.len() - 1);
let props = vec![
("symbol", xref.name.clone()),
("footprint", xref.footprint.clone()),
("description", xref.description.clone()),
];
tree.add_leaf(&xref.name, &props);
tree.pop();
}
tree.pop();
tree.push(true);
tree.begin_node(&format!(
"SchLib ({} components)",
self.schlib.component_count()
));
for (i, comp) in self.schlib.iter().enumerate() {
tree.push(i < self.schlib.component_count() - 1);
let props = vec![
("name", comp.name().to_string()),
("pins", format!("{}", comp.pin_count())),
];
tree.add_leaf(comp.name(), &props);
tree.pop();
}
tree.pop();
tree.push(false);
tree.begin_node(&format!(
"PcbLib ({} footprints)",
self.pcblib.component_count()
));
for (i, comp) in self.pcblib.iter().enumerate() {
tree.push(i < self.pcblib.component_count() - 1);
let props = vec![
("name", comp.pattern.clone()),
("pads", format!("{}", comp.pad_count())),
];
tree.add_leaf(&comp.pattern, &props);
tree.pop();
}
tree.pop();
}
}