use crate::error::{VBError, VBResult};
use crate::state::file::{self, AccessMode, LockMode, OpenMode};
use crate::value::{VBString, VBVariant};
use crate::StdPicture;
use std::path::Path;
use vb6core::error::err_number;
pub fn save_picture(picture: &VBVariant, filename: &VBVariant) -> VBResult<()> {
let object = match picture {
VBVariant::Object(object) => object,
VBVariant::Nothing => {
return Err(VBError::new(err_number::OBJECT_VARIABLE_NOT_SET));
}
_ => return Err(VBError::type_mismatch()),
};
let picture = object
.as_any()
.downcast_ref::<StdPicture>()
.ok_or_else(VBError::type_mismatch)?;
let path = VBString::try_from(filename)?;
if path.as_str().is_empty() {
return Err(VBError::new(err_number::PATH_FILE_ACCESS_ERROR));
}
let bytes = bitmap_bytes(picture.width(), picture.height());
let number = file::free_file(file::MIN_FILE_NUMBER);
if number == 0 {
return Err(VBError::new(err_number::TOO_MANY_FILES));
}
let opened = file::open_file(
Path::new(path.as_str()),
OpenMode::Output,
AccessMode::Write,
LockMode::Shared,
0,
number,
);
if let Err(error) = opened {
return Err(map_io_error(error));
}
let written = file::write_file(number, &bytes);
let closed = file::close_file(number);
written.map_err(map_io_error)?;
closed.map_err(map_io_error)?;
Ok(())
}
fn map_io_error(error: std::io::Error) -> VBError {
let number = match error.kind() {
std::io::ErrorKind::NotFound => err_number::PATH_NOT_FOUND,
_ => err_number::PATH_FILE_ACCESS_ERROR,
};
VBError::with_description(number, error.to_string())
}
fn bitmap_bytes(width: i32, height: i32) -> Vec<u8> {
let width = width.max(1) as usize;
let height = height.max(1) as usize;
let row_size = (width * 3).div_ceil(4) * 4;
let pixel_size = row_size * height;
let data_offset: u32 = 54; let file_size = data_offset as usize + pixel_size;
let mut bytes = Vec::with_capacity(file_size);
bytes.extend_from_slice(b"BM");
bytes.extend_from_slice(&(file_size as u32).to_le_bytes());
bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&data_offset.to_le_bytes());
bytes.extend_from_slice(&40u32.to_le_bytes()); bytes.extend_from_slice(&(width as u32).to_le_bytes());
bytes.extend_from_slice(&(height as u32).to_le_bytes());
bytes.extend_from_slice(&1u16.to_le_bytes()); bytes.extend_from_slice(&24u16.to_le_bytes()); bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&(pixel_size as u32).to_le_bytes());
bytes.extend_from_slice(&2835u32.to_le_bytes()); bytes.extend_from_slice(&2835u32.to_le_bytes()); bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&0u32.to_le_bytes());
for _ in 0..height {
for _ in 0..width {
bytes.extend_from_slice(&[0xFF, 0xFF, 0xFF]); }
bytes.resize(bytes.len() + row_size - width * 3, 0);
}
bytes
}
#[cfg(test)]
mod tests {
use super::*;
fn picture_variant(width: i32, height: i32) -> VBVariant {
VBVariant::from_object(Box::new(StdPicture::new(width, height)))
}
macro_rules! with_temp_file_root {
($body:block) => {{
let _guard = crate::state::test_support::lock_test();
let dir = tempfile::tempdir().unwrap();
file::reset_with_root(dir.path());
let result = $body;
file::reset();
result
}};
}
#[test]
fn save_picture_writes_a_valid_bitmap() {
with_temp_file_root!({
save_picture(&picture_variant(2, 3), &VBVariant::from_string("out.bmp")).unwrap();
let bytes = std::fs::read(file::get_root().join("out.bmp")).unwrap();
assert_eq!(&bytes[0..2], b"BM");
assert_eq!(bytes.len(), 54 + 8 * 3); assert_eq!(
u32::from_le_bytes(bytes[2..6].try_into().unwrap()),
bytes.len() as u32
);
assert_eq!(u32::from_le_bytes(bytes[10..14].try_into().unwrap()), 54);
assert_eq!(u32::from_le_bytes(bytes[14..18].try_into().unwrap()), 40);
assert_eq!(i32::from_le_bytes(bytes[18..22].try_into().unwrap()), 2); assert_eq!(i32::from_le_bytes(bytes[22..26].try_into().unwrap()), 3); assert_eq!(u16::from_le_bytes(bytes[26..28].try_into().unwrap()), 1); assert_eq!(u16::from_le_bytes(bytes[28..30].try_into().unwrap()), 24); assert!(bytes[54..60].iter().all(|&b| b == 0xFF));
assert!(bytes[60..62].iter().all(|&b| b == 0));
});
}
#[test]
fn save_picture_overwrites_an_existing_file_without_warning() {
with_temp_file_root!({
std::fs::write(file::get_root().join("out.bmp"), b"stale data").unwrap();
save_picture(&picture_variant(1, 1), &VBVariant::from_string("out.bmp")).unwrap();
let bytes = std::fs::read(file::get_root().join("out.bmp")).unwrap();
assert_eq!(bytes.len(), 54 + 4); assert_eq!(&bytes[0..2], b"BM");
});
}
#[test]
fn save_picture_accepts_absolute_paths() {
with_temp_file_root!({
let dir = file::get_root();
let target = dir.join("sub").join("out.bmp");
std::fs::create_dir(dir.join("sub")).unwrap();
save_picture(
&picture_variant(4, 4),
&VBVariant::from_string(target.to_str().unwrap()),
)
.unwrap();
assert!(target.exists());
});
}
#[test]
fn save_picture_nothing_raises_object_variable_not_set() {
with_temp_file_root!({
let error =
save_picture(&VBVariant::Nothing, &VBVariant::from_string("out.bmp")).unwrap_err();
assert_eq!(error.number, err_number::OBJECT_VARIABLE_NOT_SET);
});
}
#[test]
fn save_picture_non_object_raises_type_mismatch() {
with_temp_file_root!({
let error = save_picture(
&VBVariant::from_integer(42),
&VBVariant::from_string("out.bmp"),
)
.unwrap_err();
assert_eq!(error.number, err_number::TYPE_MISMATCH);
});
}
#[test]
fn save_picture_null_filename_raises_invalid_use_of_null() {
with_temp_file_root!({
let error = save_picture(&picture_variant(1, 1), &VBVariant::Null).unwrap_err();
assert_eq!(error.number, err_number::INVALID_USE_OF_NULL);
});
}
#[test]
fn save_picture_empty_filename_raises_path_file_access_error() {
with_temp_file_root!({
let error =
save_picture(&picture_variant(1, 1), &VBVariant::from_string("")).unwrap_err();
assert_eq!(error.number, err_number::PATH_FILE_ACCESS_ERROR);
});
}
#[test]
fn save_picture_creates_missing_parent_directories_like_output_mode() {
with_temp_file_root!({
save_picture(
&picture_variant(1, 1),
&VBVariant::from_string("new_dir/out.bmp"),
)
.unwrap();
assert!(file::get_root().join("new_dir").join("out.bmp").exists());
});
}
}