use std::collections::HashMap;
use crate::layout::elements::LayoutNode;
use crate::layout::text::resolve_style_font_family;
use crate::parser::ttf::TtfFont;
use crate::style::computed::{ComputedStyle, Display, FontWeight, VerticalAlign};
use crate::types::Size;
use super::svg::resolve_svg_size;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) enum InlineBaselineGapRounding {
#[default]
Fractional,
CssPixel,
}
impl InlineBaselineGapRounding {
fn apply(self, gap: f32) -> f32 {
match self {
Self::Fractional => gap,
Self::CssPixel => crate::fonts::ceil_to_css_pixel(gap),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct ImagePlacement {
pub width: f32,
pub height: f32,
pub offset_x: f32,
pub offset_y: f32,
pub clip: bool,
}
pub(crate) fn compute_replaced_content_placement(
content_box: Size,
intrinsic_size: Size,
object_fit: crate::style::computed::ObjectFit,
object_position: crate::style::computed::ObjectPosition,
) -> ImagePlacement {
use crate::style::computed::ObjectFit;
let Size {
width: box_w,
height: box_h,
} = content_box;
let Size {
width: intrinsic_w,
height: intrinsic_h,
} = intrinsic_size;
if intrinsic_w <= 0.0 || intrinsic_h <= 0.0 || box_w <= 0.0 || box_h <= 0.0 {
return ImagePlacement {
width: box_w,
height: box_h,
offset_x: 0.0,
offset_y: 0.0,
clip: false,
};
}
let contain_scale = (box_w / intrinsic_w).min(box_h / intrinsic_h);
let cover_scale = (box_w / intrinsic_w).max(box_h / intrinsic_h);
let (width, height) = match object_fit {
ObjectFit::Fill => (box_w, box_h),
ObjectFit::Contain => (intrinsic_w * contain_scale, intrinsic_h * contain_scale),
ObjectFit::Cover => (intrinsic_w * cover_scale, intrinsic_h * cover_scale),
ObjectFit::None => (intrinsic_w, intrinsic_h),
ObjectFit::ScaleDown => {
let scale = contain_scale.min(1.0);
(intrinsic_w * scale, intrinsic_h * scale)
}
};
let offset_x = object_position.x.resolve(box_w - width);
let offset_y = object_position.y.resolve(box_h - height);
let clip =
offset_x < 0.0 || offset_y < 0.0 || offset_x + width > box_w || offset_y + height > box_h;
ImagePlacement {
width,
height,
offset_x,
offset_y,
clip,
}
}
pub(crate) fn compute_image_placement(
box_w: f32,
box_h: f32,
source_width: u32,
source_height: u32,
object_fit: crate::style::computed::ObjectFit,
object_position: crate::style::computed::ObjectPosition,
) -> ImagePlacement {
compute_replaced_content_placement(
Size::new(box_w, box_h),
Size::new(source_width as f32 * 0.75, source_height as f32 * 0.75),
object_fit,
object_position,
)
}
pub(crate) fn svg_intrinsic_size(tree: &crate::parser::svg::SvgTree) -> Size {
let (width, height) = resolve_svg_size(tree, 0.0, 0.0, false, false);
Size::new(width, height)
}
#[derive(Clone, Copy, Debug)]
pub(super) struct ReplacedBoxSize {
width: f32,
height: f32,
width_is_auto: bool,
height_is_auto: bool,
}
impl ReplacedBoxSize {
pub(super) const fn new(
width: f32,
height: f32,
width_is_auto: bool,
height_is_auto: bool,
) -> Self {
Self {
width,
height,
width_is_auto,
height_is_auto,
}
}
pub(super) fn constrain(
mut self,
available_width: f32,
max_width: Option<f32>,
max_height: Option<f32>,
) -> Self {
if self.width <= 0.0 || self.height <= 0.0 {
self.width = self.width.max(0.0);
self.height = self.height.max(0.0);
return self;
}
let width_limit = available_width
.is_finite()
.then_some(available_width)
.filter(|limit| *limit > 0.0)
.into_iter()
.chain(max_width.filter(|limit| limit.is_finite() && *limit > 0.0))
.reduce(f32::min);
if let Some(limit) = width_limit.filter(|limit| self.width > *limit) {
let scale = limit / self.width;
self.width = limit;
if self.height_is_auto {
self.height *= scale;
}
}
if let Some(limit) = max_height
.filter(|limit| limit.is_finite() && *limit > 0.0)
.filter(|limit| self.height > *limit)
{
let scale = limit / self.height;
self.height = limit;
if self.width_is_auto {
self.width *= scale;
}
}
self
}
pub(super) const fn dimensions(self) -> (f32, f32) {
(self.width, self.height)
}
}
pub(crate) fn add_inline_replaced_baseline_gap(
mut element: LayoutNode,
style: &ComputedStyle,
fonts: &HashMap<String, TtfFont>,
rounding: InlineBaselineGapRounding,
) -> LayoutNode {
if style.display != Display::Inline || style.vertical_align != VerticalAlign::Baseline {
return element;
}
let font_family = resolve_style_font_family(style, fonts);
let (_, descender_ratio) = crate::fonts::font_metrics_ratios(
&font_family,
style.font_weight == FontWeight::Bold,
style.font_style.is_slanted(),
fonts,
);
let baseline_gap = rounding.apply(descender_ratio * style.font_size);
if baseline_gap <= 0.0 {
return element;
}
if let Some(replaced) = element.replaced_element_mut() {
replaced.add_baseline_gap(baseline_gap);
}
element
}
pub(crate) fn parse_html_image_dimension(raw: Option<&String>) -> Option<f32> {
let raw = raw?.trim();
let raw = raw.strip_suffix("px").unwrap_or(raw);
raw.parse::<f32>().ok().map(|px| px * 0.75)
}