use taffy::Size;
use crate::style::{Length, SizingContext};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AutoBackgroundAxis {
Width,
Height,
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct IntrinsicSizing {
pub width: Option<f32>,
pub height: Option<f32>,
pub ratio: Option<f32>,
}
impl IntrinsicSizing {
pub fn from_dimensions(width: f32, height: f32) -> Self {
Self {
width: Some(width),
height: Some(height),
ratio: (height != 0.0).then_some(width / height),
}
}
fn concrete_size(self, area_width: f32, area_height: f32) -> (u32, u32) {
let round = |width: f32, height: f32| (width.round() as u32, height.round() as u32);
match (self.width, self.height) {
(Some(width), Some(height)) => round(width, height),
(Some(width), None) => match self.ratio {
Some(ratio) if ratio != 0.0 => round(width, width / ratio),
_ => round(width, area_height),
},
(None, Some(height)) => match self.ratio {
Some(ratio) => round(height * ratio, height),
None => round(area_width, height),
},
(None, None) => match self.ratio {
Some(ratio) if ratio != 0.0 => {
let solution_width = area_height * ratio;
if solution_width <= area_width {
round(solution_width, area_height)
} else {
round(area_width, area_width / ratio)
}
}
_ => round(area_width, area_height),
},
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ResolvedBackgroundSize {
pub width: u32,
pub height: u32,
pub intrinsic_ratio: Option<f32>,
pub auto_axis: Option<AutoBackgroundAxis>,
}
pub(super) fn fit_ratio_to_area(ratio: f32, area: Size<u32>, cover: bool) -> (u32, u32) {
let area_width = area.width as f32;
let area_height = area.height as f32;
let width_at_area_height = area_height * ratio;
let (width, height) = if (width_at_area_height >= area_width) == cover {
(width_at_area_height, area_height)
} else {
(area_width, area_width / ratio)
};
(width.round() as u32, height.round() as u32)
}
pub(super) fn resolve_auto_background_size(
width: Length,
height: Length,
area: Size<u32>,
sizing: &SizingContext,
intrinsic: IntrinsicSizing,
) -> (u32, u32) {
let area_width = area.width as f32;
let area_height = area.height as f32;
match (width == Length::Auto, height == Length::Auto) {
(true, true) => intrinsic.concrete_size(area_width, area_height),
(true, false) => {
let fixed_height = height.to_px(sizing, area_height).max(0.0);
let resolved_width = match intrinsic.ratio {
Some(ratio) => fixed_height * ratio,
None => intrinsic.width.unwrap_or(area_width),
};
(resolved_width.round() as u32, fixed_height.round() as u32)
}
(false, true) => {
let fixed_width = width.to_px(sizing, area_width).max(0.0);
let resolved_height = match intrinsic.ratio {
Some(ratio) if ratio != 0.0 => fixed_width / ratio,
Some(_) => 0.0,
None => intrinsic.height.unwrap_or(area_height),
};
(fixed_width.round() as u32, resolved_height.round() as u32)
}
(false, false) => (0, 0),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn auto_size_uses_intrinsic_dimensions_when_present() {
let intrinsic = IntrinsicSizing::from_dimensions(102.0, 38.0);
assert_eq!(intrinsic.concrete_size(1200.0, 630.0), (102, 38));
}
#[test]
fn auto_size_contains_ratio_only_image_within_area() {
let intrinsic = IntrinsicSizing {
width: None,
height: None,
ratio: Some(1.0),
};
assert_eq!(intrinsic.concrete_size(400.0, 400.0), (400, 400));
assert_eq!(intrinsic.concrete_size(1200.0, 630.0), (630, 630));
}
#[test]
fn auto_size_fills_area_without_intrinsic_information() {
assert_eq!(
IntrinsicSizing::default().concrete_size(1200.0, 630.0),
(1200, 630)
);
}
#[test]
fn contain_and_cover_scale_ratio_to_area() {
let area = Size {
width: 800,
height: 400,
};
assert_eq!(fit_ratio_to_area(1.0, area, false), (400, 400));
assert_eq!(fit_ratio_to_area(1.0, area, true), (800, 800));
}
}