1use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
4use cfb::CompoundFile;
5use std::collections::HashMap;
6use std::fs::File;
7use std::io::{Cursor, Read, Seek, Write};
8use std::path::Path;
9
10use crate::error::{AltiumError, Result};
11use crate::io::reader::{
12 read_block, read_parameters_block, read_pascal_short_string, read_pascal_string,
13 read_string_block,
14};
15use crate::io::writer::{
16 write_block, write_parameters, write_pascal_short_string, write_string_block,
17};
18use crate::records::pcb::{
19 PcbArc, PcbComponent, PcbComponentBody, PcbFill, PcbObjectId, PcbPad, PcbRecord, PcbRegion,
20 PcbText, PcbTrack, PcbVia,
21};
22use crate::traits::{FromBinary, ToBinary};
23use crate::types::ParameterCollection;
24
25#[derive(Debug, Default)]
27pub struct PcbLib {
28 section_keys: HashMap<String, String>,
30 pub unique_id: String,
32 pub components: Vec<PcbComponent>,
34}
35
36impl PcbLib {
37 pub fn open<R: Read + Seek>(reader: R) -> Result<Self> {
39 let mut pcblib = PcbLib::default();
40 let mut cf = CompoundFile::open(reader).map_err(|e| {
41 AltiumError::Io(std::io::Error::new(
42 std::io::ErrorKind::InvalidData,
43 e.to_string(),
44 ))
45 })?;
46 pcblib.read_file_header(&mut cf)?;
48 pcblib.read_section_keys(&mut cf)?;
50 pcblib.read_library(&mut cf)?;
52 Ok(pcblib)
53 }
54
55 pub fn open_file<P: AsRef<Path>>(path: P) -> Result<Self> {
57 let file = File::open(path)?;
58 Self::open(file)
59 }
60
61 pub fn save<W: Read + Write + Seek>(&self, writer: W) -> Result<()> {
63 let mut cf = CompoundFile::create(writer)
64 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
65
66 self.write_file_header(&mut cf)?;
68
69 self.write_section_keys(&mut cf)?;
71
72 self.write_library(&mut cf)?;
74
75 cf.flush()
76 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
77
78 Ok(())
79 }
80
81 pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
83 let file = File::create(path)?;
84 self.save(file)
85 }
86
87 fn write_file_header<F: Read + Write + Seek>(&self, cf: &mut CompoundFile<F>) -> Result<()> {
89 let mut data = Vec::new();
90
91 let version = "PCB 6.0 Binary Library File";
93 data.write_i32::<LittleEndian>(version.len() as i32)?;
94 write_pascal_short_string(&mut data, version)?;
95
96 data.write_f64::<LittleEndian>(5.0)?;
99
100 write_pascal_short_string(&mut data, "DVLTOKCO")?;
102
103 let stream = cf
104 .create_stream("/FileHeader")
105 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
106
107 let mut stream = stream;
108 stream.write_all(&data)?;
109
110 Ok(())
111 }
112
113 fn write_section_keys<F: Read + Write + Seek>(&self, cf: &mut CompoundFile<F>) -> Result<()> {
116 if self.components.is_empty() {
118 return Ok(());
119 }
120
121 let mut data = Vec::new();
122 data.write_i32::<LittleEndian>(self.components.len() as i32)?;
123
124 for (i, comp) in self.components.iter().enumerate() {
125 let storage_name = format!("PCBComponent_{}", i + 1);
126 crate::io::writer::write_pascal_string(&mut data, &comp.pattern)?;
127 write_string_block(&mut data, &storage_name)?;
128 }
129
130 let stream = cf
131 .create_stream("/SectionKeys")
132 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
133
134 let mut stream = stream;
135 stream.write_all(&data)?;
136
137 Ok(())
138 }
139
140 fn write_library<F: Read + Write + Seek>(&self, cf: &mut CompoundFile<F>) -> Result<()> {
142 cf.create_storage("/Library")
144 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
145
146 self.write_library_header(cf)?;
148
149 self.write_library_data(cf)?;
151
152 self.write_library_substorages(cf)?;
154
155 self.write_file_version_info(cf)?;
157
158 for (i, comp) in self.components.iter().enumerate() {
160 let storage_name = format!("PCBComponent_{}", i + 1);
161 self.write_footprint(cf, comp, &storage_name)?;
162 }
163
164 Ok(())
165 }
166
167 fn write_library_header<F: Read + Write + Seek>(&self, cf: &mut CompoundFile<F>) -> Result<()> {
169 let mut header_data = Vec::new();
170 header_data.write_u32::<LittleEndian>(1)?; let stream = cf
173 .create_stream("/Library/Header")
174 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
175 let mut stream = stream;
176 stream.write_all(&header_data)?;
177 Ok(())
178 }
179
180 fn write_library_data<F: Read + Write + Seek>(&self, cf: &mut CompoundFile<F>) -> Result<()> {
182 let mut data = Vec::new();
183
184 let params = Self::build_library_parameters();
186 let mut params_block = Vec::new();
187 write_parameters(&mut params_block, ¶ms)?;
188 write_block(&mut data, ¶ms_block, 0)?;
189
190 data.write_u32::<LittleEndian>(self.components.len() as u32)?;
192
193 for i in 0..self.components.len() {
195 let storage_name = format!("PCBComponent_{}", i + 1);
196 write_string_block(&mut data, &storage_name)?;
197 }
198
199 let stream = cf
200 .create_stream("/Library/Data")
201 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
202 let mut stream = stream;
203 stream.write_all(&data)?;
204 Ok(())
205 }
206
207 fn build_library_parameters() -> ParameterCollection {
209 let mut params = ParameterCollection::new();
210
211 params.add("KIND", "Protel_Advanced_PCB_Library");
213 params.add("VERSION", "1.0");
214
215 params.add("BOARDVERSION", "5.01");
217 params.add("VISIBLEGRIDMULTFACTOR", "1.000");
218 params.add("BIGVISIBLEGRIDMULTFACTOR", "5.000");
219 params.add("CURRENT2D3DVIEWSTATE", "2D");
220
221 params.add("CFG2D.CURRENTLAYER", "TOP");
223 params.add("CFG2D.SHOWPADNETS", "TRUE");
224 params.add("CFG2D.SHOWPADNUMBERS", "TRUE");
225 params.add("CFG2D.SHOWVIANETS", "TRUE");
226 params.add("CFG2D.SHOWORIGINMARKER", "TRUE");
227 params.add("CFG2D.DISPLAYSPECIALSTRINGS", "FALSE");
228 params.add("CFG2D.SHOWTESTPOINTS", "FALSE");
229 params.add("CFG2D.SHOWSTATUSINFO", "TRUE");
230 params.add("CFG2D.USETRANSPARENTLAYERS", "FALSE");
231 params.add("CFG2D.PLANEDRAWMODE", "2");
232 params.add("CFG2D.DISPLAYNETNAMESONTRACKS", "1");
233 params.add("CFG2D.SINGLELAYERMODESTATE", "3");
234 params.add("CFG2D.ORIGINMARKERCOLOR", "16777215");
235
236 params.add(
238 "CFG2D.TOGGLELAYERS",
239 "1111111111111111111111111111111111111111111111111111111111111111",
240 );
241
242 params.add("EGENABLED", "TRUE");
244 params.add("EGRANGE", "8mil");
245 params.add("OGSNAPENABLED", "TRUE");
246 params.add("GRIDSNAPENABLED", "TRUE");
247
248 params
249 }
250
251 fn write_library_substorages<F: Read + Write + Seek>(
253 &self,
254 cf: &mut CompoundFile<F>,
255 ) -> Result<()> {
256 for storage in &[
258 "/Library/Models",
259 "/Library/Textures",
260 "/Library/ModelsNoEmbed",
261 ] {
262 cf.create_storage(storage)
263 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
264
265 let header_path = format!("{}/Header", storage);
267 let data_path = format!("{}/Data", storage);
268
269 let mut stream = cf
270 .create_stream(&header_path)
271 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
272 stream.write_u32::<LittleEndian>(0)?;
273
274 let mut stream = cf
275 .create_stream(&data_path)
276 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
277 stream.write_all(&[])?;
278 }
279
280 {
282 let mut stream = cf
283 .create_stream("/Library/EmbeddedFonts")
284 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
285 stream.write_u32::<LittleEndian>(0)?;
286 }
287
288 {
290 cf.create_storage("/Library/PadViaLibrary")
291 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
292
293 let mut header = cf
294 .create_stream("/Library/PadViaLibrary/Header")
295 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
296 header.write_u32::<LittleEndian>(1)?;
297
298 let mut params = ParameterCollection::new();
299 params.add(
300 "PADVIALIBRARY.LIBRARYID",
301 "{00000000-0000-0000-0000-000000000000}",
302 );
303 params.add("PADVIALIBRARY.LIBRARYNAME", "<Local>");
304 params.add("PADVIALIBRARY.DISPLAYUNITS", "1");
305 let mut block = Vec::new();
306 write_parameters(&mut block, ¶ms)?;
307 let mut data_buf = Vec::new();
308 write_block(&mut data_buf, &block, 0)?;
309
310 let mut data = cf
311 .create_stream("/Library/PadViaLibrary/Data")
312 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
313 data.write_all(&data_buf)?;
314 }
315
316 {
318 cf.create_storage("/Library/LayerKindMapping")
319 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
320
321 let mut header = cf
322 .create_stream("/Library/LayerKindMapping/Header")
323 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
324 header.write_u32::<LittleEndian>(1)?;
325
326 let mut data_buf = Vec::new();
328 data_buf.write_u32::<LittleEndian>(8)?;
330 for c in "1.0\0".encode_utf16() {
332 data_buf.write_u16::<LittleEndian>(c)?;
333 }
334
335 let mut data = cf
336 .create_stream("/Library/LayerKindMapping/Data")
337 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
338 data.write_all(&data_buf)?;
339 }
340
341 {
343 cf.create_storage("/Library/ComponentParamsTOC")
344 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
345
346 let mut header = cf
347 .create_stream("/Library/ComponentParamsTOC/Header")
348 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
349 header.write_u32::<LittleEndian>(self.components.len() as u32)?;
350
351 let mut toc_data = Vec::new();
353 for (i, comp) in self.components.iter().enumerate() {
354 let storage_name = format!("PCBComponent_{}", i + 1);
355 let pad_count = comp
356 .primitives
357 .iter()
358 .filter(|p| matches!(p, PcbRecord::Pad(_)))
359 .count();
360
361 let mut params = ParameterCollection::new();
362 params.add("Name", &storage_name);
363 params.add("Pad Count", &pad_count.to_string());
364 params.add("Height", "0");
365 params.add("Description", &comp.description);
366
367 let mut block = Vec::new();
368 write_parameters(&mut block, ¶ms)?;
369 write_block(&mut toc_data, &block, 0)?;
370 }
371
372 let mut data = cf
373 .create_stream("/Library/ComponentParamsTOC/Data")
374 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
375 data.write_all(&toc_data)?;
376 }
377
378 Ok(())
379 }
380
381 fn write_file_version_info<F: Read + Write + Seek>(
383 &self,
384 cf: &mut CompoundFile<F>,
385 ) -> Result<()> {
386 cf.create_storage("/FileVersionInfo")
387 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
388
389 let mut header = cf
390 .create_stream("/FileVersionInfo/Header")
391 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
392 header.write_u32::<LittleEndian>(1)?;
393
394 let mut params = ParameterCollection::new();
396 params.add("VERSIONNUMBER", "1.0");
397 params.add("REVISIONDATE", "2024-01-01");
398
399 let mut block = Vec::new();
400 write_parameters(&mut block, ¶ms)?;
401 let mut data_buf = Vec::new();
402 write_block(&mut data_buf, &block, 0)?;
403
404 let mut data = cf
405 .create_stream("/FileVersionInfo/Data")
406 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
407 data.write_all(&data_buf)?;
408
409 Ok(())
410 }
411
412 fn write_footprint<F: Read + Write + Seek>(
414 &self,
415 cf: &mut CompoundFile<F>,
416 comp: &PcbComponent,
417 storage_name: &str,
418 ) -> Result<()> {
419 let storage_path = format!("/{}", storage_name);
421 cf.create_storage(&storage_path)
422 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
423
424 {
426 let mut header_data = Vec::new();
427 header_data.write_u32::<LittleEndian>(comp.primitives.len() as u32)?;
428
429 let header_path = format!("{}/Header", storage_path);
430 let stream = cf
431 .create_stream(&header_path)
432 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
433 let mut stream = stream;
434 stream.write_all(&header_data)?;
435 }
436
437 {
439 let mut params_data = Vec::new();
440 let params = comp.export_to_parameters();
441 let mut block = Vec::new();
442 write_parameters(&mut block, ¶ms)?;
443 write_block(&mut params_data, &block, 0)?;
444
445 let params_path = format!("{}/Parameters", storage_path);
446 let stream = cf
447 .create_stream(¶ms_path)
448 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
449 let mut stream = stream;
450 stream.write_all(¶ms_data)?;
451 }
452
453 {
455 let mut data = Vec::new();
456
457 write_string_block(&mut data, &comp.pattern)?;
459
460 for record in &comp.primitives {
462 self.write_primitive(&mut data, record)?;
463 }
464
465 let data_path = format!("{}/Data", storage_path);
466 let stream = cf
467 .create_stream(&data_path)
468 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
469 let mut stream = stream;
470 stream.write_all(&data)?;
471 }
472
473 {
475 let mut params = ParameterCollection::new();
476 params.add("COUNT", "0");
477 let mut block = Vec::new();
478 write_parameters(&mut block, ¶ms)?;
479 let mut wide_data = Vec::new();
480 write_block(&mut wide_data, &block, 0)?;
481
482 let wide_path = format!("{}/WideStrings", storage_path);
483 let stream = cf
484 .create_stream(&wide_path)
485 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
486 let mut stream = stream;
487 stream.write_all(&wide_data)?;
488 }
489
490 {
492 let guids_path = format!("{}/PrimitiveGuids", storage_path);
493 cf.create_storage(&guids_path)
494 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
495
496 let header_path = format!("{}/Header", guids_path);
498 let mut header = cf
499 .create_stream(&header_path)
500 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
501 header.write_u32::<LittleEndian>(comp.primitives.len() as u32)?;
502
503 let data_path = format!("{}/Data", guids_path);
505 let mut data = cf
506 .create_stream(&data_path)
507 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
508 for _ in 0..comp.primitives.len() {
510 data.write_all(&[0u8; 16])?;
511 }
512 }
513
514 Ok(())
515 }
516
517 fn write_primitive<W: Write>(&self, writer: &mut W, record: &PcbRecord) -> Result<()> {
519 let object_id = match record {
521 PcbRecord::Arc(_) => 1,
522 PcbRecord::Pad(_) => 2,
523 PcbRecord::Via(_) => 3,
524 PcbRecord::Track(_) => 4,
525 PcbRecord::Text(_) => 5,
526 PcbRecord::Fill(_) => 6,
527 PcbRecord::Region(_) => 11,
528 PcbRecord::ComponentBody(_) => 12,
529 PcbRecord::Polygon(_) => {
530 return Err(AltiumError::Parse(
531 "Polygons are not supported in PcbLib footprints".to_string(),
532 ));
533 }
534 PcbRecord::Unknown { object_id, .. } => *object_id as u8,
535 };
536 writer.write_u8(object_id)?;
537
538 match record {
540 PcbRecord::Arc(arc) => {
541 let mut data = Vec::new();
542 arc.write_to(&mut data)?;
543 write_block(writer, &data, 0)?;
544 }
545 PcbRecord::Pad(pad) => {
546 pad.write_to(writer)?;
548 }
549 PcbRecord::Via(via) => {
550 let mut data = Vec::new();
551 via.write_to(&mut data)?;
552 write_block(writer, &data, 0)?;
553 }
554 PcbRecord::Track(track) => {
555 let mut data = Vec::new();
556 track.write_to(&mut data)?;
557 write_block(writer, &data, 0)?;
558 }
559 PcbRecord::Text(text) => {
560 let mut data = Vec::new();
562 text.write_to(&mut data)?;
563 write_block(writer, &data, 0)?;
564 write_string_block(writer, &text.text)?;
565 }
566 PcbRecord::Fill(fill) => {
567 let mut data = Vec::new();
568 fill.write_to(&mut data)?;
569 write_block(writer, &data, 0)?;
570 }
571 PcbRecord::Region(region) => {
572 let mut data = Vec::new();
573 region.write_to(&mut data)?;
574 write_block(writer, &data, 0)?;
575 }
576 PcbRecord::ComponentBody(body) => {
577 let mut data = Vec::new();
578 body.write_to(&mut data)?;
579 write_block(writer, &data, 0)?;
580 }
581 PcbRecord::Polygon(_) => {
582 unreachable!("Polygons should have errored in object_id match")
584 }
585 PcbRecord::Unknown { raw_data, .. } => {
586 write_block(writer, raw_data, 0)?;
587 }
588 }
589
590 Ok(())
591 }
592
593 fn get_section_key(&self, ref_name: &str) -> String {
595 self.section_keys
596 .get(ref_name)
597 .cloned()
598 .unwrap_or_else(|| ref_name.to_string())
599 }
600
601 fn read_file_header<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
603 let stream_path = "/FileHeader";
604 if cf.entry(stream_path).is_err() {
605 return Ok(());
606 }
607
608 let mut stream = cf.open_stream(stream_path).map_err(|e| {
609 AltiumError::Io(std::io::Error::new(
610 std::io::ErrorKind::NotFound,
611 e.to_string(),
612 ))
613 })?;
614
615 let mut data = Vec::new();
616 stream.read_to_end(&mut data)?;
617
618 if data.is_empty() {
619 return Ok(());
620 }
621
622 let mut cursor = Cursor::new(&data);
623
624 let _version_len = cursor.read_i32::<LittleEndian>()?;
626 let _version_text = read_pascal_short_string(&mut cursor)?;
627
628 if (cursor.position() as usize) < data.len() {
631 let field1 = read_pascal_short_string(&mut cursor).unwrap_or_default();
634 if !field1.is_empty() {
635 log::trace!("FileHeader optional field 1: {:?}", field1);
636 }
637
638 let field2 = read_pascal_short_string(&mut cursor).unwrap_or_default();
641 if !field2.is_empty() {
642 log::trace!("FileHeader optional field 2: {:?}", field2);
643 }
644
645 if let Ok(uid) = read_pascal_short_string(&mut cursor) {
648 self.unique_id = uid;
649 }
650 }
651
652 Ok(())
653 }
654
655 fn read_section_keys<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
657 let stream_path = "/SectionKeys";
658 if cf.entry(stream_path).is_err() {
659 return Ok(());
660 }
661
662 let mut stream = cf.open_stream(stream_path).map_err(|e| {
663 AltiumError::Io(std::io::Error::new(
664 std::io::ErrorKind::NotFound,
665 e.to_string(),
666 ))
667 })?;
668
669 let mut data = Vec::new();
670 stream.read_to_end(&mut data)?;
671
672 if data.is_empty() {
673 return Ok(());
674 }
675
676 let mut cursor = Cursor::new(&data);
677 let key_count = cursor.read_i32::<LittleEndian>()?;
678
679 for _ in 0..key_count {
680 let lib_ref = read_pascal_string(&mut cursor)?;
681 let section_key = read_string_block(&mut cursor)?;
682
683 if !lib_ref.is_empty() && !section_key.is_empty() {
684 self.section_keys.insert(lib_ref, section_key);
685 }
686 }
687
688 Ok(())
689 }
690
691 fn read_library<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
693 let storage_path = "/Library";
694
695 let header_path = format!("{}/Header", storage_path);
697 if cf.entry(&header_path).is_ok() {
698 let mut stream = cf.open_stream(&header_path).map_err(|e| {
699 AltiumError::Io(std::io::Error::new(
700 std::io::ErrorKind::NotFound,
701 e.to_string(),
702 ))
703 })?;
704 let _record_count = stream.read_u32::<LittleEndian>()?;
705 }
706
707 let data_path = format!("{}/Data", storage_path);
709 let mut stream = cf.open_stream(&data_path).map_err(|e| {
710 AltiumError::Io(std::io::Error::new(
711 std::io::ErrorKind::NotFound,
712 e.to_string(),
713 ))
714 })?;
715
716 let mut data = Vec::new();
717 stream.read_to_end(&mut data)?;
718
719 if data.is_empty() {
720 return Ok(());
721 }
722
723 let mut cursor = Cursor::new(&data);
724
725 let _header_params = read_parameters_block(&mut cursor)?;
727
728 let footprint_count = cursor.read_u32::<LittleEndian>()?;
730 let mut ref_names = Vec::with_capacity(footprint_count as usize);
731
732 for _ in 0..footprint_count {
733 let ref_name = read_string_block(&mut cursor)?;
734 ref_names.push(ref_name);
735 }
736
737 for ref_name in ref_names {
739 let section_key = self.get_section_key(&ref_name);
740 match self.read_footprint(cf, §ion_key) {
741 Ok(component) => {
742 self.components.push(component);
743 }
744 Err(e) => {
745 eprintln!("Warning: Failed to read footprint {:?}: {}", ref_name, e);
746 continue;
747 }
748 }
749 }
750
751 Ok(())
752 }
753
754 fn read_footprint<R: Read + Seek>(
756 &self,
757 cf: &mut CompoundFile<R>,
758 section_key: &str,
759 ) -> Result<PcbComponent> {
760 let mut storage_path = format!("/{}", section_key);
764
765 if cf.entry(&storage_path).is_err() {
767 let section_key_alt = section_key.replace('/', "_");
768 let alt_path = format!("/{}", section_key_alt);
769 if cf.entry(&alt_path).is_ok() {
770 storage_path = alt_path;
771 }
772 }
773
774 let header_path = format!("{}/Header", storage_path);
776 let _record_count = if cf.entry(&header_path).is_ok() {
777 let mut stream = cf.open_stream(&header_path).map_err(|e| {
778 AltiumError::Io(std::io::Error::new(
779 std::io::ErrorKind::NotFound,
780 e.to_string(),
781 ))
782 })?;
783 stream.read_u32::<LittleEndian>()?
784 } else {
785 0
786 };
787
788 let mut component = PcbComponent::default();
789
790 let params_path = format!("{}/Parameters", storage_path);
792 if cf.entry(¶ms_path).is_ok() {
793 let mut stream = cf.open_stream(¶ms_path).map_err(|e| {
794 AltiumError::Io(std::io::Error::new(
795 std::io::ErrorKind::NotFound,
796 e.to_string(),
797 ))
798 })?;
799 let mut data = Vec::new();
800 stream.read_to_end(&mut data)?;
801
802 if !data.is_empty() {
803 let mut cursor = Cursor::new(&data);
804 let params = read_parameters_block(&mut cursor)?;
805 component.import_from_parameters(¶ms);
806 }
807 }
808
809 let wide_strings = self.read_wide_strings(cf, &storage_path)?;
811
812 let data_path = format!("{}/Data", storage_path);
814 let mut stream = cf.open_stream(&data_path).map_err(|e| {
815 AltiumError::Io(std::io::Error::new(
816 std::io::ErrorKind::NotFound,
817 format!("Footprint data not found: {} - {}", data_path, e),
818 ))
819 })?;
820
821 let mut data = Vec::new();
822 stream.read_to_end(&mut data)?;
823
824 if data.is_empty() {
825 return Err(AltiumError::Parse("Empty footprint data".to_string()));
826 }
827
828 let mut cursor = Cursor::new(&data);
829
830 let pattern = read_string_block(&mut cursor)?;
832 if component.pattern.is_empty() {
833 component.pattern = pattern;
834 }
835
836 while (cursor.position() as usize) < data.len() {
838 match self.read_primitive(&mut cursor, &wide_strings) {
839 Ok(record) => component.primitives.push(record),
840 Err(_) => break,
841 }
842 }
843
844 Ok(component)
845 }
846
847 fn read_wide_strings<R: Read + Seek>(
849 &self,
850 cf: &mut CompoundFile<R>,
851 storage_path: &str,
852 ) -> Result<Vec<String>> {
853 let wide_path = format!("{}/WideStrings", storage_path);
854 if cf.entry(&wide_path).is_err() {
855 return Ok(Vec::new());
856 }
857
858 let mut stream = cf.open_stream(&wide_path).map_err(|e| {
859 AltiumError::Io(std::io::Error::new(
860 std::io::ErrorKind::NotFound,
861 e.to_string(),
862 ))
863 })?;
864
865 let mut data = Vec::new();
866 stream.read_to_end(&mut data)?;
867
868 if data.is_empty() {
869 return Ok(Vec::new());
870 }
871
872 let mut cursor = Cursor::new(&data);
873 let params = read_parameters_block(&mut cursor)?;
874
875 let mut strings = Vec::new();
876 let count = params.get("COUNT").map(|v| v.as_int_or(0)).unwrap_or(0) as usize;
877
878 for i in 0..count {
879 let key = format!("WIDESTRING{}", i);
880 if let Some(val) = params.get(&key) {
881 strings.push(val.as_str().to_string());
882 } else {
883 strings.push(String::new());
884 }
885 }
886
887 Ok(strings)
888 }
889
890 fn read_primitive(
892 &self,
893 cursor: &mut Cursor<&Vec<u8>>,
894 wide_strings: &[String],
895 ) -> Result<PcbRecord> {
896 let object_id = PcbObjectId::from_byte(cursor.read_u8()?);
897
898 match object_id {
899 PcbObjectId::Arc => {
900 let block = read_block(cursor)?;
901 let mut block_cursor = Cursor::new(&block);
902 let arc = <PcbArc as FromBinary>::read_from(&mut block_cursor)?;
903 Ok(PcbRecord::Arc(arc))
904 }
905 PcbObjectId::Pad => {
906 let pad = PcbPad::read_from(cursor)?;
907 Ok(PcbRecord::Pad(Box::new(pad)))
908 }
909 PcbObjectId::Via => {
910 let block = read_block(cursor)?;
911 let mut block_cursor = Cursor::new(&block);
912 let via = <PcbVia as FromBinary>::read_from(&mut block_cursor)?;
913 Ok(PcbRecord::Via(via))
914 }
915 PcbObjectId::Track => {
916 let block = read_block(cursor)?;
917 let mut block_cursor = Cursor::new(&block);
918 let track = <PcbTrack as FromBinary>::read_from(&mut block_cursor)?;
919 Ok(PcbRecord::Track(track))
920 }
921 PcbObjectId::Text => {
922 let block = read_block(cursor)?;
923 let mut block_cursor = Cursor::new(&block);
924 let mut text = <PcbText as FromBinary>::read_from(&mut block_cursor)?;
925
926 let ascii_text = read_string_block(cursor)?;
928
929 if text.wide_strings_index >= 0
931 && (text.wide_strings_index as usize) < wide_strings.len()
932 {
933 text.text = wide_strings[text.wide_strings_index as usize].clone();
934 } else {
935 text.text = ascii_text;
936 }
937
938 Ok(PcbRecord::Text(text))
939 }
940 PcbObjectId::Fill => {
941 let block = read_block(cursor)?;
942 let mut block_cursor = Cursor::new(&block);
943 let fill = <PcbFill as FromBinary>::read_from(&mut block_cursor)?;
944 Ok(PcbRecord::Fill(fill))
945 }
946 PcbObjectId::Region => {
947 let block = read_block(cursor)?;
948 let mut block_cursor = Cursor::new(&block);
949 let region = <PcbRegion as FromBinary>::read_from(&mut block_cursor)?;
950 Ok(PcbRecord::Region(region))
951 }
952 PcbObjectId::ComponentBody => {
953 let block = read_block(cursor)?;
954 let mut block_cursor = Cursor::new(&block);
955 let body = <PcbComponentBody as FromBinary>::read_from(&mut block_cursor)?;
956 Ok(PcbRecord::ComponentBody(Box::new(body)))
957 }
958 _ => {
959 let block = read_block(cursor)?;
961 Ok(PcbRecord::Unknown {
962 object_id,
963 raw_data: block,
964 })
965 }
966 }
967 }
968
969 pub fn component_count(&self) -> usize {
971 self.components.len()
972 }
973
974 pub fn iter(&self) -> impl Iterator<Item = &PcbComponent> {
976 self.components.iter()
977 }
978}
979
980use crate::dump::{DumpTree, TreeBuilder};
982
983impl DumpTree for PcbLib {
984 fn dump(&self, tree: &mut TreeBuilder) {
985 tree.root(&format!("PcbLib ({} footprints)", self.components.len()));
986
987 for (i, comp) in self.components.iter().enumerate() {
988 tree.push(i < self.components.len() - 1);
989 comp.dump(tree);
990 tree.pop();
991 }
992 }
993}