diff --git a/float-pigment-forest/tests/custom/css_aspect_ratio.rs b/float-pigment-forest/tests/custom/css_aspect_ratio.rs index 08e2893..acaf4b7 100644 --- a/float-pigment-forest/tests/custom/css_aspect_ratio.rs +++ b/float-pigment-forest/tests/custom/css_aspect_ratio.rs @@ -297,3 +297,385 @@ pub fn aspect_ratio_2() { assert_eq!(child_a.layout_position().height, 100.); } } + +#[test] +pub fn aspect_ratio_in_block_width_fixed() { + unsafe { + let root = as_ref(Node::new_ptr()); + let container = as_ref(Node::new_ptr()); + container.set_width(DefLength::Points(Len::from_f32(300.))); + container.set_height(DefLength::Auto); + root.append_child(convert_node_ref_to_ptr(container)); + + let child = as_ref(Node::new_ptr()); + child.set_width(DefLength::Points(Len::from_f32(100.))); + child.set_height(DefLength::Auto); + child.set_aspect_ratio(Some(2. / 1.)); + container.append_child(convert_node_ref_to_ptr(child)); + + let child2 = as_ref(Node::new_ptr()); + child2.set_width(DefLength::Points(Len::from_f32(100.))); + child2.set_height(DefLength::Auto); + child2.set_aspect_ratio(Some(0.5 / 1.)); + container.append_child(convert_node_ref_to_ptr(child2)); + + root.layout( + OptionSize::new(OptionNum::some(Len::from_f32(400.)), OptionNum::none()), + Size::new(Len::from_f32(0.), Len::from_f32(0.)), + ); + + assert_eq!(child.layout_position().width, 100.); + assert_eq!(child.layout_position().height, 50.); + assert_eq!(child2.layout_position().width, 100.); + assert_eq!(child2.layout_position().height, 200.); + } +} + +#[test] +pub fn aspect_ratio_in_block_height_fixed() { + unsafe { + let root = as_ref(Node::new_ptr()); + let container = as_ref(Node::new_ptr()); + container.set_width(DefLength::Auto); + container.set_height(DefLength::Points(Len::from_f32(300.))); + root.append_child(convert_node_ref_to_ptr(container)); + + let child = as_ref(Node::new_ptr()); + child.set_width(DefLength::Auto); + child.set_height(DefLength::Points(Len::from_f32(100.))); + child.set_aspect_ratio(Some(2. / 1.)); + container.append_child(convert_node_ref_to_ptr(child)); + + let child2 = as_ref(Node::new_ptr()); + child2.set_width(DefLength::Auto); + child2.set_height(DefLength::Points(Len::from_f32(200.))); + child2.set_aspect_ratio(Some(0.5 / 1.)); + container.append_child(convert_node_ref_to_ptr(child2)); + + root.layout( + OptionSize::new(OptionNum::some(Len::from_f32(400.)), OptionNum::none()), + Size::new(Len::from_f32(0.), Len::from_f32(0.)), + ); + + assert_eq!(child.layout_position().width, 200.); + assert_eq!(child.layout_position().height, 100.); + assert_eq!(child2.layout_position().width, 100.); + assert_eq!(child2.layout_position().height, 200.); + } +} + +// wpt:css/css-sizing/aspect-ratio/block-aspect-ratio-008.html +#[test] +pub fn aspect_ratio_in_parent_block_cross_size_fixed() { + unsafe { + let root = as_ref(Node::new_ptr()); + let container = as_ref(Node::new_ptr()); + container.set_width(DefLength::Points(Len::from_f32(300.))); + container.set_height(DefLength::Auto); + root.append_child(convert_node_ref_to_ptr(container)); + + let child = as_ref(Node::new_ptr()); + child.set_width(DefLength::Auto); + child.set_aspect_ratio(Some(2. / 1.)); + container.append_child(convert_node_ref_to_ptr(child)); + + let container2 = as_ref(Node::new_ptr()); + container2.set_width(DefLength::Auto); + container2.set_height(DefLength::Points(Len::from_f32(300.))); + container2.set_writing_mode(WritingMode::VerticalLr); + root.append_child(convert_node_ref_to_ptr(container2)); + + let child2 = as_ref(Node::new_ptr()); + child2.set_width(DefLength::Auto); + child2.set_aspect_ratio(Some(0.5 / 1.)); + container2.append_child(convert_node_ref_to_ptr(child2)); + + root.layout( + OptionSize::new(OptionNum::some(Len::from_f32(400.)), OptionNum::none()), + Size::new(Len::from_f32(0.), Len::from_f32(0.)), + ); + + println!( + "{}", + root.dump_to_html( + DumpOptions { + recursive: true, + layout: true, + style: DumpStyleMode::Mutation + }, + 0 + ) + ); + + assert_eq!(child.layout_position().width, 300.); + assert_eq!(child.layout_position().height, 150.); + assert_eq!(child2.layout_position().width, 150.); + assert_eq!(child2.layout_position().height, 300.); + } +} + +#[test] +pub fn aspect_ratio_with_min_width_constraint() { + unsafe { + let root = as_ref(Node::new_ptr()); + let container = as_ref(Node::new_ptr()); + container.set_width(DefLength::Points(Len::from_f32(300.))); + container.set_height(DefLength::Auto); + root.append_child(convert_node_ref_to_ptr(container)); + + let child = as_ref(Node::new_ptr()); + child.set_width(DefLength::Auto); + child.set_height(DefLength::Auto); + child.set_min_width(DefLength::Points(Len::from_f32(400.))); + child.set_aspect_ratio(Some(2. / 1.)); + container.append_child(convert_node_ref_to_ptr(child)); + + root.layout( + OptionSize::new(OptionNum::some(Len::from_f32(200.)), OptionNum::none()), + Size::new(Len::from_f32(0.), Len::from_f32(0.)), + ); + + println!( + "{}", + root.dump_to_html( + DumpOptions { + recursive: true, + layout: true, + style: DumpStyleMode::Mutation + }, + 0 + ) + ); + + assert_eq!(child.layout_position().width, 400.); + assert_eq!(child.layout_position().height, 200.); + } +} + +#[test] +pub fn aspect_ratio_with_max_width_constraint() { + unsafe { + let root = as_ref(Node::new_ptr()); + let container = as_ref(Node::new_ptr()); + container.set_width(DefLength::Points(Len::from_f32(300.))); + container.set_height(DefLength::Auto); + root.append_child(convert_node_ref_to_ptr(container)); + + let child = as_ref(Node::new_ptr()); + child.set_width(DefLength::Auto); + child.set_height(DefLength::Auto); + child.set_max_width(DefLength::Points(Len::from_f32(80.))); + child.set_aspect_ratio(Some(2. / 1.)); + container.append_child(convert_node_ref_to_ptr(child)); + + root.layout( + OptionSize::new(OptionNum::some(Len::from_f32(200.)), OptionNum::none()), + Size::new(Len::from_f32(0.), Len::from_f32(0.)), + ); + + println!( + "{}", + root.dump_to_html( + DumpOptions { + recursive: true, + layout: true, + style: DumpStyleMode::Mutation + }, + 0 + ) + ); + + assert_eq!(child.layout_position().width, 80.); + assert_eq!(child.layout_position().height, 40.); + } +} + +#[test] +pub fn aspect_ratio_with_max_width_violating_min_height_constraint() { + unsafe { + let root = as_ref(Node::new_ptr()); + let container = as_ref(Node::new_ptr()); + container.set_width(DefLength::Points(Len::from_f32(300.))); + container.set_height(DefLength::Auto); + root.append_child(convert_node_ref_to_ptr(container)); + + let child = as_ref(Node::new_ptr()); + child.set_width(DefLength::Auto); + child.set_height(DefLength::Auto); + child.set_max_width(DefLength::Points(Len::from_f32(80.))); + child.set_min_height(DefLength::Points(Len::from_f32(100.))); + child.set_aspect_ratio(Some(1. / 1.)); + container.append_child(convert_node_ref_to_ptr(child)); + + root.layout( + OptionSize::new(OptionNum::some(Len::from_f32(200.)), OptionNum::none()), + Size::new(Len::from_f32(0.), Len::from_f32(0.)), + ); + + println!( + "{}", + root.dump_to_html( + DumpOptions { + recursive: true, + layout: true, + style: DumpStyleMode::Mutation + }, + 0 + ) + ); + + assert_eq!(child.layout_position().width, 80.); + assert_eq!(child.layout_position().height, 100.); + } +} + +#[test] +pub fn aspect_ratio_block_size_with_box_sizing() { + unsafe { + let root = as_ref(Node::new_ptr()); + let container = as_ref(Node::new_ptr()); + container.set_width(DefLength::Points(Len::from_f32(300.))); + container.set_height(DefLength::Auto); + root.append_child(convert_node_ref_to_ptr(container)); + + let child = as_ref(Node::new_ptr()); + child.set_width(DefLength::Auto); + child.set_height(DefLength::Auto); + child.set_width(DefLength::Points(Len::from_f32(50.))); + child.set_padding_left(DefLength::Points(Len::from_f32(30.))); + child.set_border_left(DefLength::Points(Len::from_f32(20.))); + child.set_box_sizing(BoxSizing::BorderBox); + child.set_aspect_ratio(Some(2. / 1.)); + container.append_child(convert_node_ref_to_ptr(child)); + + let child2 = as_ref(Node::new_ptr()); + child2.set_width(DefLength::Auto); + child2.set_height(DefLength::Auto); + child2.set_width(DefLength::Points(Len::from_f32(50.))); + child2.set_padding_left(DefLength::Points(Len::from_f32(30.))); + child2.set_border_left(DefLength::Points(Len::from_f32(20.))); + child2.set_box_sizing(BoxSizing::PaddingBox); + child2.set_aspect_ratio(Some(2. / 1.)); + container.append_child(convert_node_ref_to_ptr(child2)); + + let child3 = as_ref(Node::new_ptr()); + child3.set_width(DefLength::Auto); + child3.set_height(DefLength::Auto); + child3.set_width(DefLength::Points(Len::from_f32(50.))); + child3.set_padding_left(DefLength::Points(Len::from_f32(30.))); + child3.set_border_left(DefLength::Points(Len::from_f32(20.))); + child3.set_aspect_ratio(Some(2. / 1.)); + container.append_child(convert_node_ref_to_ptr(child3)); + + root.layout( + OptionSize::new(OptionNum::some(Len::from_f32(200.)), OptionNum::none()), + Size::new(Len::from_f32(0.), Len::from_f32(0.)), + ); + + println!( + "{}", + root.dump_to_html( + DumpOptions { + recursive: true, + layout: true, + style: DumpStyleMode::Mutation + }, + 0 + ) + ); + + assert_eq!(child.layout_position().width, 50.); + assert_eq!(child.layout_position().height, 25.); + assert_eq!(child2.layout_position().width, 80.); + assert_eq!(child2.layout_position().height, 25.); + assert_eq!(child3.layout_position().width, 100.); + assert_eq!(child3.layout_position().height, 25.); + } +} + +#[test] +pub fn aspect_ratio_block_size_with_box_sizing_and_writing_mode() { + unsafe { + let root = as_ref(Node::new_ptr()); + let container = as_ref(Node::new_ptr()); + container.set_width(DefLength::Points(Len::from_f32(300.))); + container.set_height(DefLength::Auto); + container.set_writing_mode(WritingMode::VerticalLr); + root.append_child(convert_node_ref_to_ptr(container)); + + let child = as_ref(Node::new_ptr()); + child.set_width(DefLength::Auto); + child.set_height(DefLength::Auto); + child.set_height(DefLength::Points(Len::from_f32(50.))); + child.set_padding_top(DefLength::Points(Len::from_f32(30.))); + child.set_border_top(DefLength::Points(Len::from_f32(20.))); + child.set_box_sizing(BoxSizing::BorderBox); + child.set_aspect_ratio(Some(2. / 1.)); + container.append_child(convert_node_ref_to_ptr(child)); + + let child2 = as_ref(Node::new_ptr()); + child2.set_width(DefLength::Auto); + child2.set_height(DefLength::Auto); + child2.set_height(DefLength::Points(Len::from_f32(50.))); + child2.set_padding_top(DefLength::Points(Len::from_f32(30.))); + child2.set_border_top(DefLength::Points(Len::from_f32(20.))); + child2.set_box_sizing(BoxSizing::PaddingBox); + child2.set_aspect_ratio(Some(2. / 1.)); + container.append_child(convert_node_ref_to_ptr(child2)); + + let child3 = as_ref(Node::new_ptr()); + child3.set_width(DefLength::Auto); + child3.set_height(DefLength::Auto); + child3.set_height(DefLength::Points(Len::from_f32(50.))); + child3.set_padding_top(DefLength::Points(Len::from_f32(30.))); + child3.set_border_top(DefLength::Points(Len::from_f32(20.))); + child3.set_aspect_ratio(Some(2. / 1.)); + container.append_child(convert_node_ref_to_ptr(child3)); + + root.layout( + OptionSize::new(OptionNum::some(Len::from_f32(200.)), OptionNum::none()), + Size::new(Len::from_f32(0.), Len::from_f32(0.)), + ); + + println!( + "{}", + root.dump_to_html( + DumpOptions { + recursive: true, + layout: true, + style: DumpStyleMode::Mutation + }, + 0 + ) + ); + + assert_eq!(child.layout_position().width, 100.); + assert_eq!(child.layout_position().height, 50.); + assert_eq!(child2.layout_position().width, 100.); + assert_eq!(child2.layout_position().height, 80.); + assert_eq!(child3.layout_position().width, 100.); + assert_eq!(child3.layout_position().height, 100.); + } +} + +#[test] +pub fn apsect_ratio_writing_mode_streched() { + assert_xml!( + r#" +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ "# + ) +} diff --git a/float-pigment-layout/src/algo/flow.rs b/float-pigment-layout/src/algo/flow.rs index 8a43363..40960ba 100644 --- a/float-pigment-layout/src/algo/flow.rs +++ b/float-pigment-layout/src/algo/flow.rs @@ -1,6 +1,6 @@ use crate::*; -use float_pigment_css::num_traits::Zero; +use float_pigment_css::num_traits::{Signed, Zero}; enum BlockOrInlineSeries<'a, T: LayoutTreeNode> { Block(&'a T), @@ -361,12 +361,89 @@ impl Flow for LayoutUnit { child_border, child_padding_border, ); + let stretched_cross_size = node_inner_size.cross_size(axis_info.dir) + - child_margin.cross_axis_sum(axis_info.dir); + let aspect_ratio = child_node.style().aspect_ratio(); + let has_aspect_ratio = aspect_ratio.is_some() && aspect_ratio.unwrap() > 0.; + if has_aspect_ratio { + if css_size.width.is_none() ^ css_size.height.is_none() { + if css_size.height.is_none() { + css_size.height = OptionNum::some(resolve_height_from_aspect_ratio( + child_border, + child_padding_border, + &child_node.style().box_sizing(), + aspect_ratio.unwrap(), + css_size.width.val().unwrap(), + )) + } else { + css_size.width = OptionNum::some(resolve_width_from_aspect_ratio( + child_border, + child_padding_border, + &child_node.style().box_sizing(), + aspect_ratio.unwrap(), + css_size.height.val().unwrap(), + )) + } + } else if css_size.width.is_none() + && css_size.height.is_none() + && stretched_cross_size.is_some() + { + let transfer_limit = transfer_min_max_size( + aspect_ratio.unwrap(), + css_size, + axis_info.dir, + min_max_limit, + child_node.style().box_sizing(), + child_border, + child_padding_border, + ); + let min_cross_size = transfer_limit.min_cross_size(axis_info.dir); + let min_main_size = transfer_limit.min_main_size(axis_info.dir); + let max_cross_size = transfer_limit.max_cross_size(axis_info.dir); + let max_main_size = transfer_limit.max_main_size(axis_info.dir); + if min_cross_size.is_positive() + && stretched_cross_size.or_zero() < min_cross_size + { + css_size + .set_cross_size(axis_info.dir, OptionNum::some(min_cross_size)); + css_size + .set_main_size(axis_info.dir, OptionNum::some(min_main_size)); + } else if max_cross_size.is_some() + && stretched_cross_size.or_zero() > max_cross_size.or_zero() + { + css_size.set_cross_size(axis_info.dir, max_cross_size); + css_size.set_main_size(axis_info.dir, max_main_size); + } else { + css_size.set_cross_size(axis_info.dir, stretched_cross_size); + css_size.set_main_size( + axis_info.dir, + match axis_info.dir { + AxisDirection::Horizontal => { + OptionNum::some(resolve_width_from_aspect_ratio( + child_border, + child_padding_border, + &child_node.style().box_sizing(), + aspect_ratio.unwrap(), + stretched_cross_size.or_zero(), + )) + } + AxisDirection::Vertical => { + OptionNum::some(resolve_height_from_aspect_ratio( + child_border, + child_padding_border, + &child_node.style().box_sizing(), + aspect_ratio.unwrap(), + stretched_cross_size.or_zero(), + )) + } + }, + ); + } + } + } css_size.set_cross_size( axis_info.dir, - css_size - .cross_size(axis_info.dir) - .or(node_inner_size.cross_size(axis_info.dir) - - child_margin.cross_axis_sum(axis_info.dir)), + css_size.cross_size(axis_info.dir).or(stretched_cross_size), ); let size = min_max_limit.normalized_size(css_size); let mut max_content = OptionSize::new(OptionNum::none(), OptionNum::none()); @@ -729,3 +806,107 @@ impl Flow for LayoutUnit { } } } + +// This implements the transferred min/max sizes per: +// https://drafts.csswg.org/css-sizing-4/#aspect-ratio-size-transfers +#[inline] +pub(crate) fn transfer_min_max_size( + ratio: f32, + css_size: OptionSize, + main_dir: AxisDirection, + min_max_limit: MinMaxLimit, // min/max size + box_sizing: BoxSizing, + border: Edge, + padding_border: Edge, +) -> MinMaxLimit { + if ratio <= 0. { + return min_max_limit; + } + let mut transfer_limit = min_max_limit.clone(); + let resolve_func = match main_dir { + AxisDirection::Horizontal => resolve_width_from_aspect_ratio, + AxisDirection::Vertical => resolve_height_from_aspect_ratio, + }; + + if min_max_limit.min_cross_size(main_dir).is_positive() { + let min_main_size = resolve_func( + border, + padding_border, + &box_sizing, + ratio, + min_max_limit.min_cross_size(main_dir), + ) + .maybe_max(css_size.main_size(main_dir)) + .maybe_max(min_max_limit.max_main_size(main_dir)); + transfer_limit.set_min_main_size(min_main_size, main_dir); + } + + if min_max_limit.max_cross_size(main_dir).is_some() { + let mut max_main_size = resolve_func( + border, + padding_border, + &box_sizing, + ratio, + min_max_limit.max_cross_size(main_dir).or_zero(), + ) + .maybe_min(css_size.main_size(main_dir)) + .maybe_min(min_max_limit.max_main_size(main_dir)); + if min_max_limit.min_main_size(main_dir).is_positive() { + max_main_size = + max_main_size.maybe_max(OptionNum::some(transfer_limit.min_main_size(main_dir))); + } + transfer_limit.set_max_main_size(OptionNum::some(max_main_size), main_dir); + } + + transfer_limit +} + +#[inline] +pub(crate) fn resolve_width_from_aspect_ratio( + border: Edge, + padding_border: Edge, + box_sizing: &BoxSizing, + ratio: f32, + determining_size: L, +) -> L { + let (dependent_padding_border, determining_padding_border) = + (padding_border.horizontal(), padding_border.vertical()); + let (dependent_border, determining_border) = (border.horizontal(), border.vertical()); + match box_sizing { + BoxSizing::BorderBox => dependent_padding_border.max(determining_size.mul_f32(ratio)), + BoxSizing::PaddingBox => { + (determining_size - determining_padding_border + determining_border).mul_f32(ratio) + + dependent_padding_border + - dependent_border + } + BoxSizing::ContentBox => { + (determining_size - determining_padding_border).mul_f32(ratio) + + dependent_padding_border + } + } +} + +#[inline] +pub(crate) fn resolve_height_from_aspect_ratio( + border: Edge, + padding_border: Edge, + box_sizing: &BoxSizing, + ratio: f32, + determining_size: L, +) -> L { + let (dependent_padding_border, determining_padding_border) = + (padding_border.vertical(), padding_border.horizontal()); + let (dependent_border, determining_border) = (border.vertical(), border.horizontal()); + match box_sizing { + BoxSizing::BorderBox => dependent_padding_border.max(determining_size.div_f32(ratio)), + BoxSizing::PaddingBox => { + (determining_size - determining_padding_border + determining_border).div_f32(ratio) + + dependent_padding_border + - dependent_border + } + BoxSizing::ContentBox => { + (determining_size - determining_padding_border).div_f32(ratio) + + dependent_padding_border + } + } +} diff --git a/float-pigment-layout/src/types.rs b/float-pigment-layout/src/types.rs index 6004624..53b1af8 100644 --- a/float-pigment-layout/src/types.rs +++ b/float-pigment-layout/src/types.rs @@ -905,6 +905,42 @@ impl MinMaxLimit { AxisDirection::Vertical => self.max_width, } } + + #[inline(always)] + #[allow(unused)] + pub(crate) fn set_min_cross_size(&mut self, x: L, dir: AxisDirection) { + match dir { + AxisDirection::Horizontal => self.min_height = x, + AxisDirection::Vertical => self.min_width = x, + } + } + + #[inline(always)] + #[allow(unused)] + pub(crate) fn set_max_cross_size(&mut self, x: OptionNum, dir: AxisDirection) { + match dir { + AxisDirection::Horizontal => self.max_height = x, + AxisDirection::Vertical => self.max_width = x, + } + } + + #[inline(always)] + #[allow(unused)] + pub(crate) fn set_min_main_size(&mut self, x: L, dir: AxisDirection) { + match dir { + AxisDirection::Horizontal => self.min_width = x, + AxisDirection::Vertical => self.min_height = x, + } + } + + #[inline(always)] + #[allow(unused)] + pub(crate) fn set_max_main_size(&mut self, x: OptionNum, dir: AxisDirection) { + match dir { + AxisDirection::Horizontal => self.max_width = x, + AxisDirection::Vertical => self.max_height = x, + } + } } pub(crate) struct MinMaxLimitMaybe<'a, L: LengthNum>(&'a MinMaxLimit);