use std::io::{Write, Seek};
use indexmap::IndexMap;
use byteorder::{LittleEndian, WriteBytesExt};
use crate::error::DxfError;
use crate::types::DxfVersion;
use super::section_definition::{names, end_sentinels};
use super::section_descriptor::DwgSectionLocatorRecord;
use crate::io::dwg::crc;
const FILE_HEADER_SIZE: usize = 0x61;
pub struct DwgFileHeaderWriterAC15 {
version: DxfVersion,
records: IndexMap<String, (DwgSectionLocatorRecord, Option<Vec<u8>>)>,
}
impl DwgFileHeaderWriterAC15 {
pub fn new(version: DxfVersion) -> Self {
let mut records = IndexMap::new();
records.insert(
names::HEADER.to_string(),
(DwgSectionLocatorRecord::new(Some(0)), None),
);
records.insert(
names::CLASSES.to_string(),
(DwgSectionLocatorRecord::new(Some(1)), None),
);
records.insert(
names::OBJ_FREE_SPACE.to_string(),
(DwgSectionLocatorRecord::new(Some(3)), None),
);
records.insert(
names::TEMPLATE.to_string(),
(DwgSectionLocatorRecord::new(Some(4)), None),
);
records.insert(
names::AUX_HEADER.to_string(),
(DwgSectionLocatorRecord::new(Some(5)), None),
);
records.insert(
names::ACDB_OBJECTS.to_string(),
(DwgSectionLocatorRecord::new(None), None),
);
records.insert(
names::HANDLES.to_string(),
(DwgSectionLocatorRecord::new(Some(2)), None),
);
records.insert(
names::PREVIEW.to_string(),
(DwgSectionLocatorRecord::new(None), None),
);
Self { version, records }
}
pub fn add_section(&mut self, name: &str, data: Vec<u8>) {
if let Some(entry) = self.records.get_mut(name) {
entry.0.size = data.len() as i64;
entry.1 = Some(data);
}
}
pub fn handle_section_offset(&self) -> usize {
let mut offset = FILE_HEADER_SIZE;
for (name, (_, data)) in &self.records {
if name == names::ACDB_OBJECTS {
break;
}
offset += data.as_ref().map_or(0, |d| d.len());
}
offset
}
pub fn write_file<W: Write + Seek>(&mut self, output: &mut W) -> Result<(), DxfError> {
self.set_record_seekers();
self.write_file_header(output)?;
self.write_record_streams(output)?;
Ok(())
}
fn set_record_seekers(&mut self) {
let mut curr_offset = FILE_HEADER_SIZE as i64;
for (_, (record, data)) in self.records.iter_mut() {
record.seeker = curr_offset;
curr_offset += data.as_ref().map_or(0, |d| d.len()) as i64;
}
}
fn write_file_header<W: Write>(&self, output: &mut W) -> Result<(), DxfError> {
let mut buf: Vec<u8> = Vec::with_capacity(FILE_HEADER_SIZE);
let version_str = self.version.as_str();
buf.extend_from_slice(version_str.as_bytes());
buf.extend_from_slice(&[0, 0, 0, 0, 0, 15, 1]);
let preview_seeker = self.records.get(names::PREVIEW)
.map_or(0i32, |(r, _)| r.seeker as i32);
buf.write_i32::<LittleEndian>(preview_seeker)?;
buf.push(0x1B);
buf.push(0x19);
buf.write_u16::<LittleEndian>(30)?;
buf.write_i32::<LittleEndian>(6)?;
for (_, (record, _)) in &self.records {
if let Some(number) = record.number {
self.write_record(&mut buf, number, record)?;
}
}
let crc_value = crc::crc16(crc::CRC16_SEED, &buf);
buf.write_i16::<LittleEndian>(crc_value as i16)?;
buf.extend_from_slice(&end_sentinels::FILE_HEADER);
debug_assert_eq!(buf.len(), FILE_HEADER_SIZE,
"File header size mismatch: expected {FILE_HEADER_SIZE}, got {}", buf.len());
output.write_all(&buf)?;
Ok(())
}
fn write_record(&self, buf: &mut Vec<u8>, number: u8, record: &DwgSectionLocatorRecord) -> Result<(), DxfError> {
buf.push(number);
buf.write_i32::<LittleEndian>(record.seeker as i32)?;
buf.write_i32::<LittleEndian>(record.size as i32)?;
Ok(())
}
fn write_record_streams<W: Write>(&self, output: &mut W) -> Result<(), DxfError> {
for (_, (_, data)) in &self.records {
if let Some(data) = data {
output.write_all(data)?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_new_creates_all_sections() {
let writer = DwgFileHeaderWriterAC15::new(DxfVersion::AC1015);
assert_eq!(writer.records.len(), 8);
assert_eq!(writer.records[names::HEADER].0.number, Some(0));
assert_eq!(writer.records[names::CLASSES].0.number, Some(1));
assert_eq!(writer.records[names::HANDLES].0.number, Some(2));
assert_eq!(writer.records[names::OBJ_FREE_SPACE].0.number, Some(3));
assert_eq!(writer.records[names::TEMPLATE].0.number, Some(4));
assert_eq!(writer.records[names::AUX_HEADER].0.number, Some(5));
assert_eq!(writer.records[names::ACDB_OBJECTS].0.number, None);
assert_eq!(writer.records[names::PREVIEW].0.number, None);
}
#[test]
fn test_add_section() {
let mut writer = DwgFileHeaderWriterAC15::new(DxfVersion::AC1015);
writer.add_section(names::HEADER, vec![1, 2, 3, 4, 5]);
let (record, data) = &writer.records[names::HEADER];
assert_eq!(record.size, 5);
assert_eq!(data.as_ref().unwrap(), &vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_handle_section_offset() {
let mut writer = DwgFileHeaderWriterAC15::new(DxfVersion::AC1015);
writer.add_section(names::HEADER, vec![0; 100]);
writer.add_section(names::CLASSES, vec![0; 50]);
writer.add_section(names::OBJ_FREE_SPACE, vec![0; 10]);
writer.add_section(names::TEMPLATE, vec![0; 20]);
writer.add_section(names::AUX_HEADER, vec![0; 30]);
assert_eq!(writer.handle_section_offset(), FILE_HEADER_SIZE + 210);
}
#[test]
fn test_write_file_header_size() {
let mut writer = DwgFileHeaderWriterAC15::new(DxfVersion::AC1015);
for name in [
names::HEADER, names::CLASSES, names::OBJ_FREE_SPACE,
names::TEMPLATE, names::AUX_HEADER, names::ACDB_OBJECTS,
names::HANDLES, names::PREVIEW,
] {
writer.add_section(name, vec![]);
}
let mut output = Cursor::new(Vec::new());
writer.write_file(&mut output).unwrap();
let data = output.into_inner();
assert_eq!(data.len(), FILE_HEADER_SIZE);
}
#[test]
fn test_write_file_version_string() {
let mut writer = DwgFileHeaderWriterAC15::new(DxfVersion::AC1015);
for name in [
names::HEADER, names::CLASSES, names::OBJ_FREE_SPACE,
names::TEMPLATE, names::AUX_HEADER, names::ACDB_OBJECTS,
names::HANDLES, names::PREVIEW,
] {
writer.add_section(name, vec![]);
}
let mut output = Cursor::new(Vec::new());
writer.write_file(&mut output).unwrap();
let data = output.into_inner();
assert_eq!(&data[0..6], b"AC1015");
}
#[test]
fn test_write_file_with_data() {
let mut writer = DwgFileHeaderWriterAC15::new(DxfVersion::AC1015);
let header_data = vec![0xAA; 100];
let classes_data = vec![0xBB; 50];
let objects_data = vec![0xCC; 200];
writer.add_section(names::HEADER, header_data.clone());
writer.add_section(names::CLASSES, classes_data.clone());
writer.add_section(names::OBJ_FREE_SPACE, vec![]);
writer.add_section(names::TEMPLATE, vec![]);
writer.add_section(names::AUX_HEADER, vec![]);
writer.add_section(names::ACDB_OBJECTS, objects_data.clone());
writer.add_section(names::HANDLES, vec![]);
writer.add_section(names::PREVIEW, vec![]);
let mut output = Cursor::new(Vec::new());
writer.write_file(&mut output).unwrap();
let data = output.into_inner();
assert_eq!(data.len(), FILE_HEADER_SIZE + 100 + 50 + 200);
assert_eq!(&data[FILE_HEADER_SIZE..FILE_HEADER_SIZE + 100], &[0xAA; 100]);
assert_eq!(&data[FILE_HEADER_SIZE + 100..FILE_HEADER_SIZE + 150], &[0xBB; 50]);
assert_eq!(&data[FILE_HEADER_SIZE + 150..FILE_HEADER_SIZE + 350], &[0xCC; 200]);
}
#[test]
fn test_write_file_seekers_correct() {
let mut writer = DwgFileHeaderWriterAC15::new(DxfVersion::AC1015);
writer.add_section(names::HEADER, vec![0; 100]);
writer.add_section(names::CLASSES, vec![0; 50]);
writer.add_section(names::OBJ_FREE_SPACE, vec![]);
writer.add_section(names::TEMPLATE, vec![]);
writer.add_section(names::AUX_HEADER, vec![]);
writer.add_section(names::ACDB_OBJECTS, vec![0; 200]);
writer.add_section(names::HANDLES, vec![0; 30]);
writer.add_section(names::PREVIEW, vec![]);
let mut output = Cursor::new(Vec::new());
writer.write_file(&mut output).unwrap();
let data = output.into_inner();
let mut offset = 0x19;
let mut records: Vec<(u8, i32, i32)> = Vec::new();
for _ in 0..6 {
let number = data[offset];
let seeker = i32::from_le_bytes([
data[offset + 1], data[offset + 2],
data[offset + 3], data[offset + 4],
]);
let size = i32::from_le_bytes([
data[offset + 5], data[offset + 6],
data[offset + 7], data[offset + 8],
]);
records.push((number, seeker, size));
offset += 9;
}
let header_rec = records.iter().find(|r| r.0 == 0).unwrap();
assert_eq!(header_rec.1, 0x61);
assert_eq!(header_rec.2, 100);
let classes_rec = records.iter().find(|r| r.0 == 1).unwrap();
assert_eq!(classes_rec.1, 197);
assert_eq!(classes_rec.2, 50);
}
#[test]
fn test_file_header_crc() {
let mut writer = DwgFileHeaderWriterAC15::new(DxfVersion::AC1015);
for name in [
names::HEADER, names::CLASSES, names::OBJ_FREE_SPACE,
names::TEMPLATE, names::AUX_HEADER, names::ACDB_OBJECTS,
names::HANDLES, names::PREVIEW,
] {
writer.add_section(name, vec![]);
}
let mut output = Cursor::new(Vec::new());
writer.write_file(&mut output).unwrap();
let data = output.into_inner();
let crc_offset = 0x19 + 54;
let stored_crc = u16::from_le_bytes([data[crc_offset], data[crc_offset + 1]]);
let computed_crc = crc::crc16(crc::CRC16_SEED, &data[..crc_offset]);
assert_eq!(stored_crc, computed_crc, "File header CRC mismatch");
}
#[test]
fn test_file_header_end_sentinel() {
let mut writer = DwgFileHeaderWriterAC15::new(DxfVersion::AC1015);
for name in [
names::HEADER, names::CLASSES, names::OBJ_FREE_SPACE,
names::TEMPLATE, names::AUX_HEADER, names::ACDB_OBJECTS,
names::HANDLES, names::PREVIEW,
] {
writer.add_section(name, vec![]);
}
let mut output = Cursor::new(Vec::new());
writer.write_file(&mut output).unwrap();
let data = output.into_inner();
let sentinel_offset = FILE_HEADER_SIZE - 16;
assert_eq!(
&data[sentinel_offset..FILE_HEADER_SIZE],
&end_sentinels::FILE_HEADER,
"File header end sentinel mismatch"
);
}
#[test]
fn test_r13_version_string() {
let mut writer = DwgFileHeaderWriterAC15::new(DxfVersion::AC1012);
for name in [
names::HEADER, names::CLASSES, names::OBJ_FREE_SPACE,
names::TEMPLATE, names::AUX_HEADER, names::ACDB_OBJECTS,
names::HANDLES, names::PREVIEW,
] {
writer.add_section(name, vec![]);
}
let mut output = Cursor::new(Vec::new());
writer.write_file(&mut output).unwrap();
let data = output.into_inner();
assert_eq!(&data[0..6], b"AC1012");
}
}