use std::borrow::Cow;
use crate::error::Result;
pub trait ImageSource<'a> {
fn load(self) -> Result<Cow<'a, [u8]>>;
}
impl<'a> ImageSource<'a> for &'a [u8] {
fn load(self) -> Result<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self))
}
}
impl<'a> ImageSource<'a> for Vec<u8> {
fn load(self) -> Result<Cow<'a, [u8]>> {
Ok(Cow::Owned(self))
}
}
#[cfg(feature = "std")]
mod path_impls {
use super::*;
use crate::error::Error;
use std::path::Path;
impl ImageSource<'static> for &str {
fn load(self) -> Result<Cow<'static, [u8]>> {
std::fs::read(self).map(Cow::Owned).map_err(Error::Io)
}
}
impl ImageSource<'static> for &Path {
fn load(self) -> Result<Cow<'static, [u8]>> {
std::fs::read(self).map(Cow::Owned).map_err(Error::Io)
}
}
impl ImageSource<'static> for std::path::PathBuf {
fn load(self) -> Result<Cow<'static, [u8]>> {
std::fs::read(&self).map(Cow::Owned).map_err(Error::Io)
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ImageOptions {
pub at: Option<[f64; 2]>,
pub width: Option<f64>,
pub height: Option<f64>,
pub fit: Option<(f64, f64)>,
pub scale: Option<f64>,
pub position: Position,
}
impl ImageOptions {
pub fn new(x: f64, y: f64, width: f64, height: f64) -> Self {
ImageOptions {
at: Some([x, y]),
width: Some(width),
height: Some(height),
..Default::default()
}
}
pub fn fit(max_width: f64, max_height: f64) -> Self {
ImageOptions {
fit: Some((max_width, max_height)),
..Default::default()
}
}
pub fn fit_at(x: f64, y: f64, max_width: f64, max_height: f64) -> Self {
ImageOptions {
at: Some([x, y]),
fit: Some((max_width, max_height)),
..Default::default()
}
}
pub fn scaled(scale: f64) -> Self {
ImageOptions {
scale: Some(scale),
..Default::default()
}
}
pub fn at(x: f64, y: f64) -> Self {
ImageOptions {
at: Some([x, y]),
..Default::default()
}
}
pub fn with_position(mut self, pos: Position) -> Self {
self.position = pos;
self
}
pub fn with_scale(mut self, scale: f64) -> Self {
self.scale = Some(scale);
self
}
pub fn with_at(mut self, x: f64, y: f64) -> Self {
self.at = Some([x, y]);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Position {
#[default]
Center,
TopLeft,
TopCenter,
TopRight,
MiddleLeft,
MiddleRight,
BottomLeft,
BottomCenter,
BottomRight,
}
impl Position {
pub fn calculate_offset(
self,
image_width: f64,
image_height: f64,
bounds_width: f64,
bounds_height: f64,
) -> (f64, f64) {
let x_offset = match self {
Position::TopLeft | Position::MiddleLeft | Position::BottomLeft => 0.0,
Position::TopCenter | Position::Center | Position::BottomCenter => {
(bounds_width - image_width) / 2.0
}
Position::TopRight | Position::MiddleRight | Position::BottomRight => {
bounds_width - image_width
}
};
let y_offset = match self {
Position::BottomLeft | Position::BottomCenter | Position::BottomRight => 0.0,
Position::MiddleLeft | Position::Center | Position::MiddleRight => {
(bounds_height - image_height) / 2.0
}
Position::TopLeft | Position::TopCenter | Position::TopRight => {
bounds_height - image_height
}
};
(x_offset, y_offset)
}
}
#[derive(Debug, Clone)]
pub struct EmbeddedImage {
pub name: String,
pub width: u32,
pub height: u32,
}
impl EmbeddedImage {
pub fn aspect_ratio(&self) -> f64 {
self.width as f64 / self.height as f64
}
pub fn fit_dimensions(&self, max_width: f64, max_height: f64) -> (f64, f64) {
let aspect = self.aspect_ratio();
let mut width = max_width;
let mut height = width / aspect;
if height > max_height {
height = max_height;
width = height * aspect;
}
(width, height)
}
pub fn scaled_dimensions(&self, scale: f64) -> (f64, f64) {
(self.width as f64 * scale, self.height as f64 * scale)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_position_offset_center() {
let (x, y) = Position::Center.calculate_offset(100.0, 50.0, 200.0, 100.0);
assert_eq!(x, 50.0);
assert_eq!(y, 25.0);
}
#[test]
fn test_position_offset_top_left() {
let (x, y) = Position::TopLeft.calculate_offset(100.0, 50.0, 200.0, 100.0);
assert_eq!(x, 0.0);
assert_eq!(y, 50.0);
}
#[test]
fn test_position_offset_bottom_right() {
let (x, y) = Position::BottomRight.calculate_offset(100.0, 50.0, 200.0, 100.0);
assert_eq!(x, 100.0);
assert_eq!(y, 0.0);
}
#[test]
fn test_embedded_image_fit() {
let img = EmbeddedImage {
name: "test".to_string(),
width: 800,
height: 600,
};
let (w, h) = img.fit_dimensions(400.0, 400.0);
assert!((w - 400.0).abs() < 0.001);
assert!((h - 300.0).abs() < 0.001);
let (w, h) = img.fit_dimensions(200.0, 400.0);
assert!((w - 200.0).abs() < 0.001);
assert!((h - 150.0).abs() < 0.001);
}
#[test]
fn test_embedded_image_scaled() {
let img = EmbeddedImage {
name: "test".to_string(),
width: 100,
height: 50,
};
let (w, h) = img.scaled_dimensions(2.0);
assert_eq!(w, 200.0);
assert_eq!(h, 100.0);
let (w, h) = img.scaled_dimensions(0.5);
assert_eq!(w, 50.0);
assert_eq!(h, 25.0);
}
#[test]
fn test_image_source_from_bytes_zero_copy() {
use std::borrow::Cow;
let bytes: &[u8] = &[1, 2, 3, 4];
let result = bytes.load();
assert!(result.is_ok());
let cow = result.unwrap();
assert!(matches!(cow, Cow::Borrowed(_)));
assert_eq!(cow.as_ref(), &[1, 2, 3, 4]);
}
#[test]
fn test_image_source_from_vec() {
use std::borrow::Cow;
let bytes: Vec<u8> = vec![1, 2, 3, 4];
let result = bytes.load();
assert!(result.is_ok());
let cow = result.unwrap();
assert!(matches!(cow, Cow::Owned(_)));
assert_eq!(cow.as_ref(), &[1, 2, 3, 4]);
}
#[test]
#[cfg(feature = "std")]
fn test_image_source_from_invalid_path() {
let path = "/nonexistent/path/to/image.png";
let result = path.load();
assert!(result.is_err());
}
}