use crate::Mode;
const OFFSET_NX: usize = 0;
const OFFSET_NY: usize = 4;
const OFFSET_NZ: usize = 8;
const OFFSET_MODE: usize = 12;
const OFFSET_NXSTART: usize = 16;
const OFFSET_NYSTART: usize = 20;
const OFFSET_NZSTART: usize = 24;
const OFFSET_MX: usize = 28;
const OFFSET_MY: usize = 32;
const OFFSET_MZ: usize = 36;
const OFFSET_XLEN: usize = 40;
const OFFSET_YLEN: usize = 44;
const OFFSET_ZLEN: usize = 48;
const OFFSET_ALPHA: usize = 52;
const OFFSET_BETA: usize = 56;
const OFFSET_GAMMA: usize = 60;
const OFFSET_MAPC: usize = 64;
const OFFSET_MAPR: usize = 68;
const OFFSET_MAPS: usize = 72;
const OFFSET_DMIN: usize = 76;
const OFFSET_DMAX: usize = 80;
const OFFSET_DMEAN: usize = 84;
const OFFSET_ISPG: usize = 88;
const OFFSET_NSYMBT: usize = 92;
const OFFSET_EXTRA: usize = 96;
const OFFSET_EXTTYP: usize = 104; const OFFSET_NVERSION: usize = 108; const OFFSET_ORIGIN: usize = 196;
const OFFSET_MAP: usize = 208;
const OFFSET_MACHST: usize = 212;
const OFFSET_RMS: usize = 216;
const OFFSET_NLABL: usize = 220;
const OFFSET_LABEL: usize = 224;
const DEFAULT_EXTRA: [u8; 100] = {
let mut e = [0u8; 100];
e[12] = 0xAD;
e[13] = 0x4E;
e[14] = 0x00;
e[15] = 0x00;
e
};
#[repr(C, align(4))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Header {
pub nx: i32,
pub ny: i32,
pub nz: i32,
pub mode: i32,
pub nxstart: i32,
pub nystart: i32,
pub nzstart: i32,
pub mx: i32,
pub my: i32,
pub mz: i32,
pub xlen: f32,
pub ylen: f32,
pub zlen: f32,
pub alpha: f32,
pub beta: f32,
pub gamma: f32,
pub mapc: i32,
pub mapr: i32,
pub maps: i32,
pub dmin: f32,
pub dmax: f32,
pub dmean: f32,
pub ispg: i32,
pub nsymbt: i32,
pub extra: [u8; 100],
pub origin: [f32; 3],
pub map: [u8; 4],
pub machst: [u8; 4],
pub rms: f32,
pub nlabl: i32,
pub label: [u8; 800],
}
impl Default for Header {
fn default() -> Self {
Self::new()
}
}
impl Header {
#[inline]
pub const fn new() -> Self {
Self {
nx: 0,
ny: 0,
nz: 0,
mode: 2, nxstart: 0,
nystart: 0,
nzstart: 0,
mx: 0,
my: 0,
mz: 0,
xlen: 1.0, ylen: 1.0,
zlen: 1.0,
alpha: 90.0,
beta: 90.0,
gamma: 90.0,
mapc: 1, mapr: 2, maps: 3, dmin: 0.0, dmax: -1.0, dmean: -2.0, ispg: 1, nsymbt: 0,
extra: DEFAULT_EXTRA,
origin: [0.0; 3],
map: *b"MAP ",
machst: [0x44, 0x44, 0x00, 0x00], rms: -1.0, nlabl: 0,
label: [0; 800],
}
}
#[inline]
pub const fn data_offset(&self) -> usize {
1024 + self.nsymbt as usize
}
#[inline]
pub fn data_size(&self) -> Option<usize> {
let n = (self.nx as usize)
.checked_mul(self.ny as usize)?
.checked_mul(self.nz as usize)?;
match Mode::from_i32(self.mode) {
Some(mode) => {
let byte_size = mode.byte_size();
match mode {
Mode::Packed4Bit => Some(n.div_ceil(2)), _ => n.checked_mul(byte_size),
}
}
None => None, }
}
#[inline]
pub fn validate(&self) -> bool {
self.validate_detailed().is_ok()
}
#[inline]
pub fn validate_detailed(&self) -> Result<(), crate::HeaderValidationError> {
use crate::HeaderValidationError;
if self.nx <= 0 || self.ny <= 0 || self.nz <= 0 {
return Err(HeaderValidationError::InvalidDimensions {
nx: self.nx,
ny: self.ny,
nz: self.nz,
});
}
if Mode::from_i32(self.mode).is_none() {
return Err(HeaderValidationError::UnsupportedMode(self.mode));
}
if !self.validate_map() {
return Err(HeaderValidationError::InvalidMap(self.map));
}
if !(self.ispg == 0
|| (self.ispg >= 1 && self.ispg <= 230)
|| (self.ispg >= 400 && self.ispg <= 630))
{
return Err(HeaderValidationError::InvalidIspg(self.ispg));
}
if !(matches!(self.mapc, 1..=3)
&& matches!(self.mapr, 1..=3)
&& matches!(self.maps, 1..=3)
&& self.mapc != self.mapr
&& self.mapc != self.maps
&& self.mapr != self.maps)
{
return Err(HeaderValidationError::InvalidAxisMapping {
mapc: self.mapc,
mapr: self.mapr,
maps: self.maps,
});
}
if self.nsymbt < 0 {
return Err(HeaderValidationError::InvalidNsymbt(self.nsymbt));
}
if self.nlabl < 0 || self.nlabl > 10 {
return Err(HeaderValidationError::InvalidNlabl(self.nlabl));
}
let actual_labels = self.count_non_empty_labels();
if actual_labels != self.nlabl as usize {
return Err(HeaderValidationError::LabelCountMismatch {
nlabl: self.nlabl,
actual: actual_labels as i32,
});
}
for i in 0..self.nlabl as usize {
if self.label_is_empty(i) {
return Err(HeaderValidationError::EmptyLabelBeforeFilled { index: i as i32 });
}
}
let nversion = self.nversion();
if nversion != 20140 && nversion != 20141 {
return Err(HeaderValidationError::InvalidNversion(nversion));
}
if self.mx <= 0 || self.my <= 0 || self.mz <= 0 {
return Err(HeaderValidationError::InvalidSampling {
mx: self.mx,
my: self.my,
mz: self.mz,
});
}
if self.ispg >= 400 && self.ispg <= 630 && self.mz != 0 && self.nz % self.mz != 0 {
return Err(HeaderValidationError::InvalidVolumeStack {
nz: self.nz,
mz: self.mz,
ispg: self.ispg,
});
}
Ok(())
}
pub fn validate_permissive(&self) -> Result<Vec<String>, crate::HeaderValidationError> {
use crate::HeaderValidationError;
let mut warnings = Vec::new();
if self.nx <= 0 || self.ny <= 0 || self.nz <= 0 {
return Err(HeaderValidationError::InvalidDimensions {
nx: self.nx,
ny: self.ny,
nz: self.nz,
});
}
if Mode::from_i32(self.mode).is_none() {
return Err(HeaderValidationError::UnsupportedMode(self.mode));
}
if !self.validate_map() {
warnings.push(format!(
"MAP field is non-standard: {:?}",
String::from_utf8_lossy(&self.map)
));
}
if !(self.ispg == 0
|| (self.ispg >= 1 && self.ispg <= 230)
|| (self.ispg >= 400 && self.ispg <= 630))
{
warnings.push(format!(
"ISPG {} is outside the standard ranges (0, 1-230, 400-630)",
self.ispg
));
}
if !(matches!(self.mapc, 1..=3)
&& matches!(self.mapr, 1..=3)
&& matches!(self.maps, 1..=3)
&& self.mapc != self.mapr
&& self.mapc != self.maps
&& self.mapr != self.maps)
{
warnings.push(format!(
"Axis mapping ({}, {}, {}) is not a permutation of (1, 2, 3)",
self.mapc, self.mapr, self.maps
));
}
if self.nsymbt < 0 {
warnings.push(format!("NSYMBT is negative ({})", self.nsymbt));
}
if self.nlabl < 0 || self.nlabl > 10 {
warnings.push(format!("NLABL {} is outside 0-10", self.nlabl));
}
let nversion = self.nversion();
if nversion != 20140 && nversion != 20141 {
warnings.push(format!("NVERSION {} is not 20140 or 20141", nversion));
}
if self.mx <= 0 || self.my <= 0 || self.mz <= 0 {
warnings.push(format!(
"Sampling (mx={}, my={}, mz={}) is not all positive",
self.mx, self.my, self.mz
));
}
if self.ispg >= 400 && self.ispg <= 630 && self.mz != 0 && self.nz % self.mz != 0 {
warnings.push(format!(
"Volume stack: nz ({}) is not divisible by mz ({}) for ispg={}",
self.nz, self.mz, self.ispg
));
}
Ok(warnings)
}
#[inline]
fn validate_map(&self) -> bool {
if self.map == *b"MAP " {
return true;
}
if &self.map[..3] == b"MAP"
&& (self.map[3] == b' ' || self.map[3] == 0 || self.map[3] == b'I')
{
return true;
}
if self.map == [0; 4] {
return true;
}
false
}
#[inline]
pub fn exttyp(&self) -> [u8; 4] {
[
self.extra[OFFSET_EXTTYP - OFFSET_EXTRA],
self.extra[OFFSET_EXTTYP - OFFSET_EXTRA + 1],
self.extra[OFFSET_EXTTYP - OFFSET_EXTRA + 2],
self.extra[OFFSET_EXTTYP - OFFSET_EXTRA + 3],
]
}
#[inline]
pub fn set_exttyp(&mut self, value: [u8; 4]) {
let start = OFFSET_EXTTYP - OFFSET_EXTRA;
self.extra[start..start + 4].copy_from_slice(&value);
}
#[inline]
pub fn exttyp_str(&self) -> Result<&str, core::str::Utf8Error> {
let start = OFFSET_EXTTYP - OFFSET_EXTRA;
core::str::from_utf8(&self.extra[start..start + 4])
}
#[inline]
pub fn set_exttyp_str(&mut self, value: &str) -> Result<(), &'static str> {
if value.len() != 4 {
return Err("EXTTYP must be exactly 4 characters");
}
let bytes = value.as_bytes();
let start = OFFSET_EXTTYP - OFFSET_EXTRA;
self.extra[start..start + 4].copy_from_slice(bytes);
Ok(())
}
#[inline]
pub fn nversion(&self) -> i32 {
use crate::engine::codec::EndianCodec;
let file_endian = self.detect_endian();
let start = OFFSET_NVERSION - OFFSET_EXTRA;
i32::decode(&self.extra[start..start + 4], 0, file_endian)
}
#[inline]
pub fn set_nversion(&mut self, value: i32) {
use crate::engine::codec::EndianCodec;
let file_endian = self.detect_endian();
let start = OFFSET_NVERSION - OFFSET_EXTRA;
value.encode(&mut self.extra[start..start + 4], 0, file_endian);
}
pub fn get_labels(&self) -> Vec<String> {
let count = self.nlabl.clamp(0, 10) as usize;
let mut labels = Vec::with_capacity(count);
for i in 0..count {
let start = i * 80;
let bytes = &self.label[start..start + 80];
let text = String::from_utf8_lossy(bytes);
labels.push(text.trim_end().to_string());
}
labels
}
fn label_is_empty(&self, index: usize) -> bool {
let start = index * 80;
self.label[start..start + 80]
.iter()
.all(|&b| b == 0 || b == b' ')
}
fn count_non_empty_labels(&self) -> usize {
(0..10).filter(|&i| !self.label_is_empty(i)).count()
}
pub fn add_label(&mut self, text: &str) {
let filtered: String = text
.chars()
.map(|c| {
if c.is_ascii_graphic() || c == ' ' {
c
} else {
' '
}
})
.take(80)
.collect();
let bytes = filtered.as_bytes();
let len = bytes.len();
let count = self.count_non_empty_labels();
if count < 10 {
let slot = (0..10).find(|&i| self.label_is_empty(i)).unwrap_or(count);
let start = slot * 80;
self.label[start..start + 80].fill(b' ');
self.label[start..start + len].copy_from_slice(bytes);
} else {
for i in 1..10 {
let src = (i - 1) * 80;
let dst = i * 80;
self.label.copy_within(src..src + 80, dst);
}
let start = 9 * 80;
self.label[start..start + 80].fill(b' ');
self.label[start..start + len].copy_from_slice(bytes);
}
self.nlabl = self.count_non_empty_labels() as i32;
}
#[inline]
pub fn detect_endian(&self) -> crate::FileEndian {
crate::FileEndian::from_machst(&self.machst)
}
#[inline]
pub fn set_file_endian(&mut self, endian: crate::FileEndian) {
let current_nversion = self.nversion();
self.machst = endian.to_machst();
self.set_nversion(current_nversion);
}
pub fn is_single_image(&self) -> bool {
self.nz == 1
}
pub fn is_image_stack(&self) -> bool {
self.ispg == 0
}
pub fn is_volume(&self) -> bool {
!self.is_image_stack() && !self.is_volume_stack()
}
pub fn is_volume_stack(&self) -> bool {
(400..=630).contains(&self.ispg)
}
pub fn set_image_stack(&mut self) {
self.ispg = 0;
self.mz = 1;
}
pub fn set_volume(&mut self) {
self.ispg = 1;
self.mz = self.nz;
}
pub fn set_volume_stack(&mut self, mz: i32) {
self.ispg = 401;
self.mz = mz;
}
pub fn voxel_size(&self) -> [f32; 3] {
[
if self.mx == 0 {
0.0
} else {
self.xlen / self.mx as f32
},
if self.my == 0 {
0.0
} else {
self.ylen / self.my as f32
},
if self.mz == 0 {
0.0
} else {
self.zlen / self.mz as f32
},
]
}
pub fn nstart(&self) -> [i32; 3] {
[self.nxstart, self.nystart, self.nzstart]
}
pub fn cell_lengths(&self) -> [f32; 3] {
[self.xlen, self.ylen, self.zlen]
}
pub fn cell_angles(&self) -> [f32; 3] {
[self.alpha, self.beta, self.gamma]
}
pub fn logical_shape(&self) -> [usize; 4] {
if self.is_volume_stack() && self.mz > 0 {
let nvolumes = (self.nz / self.mz) as usize;
[
nvolumes,
self.mz as usize,
self.ny as usize,
self.nx as usize,
]
} else {
[1, self.nz as usize, self.ny as usize, self.nx as usize]
}
}
pub fn decode_from_bytes(bytes: &[u8; 1024]) -> Self {
Self::decode_from_bytes_with_info(bytes).0
}
pub fn decode_from_bytes_with_info(bytes: &[u8; 1024]) -> (Self, Option<&'static str>) {
use crate::engine::endian::FileEndian;
let machst = [
bytes[OFFSET_MACHST],
bytes[OFFSET_MACHST + 1],
bytes[OFFSET_MACHST + 2],
bytes[OFFSET_MACHST + 3],
];
let detected = FileEndian::from_machst(&machst);
let header = Self::decode_with_endian(bytes, detected);
if crate::Mode::from_i32(header.mode).is_none() {
let opposite = detected.opposite();
let candidate = Self::decode_with_endian(bytes, opposite);
if crate::Mode::from_i32(candidate.mode).is_some() {
let warning = match detected {
FileEndian::LittleEndian => {
"MACHST indicates little-endian but MODE is valid only as big-endian; using big-endian"
}
FileEndian::BigEndian => {
"MACHST indicates big-endian but MODE is valid only as little-endian; using little-endian"
}
};
return (candidate, Some(warning));
}
}
(header, None)
}
fn decode_with_endian(bytes: &[u8; 1024], file_endian: crate::FileEndian) -> Self {
use crate::engine::codec::EndianCodec;
let mut header = Self::new();
header.nx = i32::decode(bytes, OFFSET_NX, file_endian);
header.ny = i32::decode(bytes, OFFSET_NY, file_endian);
header.nz = i32::decode(bytes, OFFSET_NZ, file_endian);
header.mode = i32::decode(bytes, OFFSET_MODE, file_endian);
header.nxstart = i32::decode(bytes, OFFSET_NXSTART, file_endian);
header.nystart = i32::decode(bytes, OFFSET_NYSTART, file_endian);
header.nzstart = i32::decode(bytes, OFFSET_NZSTART, file_endian);
header.mx = i32::decode(bytes, OFFSET_MX, file_endian);
header.my = i32::decode(bytes, OFFSET_MY, file_endian);
header.mz = i32::decode(bytes, OFFSET_MZ, file_endian);
header.xlen = f32::decode(bytes, OFFSET_XLEN, file_endian);
header.ylen = f32::decode(bytes, OFFSET_YLEN, file_endian);
header.zlen = f32::decode(bytes, OFFSET_ZLEN, file_endian);
header.alpha = f32::decode(bytes, OFFSET_ALPHA, file_endian);
header.beta = f32::decode(bytes, OFFSET_BETA, file_endian);
header.gamma = f32::decode(bytes, OFFSET_GAMMA, file_endian);
header.mapc = i32::decode(bytes, OFFSET_MAPC, file_endian);
header.mapr = i32::decode(bytes, OFFSET_MAPR, file_endian);
header.maps = i32::decode(bytes, OFFSET_MAPS, file_endian);
header.dmin = f32::decode(bytes, OFFSET_DMIN, file_endian);
header.dmax = f32::decode(bytes, OFFSET_DMAX, file_endian);
header.dmean = f32::decode(bytes, OFFSET_DMEAN, file_endian);
header.ispg = i32::decode(bytes, OFFSET_ISPG, file_endian);
header.nsymbt = i32::decode(bytes, OFFSET_NSYMBT, file_endian);
header
.extra
.copy_from_slice(&bytes[OFFSET_EXTRA..OFFSET_ORIGIN]);
header.origin[0] = f32::decode(bytes, OFFSET_ORIGIN, file_endian);
header.origin[1] = f32::decode(bytes, OFFSET_ORIGIN + 4, file_endian);
header.origin[2] = f32::decode(bytes, OFFSET_ORIGIN + 8, file_endian);
header
.map
.copy_from_slice(&bytes[OFFSET_MAP..OFFSET_MACHST]);
header
.machst
.copy_from_slice(&bytes[OFFSET_MACHST..OFFSET_RMS]);
header.rms = f32::decode(bytes, OFFSET_RMS, file_endian);
header.nlabl = i32::decode(bytes, OFFSET_NLABL, file_endian);
header.label.copy_from_slice(&bytes[OFFSET_LABEL..1024]);
header
}
pub fn encode_to_bytes(&self, out: &mut [u8; 1024]) {
use crate::engine::codec::EndianCodec;
let file_endian = self.detect_endian();
self.nx.encode(out, OFFSET_NX, file_endian);
self.ny.encode(out, OFFSET_NY, file_endian);
self.nz.encode(out, OFFSET_NZ, file_endian);
self.mode.encode(out, OFFSET_MODE, file_endian);
self.nxstart.encode(out, OFFSET_NXSTART, file_endian);
self.nystart.encode(out, OFFSET_NYSTART, file_endian);
self.nzstart.encode(out, OFFSET_NZSTART, file_endian);
self.mx.encode(out, OFFSET_MX, file_endian);
self.my.encode(out, OFFSET_MY, file_endian);
self.mz.encode(out, OFFSET_MZ, file_endian);
self.xlen.encode(out, OFFSET_XLEN, file_endian);
self.ylen.encode(out, OFFSET_YLEN, file_endian);
self.zlen.encode(out, OFFSET_ZLEN, file_endian);
self.alpha.encode(out, OFFSET_ALPHA, file_endian);
self.beta.encode(out, OFFSET_BETA, file_endian);
self.gamma.encode(out, OFFSET_GAMMA, file_endian);
self.mapc.encode(out, OFFSET_MAPC, file_endian);
self.mapr.encode(out, OFFSET_MAPR, file_endian);
self.maps.encode(out, OFFSET_MAPS, file_endian);
self.dmin.encode(out, OFFSET_DMIN, file_endian);
self.dmax.encode(out, OFFSET_DMAX, file_endian);
self.dmean.encode(out, OFFSET_DMEAN, file_endian);
self.ispg.encode(out, OFFSET_ISPG, file_endian);
self.nsymbt.encode(out, OFFSET_NSYMBT, file_endian);
out[OFFSET_EXTRA..OFFSET_ORIGIN].copy_from_slice(&self.extra);
self.origin[0].encode(out, OFFSET_ORIGIN, file_endian);
self.origin[1].encode(out, OFFSET_ORIGIN + 4, file_endian);
self.origin[2].encode(out, OFFSET_ORIGIN + 8, file_endian);
out[OFFSET_MAP..OFFSET_MACHST].copy_from_slice(&self.map);
out[OFFSET_MACHST..OFFSET_RMS].copy_from_slice(&self.machst);
self.rms.encode(out, OFFSET_RMS, file_endian);
self.nlabl.encode(out, OFFSET_NLABL, file_endian);
out[OFFSET_LABEL..1024].copy_from_slice(&self.label);
}
}
#[derive(Debug, Clone)]
pub struct HeaderBuilder {
header: Header,
}
impl HeaderBuilder {
pub fn new() -> Self {
Self {
header: Header::new(),
}
}
pub fn shape(mut self, shape: [usize; 3]) -> Self {
self.header.nx = shape[0] as i32;
self.header.ny = shape[1] as i32;
self.header.nz = shape[2] as i32;
self.header.mx = self.header.nx;
self.header.my = self.header.ny;
self.header.mz = self.header.nz;
self
}
pub fn mode<T: crate::mode::Voxel>(mut self) -> Self {
self.header.mode = T::MODE.as_i32();
self
}
pub fn cell_lengths(mut self, xlen: f32, ylen: f32, zlen: f32) -> Self {
self.header.xlen = xlen;
self.header.ylen = ylen;
self.header.zlen = zlen;
self
}
pub fn cell_angles(mut self, alpha: f32, beta: f32, gamma: f32) -> Self {
self.header.alpha = alpha;
self.header.beta = beta;
self.header.gamma = gamma;
self
}
pub fn ispg(mut self, ispg: i32) -> Self {
self.header.ispg = ispg;
self
}
pub fn exttyp(mut self, exttyp: [u8; 4]) -> Self {
self.header.set_exttyp(exttyp);
self
}
pub fn nsymbt(mut self, nsymbt: i32) -> Self {
self.header.nsymbt = nsymbt;
self
}
pub fn origin(mut self, origin: [f32; 3]) -> Self {
self.header.origin = origin;
self
}
pub fn build(self) -> Result<Header, crate::HeaderValidationError> {
self.header.validate_detailed()?;
Ok(self.header)
}
}
impl Default for HeaderBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn le_header_bytes() -> [u8; 1024] {
let mut h = Header::new();
h.nx = 64;
h.ny = 64;
h.nz = 64;
h.mx = 64;
h.my = 64;
h.mz = 64;
h.mode = 2; h.set_file_endian(crate::FileEndian::LittleEndian);
let mut bytes = [0u8; 1024];
h.encode_to_bytes(&mut bytes);
bytes
}
fn be_header_bytes() -> [u8; 1024] {
let mut h = Header::new();
h.nx = 64;
h.ny = 64;
h.nz = 64;
h.mx = 64;
h.my = 64;
h.mz = 64;
h.mode = 2; h.set_file_endian(crate::FileEndian::BigEndian);
let mut bytes = [0u8; 1024];
h.encode_to_bytes(&mut bytes);
bytes
}
#[test]
fn test_decode_roundtrip_le() {
let original = le_header_bytes();
let decoded = Header::decode_from_bytes(&original);
assert_eq!(decoded.nx, 64);
assert_eq!(decoded.mode, 2);
assert_eq!(decoded.detect_endian(), crate::FileEndian::LittleEndian);
}
#[test]
fn test_decode_roundtrip_be() {
let original = be_header_bytes();
let decoded = Header::decode_from_bytes(&original);
assert_eq!(decoded.nx, 64);
assert_eq!(decoded.mode, 2);
assert_eq!(decoded.detect_endian(), crate::FileEndian::BigEndian);
}
#[test]
fn test_byte_order_fallback_le_stamp_be_data() {
let mut bytes = be_header_bytes();
bytes[212] = 0x44;
bytes[213] = 0x44;
bytes[214] = 0x00;
bytes[215] = 0x00;
let (decoded, warning) = Header::decode_from_bytes_with_info(&bytes);
assert_eq!(
decoded.nx, 64,
"byte-order fallback should have corrected nx"
);
assert_eq!(decoded.mode, 2);
assert!(
warning.is_some(),
"should emit a warning when MACHST mismatches actual byte order"
);
}
#[test]
fn test_byte_order_fallback_be_stamp_le_data() {
let mut bytes = le_header_bytes();
bytes[212] = 0x11;
bytes[213] = 0x11;
bytes[214] = 0x00;
bytes[215] = 0x00;
let (decoded, warning) = Header::decode_from_bytes_with_info(&bytes);
assert_eq!(
decoded.nx, 64,
"byte-order fallback should have corrected nx"
);
assert_eq!(decoded.mode, 2);
assert!(warning.is_some());
}
#[test]
fn test_no_fallback_when_machst_matches() {
let bytes = le_header_bytes();
let (decoded, warning) = Header::decode_from_bytes_with_info(&bytes);
assert_eq!(decoded.nx, 64);
assert!(warning.is_none(), "no warning when MACHST is correct");
}
#[test]
fn test_ccp41_machst_recognised() {
let mut bytes = le_header_bytes();
bytes[212] = 0x44;
bytes[213] = 0x41;
let decoded = Header::decode_from_bytes(&bytes);
assert_eq!(decoded.nx, 64);
assert_eq!(decoded.detect_endian(), crate::FileEndian::LittleEndian);
}
#[test]
fn test_nversion_le() {
let mut h = Header::new();
h.set_file_endian(crate::FileEndian::LittleEndian);
assert_eq!(h.nversion(), 20141);
}
#[test]
fn test_nversion_be() {
let mut h = Header::new();
h.set_file_endian(crate::FileEndian::BigEndian);
assert_eq!(h.nversion(), 20141);
}
}