use crate::document::{HeaderVariables, LegacyHeaderState};
use crate::error::{DxfError, Result};
use crate::io::dwg::crc;
use crate::types::{Color, Vector2, Vector3};
pub const HEADER_VARIABLES_END: usize = 0x6bd;
pub const ENTITIES_START: u32 = 0x6cf;
const MENU_START: usize = 0x194;
const MENU_PREFIX_LEN: usize = 15;
const MENU_CONTINUATION_START: usize = 0x1fc;
const MENU_CONTINUATION_LEN: usize = 45;
pub const ENTITIES_BEGIN: [u8; 16] = [
0xc4, 0x6e, 0x68, 0x54, 0xf8, 0x6e, 0x33, 0x30, 0x63, 0x3e, 0xc1, 0x85, 0x2a, 0xdc, 0x94, 0x01,
];
pub const ENTITIES_END: [u8; 16] = [
0x3b, 0x91, 0x97, 0xab, 0x07, 0x91, 0xcc, 0xcf, 0x9c, 0xc1, 0x3e, 0x7a, 0xd5, 0x23, 0x6b, 0xfe,
];
pub const BLOCK_ENTITIES_BEGIN: [u8; 16] = [
0x72, 0x2b, 0x7d, 0xec, 0x3e, 0x8c, 0x88, 0x6c, 0x7a, 0x72, 0x0a, 0xfd, 0xc8, 0x6c, 0x84, 0x26,
];
pub const BLOCK_ENTITIES_END: [u8; 16] = [
0x8d, 0xd4, 0x82, 0x13, 0xc1, 0x73, 0x77, 0x93, 0x85, 0x8d, 0xf5, 0x02, 0x37, 0x93, 0x7b, 0xd9,
];
pub const EXTRA_ENTITIES_BEGIN: [u8; 16] = [
0xd5, 0xf9, 0xd3, 0xbb, 0x0a, 0xa9, 0x69, 0xa6, 0xcd, 0x1c, 0x87, 0xc7, 0xee, 0x80, 0x4b, 0x17,
];
pub const EXTRA_ENTITIES_END: [u8; 16] = [
0x2a, 0x06, 0x2c, 0x44, 0xf5, 0x56, 0x96, 0x59, 0x32, 0xe3, 0x78, 0x38, 0x11, 0x7f, 0xb4, 0xe8,
];
pub const AUXHEADER_BEGIN: [u8; 16] = [
0x29, 0x8d, 0xd1, 0x49, 0xa9, 0x73, 0x1f, 0xea, 0x99, 0xde, 0x32, 0xf9, 0x4d, 0x0a, 0xe0, 0x19,
];
pub const AUXHEADER_END: [u8; 16] = [
0xd6, 0x72, 0x2e, 0xb6, 0x56, 0x8c, 0xe0, 0x15, 0x66, 0x21, 0xcd, 0x06, 0xb2, 0xf5, 0x1f, 0xe6,
];
pub const TABLE_NAMES: [&str; 10] = [
"BLOCK", "LAYER", "STYLE", "LTYPE", "VIEW", "UCS", "VPORT", "APPID", "DIMSTYLE", "VX",
];
const TABLE_IDS: [u16; 10] = [1, 2, 3, 5, 6, 7, 8, 9, 10, 11];
const TABLE_OFFSETS: [usize; 10] = [
0x2c, 0x36, 0x40, 0x4a, 0x54, 0x3ef, 0x500, 0x512, 0x522, 0x69f,
];
pub const TABLE_BEGIN: [[u8; 16]; 10] = [
[
0xdb, 0xef, 0xb3, 0xf0, 0xc7, 0x3e, 0x6d, 0xa6, 0xc9, 0xb6, 0x24, 0x5c, 0x4c, 0x6f, 0x32,
0xcb,
],
[
0x0e, 0xc4, 0x64, 0x6f, 0xbb, 0x1d, 0xd3, 0x8b, 0x00, 0x49, 0xc2, 0xef, 0x18, 0xea, 0x6f,
0xfb,
],
[
0xe2, 0x3e, 0xc1, 0x82, 0x43, 0x9f, 0x61, 0x77, 0x50, 0xab, 0xc7, 0x66, 0x96, 0x00, 0x06,
0x18,
],
[
0xac, 0x90, 0x1a, 0xca, 0x1c, 0xbd, 0x95, 0x15, 0x16, 0x16, 0x4c, 0x14, 0xce, 0x18, 0x88,
0xaf,
],
[
0xc1, 0x3c, 0xaa, 0x56, 0x68, 0xf4, 0xb4, 0x1e, 0x4b, 0x74, 0xf4, 0x08, 0x42, 0x4d, 0xbf,
0xa5,
],
[
0x60, 0x4a, 0xfa, 0x3d, 0x84, 0x90, 0xcc, 0x5b, 0xef, 0xe7, 0xd6, 0xa5, 0x7f, 0x1e, 0x61,
0xcd,
],
[
0xf6, 0xed, 0x44, 0x61, 0x2a, 0xdc, 0xe4, 0x7b, 0x4e, 0xb9, 0x2b, 0xbb, 0x66, 0x60, 0x63,
0x8d,
],
[
0xe1, 0x25, 0xc2, 0x50, 0x36, 0x68, 0x6c, 0x0c, 0x3b, 0xd3, 0x5d, 0x56, 0xc1, 0x79, 0x1c,
0x3a,
],
[
0xb4, 0x18, 0x3e, 0x42, 0xc9, 0x9f, 0xff, 0xe5, 0xb6, 0xe2, 0xcb, 0xb3, 0x75, 0xc3, 0xc3,
0xb0,
],
[
0xe0, 0xca, 0x36, 0x7c, 0xce, 0xe7, 0x58, 0x6f, 0x2b, 0x7d, 0x74, 0x55, 0x05, 0xf1, 0x44,
0x7f,
],
];
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct TableLocator {
pub record_size: u16,
pub count: u16,
pub flags: u16,
pub address: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HeaderLayout {
pub entities_start: u32,
pub entities_end: u32,
pub blocks_start: u32,
pub blocks_size: u32,
pub extras_start: u32,
pub extras_size: u32,
}
impl Default for HeaderLayout {
fn default() -> Self {
Self {
entities_start: ENTITIES_START,
entities_end: ENTITIES_START,
blocks_start: 0,
blocks_size: 0,
extras_start: 0,
extras_size: 0,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct HeaderReferences {
pub layer_index: i16,
pub text_style_index: i16,
pub linetype_index: i16,
pub ucs_index: i16,
pub paper_ucs_index: i16,
}
#[derive(Debug, Clone)]
pub struct R12Header {
pub variables: HeaderVariables,
pub layout: HeaderLayout,
pub tables: [TableLocator; 10],
pub references: HeaderReferences,
pub maintenance_version: u8,
pub num_header_variables: u16,
pub raw: Vec<u8>,
}
impl R12Header {
pub(crate) fn apply_variables(
&self,
target: &mut HeaderVariables,
code_page_override: Option<&str>,
) -> Result<()> {
let mut codec = Codec::reading(self.raw.clone());
codec.encoding = crate::io::dxf::code_page::encoding_from_dwg_code_page(
u16::from_le_bytes(self.raw[0x3f9..0x3fb].try_into().unwrap()),
);
if let Some(encoding) =
code_page_override.and_then(crate::io::dxf::code_page::encoding_from_code_page)
{
codec.encoding = encoding;
}
codec.pos = 0x5e;
variables(
&mut codec,
target,
&mut self.references.clone(),
self.num_header_variables,
)?;
let mut state = LegacyHeaderState::default();
legacy_state(&mut codec, &mut state)?;
target.legacy = Some(state);
Ok(())
}
}
pub fn read_header(data: &[u8]) -> Result<R12Header> {
read_header_with_code_page(data, None)
}
pub(super) fn read_header_with_code_page(
data: &[u8],
code_page_override: Option<&str>,
) -> Result<R12Header> {
if data.len() < 0x6cd || !matches!(data.get(..6), Some(b"AC1009" | b"AD1009")) {
return Err(DxfError::InvalidFormat("Invalid AC1009 header".into()));
}
let num_header_variables = u16::from_le_bytes(data[0x11..0x13].try_into().unwrap());
if !matches!(num_header_variables, 204 | 205) {
return Err(DxfError::InvalidFormat(format!(
"Unsupported AC1009 header variable count: {num_header_variables}"
)));
}
let variables_end = if num_header_variables == 204 {
0x6bb
} else {
HEADER_VARIABLES_END
};
let stored_crc = u16::from_le_bytes(data[variables_end..variables_end + 2].try_into().unwrap());
if stored_crc != crc::crc16(crc::CRC16_SEED, &data[..variables_end]) {
return Err(DxfError::InvalidFormat("AC1009 header CRC mismatch".into()));
}
if data.get(variables_end + 2..variables_end + 18) != Some(ENTITIES_BEGIN.as_slice()) {
return Err(DxfError::InvalidFormat(
"Missing AC1009 entity sentinel".into(),
));
}
let mut header = R12Header {
variables: HeaderVariables::default(),
layout: HeaderLayout::default(),
tables: [TableLocator::default(); 10],
references: HeaderReferences::default(),
maintenance_version: data[0x0b],
num_header_variables,
raw: data[..variables_end].to_vec(),
};
let mut c = Codec::reading(data[..variables_end].to_vec());
let page = u16::from_le_bytes(data[0x3F9..0x3FB].try_into().unwrap());
c.encoding = crate::io::dxf::code_page::encoding_from_dwg_code_page(page);
if let Some(encoding) =
code_page_override.and_then(crate::io::dxf::code_page::encoding_from_code_page)
{
c.encoding = encoding;
}
c.pos = 0x14;
c.long(&mut header.layout.entities_start);
c.long(&mut header.layout.entities_end);
c.long(&mut header.layout.blocks_start);
c.long(&mut header.layout.blocks_size);
c.long(&mut header.layout.extras_start);
c.long(&mut header.layout.extras_size);
header.layout.blocks_size &= 0x3fffffff;
header.layout.extras_size &= 0x3fffffff;
for (table, offset) in header.tables.iter_mut().zip(TABLE_OFFSETS) {
c.pos = offset;
c.locator(table);
let end = u64::from(table.address) + u64::from(table.record_size) * u64::from(table.count);
if table.count != 0 && (table.record_size == 0 || end > data.len() as u64) {
return Err(DxfError::InvalidFormat(
"AC1009 table exceeds file bounds".into(),
));
}
}
c.pos = 0x5e;
variables(
&mut c,
&mut header.variables,
&mut header.references,
num_header_variables,
)?;
let mut state = LegacyHeaderState::default();
legacy_state(&mut c, &mut state)?;
header.variables.legacy = Some(state);
if header.layout.entities_start < (variables_end + 18) as u32
|| header.layout.entities_end < header.layout.entities_start
|| header.layout.entities_end as usize > data.len()
{
return Err(DxfError::InvalidFormat(
"Invalid AC1009 entity bounds".into(),
));
}
Ok(header)
}
pub fn write_header(
header: &HeaderVariables,
layout: HeaderLayout,
tables: &[TableLocator; 10],
references: HeaderReferences,
maintenance_version: u8,
source_raw: Option<&[u8]>,
) -> Result<Vec<u8>> {
let mut c = Codec::writing(source_raw);
c.encoding = crate::io::dxf::code_page::encoding_from_dwg_code_page(
crate::io::dxf::code_page::dwg_code_page_index(&header.code_page),
);
c.data[..6].copy_from_slice(b"AC1009");
c.data[6..11].fill(0);
c.data[0x0b] = maintenance_version;
c.data[0x0c] = 1;
c.pos = 0x0d;
c.short(&mut 3);
c.short(&mut 5);
c.short(&mut 205);
c.byte(&mut 0);
c.long(&mut layout.entities_start.clone());
c.long(&mut layout.entities_end.clone());
c.long(&mut layout.blocks_start.clone());
c.long(&mut (layout.blocks_size | 0x40000000));
c.long(&mut layout.extras_start.clone());
c.long(&mut (layout.extras_size | 0x80000000));
for (table, offset) in tables.iter().zip(TABLE_OFFSETS) {
c.pos = offset;
let mut table = *table;
c.locator(&mut table);
}
c.pos = 0x5e;
variables(&mut c, &mut header.clone(), &mut references.clone(), 205)?;
if let Some(state) = &header.legacy {
legacy_state(&mut c, &mut state.clone())?;
}
let checksum = crc::crc16(crc::CRC16_SEED, &c.data);
c.data.extend_from_slice(&checksum.to_le_bytes());
c.data.extend_from_slice(&ENTITIES_BEGIN);
Ok(c.data)
}
pub fn write_auxheader(
layout: HeaderLayout,
tables: &[TableLocator; 10],
handle_seed: u64,
address: u32,
) -> Vec<u8> {
let mut data = Vec::with_capacity(170);
data.extend_from_slice(&AUXHEADER_BEGIN);
data.extend_from_slice(&16u16.to_le_bytes());
data.extend_from_slice(&138u16.to_le_bytes());
for value in [
layout.entities_start,
layout.entities_end,
layout.blocks_start,
layout.extras_start,
] {
data.extend_from_slice(&value.to_le_bytes());
}
data.extend_from_slice(&1u16.to_le_bytes());
data.extend_from_slice(&handle_seed.to_be_bytes());
data.extend_from_slice(&10u16.to_le_bytes());
for (table, id) in tables.iter().zip(TABLE_IDS) {
data.extend_from_slice(&id.to_le_bytes());
data.extend_from_slice(&table.record_size.to_le_bytes());
data.extend_from_slice(&table.count.to_le_bytes());
data.extend_from_slice(&table.address.to_le_bytes());
}
data.extend_from_slice(&address.to_le_bytes());
let checksum = crc::crc16(crc::CRC16_SEED, &data[16..]);
data.extend_from_slice(&checksum.to_le_bytes());
data.extend_from_slice(&AUXHEADER_END);
data
}
fn legacy_state(c: &mut Codec, state: &mut LegacyHeaderState) -> Result<()> {
for (offset, value) in [
(0xc8, &mut state.view_center),
(0x239, &mut state.view_direction),
(0x443, &mut state.view_target),
] {
c.pos = offset;
c.point3(value);
}
for (offset, value) in [
(0xea, &mut state.snap_spacing),
(0xfa, &mut state.snap_base),
(0x118, &mut state.grid_spacing),
(0x16a, &mut state.axis_spacing),
] {
c.pos = offset;
c.point2(value);
}
for (offset, value) in [
(0xe0, &mut state.view_height),
(0x15c, &mut state.view_aspect_ratio),
(0x10a, &mut state.snap_angle),
(0x46b, &mut state.front_clip),
(0x473, &mut state.back_clip),
] {
c.pos = offset;
c.double(value);
}
for (offset, value) in [
(0xe8, &mut state.snap_mode),
(0x112, &mut state.snap_style),
(0x114, &mut state.snap_isopair),
(0x116, &mut state.grid_mode),
(0x168, &mut state.axis_mode),
(0x317, &mut state.circle_zoom),
(0x341, &mut state.fast_zoom),
(0x47b, &mut state.view_mode),
(0x50a, &mut state.flatland),
(0x50e, &mut state.ucs_icon),
(0x53d, &mut state.unit_mode),
] {
c.pos = offset;
c.short(value);
}
c.pos = 0x53f;
for value in &mut state.user_unit_ratios {
c.double(value);
}
c.pos = 0x55f;
for value in &mut state.user_unit_names {
if c.read {
c.text(value, 32)?;
} else {
let encoded = crate::io::dxf::code_page::encode_legacy_string(value, c.encoding);
if encoded.len() > 32 || encoded.contains(&0) {
return Err(DxfError::InvalidFormat(
"AC1009 user-unit name exceeds 32 encoded bytes or contains NUL".into(),
));
}
c.data[c.pos..c.pos + 32].fill(0);
c.data[c.pos..c.pos + encoded.len()].copy_from_slice(&encoded);
c.pos += 32;
}
}
Ok(())
}
struct Codec {
data: Vec<u8>,
pos: usize,
read: bool,
encoding: crate::io::dxf::code_page::LegacyEncoding,
}
impl Codec {
fn reading(data: Vec<u8>) -> Self {
Self {
data,
pos: 0,
read: true,
encoding: encoding_rs::WINDOWS_1252.into(),
}
}
fn writing(raw: Option<&[u8]>) -> Self {
let mut data = vec![0; HEADER_VARIABLES_END];
for (offset, value) in [
(0xe0, 10.0_f64),
(0xea, 0.5),
(0xf2, 0.5),
(0x118, 0.5),
(0x120, 0.5),
(0x249, 1.0),
(0x53f, 1.0),
(0x547, 1.0),
(0x54f, 1.0),
(0x557, 1.0),
] {
data[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
}
data[0x317..0x319].copy_from_slice(&100_i16.to_le_bytes());
data[0x341..0x343].copy_from_slice(&1_i16.to_le_bytes());
data[0x50e..0x510].copy_from_slice(&3_i16.to_le_bytes());
data[0x14c..0x150].copy_from_slice(&15_u32.to_le_bytes());
data[0x51e..0x520].copy_from_slice(&5_i16.to_le_bytes());
data[0x520..0x522].copy_from_slice(&1_i16.to_le_bytes());
if let Some(raw) = raw {
let count = raw.len().min(data.len());
data[..count].copy_from_slice(&raw[..count]);
}
Self {
data,
pos: 0,
read: false,
encoding: encoding_rs::WINDOWS_1252.into(),
}
}
fn bytes<const N: usize>(&mut self, value: &mut [u8; N]) {
if self.read {
value.copy_from_slice(&self.data[self.pos..self.pos + N]);
} else {
self.data[self.pos..self.pos + N].copy_from_slice(value);
}
self.pos += N;
}
fn byte(&mut self, value: &mut u8) {
let mut bytes = [*value];
self.bytes(&mut bytes);
*value = bytes[0];
}
fn short(&mut self, value: &mut i16) {
let mut bytes = value.to_le_bytes();
self.bytes(&mut bytes);
*value = i16::from_le_bytes(bytes);
}
fn long(&mut self, value: &mut u32) {
let mut bytes = value.to_le_bytes();
self.bytes(&mut bytes);
*value = u32::from_le_bytes(bytes);
}
fn double(&mut self, value: &mut f64) {
let mut bytes = value.to_le_bytes();
self.bytes(&mut bytes);
*value = f64::from_le_bytes(bytes);
}
fn flag(&mut self, value: &mut bool) {
let mut number = i16::from(*value);
self.short(&mut number);
*value = number != 0;
}
fn byte_flag(&mut self, value: &mut bool) {
let mut number = u8::from(*value);
self.byte(&mut number);
*value = number != 0;
}
fn byte_short(&mut self, value: &mut i16) {
let mut number = *value as u8;
self.byte(&mut number);
*value = i16::from(number);
}
fn color(&mut self, value: &mut Color) {
let mut number = value.approximate_index();
self.short(&mut number);
*value = Color::from_index(number);
}
fn point2(&mut self, value: &mut Vector2) {
self.double(&mut value.x);
self.double(&mut value.y);
}
fn point3(&mut self, value: &mut Vector3) {
self.double(&mut value.x);
self.double(&mut value.y);
self.double(&mut value.z);
}
fn text(&mut self, value: &mut String, length: usize) -> Result<()> {
if self.read {
let bytes = &self.data[self.pos..self.pos + length];
let end = bytes.iter().position(|byte| *byte == 0).unwrap_or(length);
*value = crate::io::dxf::code_page::decode_legacy_escapes(
&self.encoding.decode(&bytes[..end]).0,
);
} else {
let bytes = crate::io::dxf::code_page::encode_legacy_string(value, self.encoding);
if bytes.contains(&0) || bytes.len() >= length {
return Err(DxfError::InvalidFormat(format!(
"AC1009 header string at {:#x} must fit in {} encoded bytes without NUL (got {})",
self.pos,
length.saturating_sub(1),
bytes.len()
)));
}
self.data[self.pos..self.pos + length].fill(0);
self.data[self.pos..self.pos + bytes.len()].copy_from_slice(&bytes);
}
self.pos += length;
Ok(())
}
fn menu(&mut self, value: &mut String) -> Result<()> {
debug_assert_eq!(self.pos, MENU_START);
let prefix = MENU_START..MENU_START + MENU_PREFIX_LEN;
let continuation = MENU_CONTINUATION_START..MENU_CONTINUATION_START + MENU_CONTINUATION_LEN;
if self.read {
let mut bytes = Vec::with_capacity(MENU_PREFIX_LEN + MENU_CONTINUATION_LEN);
for span in [prefix, continuation] {
bytes.extend(
self.data[span]
.iter()
.copied()
.take_while(|byte| *byte != 0),
);
}
*value =
crate::io::dxf::code_page::decode_legacy_escapes(&self.encoding.decode(&bytes).0);
} else {
let bytes = crate::io::dxf::code_page::encode_legacy_string(value, self.encoding);
if bytes.contains(&0) || bytes.len() > MENU_PREFIX_LEN + MENU_CONTINUATION_LEN {
return Err(DxfError::InvalidFormat(format!(
"AC1009 MENU must fit in 60 encoded bytes without NUL (got {})",
bytes.len()
)));
}
self.data[prefix.clone()].fill(0);
self.data[continuation.clone()].fill(0);
let split = bytes.len().min(MENU_PREFIX_LEN);
self.data[prefix.start..prefix.start + split].copy_from_slice(&bytes[..split]);
self.data[continuation.start..continuation.start + bytes.len() - split]
.copy_from_slice(&bytes[split..]);
}
self.pos += MENU_PREFIX_LEN;
Ok(())
}
fn timer(&mut self, value: &mut f64) {
let mut day = value.floor() as u32;
let mut milliseconds = ((value.fract() * 86_400_000.0).round().max(0.0)) as u32;
self.long(&mut day);
self.long(&mut milliseconds);
*value = f64::from(day) + f64::from(milliseconds) / 86_400_000.0;
}
fn locator(&mut self, value: &mut TableLocator) {
let mut size = value.record_size as i16;
let mut count = value.count as i16;
let mut flags = value.flags as i16;
self.short(&mut size);
self.short(&mut count);
self.short(&mut flags);
self.long(&mut value.address);
value.record_size = size as u16;
value.count = count as u16;
value.flags = flags as u16;
}
fn skip(&mut self, length: usize) {
self.pos += length;
}
}
fn variables(
c: &mut Codec,
h: &mut HeaderVariables,
r: &mut HeaderReferences,
count: u16,
) -> Result<()> {
c.point3(&mut h.model_space_insertion_base);
c.flag(&mut h.polyline_linetype_generation);
c.point3(&mut h.model_space_extents_min);
c.point3(&mut h.model_space_extents_max);
c.point2(&mut h.model_space_limits_min);
c.point2(&mut h.model_space_limits_max);
c.skip(24 + 8 + 2 + 16 + 16 + 8 + 2 + 2 + 2 + 16); c.flag(&mut h.ortho_mode);
c.flag(&mut h.regen_mode);
c.flag(&mut h.fill_mode);
c.flag(&mut h.quick_text_mode);
c.short(&mut h.drag_mode);
c.double(&mut h.linetype_scale);
c.double(&mut h.text_height);
c.double(&mut h.trace_width);
c.short(&mut r.layer_index);
c.skip(8 + 2);
c.flag(&mut h.paper_space_linetype_scaling);
c.short(&mut h.tree_depth);
c.skip(2 + 8);
c.short(&mut h.linear_unit_format);
c.short(&mut h.linear_unit_precision);
c.skip(2 + 16);
c.double(&mut h.sketch_increment);
c.double(&mut h.fillet_radius);
c.short(&mut h.angular_unit_format);
c.short(&mut h.angular_unit_precision);
c.short(&mut r.text_style_index);
let mut osmode = h.object_snap_mode as i16;
c.short(&mut osmode);
h.object_snap_mode = i32::from(osmode);
c.short(&mut h.attribute_visibility);
c.menu(&mut h.menu_name)?;
c.double(&mut h.dim_scale);
c.double(&mut h.dim_arrow_size);
c.double(&mut h.dim_ext_line_offset);
c.double(&mut h.dim_line_increment);
c.double(&mut h.dim_ext_line_extension);
c.double(&mut h.dim_tolerance_plus);
c.double(&mut h.dim_tolerance_minus);
c.double(&mut h.dim_text_height);
c.double(&mut h.dim_center_mark);
c.double(&mut h.dim_tick_size);
c.byte_flag(&mut h.dim_tolerance);
c.byte_flag(&mut h.dim_limits);
c.byte_flag(&mut h.dim_text_inside_horizontal);
c.byte_flag(&mut h.dim_text_outside_horizontal);
c.byte_flag(&mut h.dim_suppress_ext1);
c.byte_flag(&mut h.dim_suppress_ext2);
c.byte_short(&mut h.dim_text_above);
c.byte_flag(&mut h.limit_check);
c.skip(46);
c.double(&mut h.elevation);
c.double(&mut h.thickness);
c.skip(24 * 7 + 2);
c.flag(&mut h.blip_mode);
c.byte_short(&mut h.dim_zero_suppression);
c.double(&mut h.dim_rounding);
c.double(&mut h.dim_line_extension);
c.text(&mut h.dim_arrow_block, 33)?;
c.skip(2);
c.short(&mut h.coords_mode);
c.color(&mut h.current_entity_color);
c.short(&mut r.linetype_index);
c.timer(&mut h.create_date_julian);
c.timer(&mut h.update_date_julian);
c.timer(&mut h.total_editing_time);
c.timer(&mut h.user_elapsed_time);
c.flag(&mut h.user_timer);
c.skip(2);
let mut skpoly = h.sketch_type != 0;
c.flag(&mut skpoly);
h.sketch_type = i16::from(skpoly);
c.skip(14);
c.double(&mut h.angle_base);
c.short(&mut h.angle_direction);
c.short(&mut h.point_display_mode);
c.double(&mut h.point_display_size);
c.double(&mut h.polyline_width);
c.short(&mut h.user_int1);
c.short(&mut h.user_int2);
c.short(&mut h.user_int3);
c.short(&mut h.user_int4);
c.short(&mut h.user_int5);
c.double(&mut h.user_real1);
c.double(&mut h.user_real2);
c.double(&mut h.user_real3);
c.double(&mut h.user_real4);
c.double(&mut h.user_real5);
c.byte_flag(&mut h.dim_alternate_units);
c.byte_short(&mut h.dim_alt_decimal_places);
c.byte_flag(&mut h.associate_dimensions);
c.byte_flag(&mut h.update_dimensions_while_dragging);
c.text(&mut h.dim_post, 16)?;
c.text(&mut h.dim_alt_post, 16)?;
c.double(&mut h.dim_alt_scale);
c.double(&mut h.dim_linear_scale);
c.short(&mut h.spline_segments);
c.flag(&mut h.spline_frame);
c.flag(&mut h.attribute_request);
c.flag(&mut h.attribute_dialog);
c.double(&mut h.chamfer_distance_a);
c.double(&mut h.chamfer_distance_b);
c.flag(&mut h.mirror_text);
if c.pos != 0x3ef {
return Err(DxfError::InvalidFormat(format!(
"AC1009 header layout error at {:x}",
c.pos
)));
}
c.skip(10);
let mut page = crate::io::dxf::code_page::dwg_code_page_index(&h.code_page) as i16;
c.short(&mut page);
h.code_page = crate::io::dxf::code_page::dwg_code_page_name(page as u16).to_owned();
c.point3(&mut h.model_space_ucs_origin);
c.point3(&mut h.model_space_ucs_x_axis);
c.point3(&mut h.model_space_ucs_y_axis);
c.skip(24);
c.double(&mut h.lens_length);
c.double(&mut h.view_twist);
c.skip(8 + 8 + 2);
c.byte_flag(&mut h.dim_force_line_inside);
c.text(&mut h.dim_arrow_block1, 33)?;
c.text(&mut h.dim_arrow_block2, 33)?;
c.byte_flag(&mut h.dim_separate_arrows);
c.byte_flag(&mut h.dim_force_text_inside);
c.byte_flag(&mut h.dim_suppress_outside_ext);
c.double(&mut h.dim_text_vertical_pos);
c.skip(33);
c.short(&mut 1);
let mut seed = h.handle_seed.to_be_bytes();
c.bytes(&mut seed);
h.handle_seed = u64::from_be_bytes(seed);
c.short(&mut h.surface_u_density);
c.short(&mut h.surface_v_density);
c.short(&mut h.surface_type);
c.short(&mut h.surface_tab1);
c.short(&mut h.surface_tab2);
if c.pos != 0x500 {
return Err(DxfError::InvalidFormat(format!(
"AC1009 VPORT layout error at {:x}",
c.pos
)));
}
c.skip(10 + 2);
c.short(&mut h.spline_type);
c.skip(2);
c.short(&mut r.ucs_index);
c.skip(10);
c.flag(&mut h.world_view);
c.skip(4 + 10 + 5);
c.color(&mut h.dim_line_color);
c.color(&mut h.dim_ext_line_color);
c.color(&mut h.dim_text_color);
c.short(&mut h.shade_edge);
c.short(&mut h.shade_diffuse);
c.skip(2 + 2 + 32 + 128);
c.double(&mut h.dim_tolerance_scale);
c.point3(&mut h.paper_space_ucs_origin);
c.point3(&mut h.paper_space_ucs_x_axis);
c.point3(&mut h.paper_space_ucs_y_axis);
c.short(&mut r.paper_ucs_index);
c.flag(&mut h.show_model_space);
c.flag(&mut h.paper_space_limit_check);
c.skip(2);
c.point3(&mut h.paper_space_extents_min);
c.point3(&mut h.paper_space_extents_max);
c.point2(&mut h.paper_space_limits_min);
c.point2(&mut h.paper_space_limits_max);
c.point3(&mut h.paper_space_insertion_base);
if c.pos != 0x69f {
return Err(DxfError::InvalidFormat(format!(
"AC1009 VX layout error at {:x}",
c.pos
)));
}
c.skip(10);
c.short(&mut h.max_active_viewports);
c.double(&mut h.dim_line_gap);
c.double(&mut h.paper_elevation);
if count >= 205 {
c.flag(&mut h.retain_xref_visibility);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header_roundtrip_preserves_variables_and_locators() {
let mut h = HeaderVariables::default();
h.model_space_insertion_base = Vector3::new(12.0, 34.0, 56.0);
h.linetype_scale = 2.5;
h.user_int3 = -24;
h.dim_arrow_block1 = "ARROW".into();
h.handle_seed = 0x12345678;
h.code_page = "ANSI_1252".into();
let layout = HeaderLayout::default();
let tables = [TableLocator::default(); 10];
let refs = HeaderReferences {
layer_index: 7,
text_style_index: 3,
linetype_index: -1,
ucs_index: -1,
paper_ucs_index: -1,
};
let encoded = write_header(&h, layout, &tables, refs, 0, None).unwrap();
assert_eq!(encoded.len(), ENTITIES_START as usize);
let decoded = read_header(&encoded).unwrap();
assert_eq!(
decoded.variables.model_space_insertion_base,
h.model_space_insertion_base
);
assert_eq!(decoded.variables.linetype_scale, h.linetype_scale);
assert_eq!(decoded.variables.user_int3, h.user_int3);
assert_eq!(decoded.variables.dim_arrow_block1, h.dim_arrow_block1);
assert_eq!(decoded.variables.handle_seed, h.handle_seed);
assert_eq!(decoded.layout, layout);
assert_eq!(decoded.references, refs);
assert_eq!(decoded.tables, tables);
let mut corrupt = encoded;
corrupt[0x100] ^= 1;
assert!(read_header(&corrupt).is_err());
}
#[test]
fn auxheader_has_native_size_and_crc() {
let encoded = write_auxheader(
HeaderLayout::default(),
&[TableLocator::default(); 10],
42,
2000,
);
assert_eq!(encoded.len(), 170);
assert_eq!(&encoded[..16], &AUXHEADER_BEGIN);
assert_eq!(&encoded[154..], &AUXHEADER_END);
assert_eq!(
u16::from_le_bytes(encoded[152..154].try_into().unwrap()),
crc::crc16(crc::CRC16_SEED, &encoded[16..152])
);
}
}