use std::io::Write;
#[cfg(test)]
use std::io::Cursor;
use byteorder::{LittleEndian, WriteBytesExt};
use encoding_rs::WINDOWS_1252;
use flate2::Compression;
use flate2::write::ZlibEncoder;
use crate::error::Result;
use crate::types::{Coord, CoordPoint, ParameterCollection};
pub fn write_block<W: Write>(writer: &mut W, data: &[u8], flags: u8) -> Result<()> {
let size = data.len() as i32;
let header = ((flags as i32) << 24) | size;
writer.write_i32::<LittleEndian>(header)?;
if !data.is_empty() {
writer.write_all(data)?;
}
Ok(())
}
pub fn write_block_with<W: Write, F>(writer: &mut W, serializer: F, flags: u8) -> Result<()>
where
F: FnOnce(&mut Vec<u8>) -> Result<()>,
{
let mut buffer = Vec::new();
serializer(&mut buffer)?;
write_block(writer, &buffer, flags)
}
pub fn compress_zlib(data: &[u8]) -> Result<Vec<u8>> {
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
encoder.write_all(data)?;
let compressed = encoder.finish()?;
Ok(compressed)
}
pub fn encode_windows_1252(s: &str) -> Vec<u8> {
let (bytes, _, _) = WINDOWS_1252.encode(s);
bytes.into_owned()
}
pub fn write_raw_string<W: Write>(writer: &mut W, s: &str) -> Result<()> {
let bytes = encode_windows_1252(s);
writer.write_all(&bytes)?;
Ok(())
}
pub fn write_c_string<W: Write>(writer: &mut W, s: &str) -> Result<()> {
write_raw_string(writer, s)?;
writer.write_u8(0)?;
Ok(())
}
pub fn write_pascal_string<W: Write>(writer: &mut W, s: &str) -> Result<()> {
let mut buffer = Vec::new();
write_c_string(&mut buffer, s)?;
write_block(writer, &buffer, 0)
}
pub fn write_pascal_short_string<W: Write>(writer: &mut W, s: &str) -> Result<()> {
let bytes = encode_windows_1252(s);
if bytes.len() > 255 {
writer.write_u8(255)?;
writer.write_all(&bytes[..255])?;
} else {
writer.write_u8(bytes.len() as u8)?;
writer.write_all(&bytes)?;
}
Ok(())
}
pub fn write_font_name<W: Write>(writer: &mut W, s: &str) -> Result<()> {
let utf16: Vec<u16> = s.encode_utf16().take(16).collect();
for &code in &utf16 {
writer.write_u16::<LittleEndian>(code)?;
}
let written = utf16.len() * 2;
if utf16.len() < 16 {
writer.write_u16::<LittleEndian>(0)?; let with_null = written + 2;
for _ in with_null..32 {
writer.write_u8(0)?;
}
}
Ok(())
}
pub fn write_string_block<W: Write>(writer: &mut W, s: &str) -> Result<()> {
let mut buffer = Vec::new();
write_pascal_short_string(&mut buffer, s)?;
write_block(writer, &buffer, 0)
}
pub fn write_parameters<W: Write>(writer: &mut W, params: &ParameterCollection) -> Result<()> {
let s = params.to_string();
write_c_string(writer, &s)
}
pub fn write_parameters_raw<W: Write>(writer: &mut W, params: &ParameterCollection) -> Result<()> {
let s = params.to_string();
write_raw_string(writer, &s)
}
pub fn write_parameters_block<W: Write>(
writer: &mut W,
params: &ParameterCollection,
) -> Result<()> {
let mut buffer = Vec::new();
write_parameters(&mut buffer, params)?;
write_block(writer, &buffer, 0)
}
pub fn write_coord_point<W: Write>(writer: &mut W, point: CoordPoint) -> Result<()> {
writer.write_i32::<LittleEndian>(point.x.to_raw())?;
writer.write_i32::<LittleEndian>(point.y.to_raw())?;
Ok(())
}
pub fn write_header<W: Write>(writer: &mut W, record_count: u32) -> Result<()> {
writer.write_u32::<LittleEndian>(record_count)?;
Ok(())
}
pub fn write_compressed_storage<W: Write>(writer: &mut W, id: &str, data: &[u8]) -> Result<()> {
write_block_with(
writer,
|buffer| {
buffer.write_u8(0xD0)?;
write_pascal_short_string(buffer, id)?;
let compressed = compress_zlib(data)?;
write_block(buffer, &compressed, 0)?;
Ok(())
},
0x01,
) }
pub fn write_compressed_storage_with<W: Write, F>(
writer: &mut W,
id: &str,
serializer: F,
) -> Result<()>
where
F: FnOnce(&mut Vec<u8>) -> Result<()>,
{
let mut data = Vec::new();
serializer(&mut data)?;
write_compressed_storage(writer, id, &data)
}
pub trait WriteExt: Write {
fn write_coord(&mut self, value: Coord) -> Result<()>
where
Self: Sized,
{
self.write_i32::<LittleEndian>(value.to_raw())?;
Ok(())
}
fn write_bool8(&mut self, value: bool) -> Result<()>
where
Self: Sized,
{
self.write_u8(if value { 1 } else { 0 })?;
Ok(())
}
}
impl<W: Write> WriteExt for W {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_write_block() {
let mut buffer = Vec::new();
write_block(&mut buffer, b"hello", 0).unwrap();
assert_eq!(buffer, [5, 0, 0, 0, b'h', b'e', b'l', b'l', b'o']);
}
#[test]
fn test_write_block_with_flags() {
let mut buffer = Vec::new();
write_block(&mut buffer, b"test", 0x01).unwrap();
assert_eq!(buffer[0], 4);
assert_eq!(buffer[3], 0x01);
}
#[test]
fn test_write_pascal_short_string() {
let mut buffer = Vec::new();
write_pascal_short_string(&mut buffer, "hello").unwrap();
assert_eq!(buffer, [5, b'h', b'e', b'l', b'l', b'o']);
}
#[test]
fn test_write_c_string() {
let mut buffer = Vec::new();
write_c_string(&mut buffer, "test").unwrap();
assert_eq!(buffer, [b't', b'e', b's', b't', 0]);
}
#[test]
fn test_write_coord_point() {
let mut buffer = Vec::new();
let point = CoordPoint::from_raw(65536, 131072);
write_coord_point(&mut buffer, point).unwrap();
assert_eq!(buffer.len(), 8);
let mut cursor = Cursor::new(&buffer);
use byteorder::ReadBytesExt;
assert_eq!(cursor.read_i32::<LittleEndian>().unwrap(), 65536);
assert_eq!(cursor.read_i32::<LittleEndian>().unwrap(), 131072);
}
#[test]
fn test_encode_windows_1252() {
let bytes = encode_windows_1252("Hello World");
assert_eq!(bytes, b"Hello World");
}
#[test]
fn test_write_font_name_empty() {
let mut buffer = Vec::new();
write_font_name(&mut buffer, "").unwrap();
assert_eq!(buffer.len(), 32);
assert_eq!(&buffer[0..2], &[0, 0]); assert!(buffer[2..].iter().all(|&b| b == 0)); }
#[test]
fn test_write_font_name_short() {
let mut buffer = Vec::new();
write_font_name(&mut buffer, "Arial").unwrap();
assert_eq!(buffer.len(), 32);
let expected_utf16: Vec<u16> = "Arial".encode_utf16().collect();
for (i, &code) in expected_utf16.iter().enumerate() {
let offset = i * 2;
let actual = u16::from_le_bytes([buffer[offset], buffer[offset + 1]]);
assert_eq!(actual, code);
}
let null_offset = 5 * 2;
assert_eq!(buffer[null_offset], 0);
assert_eq!(buffer[null_offset + 1], 0);
assert!(buffer[null_offset + 2..].iter().all(|&b| b == 0));
}
#[test]
fn test_write_font_name_15_chars() {
let mut buffer = Vec::new();
let name = "Times New Roman"; write_font_name(&mut buffer, name).unwrap();
assert_eq!(buffer.len(), 32);
let utf16: Vec<u16> = name.encode_utf16().collect();
assert_eq!(utf16.len(), 15);
for (i, &code) in utf16.iter().enumerate() {
let offset = i * 2;
let actual = u16::from_le_bytes([buffer[offset], buffer[offset + 1]]);
assert_eq!(actual, code);
}
assert_eq!(buffer[30], 0);
assert_eq!(buffer[31], 0);
}
#[test]
fn test_write_font_name_16_chars() {
let mut buffer = Vec::new();
let name = "1234567890ABCDEF"; write_font_name(&mut buffer, name).unwrap();
assert_eq!(buffer.len(), 32);
let utf16: Vec<u16> = name.encode_utf16().collect();
assert_eq!(utf16.len(), 16);
for (i, &code) in utf16.iter().enumerate() {
let offset = i * 2;
let actual = u16::from_le_bytes([buffer[offset], buffer[offset + 1]]);
assert_eq!(actual, code);
}
}
#[test]
fn test_write_font_name_too_long() {
let mut buffer = Vec::new();
let name = "ThisIsAVeryLongFontNameThatExceeds16Characters"; write_font_name(&mut buffer, name).unwrap();
assert_eq!(buffer.len(), 32);
let utf16: Vec<u16> = name.encode_utf16().take(16).collect();
assert_eq!(utf16.len(), 16);
for (i, &code) in utf16.iter().enumerate() {
let offset = i * 2;
let actual = u16::from_le_bytes([buffer[offset], buffer[offset + 1]]);
assert_eq!(actual, code);
}
}
#[test]
fn test_write_font_name_unicode() {
let mut buffer = Vec::new();
write_font_name(&mut buffer, "微软雅黑").unwrap();
assert_eq!(buffer.len(), 32);
let utf16: Vec<u16> = "微软雅黑".encode_utf16().collect();
for (i, &code) in utf16.iter().enumerate() {
let offset = i * 2;
let actual = u16::from_le_bytes([buffer[offset], buffer[offset + 1]]);
assert_eq!(actual, code);
}
let null_offset = utf16.len() * 2;
assert_eq!(buffer[null_offset], 0);
assert_eq!(buffer[null_offset + 1], 0);
}
}