pub fn decompress_ac18(source: &[u8], decompressed_size: usize) -> Vec<u8> {
let mut output = Vec::with_capacity(decompressed_size);
let mut si = 0;
if source.is_empty() || decompressed_size == 0 {
return output;
}
let first = source[si];
si += 1;
let lit_count = if first == 0x00 {
read_ext_length(source, &mut si) + 0x0F + 3
} else if first < 0x10 {
first as usize + 3
} else {
si -= 1;
0
};
copy_literals(source, &mut si, &mut output, lit_count, decompressed_size);
loop {
if si >= source.len() || output.len() >= decompressed_size {
break;
}
let opcode = source[si];
si += 1;
if opcode >= 0x40 {
let length = ((opcode >> 4) as usize) - 1;
if si >= source.len() { break; }
let b1 = source[si] as usize;
si += 1;
let offset = (((opcode as usize) & 0x0C) >> 2 | (b1 << 2)) + 1;
let trailing = (opcode & 0x03) as usize;
copy_match(&mut output, offset, length, decompressed_size);
copy_literals(source, &mut si, &mut output, trailing, decompressed_size);
} else if opcode >= 0x21 {
let length = (opcode & 0x1F) as usize + 2;
if si + 1 >= source.len() { break; }
let b1 = source[si] as usize;
si += 1;
let b2 = source[si] as usize;
si += 1;
let offset = ((b1 >> 2) | (b2 << 6)) + 1;
let trailing = b1 & 0x03;
copy_match(&mut output, offset, length, decompressed_size);
copy_literals(source, &mut si, &mut output, trailing, decompressed_size);
} else if opcode == 0x20 {
let length = read_ext_length(source, &mut si) + 0x21;
if si + 1 >= source.len() { break; }
let b1 = source[si] as usize;
si += 1;
let b2 = source[si] as usize;
si += 1;
let offset = ((b1 >> 2) | (b2 << 6)) + 1;
let trailing = b1 & 0x03;
copy_match(&mut output, offset, length, decompressed_size);
copy_literals(source, &mut si, &mut output, trailing, decompressed_size);
} else if opcode == 0x11 {
break;
} else if opcode >= 0x10 {
let len_bits = (opcode & 0x07) as usize;
let length = if len_bits == 0 {
read_ext_length(source, &mut si) + 0x09
} else {
len_bits + 2
};
if si + 1 >= source.len() { break; }
let b1 = source[si] as usize;
si += 1;
let b2 = source[si] as usize;
si += 1;
let offset = ((b1 >> 2) | (b2 << 6) | ((opcode as usize & 0x08) << 11)) + 0x4000;
let trailing = b1 & 0x03;
copy_match(&mut output, offset, length, decompressed_size);
copy_literals(source, &mut si, &mut output, trailing, decompressed_size);
} else {
let count = if opcode == 0x00 {
read_ext_length(source, &mut si) + 0x0F + 3
} else {
opcode as usize + 3
};
copy_literals(source, &mut si, &mut output, count, decompressed_size);
}
}
output.truncate(decompressed_size);
output
}
fn read_ext_length(source: &[u8], si: &mut usize) -> usize {
let mut total = 0usize;
loop {
if *si >= source.len() {
break;
}
let b = source[*si];
*si += 1;
if b == 0 {
total += 0xFF;
} else {
total += b as usize;
break;
}
}
total
}
#[inline]
fn copy_literals(
source: &[u8],
si: &mut usize,
output: &mut Vec<u8>,
count: usize,
max_size: usize,
) {
for _ in 0..count {
if *si >= source.len() || output.len() >= max_size {
break;
}
output.push(source[*si]);
*si += 1;
}
}
#[inline]
fn copy_match(output: &mut Vec<u8>, offset: usize, length: usize, max_size: usize) {
let start = output.len().saturating_sub(offset);
for i in 0..length {
if output.len() >= max_size {
break;
}
let idx = start + i;
if idx < output.len() {
let byte = output[idx];
output.push(byte);
} else {
output.push(0);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::io::dwg::compression::DwgLZ77AC18Compressor;
fn roundtrip(data: &[u8]) {
let mut comp = DwgLZ77AC18Compressor::new();
let compressed = comp.compress(data, 0, data.len());
let decompressed = decompress_ac18(&compressed, data.len());
assert_eq!(
decompressed.len(),
data.len(),
"Length mismatch: expected {}, got {}",
data.len(),
decompressed.len()
);
assert_eq!(
&decompressed[..],
data,
"Data mismatch after roundtrip (first diff at byte {})",
decompressed
.iter()
.zip(data.iter())
.position(|(a, b)| a != b)
.unwrap_or(0)
);
}
#[test]
fn test_roundtrip_zeros() {
roundtrip(&vec![0u8; 1000]);
}
#[test]
fn test_roundtrip_small() {
roundtrip(b"Hello, World! This is a test of the decompressor.");
}
#[test]
fn test_roundtrip_repetitive() {
let mut data = Vec::new();
for _ in 0..100 {
data.extend_from_slice(b"ABCDEFGH");
}
roundtrip(&data);
}
#[test]
fn test_roundtrip_sequential() {
let data: Vec<u8> = (0..=255).cycle().take(2000).collect();
roundtrip(&data);
}
#[test]
fn test_roundtrip_mixed() {
let mut data = Vec::new();
data.extend_from_slice(b"Unique header text 12345");
for _ in 0..50 {
data.extend_from_slice(b"REPEAT");
}
data.extend_from_slice(b"Another unique section here");
for _ in 0..30 {
data.extend_from_slice(b"XY");
}
roundtrip(&data);
}
#[test]
fn test_roundtrip_page_sized() {
roundtrip(&vec![0xAA; 0x7400]);
}
#[test]
fn test_roundtrip_page_sized_pattern() {
let mut data = vec![0u8; 0x7400];
for (i, byte) in data.iter_mut().enumerate() {
*byte = (i % 256) as u8;
}
roundtrip(&data);
}
#[test]
fn test_roundtrip_minimum_data() {
roundtrip(&[1, 2, 3, 4]);
}
#[test]
fn test_roundtrip_large_repetitive() {
roundtrip(&vec![42u8; 100_000]);
}
#[test]
fn test_decompress_empty() {
let result = decompress_ac18(&[], 0);
assert!(result.is_empty());
}
#[test]
fn test_decompress_zero_target() {
let result = decompress_ac18(&[0x01, 0xAA, 0xBB, 0xCC, 0xDD, 0x11, 0x00, 0x00], 0);
assert!(result.is_empty());
}
#[test]
fn test_read_ext_length_simple() {
let data = [5u8];
let mut si = 0;
assert_eq!(read_ext_length(&data, &mut si), 5);
assert_eq!(si, 1);
}
#[test]
fn test_read_ext_length_extended() {
let data = [0u8, 0, 3];
let mut si = 0;
assert_eq!(read_ext_length(&data, &mut si), 0x201);
assert_eq!(si, 3);
}
}