mod stream_reader;
mod text_reader;
mod binary_reader;
mod section_reader;
pub use stream_reader::DxfStreamReader;
pub use text_reader::DxfTextReader;
pub use binary_reader::DxfBinaryReader;
use section_reader::SectionReader;
use crate::document::CadDocument;
use crate::error::Result;
use std::fs::File;
use std::io::{BufReader, Read, Seek};
use std::path::Path;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DxfReaderConfiguration {
pub failsafe: bool,
}
impl Default for DxfReaderConfiguration {
fn default() -> Self {
Self { failsafe: false }
}
}
pub struct DxfReader {
reader: Box<dyn DxfStreamReader>,
config: DxfReaderConfiguration,
estimated_entities: usize,
}
impl DxfReader {
pub fn from_reader<R: Read + Seek + 'static>(reader: R) -> Result<Self> {
let mut buf_reader = BufReader::with_capacity(64 * 1024, reader);
let stream_size = buf_reader.seek(std::io::SeekFrom::End(0)).unwrap_or(0);
buf_reader.seek(std::io::SeekFrom::Start(0))?;
let estimated_entities = (stream_size as usize / 300).max(16);
let is_binary = Self::is_binary(&mut buf_reader)?;
let reader: Box<dyn DxfStreamReader> = if is_binary {
Box::new(DxfBinaryReader::new(buf_reader)?)
} else {
buf_reader.seek(std::io::SeekFrom::Start(0))?;
Box::new(DxfTextReader::new(buf_reader)?)
};
Ok(Self {
reader,
config: DxfReaderConfiguration::default(),
estimated_entities,
})
}
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let file = File::open(path)?;
let mut buf_reader = BufReader::with_capacity(64 * 1024, file);
let stream_size = buf_reader.seek(std::io::SeekFrom::End(0)).unwrap_or(0);
buf_reader.seek(std::io::SeekFrom::Start(0))?;
let estimated_entities = (stream_size as usize / 300).max(16);
let is_binary = Self::is_binary(&mut buf_reader)?;
let reader: Box<dyn DxfStreamReader> = if is_binary {
Box::new(DxfBinaryReader::new(buf_reader)?)
} else {
buf_reader.seek(std::io::SeekFrom::Start(0))?;
Box::new(DxfTextReader::new(buf_reader)?)
};
Ok(Self {
reader,
config: DxfReaderConfiguration::default(),
estimated_entities,
})
}
fn is_binary<R: Read + Seek>(reader: &mut R) -> Result<bool> {
const SENTINEL: &[u8] = b"AutoCAD Binary DXF";
let mut buffer = vec![0u8; SENTINEL.len()];
let bytes_read = reader.read(&mut buffer)?;
reader.seek(std::io::SeekFrom::Start(0))?;
if bytes_read < SENTINEL.len() {
return Ok(false);
}
Ok(buffer == SENTINEL)
}
pub fn with_configuration(mut self, config: DxfReaderConfiguration) -> Self {
self.config = config;
self
}
pub fn read(mut self) -> Result<CadDocument> {
let mut document = CadDocument::new();
document.entities.reserve(self.estimated_entities);
document.entity_index.reserve(self.estimated_entities);
let failsafe = self.config.failsafe;
while let Some(pair) = self.reader.read_pair()? {
if pair.code == 0 && pair.value_string == "SECTION" {
if let Some(section_pair) = self.reader.read_pair()? {
if section_pair.code == 2 {
let section_name = section_pair.value_string.clone();
let result = match section_name.as_str() {
"HEADER" => self.read_header_section(&mut document),
"CLASSES" => self.read_classes_section(&mut document),
"TABLES" => self.read_tables_section(&mut document),
"BLOCKS" => self.read_blocks_section(&mut document),
"ENTITIES" => self.read_entities_section(&mut document),
"OBJECTS" => self.read_objects_section(&mut document),
"THUMBNAILIMAGE" => {
document.notifications.notify(
crate::notification::NotificationType::NotImplemented,
"THUMBNAILIMAGE section skipped",
);
self.skip_section()
}
_ => {
self.skip_section()
}
};
if let Err(e) = result {
if failsafe {
document.notifications.notify(
crate::notification::NotificationType::Error,
format!("Error reading {} section: {}", section_name, e),
);
let _ = self.skip_section();
} else {
return Err(e);
}
}
}
}
} else if pair.code == 0 && pair.value_string == "EOF" {
break;
}
}
document.resolve_references();
Ok(document)
}
fn read_header_section(&mut self, document: &mut CadDocument) -> Result<()> {
let mut section_reader = SectionReader::new(&mut self.reader);
section_reader.read_header(document)
}
fn read_classes_section(&mut self, document: &mut CadDocument) -> Result<()> {
let mut section_reader = SectionReader::new(&mut self.reader);
section_reader.read_classes(document)
}
fn read_tables_section(&mut self, document: &mut CadDocument) -> Result<()> {
let mut section_reader = SectionReader::new(&mut self.reader);
section_reader.read_tables(document)
}
fn read_blocks_section(&mut self, document: &mut CadDocument) -> Result<()> {
let mut section_reader = SectionReader::new(&mut self.reader);
section_reader.read_blocks(document)
}
fn read_entities_section(&mut self, document: &mut CadDocument) -> Result<()> {
let mut section_reader = SectionReader::new(&mut self.reader);
section_reader.read_entities(document)
}
fn read_objects_section(&mut self, document: &mut CadDocument) -> Result<()> {
let mut section_reader = SectionReader::new(&mut self.reader);
section_reader.read_objects(document)
}
fn skip_section(&mut self) -> Result<()> {
while let Some(pair) = self.reader.read_pair()? {
if pair.code == 0 && pair.value_string == "ENDSEC" {
break;
}
}
Ok(())
}
}