use crate::document::{Preview, PreviewFormat};
use crate::io::dwg::file_headers::section_definition::{end_sentinels, start_sentinels};
const CODE_HEADER: u8 = 1;
const CODE_BMP: u8 = 2;
const CODE_WMF: u8 = 3;
const CODE_PNG: u8 = 6;
const HEADER_LEN: usize = 80;
pub fn container_len(overall_size: usize) -> usize {
16 + 4 + overall_size + 16
}
pub fn overall_size(bytes: &[u8]) -> Option<usize> {
if bytes.len() < 20 || bytes[..16] != start_sentinels::PREVIEW {
return None;
}
Some(u32::from_le_bytes([bytes[16], bytes[17], bytes[18], bytes[19]]) as usize)
}
pub fn parse_preview(bytes: &[u8], base: u64) -> Option<Preview> {
if bytes.len() < 21 || bytes[..16] != start_sentinels::PREVIEW {
return None;
}
let count = bytes[20] as usize;
let mut off = 21usize;
for _ in 0..count {
if off + 9 > bytes.len() {
break;
}
let code = bytes[off];
let start = u32::from_le_bytes([
bytes[off + 1],
bytes[off + 2],
bytes[off + 3],
bytes[off + 4],
]) as u64;
let size = u32::from_le_bytes([
bytes[off + 5],
bytes[off + 6],
bytes[off + 7],
bytes[off + 8],
]) as usize;
off += 9;
let format = match code {
CODE_BMP => PreviewFormat::Bmp,
CODE_WMF => PreviewFormat::Wmf,
CODE_PNG => PreviewFormat::Png,
_ => continue, };
let rel = start.checked_sub(base)? as usize;
let end = rel.checked_add(size)?;
if size == 0 || end > bytes.len() {
continue;
}
return Some(Preview {
format,
data: bytes[rel..end].to_vec(),
});
}
None
}
pub fn build_preview(preview: Option<&Preview>, base: u64) -> Vec<u8> {
let p = match preview {
Some(p) if !p.data.is_empty() => p,
_ => return empty_preview(),
};
let (code, with_header) = match p.format {
PreviewFormat::Bmp => (CODE_BMP, true),
PreviewFormat::Wmf => (CODE_WMF, false),
PreviewFormat::Png => (CODE_PNG, false),
};
let count = if with_header { 2usize } else { 1usize };
let descriptors_len = 9 * count;
let data_rel = 16 + 4 + 1 + descriptors_len;
let header_bytes = if with_header { HEADER_LEN } else { 0 };
let overall = 1 + descriptors_len + header_bytes + p.data.len();
let mut out = Vec::with_capacity(container_len(overall));
out.extend_from_slice(&start_sentinels::PREVIEW);
out.extend_from_slice(&(overall as u32).to_le_bytes());
out.push(count as u8);
if with_header {
let hstart = base + data_rel as u64;
push_descriptor(&mut out, CODE_HEADER, hstart, HEADER_LEN as u32);
push_descriptor(
&mut out,
code,
hstart + HEADER_LEN as u64,
p.data.len() as u32,
);
out.extend_from_slice(&[0u8; HEADER_LEN]);
out.extend_from_slice(&p.data);
} else {
push_descriptor(&mut out, code, base + data_rel as u64, p.data.len() as u32);
out.extend_from_slice(&p.data);
}
out.extend_from_slice(&end_sentinels::PREVIEW);
out
}
fn push_descriptor(out: &mut Vec<u8>, code: u8, start: u64, size: u32) {
out.push(code);
out.extend_from_slice(&(start as u32).to_le_bytes());
out.extend_from_slice(&size.to_le_bytes());
}
pub fn empty_preview() -> Vec<u8> {
let mut out = Vec::with_capacity(37);
out.extend_from_slice(&start_sentinels::PREVIEW);
out.extend_from_slice(&1u32.to_le_bytes());
out.push(0);
out.extend_from_slice(&end_sentinels::PREVIEW);
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_roundtrips_to_none() {
let base = 0x1c0;
let bytes = build_preview(None, base);
assert_eq!(bytes, empty_preview());
assert_eq!(parse_preview(&bytes, base), None);
}
#[test]
fn png_roundtrips() {
let base = 0x300;
let img = Preview {
format: PreviewFormat::Png,
data: vec![0x89, b'P', b'N', b'G', 1, 2, 3, 4, 5],
};
let bytes = build_preview(Some(&img), base);
assert_eq!(bytes[20], 1);
assert_eq!(parse_preview(&bytes, base), Some(img));
}
#[test]
fn bmp_roundtrips_with_header() {
let base = 0x1c0;
let dib = vec![0x28u8; 1200];
let img = Preview {
format: PreviewFormat::Bmp,
data: dib,
};
let bytes = build_preview(Some(&img), base);
assert_eq!(bytes[20], 2);
let overall = overall_size(&bytes).unwrap();
assert_eq!(container_len(overall), bytes.len());
assert_eq!(parse_preview(&bytes, base), Some(img));
}
#[test]
fn absolute_start_offsets() {
let base = 0x1c0;
let img = Preview {
format: PreviewFormat::Bmp,
data: vec![7u8; 100],
};
let bytes = build_preview(Some(&img), base);
let s = u32::from_le_bytes([bytes[31], bytes[32], bytes[33], bytes[34]]) as u64;
assert_eq!(s, base + 16 + 4 + 1 + 18 + 80);
}
}